2 * Samsung Laptop driver
4 * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
5 * Copyright (C) 2009,2011 Novell Inc.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License version 2 as published by
9 * the Free Software Foundation.
12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
14 #include <linux/kernel.h>
15 #include <linux/init.h>
16 #include <linux/module.h>
17 #include <linux/delay.h>
18 #include <linux/pci.h>
19 #include <linux/backlight.h>
21 #include <linux/dmi.h>
22 #include <linux/platform_device.h>
23 #include <linux/rfkill.h>
26 * This driver is needed because a number of Samsung laptops do not hook
27 * their control settings through ACPI. So we have to poke around in the
28 * BIOS to do things like brightness values, and "special" key controls.
32 * We have 0 - 8 as valid brightness levels. The specs say that level 0 should
33 * be reserved by the BIOS (which really doesn't make much sense), we tell
34 * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
36 #define MAX_BRIGHT 0x07
39 #define SABI_IFACE_MAIN 0x00
40 #define SABI_IFACE_SUB 0x02
41 #define SABI_IFACE_COMPLETE 0x04
42 #define SABI_IFACE_DATA 0x05
44 /* Structure to get data back to the calling function */
49 struct sabi_header_offsets
{
58 struct sabi_commands
{
60 * Brightness is 0 - 8, as described above.
61 * Value 0 is for the BIOS to use
68 * 0x00 - wireless is off
69 * 0x01 - wireless is on
73 * TODO, verify 3G is correct, that doesn't seem right...
75 u8 get_wireless_button
;
76 u8 set_wireless_button
;
78 /* 0 is off, 1 is on */
83 * 0x80 or 0x00 - no action
84 * 0x81 - recovery key pressed
90 * on seclinux: 0 is low, 1 is high,
91 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
93 u8 get_performance_level
;
94 u8 set_performance_level
;
97 * Tell the BIOS that Linux is running on this machine.
103 struct sabi_performance_level
{
109 const char *test_string
;
111 struct sabi_header_offsets header_offsets
;
112 struct sabi_commands commands
;
113 struct sabi_performance_level performance_levels
[4];
116 static struct sabi_config sabi_configs
[] = {
118 .test_string
= "SECLINUX",
120 .main_function
= 0x4c49,
128 .data_segment
= 0x07,
132 .get_brightness
= 0x00,
133 .set_brightness
= 0x01,
135 .get_wireless_button
= 0x02,
136 .set_wireless_button
= 0x03,
138 .get_backlight
= 0x04,
139 .set_backlight
= 0x05,
141 .get_recovery_mode
= 0x06,
142 .set_recovery_mode
= 0x07,
144 .get_performance_level
= 0x08,
145 .set_performance_level
= 0x09,
150 .performance_levels
= {
163 .test_string
= "SwSmi@",
165 .main_function
= 0x5843,
173 .data_segment
= 0x07,
177 .get_brightness
= 0x10,
178 .set_brightness
= 0x11,
180 .get_wireless_button
= 0x12,
181 .set_wireless_button
= 0x13,
183 .get_backlight
= 0x2d,
184 .set_backlight
= 0x2e,
186 .get_recovery_mode
= 0xff,
187 .set_recovery_mode
= 0xff,
189 .get_performance_level
= 0x31,
190 .set_performance_level
= 0x32,
195 .performance_levels
= {
214 static struct sabi_config
*sabi_config
;
216 static void __iomem
*sabi
;
217 static void __iomem
*sabi_iface
;
218 static void __iomem
*f0000_segment
;
219 static struct backlight_device
*backlight_device
;
220 static struct mutex sabi_mutex
;
221 static struct platform_device
*sdev
;
222 static struct rfkill
*rfk
;
225 module_param(force
, bool, 0);
226 MODULE_PARM_DESC(force
,
227 "Disable the DMI check and forces the driver to be loaded");
230 module_param(debug
, bool, S_IRUGO
| S_IWUSR
);
231 MODULE_PARM_DESC(debug
, "Debug enabled or not");
233 static int sabi_get_command(u8 command
, struct sabi_retval
*sretval
)
236 u16 port
= readw(sabi
+ sabi_config
->header_offsets
.port
);
237 u8 complete
, iface_data
;
239 mutex_lock(&sabi_mutex
);
241 /* enable memory to be able to write to it */
242 outb(readb(sabi
+ sabi_config
->header_offsets
.en_mem
), port
);
244 /* write out the command */
245 writew(sabi_config
->main_function
, sabi_iface
+ SABI_IFACE_MAIN
);
246 writew(command
, sabi_iface
+ SABI_IFACE_SUB
);
247 writeb(0, sabi_iface
+ SABI_IFACE_COMPLETE
);
248 outb(readb(sabi
+ sabi_config
->header_offsets
.iface_func
), port
);
250 /* write protect memory to make it safe */
251 outb(readb(sabi
+ sabi_config
->header_offsets
.re_mem
), port
);
253 /* see if the command actually succeeded */
254 complete
= readb(sabi_iface
+ SABI_IFACE_COMPLETE
);
255 iface_data
= readb(sabi_iface
+ SABI_IFACE_DATA
);
256 if (complete
!= 0xaa || iface_data
== 0xff) {
257 pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
258 command
, complete
, iface_data
);
263 * Save off the data into a structure so the caller use it.
264 * Right now we only want the first 4 bytes,
265 * There are commands that need more, but not for the ones we
266 * currently care about.
268 sretval
->retval
[0] = readb(sabi_iface
+ SABI_IFACE_DATA
);
269 sretval
->retval
[1] = readb(sabi_iface
+ SABI_IFACE_DATA
+ 1);
270 sretval
->retval
[2] = readb(sabi_iface
+ SABI_IFACE_DATA
+ 2);
271 sretval
->retval
[3] = readb(sabi_iface
+ SABI_IFACE_DATA
+ 3);
274 mutex_unlock(&sabi_mutex
);
279 static int sabi_set_command(u8 command
, u8 data
)
282 u16 port
= readw(sabi
+ sabi_config
->header_offsets
.port
);
283 u8 complete
, iface_data
;
285 mutex_lock(&sabi_mutex
);
287 /* enable memory to be able to write to it */
288 outb(readb(sabi
+ sabi_config
->header_offsets
.en_mem
), port
);
290 /* write out the command */
291 writew(sabi_config
->main_function
, sabi_iface
+ SABI_IFACE_MAIN
);
292 writew(command
, sabi_iface
+ SABI_IFACE_SUB
);
293 writeb(0, sabi_iface
+ SABI_IFACE_COMPLETE
);
294 writeb(data
, sabi_iface
+ SABI_IFACE_DATA
);
295 outb(readb(sabi
+ sabi_config
->header_offsets
.iface_func
), port
);
297 /* write protect memory to make it safe */
298 outb(readb(sabi
+ sabi_config
->header_offsets
.re_mem
), port
);
300 /* see if the command actually succeeded */
301 complete
= readb(sabi_iface
+ SABI_IFACE_COMPLETE
);
302 iface_data
= readb(sabi_iface
+ SABI_IFACE_DATA
);
303 if (complete
!= 0xaa || iface_data
== 0xff) {
304 pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
305 command
, complete
, iface_data
);
309 mutex_unlock(&sabi_mutex
);
313 static void test_backlight(void)
315 struct sabi_retval sretval
;
317 sabi_get_command(sabi_config
->commands
.get_backlight
, &sretval
);
318 printk(KERN_DEBUG
"backlight = 0x%02x\n", sretval
.retval
[0]);
320 sabi_set_command(sabi_config
->commands
.set_backlight
, 0);
321 printk(KERN_DEBUG
"backlight should be off\n");
323 sabi_get_command(sabi_config
->commands
.get_backlight
, &sretval
);
324 printk(KERN_DEBUG
"backlight = 0x%02x\n", sretval
.retval
[0]);
328 sabi_set_command(sabi_config
->commands
.set_backlight
, 1);
329 printk(KERN_DEBUG
"backlight should be on\n");
331 sabi_get_command(sabi_config
->commands
.get_backlight
, &sretval
);
332 printk(KERN_DEBUG
"backlight = 0x%02x\n", sretval
.retval
[0]);
335 static void test_wireless(void)
337 struct sabi_retval sretval
;
339 sabi_get_command(sabi_config
->commands
.get_wireless_button
, &sretval
);
340 printk(KERN_DEBUG
"wireless led = 0x%02x\n", sretval
.retval
[0]);
342 sabi_set_command(sabi_config
->commands
.set_wireless_button
, 0);
343 printk(KERN_DEBUG
"wireless led should be off\n");
345 sabi_get_command(sabi_config
->commands
.get_wireless_button
, &sretval
);
346 printk(KERN_DEBUG
"wireless led = 0x%02x\n", sretval
.retval
[0]);
350 sabi_set_command(sabi_config
->commands
.set_wireless_button
, 1);
351 printk(KERN_DEBUG
"wireless led should be on\n");
353 sabi_get_command(sabi_config
->commands
.get_wireless_button
, &sretval
);
354 printk(KERN_DEBUG
"wireless led = 0x%02x\n", sretval
.retval
[0]);
357 static u8
read_brightness(void)
359 struct sabi_retval sretval
;
360 int user_brightness
= 0;
363 retval
= sabi_get_command(sabi_config
->commands
.get_brightness
,
366 user_brightness
= sretval
.retval
[0];
367 if (user_brightness
!= 0)
369 return user_brightness
;
372 static void set_brightness(u8 user_brightness
)
374 sabi_set_command(sabi_config
->commands
.set_brightness
,
375 user_brightness
+ 1);
378 static int get_brightness(struct backlight_device
*bd
)
380 return (int)read_brightness();
383 static int update_status(struct backlight_device
*bd
)
385 set_brightness(bd
->props
.brightness
);
387 if (bd
->props
.power
== FB_BLANK_UNBLANK
)
388 sabi_set_command(sabi_config
->commands
.set_backlight
, 1);
390 sabi_set_command(sabi_config
->commands
.set_backlight
, 0);
394 static const struct backlight_ops backlight_ops
= {
395 .get_brightness
= get_brightness
,
396 .update_status
= update_status
,
399 static int rfkill_set(void *data
, bool blocked
)
401 /* Do something with blocked...*/
403 * blocked == false is on
404 * blocked == true is off
407 sabi_set_command(sabi_config
->commands
.set_wireless_button
, 0);
409 sabi_set_command(sabi_config
->commands
.set_wireless_button
, 1);
414 static struct rfkill_ops rfkill_ops
= {
415 .set_block
= rfkill_set
,
418 static int init_wireless(struct platform_device
*sdev
)
422 rfk
= rfkill_alloc("samsung-wifi", &sdev
->dev
, RFKILL_TYPE_WLAN
,
427 retval
= rfkill_register(rfk
);
436 static void destroy_wireless(void)
438 rfkill_unregister(rfk
);
442 static ssize_t
get_performance_level(struct device
*dev
,
443 struct device_attribute
*attr
, char *buf
)
445 struct sabi_retval sretval
;
450 retval
= sabi_get_command(sabi_config
->commands
.get_performance_level
,
455 /* The logic is backwards, yeah, lots of fun... */
456 for (i
= 0; sabi_config
->performance_levels
[i
].name
; ++i
) {
457 if (sretval
.retval
[0] == sabi_config
->performance_levels
[i
].value
)
458 return sprintf(buf
, "%s\n", sabi_config
->performance_levels
[i
].name
);
460 return sprintf(buf
, "%s\n", "unknown");
463 static ssize_t
set_performance_level(struct device
*dev
,
464 struct device_attribute
*attr
, const char *buf
,
469 for (i
= 0; sabi_config
->performance_levels
[i
].name
; ++i
) {
470 struct sabi_performance_level
*level
=
471 &sabi_config
->performance_levels
[i
];
472 if (!strncasecmp(level
->name
, buf
, strlen(level
->name
))) {
473 sabi_set_command(sabi_config
->commands
.set_performance_level
,
478 if (!sabi_config
->performance_levels
[i
].name
)
483 static DEVICE_ATTR(performance_level
, S_IWUSR
| S_IRUGO
,
484 get_performance_level
, set_performance_level
);
487 static int __init
dmi_check_cb(const struct dmi_system_id
*id
)
489 pr_info("found laptop model '%s'\n",
494 static struct dmi_system_id __initdata samsung_dmi_table
[] = {
498 DMI_MATCH(DMI_SYS_VENDOR
,
499 "SAMSUNG ELECTRONICS CO., LTD."),
500 DMI_MATCH(DMI_PRODUCT_NAME
, "N128"),
501 DMI_MATCH(DMI_BOARD_NAME
, "N128"),
503 .callback
= dmi_check_cb
,
508 DMI_MATCH(DMI_SYS_VENDOR
,
509 "SAMSUNG ELECTRONICS CO., LTD."),
510 DMI_MATCH(DMI_PRODUCT_NAME
, "N130"),
511 DMI_MATCH(DMI_BOARD_NAME
, "N130"),
513 .callback
= dmi_check_cb
,
518 DMI_MATCH(DMI_SYS_VENDOR
,
519 "SAMSUNG ELECTRONICS CO., LTD."),
520 DMI_MATCH(DMI_PRODUCT_NAME
, "X125"),
521 DMI_MATCH(DMI_BOARD_NAME
, "X125"),
523 .callback
= dmi_check_cb
,
526 .ident
= "X120/X170",
528 DMI_MATCH(DMI_SYS_VENDOR
,
529 "SAMSUNG ELECTRONICS CO., LTD."),
530 DMI_MATCH(DMI_PRODUCT_NAME
, "X120/X170"),
531 DMI_MATCH(DMI_BOARD_NAME
, "X120/X170"),
533 .callback
= dmi_check_cb
,
538 DMI_MATCH(DMI_SYS_VENDOR
,
539 "SAMSUNG ELECTRONICS CO., LTD."),
540 DMI_MATCH(DMI_PRODUCT_NAME
, "NC10"),
541 DMI_MATCH(DMI_BOARD_NAME
, "NC10"),
543 .callback
= dmi_check_cb
,
548 DMI_MATCH(DMI_SYS_VENDOR
,
549 "SAMSUNG ELECTRONICS CO., LTD."),
550 DMI_MATCH(DMI_PRODUCT_NAME
, "SQ45S70S"),
551 DMI_MATCH(DMI_BOARD_NAME
, "SQ45S70S"),
553 .callback
= dmi_check_cb
,
558 DMI_MATCH(DMI_SYS_VENDOR
,
559 "SAMSUNG ELECTRONICS CO., LTD."),
560 DMI_MATCH(DMI_PRODUCT_NAME
, "X360"),
561 DMI_MATCH(DMI_BOARD_NAME
, "X360"),
563 .callback
= dmi_check_cb
,
568 DMI_MATCH(DMI_SYS_VENDOR
,
569 "SAMSUNG ELECTRONICS CO., LTD."),
570 DMI_MATCH(DMI_PRODUCT_NAME
, "R518"),
571 DMI_MATCH(DMI_BOARD_NAME
, "R518"),
573 .callback
= dmi_check_cb
,
576 .ident
= "R519/R719",
578 DMI_MATCH(DMI_SYS_VENDOR
,
579 "SAMSUNG ELECTRONICS CO., LTD."),
580 DMI_MATCH(DMI_PRODUCT_NAME
, "R519/R719"),
581 DMI_MATCH(DMI_BOARD_NAME
, "R519/R719"),
583 .callback
= dmi_check_cb
,
586 .ident
= "N150/N210/N220",
588 DMI_MATCH(DMI_SYS_VENDOR
,
589 "SAMSUNG ELECTRONICS CO., LTD."),
590 DMI_MATCH(DMI_PRODUCT_NAME
, "N150/N210/N220"),
591 DMI_MATCH(DMI_BOARD_NAME
, "N150/N210/N220"),
593 .callback
= dmi_check_cb
,
596 .ident
= "R530/R730",
598 DMI_MATCH(DMI_SYS_VENDOR
, "SAMSUNG ELECTRONICS CO., LTD."),
599 DMI_MATCH(DMI_PRODUCT_NAME
, "R530/R730"),
600 DMI_MATCH(DMI_BOARD_NAME
, "R530/R730"),
602 .callback
= dmi_check_cb
,
605 .ident
= "NF110/NF210/NF310",
607 DMI_MATCH(DMI_SYS_VENDOR
, "SAMSUNG ELECTRONICS CO., LTD."),
608 DMI_MATCH(DMI_PRODUCT_NAME
, "NF110/NF210/NF310"),
609 DMI_MATCH(DMI_BOARD_NAME
, "NF110/NF210/NF310"),
611 .callback
= dmi_check_cb
,
614 .ident
= "N145P/N250P/N260P",
616 DMI_MATCH(DMI_SYS_VENDOR
, "SAMSUNG ELECTRONICS CO., LTD."),
617 DMI_MATCH(DMI_PRODUCT_NAME
, "N145P/N250P/N260P"),
618 DMI_MATCH(DMI_BOARD_NAME
, "N145P/N250P/N260P"),
620 .callback
= dmi_check_cb
,
625 DMI_MATCH(DMI_SYS_VENDOR
,
626 "SAMSUNG ELECTRONICS CO., LTD."),
627 DMI_MATCH(DMI_PRODUCT_NAME
, "R70/R71"),
628 DMI_MATCH(DMI_BOARD_NAME
, "R70/R71"),
630 .callback
= dmi_check_cb
,
634 MODULE_DEVICE_TABLE(dmi
, samsung_dmi_table
);
636 static int find_signature(void __iomem
*memcheck
, const char *testStr
)
641 for (loca
= 0; loca
< 0xffff; loca
++) {
642 char temp
= readb(memcheck
+ loca
);
644 if (temp
== testStr
[i
]) {
645 if (i
== strlen(testStr
)-1)
655 static int __init
samsung_init(void)
657 struct backlight_properties props
;
658 struct sabi_retval sretval
;
664 mutex_init(&sabi_mutex
);
666 if (!force
&& !dmi_check_system(samsung_dmi_table
))
669 f0000_segment
= ioremap(0xf0000, 0xffff);
670 if (!f0000_segment
) {
671 pr_err("Can't map the segment at 0xf0000\n");
675 /* Try to find one of the signatures in memory to find the header */
676 for (i
= 0; sabi_configs
[i
].test_string
!= 0; ++i
) {
677 sabi_config
= &sabi_configs
[i
];
678 loca
= find_signature(f0000_segment
, sabi_config
->test_string
);
683 if (loca
== 0xffff) {
684 pr_err("This computer does not support SABI\n");
685 goto error_no_signature
;
688 /* point to the SMI port Number */
690 sabi
= (f0000_segment
+ loca
);
693 printk(KERN_DEBUG
"This computer supports SABI==%x\n",
695 printk(KERN_DEBUG
"SABI header:\n");
696 printk(KERN_DEBUG
" SMI Port Number = 0x%04x\n",
697 readw(sabi
+ sabi_config
->header_offsets
.port
));
698 printk(KERN_DEBUG
" SMI Interface Function = 0x%02x\n",
699 readb(sabi
+ sabi_config
->header_offsets
.iface_func
));
700 printk(KERN_DEBUG
" SMI enable memory buffer = 0x%02x\n",
701 readb(sabi
+ sabi_config
->header_offsets
.en_mem
));
702 printk(KERN_DEBUG
" SMI restore memory buffer = 0x%02x\n",
703 readb(sabi
+ sabi_config
->header_offsets
.re_mem
));
704 printk(KERN_DEBUG
" SABI data offset = 0x%04x\n",
705 readw(sabi
+ sabi_config
->header_offsets
.data_offset
));
706 printk(KERN_DEBUG
" SABI data segment = 0x%04x\n",
707 readw(sabi
+ sabi_config
->header_offsets
.data_segment
));
710 /* Get a pointer to the SABI Interface */
711 ifaceP
= (readw(sabi
+ sabi_config
->header_offsets
.data_segment
) & 0x0ffff) << 4;
712 ifaceP
+= readw(sabi
+ sabi_config
->header_offsets
.data_offset
) & 0x0ffff;
713 sabi_iface
= ioremap(ifaceP
, 16);
715 pr_err("Can't remap %x\n", ifaceP
);
719 printk(KERN_DEBUG
"ifaceP = 0x%08x\n", ifaceP
);
720 printk(KERN_DEBUG
"sabi_iface = %p\n", sabi_iface
);
725 retval
= sabi_get_command(sabi_config
->commands
.get_brightness
,
727 printk(KERN_DEBUG
"brightness = 0x%02x\n", sretval
.retval
[0]);
730 /* Turn on "Linux" mode in the BIOS */
731 if (sabi_config
->commands
.set_linux
!= 0xff) {
732 retval
= sabi_set_command(sabi_config
->commands
.set_linux
,
735 pr_warn("Linux mode was not set!\n");
736 goto error_no_platform
;
740 /* knock up a platform device to hang stuff off of */
741 sdev
= platform_device_register_simple("samsung", -1, NULL
, 0);
743 goto error_no_platform
;
745 /* create a backlight device to talk to this one */
746 memset(&props
, 0, sizeof(struct backlight_properties
));
747 props
.max_brightness
= MAX_BRIGHT
;
748 backlight_device
= backlight_device_register("samsung", &sdev
->dev
,
749 NULL
, &backlight_ops
,
751 if (IS_ERR(backlight_device
))
752 goto error_no_backlight
;
754 backlight_device
->props
.brightness
= read_brightness();
755 backlight_device
->props
.power
= FB_BLANK_UNBLANK
;
756 backlight_update_status(backlight_device
);
758 retval
= init_wireless(sdev
);
762 retval
= device_create_file(&sdev
->dev
, &dev_attr_performance_level
);
764 goto error_file_create
;
773 backlight_device_unregister(backlight_device
);
776 platform_device_unregister(sdev
);
782 iounmap(f0000_segment
);
786 static void __exit
samsung_exit(void)
788 /* Turn off "Linux" mode in the BIOS */
789 if (sabi_config
->commands
.set_linux
!= 0xff)
790 sabi_set_command(sabi_config
->commands
.set_linux
, 0x80);
792 device_remove_file(&sdev
->dev
, &dev_attr_performance_level
);
793 backlight_device_unregister(backlight_device
);
796 iounmap(f0000_segment
);
797 platform_device_unregister(sdev
);
800 module_init(samsung_init
);
801 module_exit(samsung_exit
);
803 MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
804 MODULE_DESCRIPTION("Samsung Backlight driver");
805 MODULE_LICENSE("GPL");