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