c2a74bfc017cc309def169e3d67c93d0ce19eff8
[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 *pdev;
230         struct backlight_device *backlight_device;
231         struct rfkill *rfk;
232
233         bool has_stepping_quirk;
234 };
235
236 static struct samsung_laptop *samsung;
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(u8 command, struct sabi_retval *sretval)
248 {
249         const struct sabi_config *config = samsung->config;
250         int retval = 0;
251         u16 port = readw(samsung->sabi + config->header_offsets.port);
252         u8 complete, iface_data;
253
254         mutex_lock(&samsung->sabi_mutex);
255
256         /* enable memory to be able to write to it */
257         outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
258
259         /* write out the command */
260         writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
261         writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
262         writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
263         outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
264
265         /* write protect memory to make it safe */
266         outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
267
268         /* see if the command actually succeeded */
269         complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
270         iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
271         if (complete != 0xaa || iface_data == 0xff) {
272                 pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
273                         command, complete, iface_data);
274                 retval = -EINVAL;
275                 goto exit;
276         }
277         /*
278          * Save off the data into a structure so the caller use it.
279          * Right now we only want the first 4 bytes,
280          * There are commands that need more, but not for the ones we
281          * currently care about.
282          */
283         sretval->retval[0] = readb(samsung->sabi_iface + SABI_IFACE_DATA);
284         sretval->retval[1] = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
285         sretval->retval[2] = readb(samsung->sabi_iface + SABI_IFACE_DATA + 2);
286         sretval->retval[3] = readb(samsung->sabi_iface + SABI_IFACE_DATA + 3);
287
288 exit:
289         mutex_unlock(&samsung->sabi_mutex);
290         return retval;
291
292 }
293
294 static int sabi_set_command(u8 command, u8 data)
295 {
296         const struct sabi_config *config = samsung->config;
297         int retval = 0;
298         u16 port = readw(samsung->sabi + config->header_offsets.port);
299         u8 complete, iface_data;
300
301         mutex_lock(&samsung->sabi_mutex);
302
303         /* enable memory to be able to write to it */
304         outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
305
306         /* write out the command */
307         writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
308         writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
309         writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
310         writeb(data, samsung->sabi_iface + SABI_IFACE_DATA);
311         outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
312
313         /* write protect memory to make it safe */
314         outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
315
316         /* see if the command actually succeeded */
317         complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
318         iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
319         if (complete != 0xaa || iface_data == 0xff) {
320                 pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
321                        command, complete, iface_data);
322                 retval = -EINVAL;
323         }
324
325         mutex_unlock(&samsung->sabi_mutex);
326         return retval;
327 }
328
329 static void test_backlight(void)
330 {
331         const struct sabi_commands *commands = &samsung->config->commands;
332         struct sabi_retval sretval;
333
334         sabi_get_command(commands->get_backlight, &sretval);
335         printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
336
337         sabi_set_command(commands->set_backlight, 0);
338         printk(KERN_DEBUG "backlight should be off\n");
339
340         sabi_get_command(commands->get_backlight, &sretval);
341         printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
342
343         msleep(1000);
344
345         sabi_set_command(commands->set_backlight, 1);
346         printk(KERN_DEBUG "backlight should be on\n");
347
348         sabi_get_command(commands->get_backlight, &sretval);
349         printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
350 }
351
352 static void test_wireless(void)
353 {
354         const struct sabi_commands *commands = &samsung->config->commands;
355         struct sabi_retval sretval;
356
357         sabi_get_command(commands->get_wireless_button, &sretval);
358         printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
359
360         sabi_set_command(commands->set_wireless_button, 0);
361         printk(KERN_DEBUG "wireless led should be off\n");
362
363         sabi_get_command(commands->get_wireless_button, &sretval);
364         printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
365
366         msleep(1000);
367
368         sabi_set_command(commands->set_wireless_button, 1);
369         printk(KERN_DEBUG "wireless led should be on\n");
370
371         sabi_get_command(commands->get_wireless_button, &sretval);
372         printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
373 }
374
375 static u8 read_brightness(void)
376 {
377         const struct sabi_config *config = samsung->config;
378         const struct sabi_commands *commands = &samsung->config->commands;
379         struct sabi_retval sretval;
380         int user_brightness = 0;
381         int retval;
382
383         retval = sabi_get_command(commands->get_brightness,
384                                   &sretval);
385         if (!retval) {
386                 user_brightness = sretval.retval[0];
387                 if (user_brightness > config->min_brightness)
388                         user_brightness -= config->min_brightness;
389                 else
390                         user_brightness = 0;
391         }
392         return user_brightness;
393 }
394
395 static void set_brightness(u8 user_brightness)
396 {
397         const struct sabi_config *config = samsung->config;
398         const struct sabi_commands *commands = &samsung->config->commands;
399         u8 user_level = user_brightness + config->min_brightness;
400
401         if (samsung->has_stepping_quirk && user_level != 0) {
402                 /*
403                  * short circuit if the specified level is what's already set
404                  * to prevent the screen from flickering needlessly
405                  */
406                 if (user_brightness == read_brightness())
407                         return;
408
409                 sabi_set_command(commands->set_brightness, 0);
410         }
411
412         sabi_set_command(commands->set_brightness, user_level);
413 }
414
415 static int get_brightness(struct backlight_device *bd)
416 {
417         return (int)read_brightness();
418 }
419
420 static void check_for_stepping_quirk(void)
421 {
422         u8 initial_level;
423         u8 check_level;
424         u8 orig_level = read_brightness();
425
426         /*
427          * Some laptops exhibit the strange behaviour of stepping toward
428          * (rather than setting) the brightness except when changing to/from
429          * brightness level 0. This behaviour is checked for here and worked
430          * around in set_brightness.
431          */
432
433         if (orig_level == 0)
434                 set_brightness(1);
435
436         initial_level = read_brightness();
437
438         if (initial_level <= 2)
439                 check_level = initial_level + 2;
440         else
441                 check_level = initial_level - 2;
442
443         samsung->has_stepping_quirk = false;
444         set_brightness(check_level);
445
446         if (read_brightness() != check_level) {
447                 samsung->has_stepping_quirk = true;
448                 pr_info("enabled workaround for brightness stepping quirk\n");
449         }
450
451         set_brightness(orig_level);
452 }
453
454 static int update_status(struct backlight_device *bd)
455 {
456         const struct sabi_commands *commands = &samsung->config->commands;
457
458         set_brightness(bd->props.brightness);
459
460         if (bd->props.power == FB_BLANK_UNBLANK)
461                 sabi_set_command(commands->set_backlight, 1);
462         else
463                 sabi_set_command(commands->set_backlight, 0);
464         return 0;
465 }
466
467 static const struct backlight_ops backlight_ops = {
468         .get_brightness = get_brightness,
469         .update_status  = update_status,
470 };
471
472 static int rfkill_set(void *data, bool blocked)
473 {
474         const struct sabi_commands *commands = &samsung->config->commands;
475
476         /* Do something with blocked...*/
477         /*
478          * blocked == false is on
479          * blocked == true is off
480          */
481         if (blocked)
482                 sabi_set_command(commands->set_wireless_button, 0);
483         else
484                 sabi_set_command(commands->set_wireless_button, 1);
485
486         return 0;
487 }
488
489 static struct rfkill_ops rfkill_ops = {
490         .set_block = rfkill_set,
491 };
492
493 static int init_wireless(struct platform_device *pdev)
494 {
495         int retval;
496
497         samsung->rfk = rfkill_alloc("samsung-wifi", &samsung->pdev->dev, RFKILL_TYPE_WLAN,
498                                     &rfkill_ops, NULL);
499         if (!samsung->rfk)
500                 return -ENOMEM;
501
502         retval = rfkill_register(samsung->rfk);
503         if (retval) {
504                 rfkill_destroy(samsung->rfk);
505                 return -ENODEV;
506         }
507
508         return 0;
509 }
510
511 static void destroy_wireless(void)
512 {
513         rfkill_unregister(samsung->rfk);
514         rfkill_destroy(samsung->rfk);
515 }
516
517 static ssize_t get_performance_level(struct device *dev,
518                                      struct device_attribute *attr, char *buf)
519 {
520         const struct sabi_config *config = samsung->config;
521         struct sabi_retval sretval;
522         int retval;
523         int i;
524
525         /* Read the state */
526         retval = sabi_get_command(config->commands.get_performance_level,
527                                   &sretval);
528         if (retval)
529                 return retval;
530
531         /* The logic is backwards, yeah, lots of fun... */
532         for (i = 0; config->performance_levels[i].name; ++i) {
533                 if (sretval.retval[0] == config->performance_levels[i].value)
534                         return sprintf(buf, "%s\n", config->performance_levels[i].name);
535         }
536         return sprintf(buf, "%s\n", "unknown");
537 }
538
539 static ssize_t set_performance_level(struct device *dev,
540                                 struct device_attribute *attr, const char *buf,
541                                 size_t count)
542 {
543         const struct sabi_config *config = samsung->config;
544
545         if (count >= 1) {
546                 int i;
547                 for (i = 0; config->performance_levels[i].name; ++i) {
548                         const struct sabi_performance_level *level =
549                                 &config->performance_levels[i];
550                         if (!strncasecmp(level->name, buf, strlen(level->name))) {
551                                 sabi_set_command(config->commands.set_performance_level,
552                                                  level->value);
553                                 break;
554                         }
555                 }
556                 if (!config->performance_levels[i].name)
557                         return -EINVAL;
558         }
559         return count;
560 }
561 static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
562                    get_performance_level, set_performance_level);
563
564
565 static int __init dmi_check_cb(const struct dmi_system_id *id)
566 {
567         pr_info("found laptop model '%s'\n",
568                 id->ident);
569         return 1;
570 }
571
572 static struct dmi_system_id __initdata samsung_dmi_table[] = {
573         {
574                 .ident = "N128",
575                 .matches = {
576                         DMI_MATCH(DMI_SYS_VENDOR,
577                                         "SAMSUNG ELECTRONICS CO., LTD."),
578                         DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
579                         DMI_MATCH(DMI_BOARD_NAME, "N128"),
580                 },
581                 .callback = dmi_check_cb,
582         },
583         {
584                 .ident = "N130",
585                 .matches = {
586                         DMI_MATCH(DMI_SYS_VENDOR,
587                                         "SAMSUNG ELECTRONICS CO., LTD."),
588                         DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
589                         DMI_MATCH(DMI_BOARD_NAME, "N130"),
590                 },
591                 .callback = dmi_check_cb,
592         },
593         {
594                 .ident = "N510",
595                 .matches = {
596                         DMI_MATCH(DMI_SYS_VENDOR,
597                                         "SAMSUNG ELECTRONICS CO., LTD."),
598                         DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
599                         DMI_MATCH(DMI_BOARD_NAME, "N510"),
600                 },
601                 .callback = dmi_check_cb,
602         },
603         {
604                 .ident = "X125",
605                 .matches = {
606                         DMI_MATCH(DMI_SYS_VENDOR,
607                                         "SAMSUNG ELECTRONICS CO., LTD."),
608                         DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
609                         DMI_MATCH(DMI_BOARD_NAME, "X125"),
610                 },
611                 .callback = dmi_check_cb,
612         },
613         {
614                 .ident = "X120/X170",
615                 .matches = {
616                         DMI_MATCH(DMI_SYS_VENDOR,
617                                         "SAMSUNG ELECTRONICS CO., LTD."),
618                         DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
619                         DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
620                 },
621                 .callback = dmi_check_cb,
622         },
623         {
624                 .ident = "NC10",
625                 .matches = {
626                         DMI_MATCH(DMI_SYS_VENDOR,
627                                         "SAMSUNG ELECTRONICS CO., LTD."),
628                         DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
629                         DMI_MATCH(DMI_BOARD_NAME, "NC10"),
630                 },
631                 .callback = dmi_check_cb,
632         },
633                 {
634                 .ident = "NP-Q45",
635                 .matches = {
636                         DMI_MATCH(DMI_SYS_VENDOR,
637                                         "SAMSUNG ELECTRONICS CO., LTD."),
638                         DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
639                         DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
640                 },
641                 .callback = dmi_check_cb,
642                 },
643         {
644                 .ident = "X360",
645                 .matches = {
646                         DMI_MATCH(DMI_SYS_VENDOR,
647                                         "SAMSUNG ELECTRONICS CO., LTD."),
648                         DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
649                         DMI_MATCH(DMI_BOARD_NAME, "X360"),
650                 },
651                 .callback = dmi_check_cb,
652         },
653         {
654                 .ident = "R410 Plus",
655                 .matches = {
656                         DMI_MATCH(DMI_SYS_VENDOR,
657                                         "SAMSUNG ELECTRONICS CO., LTD."),
658                         DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
659                         DMI_MATCH(DMI_BOARD_NAME, "R460"),
660                 },
661                 .callback = dmi_check_cb,
662         },
663         {
664                 .ident = "R518",
665                 .matches = {
666                         DMI_MATCH(DMI_SYS_VENDOR,
667                                         "SAMSUNG ELECTRONICS CO., LTD."),
668                         DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
669                         DMI_MATCH(DMI_BOARD_NAME, "R518"),
670                 },
671                 .callback = dmi_check_cb,
672         },
673         {
674                 .ident = "R519/R719",
675                 .matches = {
676                         DMI_MATCH(DMI_SYS_VENDOR,
677                                         "SAMSUNG ELECTRONICS CO., LTD."),
678                         DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
679                         DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
680                 },
681                 .callback = dmi_check_cb,
682         },
683         {
684                 .ident = "N150/N210/N220",
685                 .matches = {
686                         DMI_MATCH(DMI_SYS_VENDOR,
687                                         "SAMSUNG ELECTRONICS CO., LTD."),
688                         DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
689                         DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
690                 },
691                 .callback = dmi_check_cb,
692         },
693         {
694                 .ident = "N220",
695                 .matches = {
696                         DMI_MATCH(DMI_SYS_VENDOR,
697                                         "SAMSUNG ELECTRONICS CO., LTD."),
698                         DMI_MATCH(DMI_PRODUCT_NAME, "N220"),
699                         DMI_MATCH(DMI_BOARD_NAME, "N220"),
700                 },
701                 .callback = dmi_check_cb,
702         },
703         {
704                 .ident = "N150/N210/N220/N230",
705                 .matches = {
706                         DMI_MATCH(DMI_SYS_VENDOR,
707                                         "SAMSUNG ELECTRONICS CO., LTD."),
708                         DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
709                         DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
710                 },
711                 .callback = dmi_check_cb,
712         },
713         {
714                 .ident = "N150P/N210P/N220P",
715                 .matches = {
716                         DMI_MATCH(DMI_SYS_VENDOR,
717                                         "SAMSUNG ELECTRONICS CO., LTD."),
718                         DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
719                         DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
720                 },
721                 .callback = dmi_check_cb,
722         },
723         {
724                 .ident = "R700",
725                 .matches = {
726                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
727                       DMI_MATCH(DMI_PRODUCT_NAME, "SR700"),
728                       DMI_MATCH(DMI_BOARD_NAME, "SR700"),
729                 },
730                 .callback = dmi_check_cb,
731         },
732         {
733                 .ident = "R530/R730",
734                 .matches = {
735                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
736                       DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
737                       DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
738                 },
739                 .callback = dmi_check_cb,
740         },
741         {
742                 .ident = "NF110/NF210/NF310",
743                 .matches = {
744                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
745                         DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
746                         DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
747                 },
748                 .callback = dmi_check_cb,
749         },
750         {
751                 .ident = "N145P/N250P/N260P",
752                 .matches = {
753                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
754                         DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
755                         DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
756                 },
757                 .callback = dmi_check_cb,
758         },
759         {
760                 .ident = "R70/R71",
761                 .matches = {
762                         DMI_MATCH(DMI_SYS_VENDOR,
763                                         "SAMSUNG ELECTRONICS CO., LTD."),
764                         DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
765                         DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
766                 },
767                 .callback = dmi_check_cb,
768         },
769         {
770                 .ident = "P460",
771                 .matches = {
772                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
773                         DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
774                         DMI_MATCH(DMI_BOARD_NAME, "P460"),
775                 },
776                 .callback = dmi_check_cb,
777         },
778         {
779                 .ident = "R528/R728",
780                 .matches = {
781                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
782                         DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"),
783                         DMI_MATCH(DMI_BOARD_NAME, "R528/R728"),
784                 },
785                 .callback = dmi_check_cb,
786         },
787         {
788                 .ident = "NC210/NC110",
789                 .matches = {
790                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
791                         DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
792                         DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
793                 },
794                 .callback = dmi_check_cb,
795         },
796                 {
797                 .ident = "X520",
798                 .matches = {
799                         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
800                         DMI_MATCH(DMI_PRODUCT_NAME, "X520"),
801                         DMI_MATCH(DMI_BOARD_NAME, "X520"),
802                 },
803                 .callback = dmi_check_cb,
804         },
805         { },
806 };
807 MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
808
809 static int find_signature(void __iomem *memcheck, const char *testStr)
810 {
811         int i = 0;
812         int loca;
813
814         for (loca = 0; loca < 0xffff; loca++) {
815                 char temp = readb(memcheck + loca);
816
817                 if (temp == testStr[i]) {
818                         if (i == strlen(testStr)-1)
819                                 break;
820                         ++i;
821                 } else {
822                         i = 0;
823                 }
824         }
825         return loca;
826 }
827
828 static int __init samsung_init(void)
829 {
830         const struct sabi_config *config = NULL;
831         const struct sabi_commands *commands;
832         struct backlight_device *bd;
833         struct backlight_properties props;
834         struct sabi_retval sretval;
835         unsigned int ifaceP;
836         int i;
837         int loca;
838         int retval;
839
840         if (!force && !dmi_check_system(samsung_dmi_table))
841                 return -ENODEV;
842
843         samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
844         if (!samsung)
845                 return -ENOMEM;
846
847         mutex_init(&samsung->sabi_mutex);
848
849         samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
850         if (!samsung->f0000_segment) {
851                 pr_err("Can't map the segment at 0xf0000\n");
852                 goto error_cant_map;
853         }
854
855         /* Try to find one of the signatures in memory to find the header */
856         for (i = 0; sabi_configs[i].test_string != 0; ++i) {
857                 samsung->config = &sabi_configs[i];
858                 loca = find_signature(samsung->f0000_segment,
859                                       samsung->config->test_string);
860                 if (loca != 0xffff)
861                         break;
862         }
863
864         if (loca == 0xffff) {
865                 pr_err("This computer does not support SABI\n");
866                 goto error_no_signature;
867         }
868
869         config = samsung->config;
870         commands = &config->commands;
871
872         /* point to the SMI port Number */
873         loca += 1;
874         samsung->sabi = (samsung->f0000_segment + loca);
875
876         if (debug) {
877                 printk(KERN_DEBUG "This computer supports SABI==%x\n",
878                         loca + 0xf0000 - 6);
879                 printk(KERN_DEBUG "SABI header:\n");
880                 printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
881                        readw(samsung->sabi +
882                              config->header_offsets.port));
883                 printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
884                        readb(samsung->sabi +
885                              config->header_offsets.iface_func));
886                 printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
887                        readb(samsung->sabi +
888                              config->header_offsets.en_mem));
889                 printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
890                        readb(samsung->sabi +
891                              config->header_offsets.re_mem));
892                 printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
893                        readw(samsung->sabi +
894                              config->header_offsets.data_offset));
895                 printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
896                        readw(samsung->sabi +
897                              config->header_offsets.data_segment));
898         }
899
900         /* Get a pointer to the SABI Interface */
901         ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
902         ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
903         samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
904         if (!samsung->sabi_iface) {
905                 pr_err("Can't remap %x\n", ifaceP);
906                 goto error_no_signature;
907         }
908         if (debug) {
909                 printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP);
910                 printk(KERN_DEBUG "sabi_iface = %p\n", samsung->sabi_iface);
911
912                 test_backlight();
913                 test_wireless();
914
915                 retval = sabi_get_command(commands->get_brightness,
916                                           &sretval);
917                 printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]);
918         }
919
920         /* Turn on "Linux" mode in the BIOS */
921         if (commands->set_linux != 0xff) {
922                 retval = sabi_set_command(commands->set_linux,
923                                           0x81);
924                 if (retval) {
925                         pr_warn("Linux mode was not set!\n");
926                         goto error_no_platform;
927                 }
928         }
929
930         /* Check for stepping quirk */
931         check_for_stepping_quirk();
932
933         /* knock up a platform device to hang stuff off of */
934         samsung->pdev = platform_device_register_simple("samsung", -1, NULL, 0);
935         if (IS_ERR(samsung->pdev))
936                 goto error_no_platform;
937
938         /* create a backlight device to talk to this one */
939         memset(&props, 0, sizeof(struct backlight_properties));
940         props.type = BACKLIGHT_PLATFORM;
941         props.max_brightness = config->max_brightness -
942                                 config->min_brightness;
943         bd = backlight_device_register("samsung", &samsung->pdev->dev,
944                                        NULL, &backlight_ops,
945                                        &props);
946         if (IS_ERR(bd))
947                 goto error_no_backlight;
948
949         samsung->backlight_device = bd;
950         samsung->backlight_device->props.brightness = read_brightness();
951         samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
952         backlight_update_status(samsung->backlight_device);
953
954         retval = init_wireless(samsung->pdev);
955         if (retval)
956                 goto error_no_rfk;
957
958         retval = device_create_file(&samsung->pdev->dev,
959                                     &dev_attr_performance_level);
960         if (retval)
961                 goto error_file_create;
962
963         return 0;
964
965 error_file_create:
966         destroy_wireless();
967
968 error_no_rfk:
969         backlight_device_unregister(samsung->backlight_device);
970
971 error_no_backlight:
972         platform_device_unregister(samsung->pdev);
973
974 error_no_platform:
975         iounmap(samsung->sabi_iface);
976
977 error_no_signature:
978         iounmap(samsung->f0000_segment);
979
980 error_cant_map:
981         kfree(samsung);
982         samsung = NULL;
983         return -EINVAL;
984 }
985
986 static void __exit samsung_exit(void)
987 {
988         const struct sabi_commands *commands = &samsung->config->commands;
989
990         /* Turn off "Linux" mode in the BIOS */
991         if (commands->set_linux != 0xff)
992                 sabi_set_command(commands->set_linux, 0x80);
993
994         device_remove_file(&samsung->pdev->dev, &dev_attr_performance_level);
995         backlight_device_unregister(samsung->backlight_device);
996         destroy_wireless();
997         iounmap(samsung->sabi_iface);
998         iounmap(samsung->f0000_segment);
999         platform_device_unregister(samsung->pdev);
1000         kfree(samsung);
1001         samsung = NULL;
1002 }
1003
1004 module_init(samsung_init);
1005 module_exit(samsung_exit);
1006
1007 MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1008 MODULE_DESCRIPTION("Samsung Backlight driver");
1009 MODULE_LICENSE("GPL");