aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@suse.de>2011-03-11 12:41:19 -0500
committerMatthew Garrett <mjg@redhat.com>2011-03-28 06:28:12 -0400
commit2d70b73ae5b85c9d13f5dfbb8fc4fd5edae633dc (patch)
tree63823f6eed7ca8bfa487149e71c077a724d736d9
parent298f19b2547ba11a577a15ca329daa6f4bbf5ad8 (diff)
Platform: add Samsung Laptop platform driver
This adds the samsung-laptop driver to the kernel. It now supports all known Samsung laptops that use the SABI interface. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> Signed-off-by: Matthew Garrett <mjg@redhat.com>
-rw-r--r--Documentation/ABI/testing/sysfs-driver-samsung-laptop19
-rw-r--r--drivers/platform/x86/Kconfig13
-rw-r--r--drivers/platform/x86/Makefile1
-rw-r--r--drivers/platform/x86/samsung-laptop.c832
4 files changed, 865 insertions, 0 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-samsung-laptop b/Documentation/ABI/testing/sysfs-driver-samsung-laptop
new file mode 100644
index 00000000000..0a810231aad
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-samsung-laptop
@@ -0,0 +1,19 @@
1What: /sys/devices/platform/samsung/performance_level
2Date: January 1, 2010
3KernelVersion: 2.6.33
4Contact: Greg Kroah-Hartman <gregkh@suse.de>
5Description: Some Samsung laptops have different "performance levels"
6 that are can be modified by a function key, and by this
7 sysfs file. These values don't always make a whole lot
8 of sense, but some users like to modify them to keep
9 their fans quiet at all costs. Reading from this file
10 will show the current performance level. Writing to the
11 file can change this value.
12 Valid options:
13 "silent"
14 "normal"
15 "overclock"
16 Note that not all laptops support all of these options.
17 Specifically, not all support the "overclock" option,
18 and it's still unknown if this value even changes
19 anything, other than making the user feel a bit better.
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 0253157085d..2ee442c2a5d 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -739,4 +739,17 @@ config XO15_EBOOK
739 This switch is triggered as the screen is rotated and folded down to 739 This switch is triggered as the screen is rotated and folded down to
740 convert the device into ebook form. 740 convert the device into ebook form.
741 741
742config SAMSUNG_LAPTOP
743 tristate "Samsung Laptop driver"
744 depends on RFKILL && BACKLIGHT_CLASS_DEVICE && X86
745 ---help---
746 This module implements a driver for a wide range of different
747 Samsung laptops. It offers control over the different
748 function keys, wireless LED, LCD backlight level, and
749 sometimes provides a "performance_control" sysfs file to allow
750 the performance level of the laptop to be changed.
751
752 To compile this driver as a module, choose M here: the module
753 will be called samsung-laptop.
754
742endif # X86_PLATFORM_DEVICES 755endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 0f7b23c0714..029e8861d08 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -40,4 +40,5 @@ obj-$(CONFIG_GPIO_INTEL_PMIC) += intel_pmic_gpio.o
40obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o 40obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o
41obj-$(CONFIG_XO15_EBOOK) += xo15-ebook.o 41obj-$(CONFIG_XO15_EBOOK) += xo15-ebook.o
42obj-$(CONFIG_IBM_RTL) += ibm_rtl.o 42obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
43obj-$(CONFIG_SAMSUNG_LAPTOP) += samsung-laptop.o
43obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o 44obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c
new file mode 100644
index 00000000000..e0b390d45d8
--- /dev/null
+++ b/drivers/platform/x86/samsung-laptop.c
@@ -0,0 +1,832 @@
1/*
2 * Samsung Laptop driver
3 *
4 * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
5 * Copyright (C) 2009,2011 Novell Inc.
6 *
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.
10 *
11 */
12#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13
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>
20#include <linux/fb.h>
21#include <linux/dmi.h>
22#include <linux/platform_device.h>
23#include <linux/rfkill.h>
24
25/*
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.
29 */
30
31/*
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
35 */
36#define MAX_BRIGHT 0x07
37
38
39#define SABI_IFACE_MAIN 0x00
40#define SABI_IFACE_SUB 0x02
41#define SABI_IFACE_COMPLETE 0x04
42#define SABI_IFACE_DATA 0x05
43
44/* Structure to get data back to the calling function */
45struct sabi_retval {
46 u8 retval[20];
47};
48
49struct sabi_header_offsets {
50 u8 port;
51 u8 re_mem;
52 u8 iface_func;
53 u8 en_mem;
54 u8 data_offset;
55 u8 data_segment;
56};
57
58struct sabi_commands {
59 /*
60 * Brightness is 0 - 8, as described above.
61 * Value 0 is for the BIOS to use
62 */
63 u8 get_brightness;
64 u8 set_brightness;
65
66 /*
67 * first byte:
68 * 0x00 - wireless is off
69 * 0x01 - wireless is on
70 * second byte:
71 * 0x02 - 3G is off
72 * 0x03 - 3G is on
73 * TODO, verify 3G is correct, that doesn't seem right...
74 */
75 u8 get_wireless_button;
76 u8 set_wireless_button;
77
78 /* 0 is off, 1 is on */
79 u8 get_backlight;
80 u8 set_backlight;
81
82 /*
83 * 0x80 or 0x00 - no action
84 * 0x81 - recovery key pressed
85 */
86 u8 get_recovery_mode;
87 u8 set_recovery_mode;
88
89 /*
90 * on seclinux: 0 is low, 1 is high,
91 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
92 */
93 u8 get_performance_level;
94 u8 set_performance_level;
95
96 /*
97 * Tell the BIOS that Linux is running on this machine.
98 * 81 is on, 80 is off
99 */
100 u8 set_linux;
101};
102
103struct sabi_performance_level {
104 const char *name;
105 u8 value;
106};
107
108struct sabi_config {
109 const char *test_string;
110 u16 main_function;
111 const struct sabi_header_offsets header_offsets;
112 const struct sabi_commands commands;
113 const struct sabi_performance_level performance_levels[4];
114 u8 min_brightness;
115 u8 max_brightness;
116};
117
118static const struct sabi_config sabi_configs[] = {
119 {
120 .test_string = "SECLINUX",
121
122 .main_function = 0x4c49,
123
124 .header_offsets = {
125 .port = 0x00,
126 .re_mem = 0x02,
127 .iface_func = 0x03,
128 .en_mem = 0x04,
129 .data_offset = 0x05,
130 .data_segment = 0x07,
131 },
132
133 .commands = {
134 .get_brightness = 0x00,
135 .set_brightness = 0x01,
136
137 .get_wireless_button = 0x02,
138 .set_wireless_button = 0x03,
139
140 .get_backlight = 0x04,
141 .set_backlight = 0x05,
142
143 .get_recovery_mode = 0x06,
144 .set_recovery_mode = 0x07,
145
146 .get_performance_level = 0x08,
147 .set_performance_level = 0x09,
148
149 .set_linux = 0x0a,
150 },
151
152 .performance_levels = {
153 {
154 .name = "silent",
155 .value = 0,
156 },
157 {
158 .name = "normal",
159 .value = 1,
160 },
161 { },
162 },
163 .min_brightness = 1,
164 .max_brightness = 8,
165 },
166 {
167 .test_string = "SwSmi@",
168
169 .main_function = 0x5843,
170
171 .header_offsets = {
172 .port = 0x00,
173 .re_mem = 0x04,
174 .iface_func = 0x02,
175 .en_mem = 0x03,
176 .data_offset = 0x05,
177 .data_segment = 0x07,
178 },
179
180 .commands = {
181 .get_brightness = 0x10,
182 .set_brightness = 0x11,
183
184 .get_wireless_button = 0x12,
185 .set_wireless_button = 0x13,
186
187 .get_backlight = 0x2d,
188 .set_backlight = 0x2e,
189
190 .get_recovery_mode = 0xff,
191 .set_recovery_mode = 0xff,
192
193 .get_performance_level = 0x31,
194 .set_performance_level = 0x32,
195
196 .set_linux = 0xff,
197 },
198
199 .performance_levels = {
200 {
201 .name = "normal",
202 .value = 0,
203 },
204 {
205 .name = "silent",
206 .value = 1,
207 },
208 {
209 .name = "overclock",
210 .value = 2,
211 },
212 { },
213 },
214 .min_brightness = 0,
215 .max_brightness = 8,
216 },
217 { },
218};
219
220static const struct sabi_config *sabi_config;
221
222static void __iomem *sabi;
223static void __iomem *sabi_iface;
224static void __iomem *f0000_segment;
225static struct backlight_device *backlight_device;
226static struct mutex sabi_mutex;
227static struct platform_device *sdev;
228static struct rfkill *rfk;
229
230static int force;
231module_param(force, bool, 0);
232MODULE_PARM_DESC(force,
233 "Disable the DMI check and forces the driver to be loaded");
234
235static int debug;
236module_param(debug, bool, S_IRUGO | S_IWUSR);
237MODULE_PARM_DESC(debug, "Debug enabled or not");
238
239static int sabi_get_command(u8 command, struct sabi_retval *sretval)
240{
241 int retval = 0;
242 u16 port = readw(sabi + sabi_config->header_offsets.port);
243 u8 complete, iface_data;
244
245 mutex_lock(&sabi_mutex);
246
247 /* enable memory to be able to write to it */
248 outb(readb(sabi + sabi_config->header_offsets.en_mem), port);
249
250 /* write out the command */
251 writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN);
252 writew(command, sabi_iface + SABI_IFACE_SUB);
253 writeb(0, sabi_iface + SABI_IFACE_COMPLETE);
254 outb(readb(sabi + sabi_config->header_offsets.iface_func), port);
255
256 /* write protect memory to make it safe */
257 outb(readb(sabi + sabi_config->header_offsets.re_mem), port);
258
259 /* see if the command actually succeeded */
260 complete = readb(sabi_iface + SABI_IFACE_COMPLETE);
261 iface_data = readb(sabi_iface + SABI_IFACE_DATA);
262 if (complete != 0xaa || iface_data == 0xff) {
263 pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
264 command, complete, iface_data);
265 retval = -EINVAL;
266 goto exit;
267 }
268 /*
269 * Save off the data into a structure so the caller use it.
270 * Right now we only want the first 4 bytes,
271 * There are commands that need more, but not for the ones we
272 * currently care about.
273 */
274 sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA);
275 sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1);
276 sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2);
277 sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3);
278
279exit:
280 mutex_unlock(&sabi_mutex);
281 return retval;
282
283}
284
285static int sabi_set_command(u8 command, u8 data)
286{
287 int retval = 0;
288 u16 port = readw(sabi + sabi_config->header_offsets.port);
289 u8 complete, iface_data;
290
291 mutex_lock(&sabi_mutex);
292
293 /* enable memory to be able to write to it */
294 outb(readb(sabi + sabi_config->header_offsets.en_mem), port);
295
296 /* write out the command */
297 writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN);
298 writew(command, sabi_iface + SABI_IFACE_SUB);
299 writeb(0, sabi_iface + SABI_IFACE_COMPLETE);
300 writeb(data, sabi_iface + SABI_IFACE_DATA);
301 outb(readb(sabi + sabi_config->header_offsets.iface_func), port);
302
303 /* write protect memory to make it safe */
304 outb(readb(sabi + sabi_config->header_offsets.re_mem), port);
305
306 /* see if the command actually succeeded */
307 complete = readb(sabi_iface + SABI_IFACE_COMPLETE);
308 iface_data = readb(sabi_iface + SABI_IFACE_DATA);
309 if (complete != 0xaa || iface_data == 0xff) {
310 pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
311 command, complete, iface_data);
312 retval = -EINVAL;
313 }
314
315 mutex_unlock(&sabi_mutex);
316 return retval;
317}
318
319static void test_backlight(void)
320{
321 struct sabi_retval sretval;
322
323 sabi_get_command(sabi_config->commands.get_backlight, &sretval);
324 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
325
326 sabi_set_command(sabi_config->commands.set_backlight, 0);
327 printk(KERN_DEBUG "backlight should be off\n");
328
329 sabi_get_command(sabi_config->commands.get_backlight, &sretval);
330 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
331
332 msleep(1000);
333
334 sabi_set_command(sabi_config->commands.set_backlight, 1);
335 printk(KERN_DEBUG "backlight should be on\n");
336
337 sabi_get_command(sabi_config->commands.get_backlight, &sretval);
338 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
339}
340
341static void test_wireless(void)
342{
343 struct sabi_retval sretval;
344
345 sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
346 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
347
348 sabi_set_command(sabi_config->commands.set_wireless_button, 0);
349 printk(KERN_DEBUG "wireless led should be off\n");
350
351 sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
352 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
353
354 msleep(1000);
355
356 sabi_set_command(sabi_config->commands.set_wireless_button, 1);
357 printk(KERN_DEBUG "wireless led should be on\n");
358
359 sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
360 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
361}
362
363static u8 read_brightness(void)
364{
365 struct sabi_retval sretval;
366 int user_brightness = 0;
367 int retval;
368
369 retval = sabi_get_command(sabi_config->commands.get_brightness,
370 &sretval);
371 if (!retval) {
372 user_brightness = sretval.retval[0];
373 if (user_brightness != 0)
374 user_brightness -= sabi_config->min_brightness;
375 }
376 return user_brightness;
377}
378
379static void set_brightness(u8 user_brightness)
380{
381 u8 user_level = user_brightness - sabi_config->min_brightness;
382
383 sabi_set_command(sabi_config->commands.set_brightness, user_level);
384}
385
386static int get_brightness(struct backlight_device *bd)
387{
388 return (int)read_brightness();
389}
390
391static int update_status(struct backlight_device *bd)
392{
393 set_brightness(bd->props.brightness);
394
395 if (bd->props.power == FB_BLANK_UNBLANK)
396 sabi_set_command(sabi_config->commands.set_backlight, 1);
397 else
398 sabi_set_command(sabi_config->commands.set_backlight, 0);
399 return 0;
400}
401
402static const struct backlight_ops backlight_ops = {
403 .get_brightness = get_brightness,
404 .update_status = update_status,
405};
406
407static int rfkill_set(void *data, bool blocked)
408{
409 /* Do something with blocked...*/
410 /*
411 * blocked == false is on
412 * blocked == true is off
413 */
414 if (blocked)
415 sabi_set_command(sabi_config->commands.set_wireless_button, 0);
416 else
417 sabi_set_command(sabi_config->commands.set_wireless_button, 1);
418
419 return 0;
420}
421
422static struct rfkill_ops rfkill_ops = {
423 .set_block = rfkill_set,
424};
425
426static int init_wireless(struct platform_device *sdev)
427{
428 int retval;
429
430 rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN,
431 &rfkill_ops, NULL);
432 if (!rfk)
433 return -ENOMEM;
434
435 retval = rfkill_register(rfk);
436 if (retval) {
437 rfkill_destroy(rfk);
438 return -ENODEV;
439 }
440
441 return 0;
442}
443
444static void destroy_wireless(void)
445{
446 rfkill_unregister(rfk);
447 rfkill_destroy(rfk);
448}
449
450static ssize_t get_performance_level(struct device *dev,
451 struct device_attribute *attr, char *buf)
452{
453 struct sabi_retval sretval;
454 int retval;
455 int i;
456
457 /* Read the state */
458 retval = sabi_get_command(sabi_config->commands.get_performance_level,
459 &sretval);
460 if (retval)
461 return retval;
462
463 /* The logic is backwards, yeah, lots of fun... */
464 for (i = 0; sabi_config->performance_levels[i].name; ++i) {
465 if (sretval.retval[0] == sabi_config->performance_levels[i].value)
466 return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name);
467 }
468 return sprintf(buf, "%s\n", "unknown");
469}
470
471static ssize_t set_performance_level(struct device *dev,
472 struct device_attribute *attr, const char *buf,
473 size_t count)
474{
475 if (count >= 1) {
476 int i;
477 for (i = 0; sabi_config->performance_levels[i].name; ++i) {
478 const struct sabi_performance_level *level =
479 &sabi_config->performance_levels[i];
480 if (!strncasecmp(level->name, buf, strlen(level->name))) {
481 sabi_set_command(sabi_config->commands.set_performance_level,
482 level->value);
483 break;
484 }
485 }
486 if (!sabi_config->performance_levels[i].name)
487 return -EINVAL;
488 }
489 return count;
490}
491static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
492 get_performance_level, set_performance_level);
493
494
495static int __init dmi_check_cb(const struct dmi_system_id *id)
496{
497 pr_info("found laptop model '%s'\n",
498 id->ident);
499 return 0;
500}
501
502static struct dmi_system_id __initdata samsung_dmi_table[] = {
503 {
504 .ident = "N128",
505 .matches = {
506 DMI_MATCH(DMI_SYS_VENDOR,
507 "SAMSUNG ELECTRONICS CO., LTD."),
508 DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
509 DMI_MATCH(DMI_BOARD_NAME, "N128"),
510 },
511 .callback = dmi_check_cb,
512 },
513 {
514 .ident = "N130",
515 .matches = {
516 DMI_MATCH(DMI_SYS_VENDOR,
517 "SAMSUNG ELECTRONICS CO., LTD."),
518 DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
519 DMI_MATCH(DMI_BOARD_NAME, "N130"),
520 },
521 .callback = dmi_check_cb,
522 },
523 {
524 .ident = "X125",
525 .matches = {
526 DMI_MATCH(DMI_SYS_VENDOR,
527 "SAMSUNG ELECTRONICS CO., LTD."),
528 DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
529 DMI_MATCH(DMI_BOARD_NAME, "X125"),
530 },
531 .callback = dmi_check_cb,
532 },
533 {
534 .ident = "X120/X170",
535 .matches = {
536 DMI_MATCH(DMI_SYS_VENDOR,
537 "SAMSUNG ELECTRONICS CO., LTD."),
538 DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
539 DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
540 },
541 .callback = dmi_check_cb,
542 },
543 {
544 .ident = "NC10",
545 .matches = {
546 DMI_MATCH(DMI_SYS_VENDOR,
547 "SAMSUNG ELECTRONICS CO., LTD."),
548 DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
549 DMI_MATCH(DMI_BOARD_NAME, "NC10"),
550 },
551 .callback = dmi_check_cb,
552 },
553 {
554 .ident = "NP-Q45",
555 .matches = {
556 DMI_MATCH(DMI_SYS_VENDOR,
557 "SAMSUNG ELECTRONICS CO., LTD."),
558 DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
559 DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
560 },
561 .callback = dmi_check_cb,
562 },
563 {
564 .ident = "X360",
565 .matches = {
566 DMI_MATCH(DMI_SYS_VENDOR,
567 "SAMSUNG ELECTRONICS CO., LTD."),
568 DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
569 DMI_MATCH(DMI_BOARD_NAME, "X360"),
570 },
571 .callback = dmi_check_cb,
572 },
573 {
574 .ident = "R518",
575 .matches = {
576 DMI_MATCH(DMI_SYS_VENDOR,
577 "SAMSUNG ELECTRONICS CO., LTD."),
578 DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
579 DMI_MATCH(DMI_BOARD_NAME, "R518"),
580 },
581 .callback = dmi_check_cb,
582 },
583 {
584 .ident = "R519/R719",
585 .matches = {
586 DMI_MATCH(DMI_SYS_VENDOR,
587 "SAMSUNG ELECTRONICS CO., LTD."),
588 DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
589 DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
590 },
591 .callback = dmi_check_cb,
592 },
593 {
594 .ident = "N150/N210/N220",
595 .matches = {
596 DMI_MATCH(DMI_SYS_VENDOR,
597 "SAMSUNG ELECTRONICS CO., LTD."),
598 DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
599 DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
600 },
601 .callback = dmi_check_cb,
602 },
603 {
604 .ident = "N150P/N210P/N220P",
605 .matches = {
606 DMI_MATCH(DMI_SYS_VENDOR,
607 "SAMSUNG ELECTRONICS CO., LTD."),
608 DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
609 DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
610 },
611 .callback = dmi_check_cb,
612 },
613 {
614 .ident = "R530/R730",
615 .matches = {
616 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
617 DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
618 DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
619 },
620 .callback = dmi_check_cb,
621 },
622 {
623 .ident = "NF110/NF210/NF310",
624 .matches = {
625 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
626 DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
627 DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
628 },
629 .callback = dmi_check_cb,
630 },
631 {
632 .ident = "N145P/N250P/N260P",
633 .matches = {
634 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
635 DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
636 DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
637 },
638 .callback = dmi_check_cb,
639 },
640 {
641 .ident = "R70/R71",
642 .matches = {
643 DMI_MATCH(DMI_SYS_VENDOR,
644 "SAMSUNG ELECTRONICS CO., LTD."),
645 DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
646 DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
647 },
648 .callback = dmi_check_cb,
649 },
650 {
651 .ident = "P460",
652 .matches = {
653 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
654 DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
655 DMI_MATCH(DMI_BOARD_NAME, "P460"),
656 },
657 .callback = dmi_check_cb,
658 },
659 { },
660};
661MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
662
663static int find_signature(void __iomem *memcheck, const char *testStr)
664{
665 int i = 0;
666 int loca;
667
668 for (loca = 0; loca < 0xffff; loca++) {
669 char temp = readb(memcheck + loca);
670
671 if (temp == testStr[i]) {
672 if (i == strlen(testStr)-1)
673 break;
674 ++i;
675 } else {
676 i = 0;
677 }
678 }
679 return loca;
680}
681
682static int __init samsung_init(void)
683{
684 struct backlight_properties props;
685 struct sabi_retval sretval;
686 unsigned int ifaceP;
687 int i;
688 int loca;
689 int retval;
690
691 mutex_init(&sabi_mutex);
692
693 if (!force && !dmi_check_system(samsung_dmi_table))
694 return -ENODEV;
695
696 f0000_segment = ioremap_nocache(0xf0000, 0xffff);
697 if (!f0000_segment) {
698 pr_err("Can't map the segment at 0xf0000\n");
699 return -EINVAL;
700 }
701
702 /* Try to find one of the signatures in memory to find the header */
703 for (i = 0; sabi_configs[i].test_string != 0; ++i) {
704 sabi_config = &sabi_configs[i];
705 loca = find_signature(f0000_segment, sabi_config->test_string);
706 if (loca != 0xffff)
707 break;
708 }
709
710 if (loca == 0xffff) {
711 pr_err("This computer does not support SABI\n");
712 goto error_no_signature;
713 }
714
715 /* point to the SMI port Number */
716 loca += 1;
717 sabi = (f0000_segment + loca);
718
719 if (debug) {
720 printk(KERN_DEBUG "This computer supports SABI==%x\n",
721 loca + 0xf0000 - 6);
722 printk(KERN_DEBUG "SABI header:\n");
723 printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
724 readw(sabi + sabi_config->header_offsets.port));
725 printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
726 readb(sabi + sabi_config->header_offsets.iface_func));
727 printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
728 readb(sabi + sabi_config->header_offsets.en_mem));
729 printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
730 readb(sabi + sabi_config->header_offsets.re_mem));
731 printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
732 readw(sabi + sabi_config->header_offsets.data_offset));
733 printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
734 readw(sabi + sabi_config->header_offsets.data_segment));
735 }
736
737 /* Get a pointer to the SABI Interface */
738 ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4;
739 ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff;
740 sabi_iface = ioremap_nocache(ifaceP, 16);
741 if (!sabi_iface) {
742 pr_err("Can't remap %x\n", ifaceP);
743 goto exit;
744 }
745 if (debug) {
746 printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP);
747 printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface);
748
749 test_backlight();
750 test_wireless();
751
752 retval = sabi_get_command(sabi_config->commands.get_brightness,
753 &sretval);
754 printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]);
755 }
756
757 /* Turn on "Linux" mode in the BIOS */
758 if (sabi_config->commands.set_linux != 0xff) {
759 retval = sabi_set_command(sabi_config->commands.set_linux,
760 0x81);
761 if (retval) {
762 pr_warn("Linux mode was not set!\n");
763 goto error_no_platform;
764 }
765 }
766
767 /* knock up a platform device to hang stuff off of */
768 sdev = platform_device_register_simple("samsung", -1, NULL, 0);
769 if (IS_ERR(sdev))
770 goto error_no_platform;
771
772 /* create a backlight device to talk to this one */
773 memset(&props, 0, sizeof(struct backlight_properties));
774 props.max_brightness = sabi_config->max_brightness;
775 backlight_device = backlight_device_register("samsung", &sdev->dev,
776 NULL, &backlight_ops,
777 &props);
778 if (IS_ERR(backlight_device))
779 goto error_no_backlight;
780
781 backlight_device->props.brightness = read_brightness();
782 backlight_device->props.power = FB_BLANK_UNBLANK;
783 backlight_update_status(backlight_device);
784
785 retval = init_wireless(sdev);
786 if (retval)
787 goto error_no_rfk;
788
789 retval = device_create_file(&sdev->dev, &dev_attr_performance_level);
790 if (retval)
791 goto error_file_create;
792
793exit:
794 return 0;
795
796error_file_create:
797 destroy_wireless();
798
799error_no_rfk:
800 backlight_device_unregister(backlight_device);
801
802error_no_backlight:
803 platform_device_unregister(sdev);
804
805error_no_platform:
806 iounmap(sabi_iface);
807
808error_no_signature:
809 iounmap(f0000_segment);
810 return -EINVAL;
811}
812
813static void __exit samsung_exit(void)
814{
815 /* Turn off "Linux" mode in the BIOS */
816 if (sabi_config->commands.set_linux != 0xff)
817 sabi_set_command(sabi_config->commands.set_linux, 0x80);
818
819 device_remove_file(&sdev->dev, &dev_attr_performance_level);
820 backlight_device_unregister(backlight_device);
821 destroy_wireless();
822 iounmap(sabi_iface);
823 iounmap(f0000_segment);
824 platform_device_unregister(sdev);
825}
826
827module_init(samsung_init);
828module_exit(samsung_exit);
829
830MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
831MODULE_DESCRIPTION("Samsung Backlight driver");
832MODULE_LICENSE("GPL");