]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/platform/x86/samsung-laptop.c
samsung-laptop: add battery life extender support
[karo-tx-linux.git] / drivers / platform / x86 / samsung-laptop.c
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 #include <linux/acpi.h>
25 #include <linux/seq_file.h>
26 #include <linux/debugfs.h>
27
28 /*
29  * This driver is needed because a number of Samsung laptops do not hook
30  * their control settings through ACPI.  So we have to poke around in the
31  * BIOS to do things like brightness values, and "special" key controls.
32  */
33
34 /*
35  * We have 0 - 8 as valid brightness levels.  The specs say that level 0 should
36  * be reserved by the BIOS (which really doesn't make much sense), we tell
37  * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
38  */
39 #define MAX_BRIGHT      0x07
40
41
42 #define SABI_IFACE_MAIN                 0x00
43 #define SABI_IFACE_SUB                  0x02
44 #define SABI_IFACE_COMPLETE             0x04
45 #define SABI_IFACE_DATA                 0x05
46
47 /* Structure get/set data using sabi */
48 struct sabi_data {
49         union {
50                 struct {
51                         u32 d0;
52                         u32 d1;
53                         u16 d2;
54                         u8  d3;
55                 };
56                 u8 data[11];
57         };
58 };
59
60 struct sabi_header_offsets {
61         u8 port;
62         u8 re_mem;
63         u8 iface_func;
64         u8 en_mem;
65         u8 data_offset;
66         u8 data_segment;
67 };
68
69 struct sabi_commands {
70         /*
71          * Brightness is 0 - 8, as described above.
72          * Value 0 is for the BIOS to use
73          */
74         u16 get_brightness;
75         u16 set_brightness;
76
77         /*
78          * first byte:
79          * 0x00 - wireless is off
80          * 0x01 - wireless is on
81          * second byte:
82          * 0x02 - 3G is off
83          * 0x03 - 3G is on
84          * TODO, verify 3G is correct, that doesn't seem right...
85          */
86         u16 get_wireless_button;
87         u16 set_wireless_button;
88
89         /* 0 is off, 1 is on */
90         u16 get_backlight;
91         u16 set_backlight;
92
93         /*
94          * 0x80 or 0x00 - no action
95          * 0x81 - recovery key pressed
96          */
97         u16 get_recovery_mode;
98         u16 set_recovery_mode;
99
100         /*
101          * on seclinux: 0 is low, 1 is high,
102          * on swsmi: 0 is normal, 1 is silent, 2 is turbo
103          */
104         u16 get_performance_level;
105         u16 set_performance_level;
106
107         /* 0x80 is off, 0x81 is on */
108         u16 get_battery_life_extender;
109         u16 set_battery_life_extender;
110
111         /*
112          * Tell the BIOS that Linux is running on this machine.
113          * 81 is on, 80 is off
114          */
115         u16 set_linux;
116 };
117
118 struct sabi_performance_level {
119         const char *name;
120         u16 value;
121 };
122
123 struct sabi_config {
124         const char *test_string;
125         u16 main_function;
126         const struct sabi_header_offsets header_offsets;
127         const struct sabi_commands commands;
128         const struct sabi_performance_level performance_levels[4];
129         u8 min_brightness;
130         u8 max_brightness;
131 };
132
133 static const struct sabi_config sabi_configs[] = {
134         {
135                 .test_string = "SECLINUX",
136
137                 .main_function = 0x4c49,
138
139                 .header_offsets = {
140                         .port = 0x00,
141                         .re_mem = 0x02,
142                         .iface_func = 0x03,
143                         .en_mem = 0x04,
144                         .data_offset = 0x05,
145                         .data_segment = 0x07,
146                 },
147
148                 .commands = {
149                         .get_brightness = 0x00,
150                         .set_brightness = 0x01,
151
152                         .get_wireless_button = 0x02,
153                         .set_wireless_button = 0x03,
154
155                         .get_backlight = 0x04,
156                         .set_backlight = 0x05,
157
158                         .get_recovery_mode = 0x06,
159                         .set_recovery_mode = 0x07,
160
161                         .get_performance_level = 0x08,
162                         .set_performance_level = 0x09,
163
164                         .get_battery_life_extender = 0xFFFF,
165                         .set_battery_life_extender = 0xFFFF,
166
167                         .set_linux = 0x0a,
168                 },
169
170                 .performance_levels = {
171                         {
172                                 .name = "silent",
173                                 .value = 0,
174                         },
175                         {
176                                 .name = "normal",
177                                 .value = 1,
178                         },
179                         { },
180                 },
181                 .min_brightness = 1,
182                 .max_brightness = 8,
183         },
184         {
185                 .test_string = "SwSmi@",
186
187                 .main_function = 0x5843,
188
189                 .header_offsets = {
190                         .port = 0x00,
191                         .re_mem = 0x04,
192                         .iface_func = 0x02,
193                         .en_mem = 0x03,
194                         .data_offset = 0x05,
195                         .data_segment = 0x07,
196                 },
197
198                 .commands = {
199                         .get_brightness = 0x10,
200                         .set_brightness = 0x11,
201
202                         .get_wireless_button = 0x12,
203                         .set_wireless_button = 0x13,
204
205                         .get_backlight = 0x2d,
206                         .set_backlight = 0x2e,
207
208                         .get_recovery_mode = 0xff,
209                         .set_recovery_mode = 0xff,
210
211                         .get_performance_level = 0x31,
212                         .set_performance_level = 0x32,
213
214                         .get_battery_life_extender = 0x65,
215                         .set_battery_life_extender = 0x66,
216
217                         .set_linux = 0xff,
218                 },
219
220                 .performance_levels = {
221                         {
222                                 .name = "normal",
223                                 .value = 0,
224                         },
225                         {
226                                 .name = "silent",
227                                 .value = 1,
228                         },
229                         {
230                                 .name = "overclock",
231                                 .value = 2,
232                         },
233                         { },
234                 },
235                 .min_brightness = 0,
236                 .max_brightness = 8,
237         },
238         { },
239 };
240
241 /*
242  * samsung-laptop/    - debugfs root directory
243  *   f0000_segment    - dump f0000 segment
244  *   command          - current command
245  *   data             - current data
246  *   d0, d1, d2, d3   - data fields
247  *   call             - call SABI using command and data
248  *
249  * This allow to call arbitrary sabi commands wihout
250  * modifying the driver at all.
251  * For example, setting the keyboard backlight brightness to 5
252  *
253  *  echo 0x78 > command
254  *  echo 0x0582 > d0
255  *  echo 0 > d1
256  *  echo 0 > d2
257  *  echo 0 > d3
258  *  cat call
259  */
260
261 struct samsung_laptop_debug {
262         struct dentry *root;
263         struct sabi_data data;
264         u16 command;
265
266         struct debugfs_blob_wrapper f0000_wrapper;
267         struct debugfs_blob_wrapper data_wrapper;
268 };
269
270 struct samsung_laptop {
271         const struct sabi_config *config;
272
273         void __iomem *sabi;
274         void __iomem *sabi_iface;
275         void __iomem *f0000_segment;
276
277         struct mutex sabi_mutex;
278
279         struct platform_device *platform_device;
280         struct backlight_device *backlight_device;
281         struct rfkill *rfk;
282
283         struct samsung_laptop_debug debug;
284
285         bool handle_backlight;
286         bool has_stepping_quirk;
287 };
288
289
290
291 static bool force;
292 module_param(force, bool, 0);
293 MODULE_PARM_DESC(force,
294                 "Disable the DMI check and forces the driver to be loaded");
295
296 static bool debug;
297 module_param(debug, bool, S_IRUGO | S_IWUSR);
298 MODULE_PARM_DESC(debug, "Debug enabled or not");
299
300 static int sabi_command(struct samsung_laptop *samsung, u16 command,
301                         struct sabi_data *in,
302                         struct sabi_data *out)
303 {
304         const struct sabi_config *config = samsung->config;
305         int ret = 0;
306         u16 port = readw(samsung->sabi + config->header_offsets.port);
307         u8 complete, iface_data;
308
309         mutex_lock(&samsung->sabi_mutex);
310
311         if (debug) {
312                 if (in)
313                         pr_info("SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
314                                 command, in->d0, in->d1, in->d2, in->d3);
315                 else
316                         pr_info("SABI 0x%04x", command);
317         }
318
319         /* enable memory to be able to write to it */
320         outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
321
322         /* write out the command */
323         writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
324         writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
325         writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
326         if (in) {
327                 writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
328                 writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
329                 writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
330                 writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
331         }
332         outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
333
334         /* write protect memory to make it safe */
335         outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
336
337         /* see if the command actually succeeded */
338         complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
339         iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
340         if (complete != 0xaa || iface_data == 0xff) {
341                 pr_warn("SABI command 0x%04x failed with"
342                         " completion flag 0x%02x and interface data 0x%02x",
343                         command, complete, iface_data);
344                 ret = -EINVAL;
345                 goto exit;
346         }
347
348         if (out) {
349                 out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
350                 out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
351                 out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
352                 out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
353         }
354
355         if (debug && out) {
356                 pr_info("SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
357                         out->d0, out->d1, out->d2, out->d3);
358         }
359
360 exit:
361         mutex_unlock(&samsung->sabi_mutex);
362         return ret;
363 }
364
365 /* simple wrappers usable with most commands */
366 static int sabi_set_commandb(struct samsung_laptop *samsung,
367                              u16 command, u8 data)
368 {
369         struct sabi_data in = { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 };
370
371         in.data[0] = data;
372         return sabi_command(samsung, command, &in, NULL);
373 }
374
375 static int read_brightness(struct samsung_laptop *samsung)
376 {
377         const struct sabi_config *config = samsung->config;
378         const struct sabi_commands *commands = &samsung->config->commands;
379         struct sabi_data sretval;
380         int user_brightness = 0;
381         int retval;
382
383         retval = sabi_command(samsung, commands->get_brightness,
384                               NULL, &sretval);
385         if (retval)
386                 return retval;
387
388         user_brightness = sretval.data[0];
389         if (user_brightness > config->min_brightness)
390                 user_brightness -= config->min_brightness;
391         else
392                 user_brightness = 0;
393
394         return user_brightness;
395 }
396
397 static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
398 {
399         const struct sabi_config *config = samsung->config;
400         const struct sabi_commands *commands = &samsung->config->commands;
401         u8 user_level = user_brightness + config->min_brightness;
402
403         if (samsung->has_stepping_quirk && user_level != 0) {
404                 /*
405                  * short circuit if the specified level is what's already set
406                  * to prevent the screen from flickering needlessly
407                  */
408                 if (user_brightness == read_brightness(samsung))
409                         return;
410
411                 sabi_set_commandb(samsung, commands->set_brightness, 0);
412         }
413
414         sabi_set_commandb(samsung, commands->set_brightness, user_level);
415 }
416
417 static int get_brightness(struct backlight_device *bd)
418 {
419         struct samsung_laptop *samsung = bl_get_data(bd);
420
421         return read_brightness(samsung);
422 }
423
424 static void check_for_stepping_quirk(struct samsung_laptop *samsung)
425 {
426         int initial_level;
427         int check_level;
428         int orig_level = read_brightness(samsung);
429
430         /*
431          * Some laptops exhibit the strange behaviour of stepping toward
432          * (rather than setting) the brightness except when changing to/from
433          * brightness level 0. This behaviour is checked for here and worked
434          * around in set_brightness.
435          */
436
437         if (orig_level == 0)
438                 set_brightness(samsung, 1);
439
440         initial_level = read_brightness(samsung);
441
442         if (initial_level <= 2)
443                 check_level = initial_level + 2;
444         else
445                 check_level = initial_level - 2;
446
447         samsung->has_stepping_quirk = false;
448         set_brightness(samsung, check_level);
449
450         if (read_brightness(samsung) != check_level) {
451                 samsung->has_stepping_quirk = true;
452                 pr_info("enabled workaround for brightness stepping quirk\n");
453         }
454
455         set_brightness(samsung, orig_level);
456 }
457
458 static int update_status(struct backlight_device *bd)
459 {
460         struct samsung_laptop *samsung = bl_get_data(bd);
461         const struct sabi_commands *commands = &samsung->config->commands;
462
463         set_brightness(samsung, bd->props.brightness);
464
465         if (bd->props.power == FB_BLANK_UNBLANK)
466                 sabi_set_commandb(samsung, commands->set_backlight, 1);
467         else
468                 sabi_set_commandb(samsung, commands->set_backlight, 0);
469
470         return 0;
471 }
472
473 static const struct backlight_ops backlight_ops = {
474         .get_brightness = get_brightness,
475         .update_status  = update_status,
476 };
477
478 static int rfkill_set(void *data, bool blocked)
479 {
480         struct samsung_laptop *samsung = data;
481         const struct sabi_commands *commands = &samsung->config->commands;
482
483         /* Do something with blocked...*/
484         /*
485          * blocked == false is on
486          * blocked == true is off
487          */
488         if (blocked)
489                 sabi_set_commandb(samsung, commands->set_wireless_button, 0);
490         else
491                 sabi_set_commandb(samsung, commands->set_wireless_button, 1);
492
493         return 0;
494 }
495
496 static struct rfkill_ops rfkill_ops = {
497         .set_block = rfkill_set,
498 };
499
500 static ssize_t get_performance_level(struct device *dev,
501                                      struct device_attribute *attr, char *buf)
502 {
503         struct samsung_laptop *samsung = dev_get_drvdata(dev);
504         const struct sabi_config *config = samsung->config;
505         const struct sabi_commands *commands = &config->commands;
506         struct sabi_data sretval;
507         int retval;
508         int i;
509
510         /* Read the state */
511         retval = sabi_command(samsung, commands->get_performance_level,
512                               NULL, &sretval);
513         if (retval)
514                 return retval;
515
516         /* The logic is backwards, yeah, lots of fun... */
517         for (i = 0; config->performance_levels[i].name; ++i) {
518                 if (sretval.data[0] == config->performance_levels[i].value)
519                         return sprintf(buf, "%s\n", config->performance_levels[i].name);
520         }
521         return sprintf(buf, "%s\n", "unknown");
522 }
523
524 static ssize_t set_performance_level(struct device *dev,
525                                 struct device_attribute *attr, const char *buf,
526                                 size_t count)
527 {
528         struct samsung_laptop *samsung = dev_get_drvdata(dev);
529         const struct sabi_config *config = samsung->config;
530         const struct sabi_commands *commands = &config->commands;
531         int i;
532
533         if (count < 1)
534                 return count;
535
536         for (i = 0; config->performance_levels[i].name; ++i) {
537                 const struct sabi_performance_level *level =
538                         &config->performance_levels[i];
539                 if (!strncasecmp(level->name, buf, strlen(level->name))) {
540                         sabi_set_commandb(samsung,
541                                           commands->set_performance_level,
542                                           level->value);
543                         break;
544                 }
545         }
546
547         if (!config->performance_levels[i].name)
548                 return -EINVAL;
549
550         return count;
551 }
552
553 static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
554                    get_performance_level, set_performance_level);
555
556 static int read_battery_life_extender(struct samsung_laptop *samsung)
557 {
558         const struct sabi_commands *commands = &samsung->config->commands;
559         struct sabi_data data;
560         int retval;
561
562         if (commands->get_battery_life_extender == 0xFFFF)
563                 return -ENODEV;
564
565         memset(&data, 0, sizeof(data));
566         data.data[0] = 0x80;
567         retval = sabi_command(samsung, commands->get_battery_life_extender,
568                               &data, &data);
569
570         if (retval)
571                 return retval;
572
573         if (data.data[0] != 0 && data.data[0] != 1)
574                 return -ENODEV;
575
576         return data.data[0];
577 }
578
579 static int write_battery_life_extender(struct samsung_laptop *samsung,
580                                        int enabled)
581 {
582         const struct sabi_commands *commands = &samsung->config->commands;
583         struct sabi_data data;
584
585         memset(&data, 0, sizeof(data));
586         data.data[0] = 0x80 | enabled;
587         return sabi_command(samsung, commands->set_battery_life_extender,
588                             &data, NULL);
589 }
590
591 static ssize_t get_battery_life_extender(struct device *dev,
592                                          struct device_attribute *attr,
593                                          char *buf)
594 {
595         struct samsung_laptop *samsung = dev_get_drvdata(dev);
596         int ret;
597
598         ret = read_battery_life_extender(samsung);
599         if (ret < 0)
600                 return ret;
601
602         return sprintf(buf, "%d\n", ret);
603 }
604
605 static ssize_t set_battery_life_extender(struct device *dev,
606                                         struct device_attribute *attr,
607                                         const char *buf, size_t count)
608 {
609         struct samsung_laptop *samsung = dev_get_drvdata(dev);
610         int ret, value;
611
612         if (!count || sscanf(buf, "%i", &value) != 1)
613                 return -EINVAL;
614
615         ret = write_battery_life_extender(samsung, !!value);
616         if (ret < 0)
617                 return ret;
618
619         return count;
620 }
621
622 static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
623                    get_battery_life_extender, set_battery_life_extender);
624
625 static struct attribute *platform_attributes[] = {
626         &dev_attr_performance_level.attr,
627         &dev_attr_battery_life_extender.attr,
628         NULL
629 };
630
631 static int find_signature(void __iomem *memcheck, const char *testStr)
632 {
633         int i = 0;
634         int loca;
635
636         for (loca = 0; loca < 0xffff; loca++) {
637                 char temp = readb(memcheck + loca);
638
639                 if (temp == testStr[i]) {
640                         if (i == strlen(testStr)-1)
641                                 break;
642                         ++i;
643                 } else {
644                         i = 0;
645                 }
646         }
647         return loca;
648 }
649
650 static void samsung_rfkill_exit(struct samsung_laptop *samsung)
651 {
652         if (samsung->rfk) {
653                 rfkill_unregister(samsung->rfk);
654                 rfkill_destroy(samsung->rfk);
655                 samsung->rfk = NULL;
656         }
657 }
658
659 static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
660 {
661         int retval;
662
663         samsung->rfk = rfkill_alloc("samsung-wifi",
664                                     &samsung->platform_device->dev,
665                                     RFKILL_TYPE_WLAN,
666                                     &rfkill_ops, samsung);
667         if (!samsung->rfk)
668                 return -ENOMEM;
669
670         retval = rfkill_register(samsung->rfk);
671         if (retval) {
672                 rfkill_destroy(samsung->rfk);
673                 samsung->rfk = NULL;
674                 return -ENODEV;
675         }
676
677         return 0;
678 }
679
680 static void samsung_backlight_exit(struct samsung_laptop *samsung)
681 {
682         if (samsung->backlight_device) {
683                 backlight_device_unregister(samsung->backlight_device);
684                 samsung->backlight_device = NULL;
685         }
686 }
687
688 static int __init samsung_backlight_init(struct samsung_laptop *samsung)
689 {
690         struct backlight_device *bd;
691         struct backlight_properties props;
692
693         if (!samsung->handle_backlight)
694                 return 0;
695
696         memset(&props, 0, sizeof(struct backlight_properties));
697         props.type = BACKLIGHT_PLATFORM;
698         props.max_brightness = samsung->config->max_brightness -
699                 samsung->config->min_brightness;
700
701         bd = backlight_device_register("samsung",
702                                        &samsung->platform_device->dev,
703                                        samsung, &backlight_ops,
704                                        &props);
705         if (IS_ERR(bd))
706                 return PTR_ERR(bd);
707
708         samsung->backlight_device = bd;
709         samsung->backlight_device->props.brightness = read_brightness(samsung);
710         samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
711         backlight_update_status(samsung->backlight_device);
712
713         return 0;
714 }
715
716 static mode_t samsung_sysfs_is_visible(struct kobject *kobj,
717                                        struct attribute *attr, int idx)
718 {
719         struct device *dev = container_of(kobj, struct device, kobj);
720         struct platform_device *pdev = to_platform_device(dev);
721         struct samsung_laptop *samsung = platform_get_drvdata(pdev);
722         bool ok = true;
723
724         if (attr == &dev_attr_performance_level.attr)
725                 ok = !!samsung->config->performance_levels[0].name;
726         if (attr == &dev_attr_battery_life_extender.attr)
727                 ok = !!(read_battery_life_extender(samsung) >= 0);
728
729         return ok ? attr->mode : 0;
730 }
731
732 static struct attribute_group platform_attribute_group = {
733         .is_visible = samsung_sysfs_is_visible,
734         .attrs = platform_attributes
735 };
736
737 static void samsung_sysfs_exit(struct samsung_laptop *samsung)
738 {
739         struct platform_device *device = samsung->platform_device;
740
741         sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
742 }
743
744 static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
745 {
746         struct platform_device *device = samsung->platform_device;
747
748         return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
749
750 }
751
752 static int show_call(struct seq_file *m, void *data)
753 {
754         struct samsung_laptop *samsung = m->private;
755         struct sabi_data *sdata = &samsung->debug.data;
756         int ret;
757
758         seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
759                    samsung->debug.command,
760                    sdata->d0, sdata->d1, sdata->d2, sdata->d3);
761
762         ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
763
764         if (ret) {
765                 seq_printf(m, "SABI command 0x%04x failed\n",
766                            samsung->debug.command);
767                 return ret;
768         }
769
770         seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
771                    sdata->d0, sdata->d1, sdata->d2, sdata->d3);
772         return 0;
773 }
774
775 static int samsung_debugfs_open(struct inode *inode, struct file *file)
776 {
777         return single_open(file, show_call, inode->i_private);
778 }
779
780 static const struct file_operations samsung_laptop_call_io_ops = {
781         .owner = THIS_MODULE,
782         .open = samsung_debugfs_open,
783         .read = seq_read,
784         .llseek = seq_lseek,
785         .release = single_release,
786 };
787
788 static void samsung_debugfs_exit(struct samsung_laptop *samsung)
789 {
790         debugfs_remove_recursive(samsung->debug.root);
791 }
792
793 static int samsung_debugfs_init(struct samsung_laptop *samsung)
794 {
795         struct dentry *dent;
796
797         samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
798         if (!samsung->debug.root) {
799                 pr_err("failed to create debugfs directory");
800                 goto error_debugfs;
801         }
802
803         samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
804         samsung->debug.f0000_wrapper.size = 0xffff;
805
806         samsung->debug.data_wrapper.data = &samsung->debug.data;
807         samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
808
809         dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
810                                   samsung->debug.root, &samsung->debug.command);
811         if (!dent)
812                 goto error_debugfs;
813
814         dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
815                                   &samsung->debug.data.d0);
816         if (!dent)
817                 goto error_debugfs;
818
819         dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
820                                   &samsung->debug.data.d1);
821         if (!dent)
822                 goto error_debugfs;
823
824         dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
825                                   &samsung->debug.data.d2);
826         if (!dent)
827                 goto error_debugfs;
828
829         dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
830                                  &samsung->debug.data.d3);
831         if (!dent)
832                 goto error_debugfs;
833
834         dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
835                                    samsung->debug.root,
836                                    &samsung->debug.data_wrapper);
837         if (!dent)
838                 goto error_debugfs;
839
840         dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
841                                    samsung->debug.root,
842                                    &samsung->debug.f0000_wrapper);
843         if (!dent)
844                 goto error_debugfs;
845
846         dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
847                                    samsung->debug.root, samsung,
848                                    &samsung_laptop_call_io_ops);
849         if (!dent)
850                 goto error_debugfs;
851
852         return 0;
853
854 error_debugfs:
855         samsung_debugfs_exit(samsung);
856         return -ENOMEM;
857 }
858
859 static void samsung_sabi_exit(struct samsung_laptop *samsung)
860 {
861         const struct sabi_config *config = samsung->config;
862
863         /* Turn off "Linux" mode in the BIOS */
864         if (config && config->commands.set_linux != 0xff)
865                 sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
866
867         if (samsung->sabi_iface) {
868                 iounmap(samsung->sabi_iface);
869                 samsung->sabi_iface = NULL;
870         }
871         if (samsung->f0000_segment) {
872                 iounmap(samsung->f0000_segment);
873                 samsung->f0000_segment = NULL;
874         }
875
876         samsung->config = NULL;
877 }
878
879 static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
880                                       unsigned int ifaceP)
881 {
882         const struct sabi_config *config = samsung->config;
883
884         printk(KERN_DEBUG "This computer supports SABI==%x\n",
885                loca + 0xf0000 - 6);
886
887         printk(KERN_DEBUG "SABI header:\n");
888         printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
889                readw(samsung->sabi + config->header_offsets.port));
890         printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
891                readb(samsung->sabi + config->header_offsets.iface_func));
892         printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
893                readb(samsung->sabi + config->header_offsets.en_mem));
894         printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
895                readb(samsung->sabi + config->header_offsets.re_mem));
896         printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
897                readw(samsung->sabi + config->header_offsets.data_offset));
898         printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
899                readw(samsung->sabi + config->header_offsets.data_segment));
900
901         printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
902 }
903
904 static int __init samsung_sabi_init(struct samsung_laptop *samsung)
905 {
906         const struct sabi_config *config = NULL;
907         const struct sabi_commands *commands;
908         unsigned int ifaceP;
909         int ret = 0;
910         int i;
911         int loca;
912
913         samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
914         if (!samsung->f0000_segment) {
915                 pr_err("Can't map the segment at 0xf0000\n");
916                 ret = -EINVAL;
917                 goto exit;
918         }
919
920         /* Try to find one of the signatures in memory to find the header */
921         for (i = 0; sabi_configs[i].test_string != 0; ++i) {
922                 samsung->config = &sabi_configs[i];
923                 loca = find_signature(samsung->f0000_segment,
924                                       samsung->config->test_string);
925                 if (loca != 0xffff)
926                         break;
927         }
928
929         if (loca == 0xffff) {
930                 pr_err("This computer does not support SABI\n");
931                 ret = -ENODEV;
932                 goto exit;
933         }
934
935         config = samsung->config;
936         commands = &config->commands;
937
938         /* point to the SMI port Number */
939         loca += 1;
940         samsung->sabi = (samsung->f0000_segment + loca);
941
942         /* Get a pointer to the SABI Interface */
943         ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
944         ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
945
946         if (debug)
947                 samsung_sabi_infos(samsung, loca, ifaceP);
948
949         samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
950         if (!samsung->sabi_iface) {
951                 pr_err("Can't remap %x\n", ifaceP);
952                 ret = -EINVAL;
953                 goto exit;
954         }
955
956         /* Turn on "Linux" mode in the BIOS */
957         if (commands->set_linux != 0xff) {
958                 int retval = sabi_set_commandb(samsung,
959                                                commands->set_linux, 0x81);
960                 if (retval) {
961                         pr_warn("Linux mode was not set!\n");
962                         ret = -ENODEV;
963                         goto exit;
964                 }
965         }
966
967         /* Check for stepping quirk */
968         if (samsung->handle_backlight)
969                 check_for_stepping_quirk(samsung);
970
971 exit:
972         if (ret)
973                 samsung_sabi_exit(samsung);
974
975         return ret;
976 }
977
978 static void samsung_platform_exit(struct samsung_laptop *samsung)
979 {
980         if (samsung->platform_device) {
981                 platform_device_unregister(samsung->platform_device);
982                 samsung->platform_device = NULL;
983         }
984 }
985
986 static int __init samsung_platform_init(struct samsung_laptop *samsung)
987 {
988         struct platform_device *pdev;
989
990         pdev = platform_device_register_simple("samsung", -1, NULL, 0);
991         if (IS_ERR(pdev))
992                 return PTR_ERR(pdev);
993
994         samsung->platform_device = pdev;
995         platform_set_drvdata(samsung->platform_device, samsung);
996         return 0;
997 }
998
999 static int __init dmi_check_cb(const struct dmi_system_id *id)
1000 {
1001         pr_info("found laptop model '%s'\n", id->ident);
1002         return 1;
1003 }
1004
1005 static struct dmi_system_id __initdata samsung_dmi_table[] = {
1006         {
1007                 .ident = "N128",
1008                 .matches = {
1009                         DMI_MATCH(DMI_SYS_VENDOR,
1010                                         "SAMSUNG ELECTRONICS CO., LTD."),
1011                         DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
1012                         DMI_MATCH(DMI_BOARD_NAME, "N128"),
1013                 },
1014                 .callback = dmi_check_cb,
1015         },
1016         {
1017                 .ident = "N130",
1018                 .matches = {
1019                         DMI_MATCH(DMI_SYS_VENDOR,
1020                                         "SAMSUNG ELECTRONICS CO., LTD."),
1021                         DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
1022                         DMI_MATCH(DMI_BOARD_NAME, "N130"),
1023                 },
1024                 .callback = dmi_check_cb,
1025         },
1026         {
1027                 .ident = "N510",
1028                 .matches = {
1029                         DMI_MATCH(DMI_SYS_VENDOR,
1030                                         "SAMSUNG ELECTRONICS CO., LTD."),
1031                         DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
1032                         DMI_MATCH(DMI_BOARD_NAME, "N510"),
1033                 },
1034                 .callback = dmi_check_cb,
1035         },
1036         {
1037                 .ident = "X125",
1038                 .matches = {
1039                         DMI_MATCH(DMI_SYS_VENDOR,
1040                                         "SAMSUNG ELECTRONICS CO., LTD."),
1041                         DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
1042                         DMI_MATCH(DMI_BOARD_NAME, "X125"),
1043                 },
1044                 .callback = dmi_check_cb,
1045         },
1046         {
1047                 .ident = "X120/X170",
1048                 .matches = {
1049                         DMI_MATCH(DMI_SYS_VENDOR,
1050                                         "SAMSUNG ELECTRONICS CO., LTD."),
1051                         DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
1052                         DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
1053                 },
1054                 .callback = dmi_check_cb,
1055         },
1056         {
1057                 .ident = "NC10",
1058                 .matches = {
1059                         DMI_MATCH(DMI_SYS_VENDOR,
1060                                         "SAMSUNG ELECTRONICS CO., LTD."),
1061                         DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
1062                         DMI_MATCH(DMI_BOARD_NAME, "NC10"),
1063                 },
1064                 .callback = dmi_check_cb,
1065         },
1066                 {
1067                 .ident = "NP-Q45",
1068                 .matches = {
1069                         DMI_MATCH(DMI_SYS_VENDOR,
1070                                         "SAMSUNG ELECTRONICS CO., LTD."),
1071                         DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
1072                         DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
1073                 },
1074                 .callback = dmi_check_cb,
1075                 },
1076         {
1077                 .ident = "X360",
1078                 .matches = {
1079                         DMI_MATCH(DMI_SYS_VENDOR,
1080                                         "SAMSUNG ELECTRONICS CO., LTD."),
1081                         DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
1082                         DMI_MATCH(DMI_BOARD_NAME, "X360"),
1083                 },
1084                 .callback = dmi_check_cb,
1085         },
1086         {
1087                 .ident = "R410 Plus",
1088                 .matches = {
1089                         DMI_MATCH(DMI_SYS_VENDOR,
1090                                         "SAMSUNG ELECTRONICS CO., LTD."),
1091                         DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
1092                         DMI_MATCH(DMI_BOARD_NAME, "R460"),
1093                 },
1094                 .callback = dmi_check_cb,
1095         },
1096         {
1097                 .ident = "R518",
1098                 .matches = {
1099                         DMI_MATCH(DMI_SYS_VENDOR,
1100                                         "SAMSUNG ELECTRONICS CO., LTD."),
1101                         DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
1102                         DMI_MATCH(DMI_BOARD_NAME, "R518"),
1103                 },
1104                 .callback = dmi_check_cb,
1105         },
1106         {
1107                 .ident = "R519/R719",
1108                 .matches = {
1109                         DMI_MATCH(DMI_SYS_VENDOR,
1110                                         "SAMSUNG ELECTRONICS CO., LTD."),
1111                         DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
1112                         DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
1113                 },
1114                 .callback = dmi_check_cb,
1115         },
1116         {
1117                 .ident = "N150/N210/N220",
1118                 .matches = {
1119                         DMI_MATCH(DMI_SYS_VENDOR,
1120                                         "SAMSUNG ELECTRONICS CO., LTD."),
1121                         DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
1122                         DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
1123                 },
1124                 .callback = dmi_check_cb,
1125         },
1126         {
1127                 .ident = "N220",
1128                 .matches = {
1129                         DMI_MATCH(DMI_SYS_VENDOR,
1130                                         "SAMSUNG ELECTRONICS CO., LTD."),
1131                         DMI_MATCH(DMI_PRODUCT_NAME, "N220"),
1132                         DMI_MATCH(DMI_BOARD_NAME, "N220"),
1133                 },
1134                 .callback = dmi_check_cb,
1135         },
1136         {
1137                 .ident = "N150/N210/N220/N230",
1138                 .matches = {
1139                         DMI_MATCH(DMI_SYS_VENDOR,
1140                                         "SAMSUNG ELECTRONICS CO., LTD."),
1141                         DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
1142                         DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
1143                 },
1144                 .callback = dmi_check_cb,
1145         },
1146         {
1147                 .ident = "N150P/N210P/N220P",
1148                 .matches = {
1149                         DMI_MATCH(DMI_SYS_VENDOR,
1150                                         "SAMSUNG ELECTRONICS CO., LTD."),
1151                         DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
1152                         DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
1153                 },
1154                 .callback = dmi_check_cb,
1155         },
1156         {
1157                 .ident = "R700",
1158                 .matches = {
1159                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1160                       DMI_MATCH(DMI_PRODUCT_NAME, "SR700"),
1161                       DMI_MATCH(DMI_BOARD_NAME, "SR700"),
1162                 },
1163                 .callback = dmi_check_cb,
1164         },
1165         {
1166                 .ident = "R530/R730",
1167                 .matches = {
1168                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1169                       DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
1170                       DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
1171                 },
1172                 .callback = dmi_check_cb,
1173         },
1174         {
1175                 .ident = "NF110/NF210/NF310",
1176                 .matches = {
1177                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1178                         DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
1179                         DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
1180                 },
1181                 .callback = dmi_check_cb,
1182         },
1183         {
1184                 .ident = "N145P/N250P/N260P",
1185                 .matches = {
1186                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1187                         DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
1188                         DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
1189                 },
1190                 .callback = dmi_check_cb,
1191         },
1192         {
1193                 .ident = "R70/R71",
1194                 .matches = {
1195                         DMI_MATCH(DMI_SYS_VENDOR,
1196                                         "SAMSUNG ELECTRONICS CO., LTD."),
1197                         DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
1198                         DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
1199                 },
1200                 .callback = dmi_check_cb,
1201         },
1202         {
1203                 .ident = "P460",
1204                 .matches = {
1205                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1206                         DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
1207                         DMI_MATCH(DMI_BOARD_NAME, "P460"),
1208                 },
1209                 .callback = dmi_check_cb,
1210         },
1211         {
1212                 .ident = "R528/R728",
1213                 .matches = {
1214                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1215                         DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"),
1216                         DMI_MATCH(DMI_BOARD_NAME, "R528/R728"),
1217                 },
1218                 .callback = dmi_check_cb,
1219         },
1220         {
1221                 .ident = "NC210/NC110",
1222                 .matches = {
1223                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1224                         DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
1225                         DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
1226                 },
1227                 .callback = dmi_check_cb,
1228         },
1229                 {
1230                 .ident = "X520",
1231                 .matches = {
1232                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1233                         DMI_MATCH(DMI_PRODUCT_NAME, "X520"),
1234                         DMI_MATCH(DMI_BOARD_NAME, "X520"),
1235                 },
1236                 .callback = dmi_check_cb,
1237         },
1238         { },
1239 };
1240 MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
1241
1242 static struct platform_device *samsung_platform_device;
1243
1244 static int __init samsung_init(void)
1245 {
1246         struct samsung_laptop *samsung;
1247         int ret;
1248
1249         if (!force && !dmi_check_system(samsung_dmi_table))
1250                 return -ENODEV;
1251
1252         samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1253         if (!samsung)
1254                 return -ENOMEM;
1255
1256         mutex_init(&samsung->sabi_mutex);
1257         samsung->handle_backlight = true;
1258
1259 #ifdef CONFIG_ACPI
1260         /* Don't handle backlight here if the acpi video already handle it */
1261         if (acpi_video_backlight_support()) {
1262                 pr_info("Backlight controlled by ACPI video driver\n");
1263                 samsung->handle_backlight = false;
1264         }
1265 #endif
1266
1267         ret = samsung_platform_init(samsung);
1268         if (ret)
1269                 goto error_platform;
1270
1271         ret = samsung_sabi_init(samsung);
1272         if (ret)
1273                 goto error_sabi;
1274
1275         ret = samsung_sysfs_init(samsung);
1276         if (ret)
1277                 goto error_sysfs;
1278
1279         ret = samsung_backlight_init(samsung);
1280         if (ret)
1281                 goto error_backlight;
1282
1283         ret = samsung_rfkill_init(samsung);
1284         if (ret)
1285                 goto error_rfkill;
1286
1287         ret = samsung_debugfs_init(samsung);
1288         if (ret)
1289                 goto error_debugfs;
1290
1291         samsung_platform_device = samsung->platform_device;
1292         return ret;
1293
1294 error_debugfs:
1295         samsung_rfkill_exit(samsung);
1296 error_rfkill:
1297         samsung_backlight_exit(samsung);
1298 error_backlight:
1299         samsung_sysfs_exit(samsung);
1300 error_sysfs:
1301         samsung_sabi_exit(samsung);
1302 error_sabi:
1303         samsung_platform_exit(samsung);
1304 error_platform:
1305         kfree(samsung);
1306         return ret;
1307 }
1308
1309 static void __exit samsung_exit(void)
1310 {
1311         struct samsung_laptop *samsung;
1312
1313         samsung = platform_get_drvdata(samsung_platform_device);
1314
1315         samsung_debugfs_exit(samsung);
1316         samsung_rfkill_exit(samsung);
1317         samsung_backlight_exit(samsung);
1318         samsung_sysfs_exit(samsung);
1319         samsung_sabi_exit(samsung);
1320         samsung_platform_exit(samsung);
1321
1322         kfree(samsung);
1323         samsung_platform_device = NULL;
1324 }
1325
1326 module_init(samsung_init);
1327 module_exit(samsung_exit);
1328
1329 MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1330 MODULE_DESCRIPTION("Samsung Backlight driver");
1331 MODULE_LICENSE("GPL");