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