samsung-laptop: move code into init/exit functions
[firefly-linux-kernel-4.4.55.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
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 */
45 struct sabi_retval {
46         u8 retval[20];
47 };
48
49 struct 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
58 struct 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
103 struct sabi_performance_level {
104         const char *name;
105         u8 value;
106 };
107
108 struct 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
118 static 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
220 struct samsung_laptop {
221         const struct sabi_config *config;
222
223         void __iomem *sabi;
224         void __iomem *sabi_iface;
225         void __iomem *f0000_segment;
226
227         struct mutex sabi_mutex;
228
229         struct platform_device *platform_device;
230         struct backlight_device *backlight_device;
231         struct rfkill *rfk;
232
233         bool has_stepping_quirk;
234 };
235
236
237
238 static bool force;
239 module_param(force, bool, 0);
240 MODULE_PARM_DESC(force,
241                 "Disable the DMI check and forces the driver to be loaded");
242
243 static bool debug;
244 module_param(debug, bool, S_IRUGO | S_IWUSR);
245 MODULE_PARM_DESC(debug, "Debug enabled or not");
246
247 static int sabi_get_command(struct samsung_laptop *samsung,
248                             u8 command, struct sabi_retval *sretval)
249 {
250         const struct sabi_config *config = samsung->config;
251         int retval = 0;
252         u16 port = readw(samsung->sabi + config->header_offsets.port);
253         u8 complete, iface_data;
254
255         mutex_lock(&samsung->sabi_mutex);
256
257         /* enable memory to be able to write to it */
258         outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
259
260         /* write out the command */
261         writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
262         writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
263         writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
264         outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
265
266         /* write protect memory to make it safe */
267         outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
268
269         /* see if the command actually succeeded */
270         complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
271         iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
272         if (complete != 0xaa || iface_data == 0xff) {
273                 pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
274                         command, complete, iface_data);
275                 retval = -EINVAL;
276                 goto exit;
277         }
278         /*
279          * Save off the data into a structure so the caller use it.
280          * Right now we only want the first 4 bytes,
281          * There are commands that need more, but not for the ones we
282          * currently care about.
283          */
284         sretval->retval[0] = readb(samsung->sabi_iface + SABI_IFACE_DATA);
285         sretval->retval[1] = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
286         sretval->retval[2] = readb(samsung->sabi_iface + SABI_IFACE_DATA + 2);
287         sretval->retval[3] = readb(samsung->sabi_iface + SABI_IFACE_DATA + 3);
288
289 exit:
290         mutex_unlock(&samsung->sabi_mutex);
291         return retval;
292
293 }
294
295 static int sabi_set_command(struct samsung_laptop *samsung,
296                             u8 command, u8 data)
297 {
298         const struct sabi_config *config = samsung->config;
299         int retval = 0;
300         u16 port = readw(samsung->sabi + config->header_offsets.port);
301         u8 complete, iface_data;
302
303         mutex_lock(&samsung->sabi_mutex);
304
305         /* enable memory to be able to write to it */
306         outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
307
308         /* write out the command */
309         writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
310         writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
311         writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
312         writeb(data, samsung->sabi_iface + SABI_IFACE_DATA);
313         outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
314
315         /* write protect memory to make it safe */
316         outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
317
318         /* see if the command actually succeeded */
319         complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
320         iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
321         if (complete != 0xaa || iface_data == 0xff) {
322                 pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
323                        command, complete, iface_data);
324                 retval = -EINVAL;
325         }
326
327         mutex_unlock(&samsung->sabi_mutex);
328         return retval;
329 }
330
331 static void test_backlight(struct samsung_laptop *samsung)
332 {
333         const struct sabi_commands *commands = &samsung->config->commands;
334         struct sabi_retval sretval;
335
336         sabi_get_command(samsung, commands->get_backlight, &sretval);
337         printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
338
339         sabi_set_command(samsung, commands->set_backlight, 0);
340         printk(KERN_DEBUG "backlight should be off\n");
341
342         sabi_get_command(samsung, commands->get_backlight, &sretval);
343         printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
344
345         msleep(1000);
346
347         sabi_set_command(samsung, commands->set_backlight, 1);
348         printk(KERN_DEBUG "backlight should be on\n");
349
350         sabi_get_command(samsung, commands->get_backlight, &sretval);
351         printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
352 }
353
354 static void test_wireless(struct samsung_laptop *samsung)
355 {
356         const struct sabi_commands *commands = &samsung->config->commands;
357         struct sabi_retval sretval;
358
359         sabi_get_command(samsung, commands->get_wireless_button, &sretval);
360         printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
361
362         sabi_set_command(samsung, commands->set_wireless_button, 0);
363         printk(KERN_DEBUG "wireless led should be off\n");
364
365         sabi_get_command(samsung, commands->get_wireless_button, &sretval);
366         printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
367
368         msleep(1000);
369
370         sabi_set_command(samsung, commands->set_wireless_button, 1);
371         printk(KERN_DEBUG "wireless led should be on\n");
372
373         sabi_get_command(samsung, commands->get_wireless_button, &sretval);
374         printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
375 }
376
377 static int read_brightness(struct samsung_laptop *samsung)
378 {
379         const struct sabi_config *config = samsung->config;
380         const struct sabi_commands *commands = &samsung->config->commands;
381         struct sabi_retval sretval;
382         int user_brightness = 0;
383         int retval;
384
385         retval = sabi_get_command(samsung, commands->get_brightness,
386                                   &sretval);
387         if (!retval) {
388                 user_brightness = sretval.retval[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_command(samsung, commands->set_brightness, 0);
412         }
413
414         sabi_set_command(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_command(samsung, commands->set_backlight, 1);
467         else
468                 sabi_set_command(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_command(samsung, commands->set_wireless_button, 0);
490         else
491                 sabi_set_command(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_retval sretval;
507         int retval;
508         int i;
509
510         /* Read the state */
511         retval = sabi_get_command(samsung, commands->get_performance_level,
512                                   &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.retval[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_command(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
557 static int find_signature(void __iomem *memcheck, const char *testStr)
558 {
559         int i = 0;
560         int loca;
561
562         for (loca = 0; loca < 0xffff; loca++) {
563                 char temp = readb(memcheck + loca);
564
565                 if (temp == testStr[i]) {
566                         if (i == strlen(testStr)-1)
567                                 break;
568                         ++i;
569                 } else {
570                         i = 0;
571                 }
572         }
573         return loca;
574 }
575
576 static void samsung_rfkill_exit(struct samsung_laptop *samsung)
577 {
578         if (samsung->rfk) {
579                 rfkill_unregister(samsung->rfk);
580                 rfkill_destroy(samsung->rfk);
581                 samsung->rfk = NULL;
582         }
583 }
584
585 static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
586 {
587         int retval;
588
589         samsung->rfk = rfkill_alloc("samsung-wifi",
590                                     &samsung->platform_device->dev,
591                                     RFKILL_TYPE_WLAN,
592                                     &rfkill_ops, samsung);
593         if (!samsung->rfk)
594                 return -ENOMEM;
595
596         retval = rfkill_register(samsung->rfk);
597         if (retval) {
598                 rfkill_destroy(samsung->rfk);
599                 samsung->rfk = NULL;
600                 return -ENODEV;
601         }
602
603         return 0;
604 }
605
606 static void samsung_backlight_exit(struct samsung_laptop *samsung)
607 {
608         if (samsung->backlight_device) {
609                 backlight_device_unregister(samsung->backlight_device);
610                 samsung->backlight_device = NULL;
611         }
612 }
613
614 static int __init samsung_backlight_init(struct samsung_laptop *samsung)
615 {
616         struct backlight_device *bd;
617         struct backlight_properties props;
618
619         memset(&props, 0, sizeof(struct backlight_properties));
620         props.type = BACKLIGHT_PLATFORM;
621         props.max_brightness = samsung->config->max_brightness -
622                 samsung->config->min_brightness;
623
624         bd = backlight_device_register("samsung",
625                                        &samsung->platform_device->dev,
626                                        samsung, &backlight_ops,
627                                        &props);
628         if (IS_ERR(bd))
629                 return PTR_ERR(bd);
630
631         samsung->backlight_device = bd;
632         samsung->backlight_device->props.brightness = read_brightness(samsung);
633         samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
634         backlight_update_status(samsung->backlight_device);
635
636         return 0;
637 }
638
639 static void samsung_sysfs_exit(struct samsung_laptop *samsung)
640 {
641         device_remove_file(&samsung->platform_device->dev,
642                            &dev_attr_performance_level);
643 }
644
645 static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
646 {
647         return device_create_file(&samsung->platform_device->dev,
648                                   &dev_attr_performance_level);
649 }
650
651 static void samsung_sabi_exit(struct samsung_laptop *samsung)
652 {
653         const struct sabi_config *config = samsung->config;
654
655         /* Turn off "Linux" mode in the BIOS */
656         if (config && config->commands.set_linux != 0xff)
657                 sabi_set_command(samsung, config->commands.set_linux, 0x80);
658
659         if (samsung->sabi_iface) {
660                 iounmap(samsung->sabi_iface);
661                 samsung->sabi_iface = NULL;
662         }
663         if (samsung->f0000_segment) {
664                 iounmap(samsung->f0000_segment);
665                 samsung->f0000_segment = NULL;
666         }
667
668         samsung->config = NULL;
669 }
670
671 static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca)
672 {
673         const struct sabi_config *config = samsung->config;
674
675         printk(KERN_DEBUG "This computer supports SABI==%x\n",
676                loca + 0xf0000 - 6);
677         printk(KERN_DEBUG "SABI header:\n");
678         printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
679                readw(samsung->sabi + config->header_offsets.port));
680         printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
681                readb(samsung->sabi + config->header_offsets.iface_func));
682         printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
683                readb(samsung->sabi + config->header_offsets.en_mem));
684         printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
685                readb(samsung->sabi + config->header_offsets.re_mem));
686         printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
687                readw(samsung->sabi + config->header_offsets.data_offset));
688         printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
689                readw(samsung->sabi + config->header_offsets.data_segment));
690 }
691
692 static void __init samsung_sabi_selftest(struct samsung_laptop *samsung,
693                                         unsigned int ifaceP)
694 {
695         const struct sabi_config *config = samsung->config;
696         struct sabi_retval sretval;
697
698         printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP);
699         printk(KERN_DEBUG "sabi_iface = %p\n", samsung->sabi_iface);
700
701         test_backlight(samsung);
702         test_wireless(samsung);
703
704         sabi_get_command(samsung, config->commands.get_brightness, &sretval);
705         printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]);
706 }
707
708 static int __init samsung_sabi_init(struct samsung_laptop *samsung)
709 {
710         const struct sabi_config *config = NULL;
711         const struct sabi_commands *commands;
712         unsigned int ifaceP;
713         int ret = 0;
714         int i;
715         int loca;
716
717         samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
718         if (!samsung->f0000_segment) {
719                 pr_err("Can't map the segment at 0xf0000\n");
720                 ret = -EINVAL;
721                 goto exit;
722         }
723
724         /* Try to find one of the signatures in memory to find the header */
725         for (i = 0; sabi_configs[i].test_string != 0; ++i) {
726                 samsung->config = &sabi_configs[i];
727                 loca = find_signature(samsung->f0000_segment,
728                                       samsung->config->test_string);
729                 if (loca != 0xffff)
730                         break;
731         }
732
733         if (loca == 0xffff) {
734                 pr_err("This computer does not support SABI\n");
735                 ret = -ENODEV;
736                 goto exit;
737         }
738
739         config = samsung->config;
740         commands = &config->commands;
741
742         /* point to the SMI port Number */
743         loca += 1;
744         samsung->sabi = (samsung->f0000_segment + loca);
745
746         if (debug)
747                 samsung_sabi_infos(samsung, loca);
748
749         /* Get a pointer to the SABI Interface */
750         ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
751         ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
752         samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
753         if (!samsung->sabi_iface) {
754                 pr_err("Can't remap %x\n", ifaceP);
755                 ret = -EINVAL;
756                 goto exit;
757         }
758
759         if (debug)
760                 samsung_sabi_selftest(samsung, ifaceP);
761
762         /* Turn on "Linux" mode in the BIOS */
763         if (commands->set_linux != 0xff) {
764                 int retval = sabi_set_command(samsung,
765                                               commands->set_linux, 0x81);
766                 if (retval) {
767                         pr_warn("Linux mode was not set!\n");
768                         ret = -ENODEV;
769                         goto exit;
770                 }
771         }
772
773         /* Check for stepping quirk */
774         check_for_stepping_quirk(samsung);
775
776 exit:
777         if (ret)
778                 samsung_sabi_exit(samsung);
779
780         return ret;
781 }
782
783 static void samsung_platform_exit(struct samsung_laptop *samsung)
784 {
785         if (samsung->platform_device) {
786                 platform_device_unregister(samsung->platform_device);
787                 samsung->platform_device = NULL;
788         }
789 }
790
791 static int __init samsung_platform_init(struct samsung_laptop *samsung)
792 {
793         struct platform_device *pdev;
794
795         pdev = platform_device_register_simple("samsung", -1, NULL, 0);
796         if (IS_ERR(pdev))
797                 return PTR_ERR(pdev);
798
799         samsung->platform_device = pdev;
800         platform_set_drvdata(samsung->platform_device, samsung);
801         return 0;
802 }
803
804 static int __init dmi_check_cb(const struct dmi_system_id *id)
805 {
806         pr_info("found laptop model '%s'\n", id->ident);
807         return 1;
808 }
809
810 static struct dmi_system_id __initdata samsung_dmi_table[] = {
811         {
812                 .ident = "N128",
813                 .matches = {
814                         DMI_MATCH(DMI_SYS_VENDOR,
815                                         "SAMSUNG ELECTRONICS CO., LTD."),
816                         DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
817                         DMI_MATCH(DMI_BOARD_NAME, "N128"),
818                 },
819                 .callback = dmi_check_cb,
820         },
821         {
822                 .ident = "N130",
823                 .matches = {
824                         DMI_MATCH(DMI_SYS_VENDOR,
825                                         "SAMSUNG ELECTRONICS CO., LTD."),
826                         DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
827                         DMI_MATCH(DMI_BOARD_NAME, "N130"),
828                 },
829                 .callback = dmi_check_cb,
830         },
831         {
832                 .ident = "N510",
833                 .matches = {
834                         DMI_MATCH(DMI_SYS_VENDOR,
835                                         "SAMSUNG ELECTRONICS CO., LTD."),
836                         DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
837                         DMI_MATCH(DMI_BOARD_NAME, "N510"),
838                 },
839                 .callback = dmi_check_cb,
840         },
841         {
842                 .ident = "X125",
843                 .matches = {
844                         DMI_MATCH(DMI_SYS_VENDOR,
845                                         "SAMSUNG ELECTRONICS CO., LTD."),
846                         DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
847                         DMI_MATCH(DMI_BOARD_NAME, "X125"),
848                 },
849                 .callback = dmi_check_cb,
850         },
851         {
852                 .ident = "X120/X170",
853                 .matches = {
854                         DMI_MATCH(DMI_SYS_VENDOR,
855                                         "SAMSUNG ELECTRONICS CO., LTD."),
856                         DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
857                         DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
858                 },
859                 .callback = dmi_check_cb,
860         },
861         {
862                 .ident = "NC10",
863                 .matches = {
864                         DMI_MATCH(DMI_SYS_VENDOR,
865                                         "SAMSUNG ELECTRONICS CO., LTD."),
866                         DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
867                         DMI_MATCH(DMI_BOARD_NAME, "NC10"),
868                 },
869                 .callback = dmi_check_cb,
870         },
871                 {
872                 .ident = "NP-Q45",
873                 .matches = {
874                         DMI_MATCH(DMI_SYS_VENDOR,
875                                         "SAMSUNG ELECTRONICS CO., LTD."),
876                         DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
877                         DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
878                 },
879                 .callback = dmi_check_cb,
880                 },
881         {
882                 .ident = "X360",
883                 .matches = {
884                         DMI_MATCH(DMI_SYS_VENDOR,
885                                         "SAMSUNG ELECTRONICS CO., LTD."),
886                         DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
887                         DMI_MATCH(DMI_BOARD_NAME, "X360"),
888                 },
889                 .callback = dmi_check_cb,
890         },
891         {
892                 .ident = "R410 Plus",
893                 .matches = {
894                         DMI_MATCH(DMI_SYS_VENDOR,
895                                         "SAMSUNG ELECTRONICS CO., LTD."),
896                         DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
897                         DMI_MATCH(DMI_BOARD_NAME, "R460"),
898                 },
899                 .callback = dmi_check_cb,
900         },
901         {
902                 .ident = "R518",
903                 .matches = {
904                         DMI_MATCH(DMI_SYS_VENDOR,
905                                         "SAMSUNG ELECTRONICS CO., LTD."),
906                         DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
907                         DMI_MATCH(DMI_BOARD_NAME, "R518"),
908                 },
909                 .callback = dmi_check_cb,
910         },
911         {
912                 .ident = "R519/R719",
913                 .matches = {
914                         DMI_MATCH(DMI_SYS_VENDOR,
915                                         "SAMSUNG ELECTRONICS CO., LTD."),
916                         DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
917                         DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
918                 },
919                 .callback = dmi_check_cb,
920         },
921         {
922                 .ident = "N150/N210/N220",
923                 .matches = {
924                         DMI_MATCH(DMI_SYS_VENDOR,
925                                         "SAMSUNG ELECTRONICS CO., LTD."),
926                         DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
927                         DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
928                 },
929                 .callback = dmi_check_cb,
930         },
931         {
932                 .ident = "N220",
933                 .matches = {
934                         DMI_MATCH(DMI_SYS_VENDOR,
935                                         "SAMSUNG ELECTRONICS CO., LTD."),
936                         DMI_MATCH(DMI_PRODUCT_NAME, "N220"),
937                         DMI_MATCH(DMI_BOARD_NAME, "N220"),
938                 },
939                 .callback = dmi_check_cb,
940         },
941         {
942                 .ident = "N150/N210/N220/N230",
943                 .matches = {
944                         DMI_MATCH(DMI_SYS_VENDOR,
945                                         "SAMSUNG ELECTRONICS CO., LTD."),
946                         DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
947                         DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
948                 },
949                 .callback = dmi_check_cb,
950         },
951         {
952                 .ident = "N150P/N210P/N220P",
953                 .matches = {
954                         DMI_MATCH(DMI_SYS_VENDOR,
955                                         "SAMSUNG ELECTRONICS CO., LTD."),
956                         DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
957                         DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
958                 },
959                 .callback = dmi_check_cb,
960         },
961         {
962                 .ident = "R700",
963                 .matches = {
964                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
965                       DMI_MATCH(DMI_PRODUCT_NAME, "SR700"),
966                       DMI_MATCH(DMI_BOARD_NAME, "SR700"),
967                 },
968                 .callback = dmi_check_cb,
969         },
970         {
971                 .ident = "R530/R730",
972                 .matches = {
973                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
974                       DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
975                       DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
976                 },
977                 .callback = dmi_check_cb,
978         },
979         {
980                 .ident = "NF110/NF210/NF310",
981                 .matches = {
982                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
983                         DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
984                         DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
985                 },
986                 .callback = dmi_check_cb,
987         },
988         {
989                 .ident = "N145P/N250P/N260P",
990                 .matches = {
991                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
992                         DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
993                         DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
994                 },
995                 .callback = dmi_check_cb,
996         },
997         {
998                 .ident = "R70/R71",
999                 .matches = {
1000                         DMI_MATCH(DMI_SYS_VENDOR,
1001                                         "SAMSUNG ELECTRONICS CO., LTD."),
1002                         DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
1003                         DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
1004                 },
1005                 .callback = dmi_check_cb,
1006         },
1007         {
1008                 .ident = "P460",
1009                 .matches = {
1010                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1011                         DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
1012                         DMI_MATCH(DMI_BOARD_NAME, "P460"),
1013                 },
1014                 .callback = dmi_check_cb,
1015         },
1016         {
1017                 .ident = "R528/R728",
1018                 .matches = {
1019                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1020                         DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"),
1021                         DMI_MATCH(DMI_BOARD_NAME, "R528/R728"),
1022                 },
1023                 .callback = dmi_check_cb,
1024         },
1025         {
1026                 .ident = "NC210/NC110",
1027                 .matches = {
1028                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1029                         DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
1030                         DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
1031                 },
1032                 .callback = dmi_check_cb,
1033         },
1034                 {
1035                 .ident = "X520",
1036                 .matches = {
1037                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1038                         DMI_MATCH(DMI_PRODUCT_NAME, "X520"),
1039                         DMI_MATCH(DMI_BOARD_NAME, "X520"),
1040                 },
1041                 .callback = dmi_check_cb,
1042         },
1043         { },
1044 };
1045 MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
1046
1047 static struct platform_device *samsung_platform_device;
1048
1049 static int __init samsung_init(void)
1050 {
1051         struct samsung_laptop *samsung;
1052         int ret;
1053
1054         if (!force && !dmi_check_system(samsung_dmi_table))
1055                 return -ENODEV;
1056
1057         samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1058         if (!samsung)
1059                 return -ENOMEM;
1060
1061         mutex_init(&samsung->sabi_mutex);
1062
1063         ret = samsung_platform_init(samsung);
1064         if (ret)
1065                 goto error_platform;
1066
1067         ret = samsung_sabi_init(samsung);
1068         if (ret)
1069                 goto error_sabi;
1070
1071         ret = samsung_sysfs_init(samsung);
1072         if (ret)
1073                 goto error_sysfs;
1074
1075         ret = samsung_backlight_init(samsung);
1076         if (ret)
1077                 goto error_backlight;
1078
1079         ret = samsung_rfkill_init(samsung);
1080         if (ret)
1081                 goto error_rfkill;
1082
1083         samsung_platform_device = samsung->platform_device;
1084         return ret;
1085
1086 error_rfkill:
1087         samsung_backlight_exit(samsung);
1088 error_backlight:
1089         samsung_sysfs_exit(samsung);
1090 error_sysfs:
1091         samsung_sabi_exit(samsung);
1092 error_sabi:
1093         samsung_platform_exit(samsung);
1094 error_platform:
1095         kfree(samsung);
1096         return ret;
1097 }
1098
1099 static void __exit samsung_exit(void)
1100 {
1101         struct samsung_laptop *samsung;
1102
1103         samsung = platform_get_drvdata(samsung_platform_device);
1104
1105         samsung_rfkill_exit(samsung);
1106         samsung_backlight_exit(samsung);
1107         samsung_sysfs_exit(samsung);
1108         samsung_sabi_exit(samsung);
1109         samsung_platform_exit(samsung);
1110
1111         kfree(samsung);
1112         samsung_platform_device = NULL;
1113 }
1114
1115 module_init(samsung_init);
1116 module_exit(samsung_exit);
1117
1118 MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1119 MODULE_DESCRIPTION("Samsung Backlight driver");
1120 MODULE_LICENSE("GPL");