2 * Samsung N130 Laptop driver
4 * Copyright (C) 2009 Greg Kroah-Hartman (gregkh@suse.de)
5 * Copyright (C) 2009 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 #include <linux/kernel.h>
13 #include <linux/init.h>
14 #include <linux/module.h>
15 #include <linux/delay.h>
16 #include <linux/pci.h>
17 #include <linux/backlight.h>
19 #include <linux/dmi.h>
20 #include <linux/platform_device.h>
21 #include <linux/rfkill.h>
24 * This driver is needed because a number of Samsung laptops do not hook
25 * their control settings through ACPI. So we have to poke around in the
26 * BIOS to do things like brightness values, and "special" key controls.
30 * We have 0 - 8 as valid brightness levels. The specs say that level 0 should
31 * be reserved by the BIOS (which really doesn't make much sense), we tell
32 * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
34 #define MAX_BRIGHT 0x07
37 #define SABI_IFACE_MAIN 0x00
38 #define SABI_IFACE_SUB 0x02
39 #define SABI_IFACE_COMPLETE 0x04
40 #define SABI_IFACE_DATA 0x05
42 /* Structure to get data back to the calling function */
47 struct sabi_header_offsets
{
56 struct sabi_commands
{
58 * Brightness is 0 - 8, as described above.
59 * Value 0 is for the BIOS to use
66 * 0x00 - wireless is off
67 * 0x01 - wireless is on
71 * TODO, verify 3G is correct, that doesn't seem right...
73 u8 get_wireless_button
;
74 u8 set_wireless_button
;
76 /* 0 is off, 1 is on */
81 * 0x80 or 0x00 - no action
82 * 0x81 - recovery key pressed
88 * on seclinux: 0 is low, 1 is high,
89 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
91 u8 get_performance_level
;
92 u8 set_performance_level
;
95 * Tell the BIOS that Linux is running on this machine.
101 struct sabi_performance_level
{
107 const char *test_string
;
109 struct sabi_header_offsets header_offsets
;
110 struct sabi_commands commands
;
111 struct sabi_performance_level performance_levels
[4];
114 static struct sabi_config sabi_configs
[] = {
116 .test_string
= "SECLINUX",
118 .main_function
= 0x4c59,
126 .data_segment
= 0x07,
130 .get_brightness
= 0x00,
131 .set_brightness
= 0x01,
133 .get_wireless_button
= 0x02,
134 .set_wireless_button
= 0x03,
136 .get_backlight
= 0x04,
137 .set_backlight
= 0x05,
139 .get_recovery_mode
= 0x06,
140 .set_recovery_mode
= 0x07,
142 .get_performance_level
= 0x08,
143 .set_performance_level
= 0x09,
148 .performance_levels
= {
161 .test_string
= "SwSmi@",
163 .main_function
= 0x5843,
171 .data_segment
= 0x07,
175 .get_brightness
= 0x10,
176 .set_brightness
= 0x11,
178 .get_wireless_button
= 0x12,
179 .set_wireless_button
= 0x13,
181 .get_backlight
= 0x2d,
182 .set_backlight
= 0x2e,
184 .get_recovery_mode
= 0xff,
185 .set_recovery_mode
= 0xff,
187 .get_performance_level
= 0x31,
188 .set_performance_level
= 0x32,
193 .performance_levels
= {
212 static struct sabi_config
*sabi_config
;
214 static void __iomem
*sabi
;
215 static void __iomem
*sabi_iface
;
216 static void __iomem
*f0000_segment
;
217 static struct backlight_device
*backlight_device
;
218 static struct mutex sabi_mutex
;
219 static struct platform_device
*sdev
;
220 static struct rfkill
*rfk
;
223 module_param(force
, bool, 0);
224 MODULE_PARM_DESC(force
,
225 "Disable the DMI check and forces the driver to be loaded");
228 module_param(debug
, bool, S_IRUGO
| S_IWUSR
);
229 MODULE_PARM_DESC(debug
, "Debug enabled or not");
231 static int sabi_get_command(u8 command
, struct sabi_retval
*sretval
)
234 u16 port
= readw(sabi
+ sabi_config
->header_offsets
.port
);
236 mutex_lock(&sabi_mutex
);
238 /* enable memory to be able to write to it */
239 outb(readb(sabi
+ sabi_config
->header_offsets
.en_mem
), port
);
241 /* write out the command */
242 writew(sabi_config
->main_function
, sabi_iface
+ SABI_IFACE_MAIN
);
243 writew(command
, sabi_iface
+ SABI_IFACE_SUB
);
244 writeb(0, sabi_iface
+ SABI_IFACE_COMPLETE
);
245 outb(readb(sabi
+ sabi_config
->header_offsets
.iface_func
), port
);
247 /* write protect memory to make it safe */
248 outb(readb(sabi
+ sabi_config
->header_offsets
.re_mem
), port
);
250 /* see if the command actually succeeded */
251 if (readb(sabi_iface
+ SABI_IFACE_COMPLETE
) == 0xaa &&
252 readb(sabi_iface
+ SABI_IFACE_DATA
) != 0xff) {
255 * Save off the data into a structure so the caller use it.
256 * Right now we only care about the first 4 bytes,
257 * I suppose there are commands that need more, but I don't
260 sretval
->retval
[0] = readb(sabi_iface
+ SABI_IFACE_DATA
);
261 sretval
->retval
[1] = readb(sabi_iface
+ SABI_IFACE_DATA
+ 1);
262 sretval
->retval
[2] = readb(sabi_iface
+ SABI_IFACE_DATA
+ 2);
263 sretval
->retval
[3] = readb(sabi_iface
+ SABI_IFACE_DATA
+ 3);
267 /* Something bad happened, so report it and error out */
268 printk(KERN_WARNING
"SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n",
269 command
, readb(sabi_iface
+ SABI_IFACE_COMPLETE
),
270 readb(sabi_iface
+ SABI_IFACE_DATA
));
273 mutex_unlock(&sabi_mutex
);
278 static int sabi_set_command(u8 command
, u8 data
)
281 u16 port
= readw(sabi
+ sabi_config
->header_offsets
.port
);
283 mutex_lock(&sabi_mutex
);
285 /* enable memory to be able to write to it */
286 outb(readb(sabi
+ sabi_config
->header_offsets
.en_mem
), port
);
288 /* write out the command */
289 writew(sabi_config
->main_function
, sabi_iface
+ SABI_IFACE_MAIN
);
290 writew(command
, sabi_iface
+ SABI_IFACE_SUB
);
291 writeb(0, sabi_iface
+ SABI_IFACE_COMPLETE
);
292 writeb(data
, sabi_iface
+ SABI_IFACE_DATA
);
293 outb(readb(sabi
+ sabi_config
->header_offsets
.iface_func
), port
);
295 /* write protect memory to make it safe */
296 outb(readb(sabi
+ sabi_config
->header_offsets
.re_mem
), port
);
298 /* see if the command actually succeeded */
299 if (readb(sabi_iface
+ SABI_IFACE_COMPLETE
) == 0xaa &&
300 readb(sabi_iface
+ SABI_IFACE_DATA
) != 0xff) {
305 /* Something bad happened, so report it and error out */
306 printk(KERN_WARNING
"SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n",
307 command
, readb(sabi_iface
+ SABI_IFACE_COMPLETE
),
308 readb(sabi_iface
+ SABI_IFACE_DATA
));
311 mutex_unlock(&sabi_mutex
);
315 static void test_backlight(void)
317 struct sabi_retval sretval
;
319 sabi_get_command(sabi_config
->commands
.get_backlight
, &sretval
);
320 printk(KERN_DEBUG
"backlight = 0x%02x\n", sretval
.retval
[0]);
322 sabi_set_command(sabi_config
->commands
.set_backlight
, 0);
323 printk(KERN_DEBUG
"backlight should be off\n");
325 sabi_get_command(sabi_config
->commands
.get_backlight
, &sretval
);
326 printk(KERN_DEBUG
"backlight = 0x%02x\n", sretval
.retval
[0]);
330 sabi_set_command(sabi_config
->commands
.set_backlight
, 1);
331 printk(KERN_DEBUG
"backlight should be on\n");
333 sabi_get_command(sabi_config
->commands
.get_backlight
, &sretval
);
334 printk(KERN_DEBUG
"backlight = 0x%02x\n", sretval
.retval
[0]);
337 static void test_wireless(void)
339 struct sabi_retval sretval
;
341 sabi_get_command(sabi_config
->commands
.get_wireless_button
, &sretval
);
342 printk(KERN_DEBUG
"wireless led = 0x%02x\n", sretval
.retval
[0]);
344 sabi_set_command(sabi_config
->commands
.set_wireless_button
, 0);
345 printk(KERN_DEBUG
"wireless led should be off\n");
347 sabi_get_command(sabi_config
->commands
.get_wireless_button
, &sretval
);
348 printk(KERN_DEBUG
"wireless led = 0x%02x\n", sretval
.retval
[0]);
352 sabi_set_command(sabi_config
->commands
.set_wireless_button
, 1);
353 printk(KERN_DEBUG
"wireless led should be on\n");
355 sabi_get_command(sabi_config
->commands
.get_wireless_button
, &sretval
);
356 printk(KERN_DEBUG
"wireless led = 0x%02x\n", sretval
.retval
[0]);
359 static u8
read_brightness(void)
361 struct sabi_retval sretval
;
362 int user_brightness
= 0;
365 retval
= sabi_get_command(sabi_config
->commands
.get_brightness
,
368 user_brightness
= sretval
.retval
[0];
369 if (user_brightness
!= 0)
371 return user_brightness
;
374 static void set_brightness(u8 user_brightness
)
376 sabi_set_command(sabi_config
->commands
.set_brightness
,
377 user_brightness
+ 1);
380 static int get_brightness(struct backlight_device
*bd
)
382 return (int)read_brightness();
385 static int update_status(struct backlight_device
*bd
)
387 set_brightness(bd
->props
.brightness
);
389 if (bd
->props
.power
== FB_BLANK_UNBLANK
)
390 sabi_set_command(sabi_config
->commands
.set_backlight
, 1);
392 sabi_set_command(sabi_config
->commands
.set_backlight
, 0);
396 static const struct backlight_ops backlight_ops
= {
397 .get_brightness
= get_brightness
,
398 .update_status
= update_status
,
401 static int rfkill_set(void *data
, bool blocked
)
403 /* Do something with blocked...*/
405 * blocked == false is on
406 * blocked == true is off
409 sabi_set_command(sabi_config
->commands
.set_wireless_button
, 0);
411 sabi_set_command(sabi_config
->commands
.set_wireless_button
, 1);
416 static struct rfkill_ops rfkill_ops
= {
417 .set_block
= rfkill_set
,
420 static int init_wireless(struct platform_device
*sdev
)
424 rfk
= rfkill_alloc("samsung-wifi", &sdev
->dev
, RFKILL_TYPE_WLAN
,
429 retval
= rfkill_register(rfk
);
438 static void destroy_wireless(void)
440 rfkill_unregister(rfk
);
444 static ssize_t
get_performance_level(struct device
*dev
,
445 struct device_attribute
*attr
, char *buf
)
447 struct sabi_retval sretval
;
452 retval
= sabi_get_command(sabi_config
->commands
.get_performance_level
,
457 /* The logic is backwards, yeah, lots of fun... */
458 for (i
= 0; sabi_config
->performance_levels
[i
].name
; ++i
) {
459 if (sretval
.retval
[0] == sabi_config
->performance_levels
[i
].value
)
460 return sprintf(buf
, "%s\n", sabi_config
->performance_levels
[i
].name
);
462 return sprintf(buf
, "%s\n", "unknown");
465 static ssize_t
set_performance_level(struct device
*dev
,
466 struct device_attribute
*attr
, const char *buf
,
471 for (i
= 0; sabi_config
->performance_levels
[i
].name
; ++i
) {
472 struct sabi_performance_level
*level
=
473 &sabi_config
->performance_levels
[i
];
474 if (!strncasecmp(level
->name
, buf
, strlen(level
->name
))) {
475 sabi_set_command(sabi_config
->commands
.set_performance_level
,
480 if (!sabi_config
->performance_levels
[i
].name
)
485 static DEVICE_ATTR(performance_level
, S_IWUSR
| S_IRUGO
,
486 get_performance_level
, set_performance_level
);
489 static int __init
dmi_check_cb(const struct dmi_system_id
*id
)
491 printk(KERN_INFO KBUILD_MODNAME
": found laptop model '%s'\n",
496 static struct dmi_system_id __initdata samsung_dmi_table
[] = {
500 DMI_MATCH(DMI_SYS_VENDOR
,
501 "SAMSUNG ELECTRONICS CO., LTD."),
502 DMI_MATCH(DMI_PRODUCT_NAME
, "N128"),
503 DMI_MATCH(DMI_BOARD_NAME
, "N128"),
505 .callback
= dmi_check_cb
,
510 DMI_MATCH(DMI_SYS_VENDOR
,
511 "SAMSUNG ELECTRONICS CO., LTD."),
512 DMI_MATCH(DMI_PRODUCT_NAME
, "N130"),
513 DMI_MATCH(DMI_BOARD_NAME
, "N130"),
515 .callback
= dmi_check_cb
,
520 DMI_MATCH(DMI_SYS_VENDOR
,
521 "SAMSUNG ELECTRONICS CO., LTD."),
522 DMI_MATCH(DMI_PRODUCT_NAME
, "X125"),
523 DMI_MATCH(DMI_BOARD_NAME
, "X125"),
525 .callback
= dmi_check_cb
,
530 DMI_MATCH(DMI_SYS_VENDOR
,
531 "SAMSUNG ELECTRONICS CO., LTD."),
532 DMI_MATCH(DMI_PRODUCT_NAME
, "NC10"),
533 DMI_MATCH(DMI_BOARD_NAME
, "NC10"),
535 .callback
= dmi_check_cb
,
540 DMI_MATCH(DMI_SYS_VENDOR
,
541 "SAMSUNG ELECTRONICS CO., LTD."),
542 DMI_MATCH(DMI_PRODUCT_NAME
, "SQ45S70S"),
543 DMI_MATCH(DMI_BOARD_NAME
, "SQ45S70S"),
545 .callback
= dmi_check_cb
,
549 MODULE_DEVICE_TABLE(dmi
, samsung_dmi_table
);
551 static int find_signature(void __iomem
*memcheck
, const char *testStr
)
556 for (loca
= 0; loca
< 0xffff; loca
++) {
557 char temp
= readb(memcheck
+ loca
);
559 if (temp
== testStr
[i
]) {
560 if (i
== strlen(testStr
)-1)
570 static int __init
samsung_init(void)
572 struct backlight_properties props
;
573 struct sabi_retval sretval
;
579 mutex_init(&sabi_mutex
);
581 if (!force
&& !dmi_check_system(samsung_dmi_table
))
584 f0000_segment
= ioremap(0xf0000, 0xffff);
585 if (!f0000_segment
) {
586 printk(KERN_ERR
"Can't map the segment at 0xf0000\n");
590 /* Try to find one of the signatures in memory to find the header */
591 for (i
= 0; sabi_configs
[i
].test_string
!= 0; ++i
) {
592 sabi_config
= &sabi_configs
[i
];
593 loca
= find_signature(f0000_segment
, sabi_config
->test_string
);
598 if (loca
== 0xffff) {
599 printk(KERN_ERR
"This computer does not support SABI\n");
600 goto error_no_signature
;
603 /* point to the SMI port Number */
605 sabi
= (f0000_segment
+ loca
);
608 printk(KERN_DEBUG
"This computer supports SABI==%x\n",
610 printk(KERN_DEBUG
"SABI header:\n");
611 printk(KERN_DEBUG
" SMI Port Number = 0x%04x\n",
612 readw(sabi
+ sabi_config
->header_offsets
.port
));
613 printk(KERN_DEBUG
" SMI Interface Function = 0x%02x\n",
614 readb(sabi
+ sabi_config
->header_offsets
.iface_func
));
615 printk(KERN_DEBUG
" SMI enable memory buffer = 0x%02x\n",
616 readb(sabi
+ sabi_config
->header_offsets
.en_mem
));
617 printk(KERN_DEBUG
" SMI restore memory buffer = 0x%02x\n",
618 readb(sabi
+ sabi_config
->header_offsets
.re_mem
));
619 printk(KERN_DEBUG
" SABI data offset = 0x%04x\n",
620 readw(sabi
+ sabi_config
->header_offsets
.data_offset
));
621 printk(KERN_DEBUG
" SABI data segment = 0x%04x\n",
622 readw(sabi
+ sabi_config
->header_offsets
.data_segment
));
625 /* Get a pointer to the SABI Interface */
626 ifaceP
= (readw(sabi
+ sabi_config
->header_offsets
.data_segment
) & 0x0ffff) << 4;
627 ifaceP
+= readw(sabi
+ sabi_config
->header_offsets
.data_offset
) & 0x0ffff;
628 sabi_iface
= ioremap(ifaceP
, 16);
630 printk(KERN_ERR
"Can't remap %x\n", ifaceP
);
634 printk(KERN_DEBUG
"ifaceP = 0x%08x\n", ifaceP
);
635 printk(KERN_DEBUG
"sabi_iface = %p\n", sabi_iface
);
640 retval
= sabi_get_command(sabi_config
->commands
.get_brightness
,
642 printk(KERN_DEBUG
"brightness = 0x%02x\n", sretval
.retval
[0]);
645 /* Turn on "Linux" mode in the BIOS */
646 if (sabi_config
->commands
.set_linux
!= 0xff) {
647 retval
= sabi_set_command(sabi_config
->commands
.set_linux
,
650 printk(KERN_ERR KBUILD_MODNAME
": Linux mode was not set!\n");
651 goto error_no_platform
;
655 /* knock up a platform device to hang stuff off of */
656 sdev
= platform_device_register_simple("samsung", -1, NULL
, 0);
658 goto error_no_platform
;
660 /* create a backlight device to talk to this one */
661 memset(&props
, 0, sizeof(struct backlight_properties
));
662 props
.max_brightness
= MAX_BRIGHT
;
663 backlight_device
= backlight_device_register("samsung", &sdev
->dev
,
664 NULL
, &backlight_ops
,
666 if (IS_ERR(backlight_device
))
667 goto error_no_backlight
;
669 backlight_device
->props
.brightness
= read_brightness();
670 backlight_device
->props
.power
= FB_BLANK_UNBLANK
;
671 backlight_update_status(backlight_device
);
673 retval
= init_wireless(sdev
);
677 retval
= device_create_file(&sdev
->dev
, &dev_attr_performance_level
);
679 goto error_file_create
;
688 backlight_device_unregister(backlight_device
);
691 platform_device_unregister(sdev
);
697 iounmap(f0000_segment
);
701 static void __exit
samsung_exit(void)
703 /* Turn off "Linux" mode in the BIOS */
704 if (sabi_config
->commands
.set_linux
!= 0xff)
705 sabi_set_command(sabi_config
->commands
.set_linux
, 0x80);
707 device_remove_file(&sdev
->dev
, &dev_attr_performance_level
);
708 backlight_device_unregister(backlight_device
);
711 iounmap(f0000_segment
);
712 platform_device_unregister(sdev
);
715 module_init(samsung_init
);
716 module_exit(samsung_exit
);
718 MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
719 MODULE_DESCRIPTION("Samsung Backlight driver");
720 MODULE_LICENSE("GPL");