pandora: defconfig: update
[pandora-kernel.git] / drivers / platform / x86 / samsung-laptop.c
1 /*
2  * Samsung Laptop driver
3  *
4  * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
5  * Copyright (C) 2009,2011 Novell Inc.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License version 2 as published by
9  * the Free Software Foundation.
10  *
11  */
12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13
14 #include <linux/kernel.h>
15 #include <linux/init.h>
16 #include <linux/module.h>
17 #include <linux/delay.h>
18 #include <linux/pci.h>
19 #include <linux/backlight.h>
20 #include <linux/fb.h>
21 #include <linux/dmi.h>
22 #include <linux/platform_device.h>
23 #include <linux/rfkill.h>
24 #include <linux/acpi.h>
25 #include <linux/efi.h>
26
27 /*
28  * This driver is needed because a number of Samsung laptops do not hook
29  * their control settings through ACPI.  So we have to poke around in the
30  * BIOS to do things like brightness values, and "special" key controls.
31  */
32
33 /*
34  * We have 0 - 8 as valid brightness levels.  The specs say that level 0 should
35  * be reserved by the BIOS (which really doesn't make much sense), we tell
36  * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
37  */
38 #define MAX_BRIGHT      0x07
39
40
41 #define SABI_IFACE_MAIN                 0x00
42 #define SABI_IFACE_SUB                  0x02
43 #define SABI_IFACE_COMPLETE             0x04
44 #define SABI_IFACE_DATA                 0x05
45
46 /* Structure to get data back to the calling function */
47 struct sabi_retval {
48         u8 retval[20];
49 };
50
51 struct sabi_header_offsets {
52         u8 port;
53         u8 re_mem;
54         u8 iface_func;
55         u8 en_mem;
56         u8 data_offset;
57         u8 data_segment;
58 };
59
60 struct sabi_commands {
61         /*
62          * Brightness is 0 - 8, as described above.
63          * Value 0 is for the BIOS to use
64          */
65         u8 get_brightness;
66         u8 set_brightness;
67
68         /*
69          * first byte:
70          * 0x00 - wireless is off
71          * 0x01 - wireless is on
72          * second byte:
73          * 0x02 - 3G is off
74          * 0x03 - 3G is on
75          * TODO, verify 3G is correct, that doesn't seem right...
76          */
77         u8 get_wireless_button;
78         u8 set_wireless_button;
79
80         /* 0 is off, 1 is on */
81         u8 get_backlight;
82         u8 set_backlight;
83
84         /*
85          * 0x80 or 0x00 - no action
86          * 0x81 - recovery key pressed
87          */
88         u8 get_recovery_mode;
89         u8 set_recovery_mode;
90
91         /*
92          * on seclinux: 0 is low, 1 is high,
93          * on swsmi: 0 is normal, 1 is silent, 2 is turbo
94          */
95         u8 get_performance_level;
96         u8 set_performance_level;
97
98         /*
99          * Tell the BIOS that Linux is running on this machine.
100          * 81 is on, 80 is off
101          */
102         u8 set_linux;
103 };
104
105 struct sabi_performance_level {
106         const char *name;
107         u8 value;
108 };
109
110 struct sabi_config {
111         const char *test_string;
112         u16 main_function;
113         const struct sabi_header_offsets header_offsets;
114         const struct sabi_commands commands;
115         const struct sabi_performance_level performance_levels[4];
116         u8 min_brightness;
117         u8 max_brightness;
118 };
119
120 static const struct sabi_config sabi_configs[] = {
121         {
122                 .test_string = "SECLINUX",
123
124                 .main_function = 0x4c49,
125
126                 .header_offsets = {
127                         .port = 0x00,
128                         .re_mem = 0x02,
129                         .iface_func = 0x03,
130                         .en_mem = 0x04,
131                         .data_offset = 0x05,
132                         .data_segment = 0x07,
133                 },
134
135                 .commands = {
136                         .get_brightness = 0x00,
137                         .set_brightness = 0x01,
138
139                         .get_wireless_button = 0x02,
140                         .set_wireless_button = 0x03,
141
142                         .get_backlight = 0x04,
143                         .set_backlight = 0x05,
144
145                         .get_recovery_mode = 0x06,
146                         .set_recovery_mode = 0x07,
147
148                         .get_performance_level = 0x08,
149                         .set_performance_level = 0x09,
150
151                         .set_linux = 0x0a,
152                 },
153
154                 .performance_levels = {
155                         {
156                                 .name = "silent",
157                                 .value = 0,
158                         },
159                         {
160                                 .name = "normal",
161                                 .value = 1,
162                         },
163                         { },
164                 },
165                 .min_brightness = 1,
166                 .max_brightness = 8,
167         },
168         {
169                 .test_string = "SwSmi@",
170
171                 .main_function = 0x5843,
172
173                 .header_offsets = {
174                         .port = 0x00,
175                         .re_mem = 0x04,
176                         .iface_func = 0x02,
177                         .en_mem = 0x03,
178                         .data_offset = 0x05,
179                         .data_segment = 0x07,
180                 },
181
182                 .commands = {
183                         .get_brightness = 0x10,
184                         .set_brightness = 0x11,
185
186                         .get_wireless_button = 0x12,
187                         .set_wireless_button = 0x13,
188
189                         .get_backlight = 0x2d,
190                         .set_backlight = 0x2e,
191
192                         .get_recovery_mode = 0xff,
193                         .set_recovery_mode = 0xff,
194
195                         .get_performance_level = 0x31,
196                         .set_performance_level = 0x32,
197
198                         .set_linux = 0xff,
199                 },
200
201                 .performance_levels = {
202                         {
203                                 .name = "normal",
204                                 .value = 0,
205                         },
206                         {
207                                 .name = "silent",
208                                 .value = 1,
209                         },
210                         {
211                                 .name = "overclock",
212                                 .value = 2,
213                         },
214                         { },
215                 },
216                 .min_brightness = 0,
217                 .max_brightness = 8,
218         },
219         { },
220 };
221
222 static const struct sabi_config *sabi_config;
223
224 static void __iomem *sabi;
225 static void __iomem *sabi_iface;
226 static void __iomem *f0000_segment;
227 static struct backlight_device *backlight_device;
228 static struct mutex sabi_mutex;
229 static struct platform_device *sdev;
230 static struct rfkill *rfk;
231 static bool handle_backlight;
232 static bool has_stepping_quirk;
233
234 static int force;
235 module_param(force, bool, 0);
236 MODULE_PARM_DESC(force,
237                 "Disable the DMI check and forces the driver to be loaded");
238
239 static int debug;
240 module_param(debug, bool, S_IRUGO | S_IWUSR);
241 MODULE_PARM_DESC(debug, "Debug enabled or not");
242
243 static int sabi_get_command(u8 command, struct sabi_retval *sretval)
244 {
245         int retval = 0;
246         u16 port = readw(sabi + sabi_config->header_offsets.port);
247         u8 complete, iface_data;
248
249         mutex_lock(&sabi_mutex);
250
251         /* enable memory to be able to write to it */
252         outb(readb(sabi + sabi_config->header_offsets.en_mem), port);
253
254         /* write out the command */
255         writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN);
256         writew(command, sabi_iface + SABI_IFACE_SUB);
257         writeb(0, sabi_iface + SABI_IFACE_COMPLETE);
258         outb(readb(sabi + sabi_config->header_offsets.iface_func), port);
259
260         /* write protect memory to make it safe */
261         outb(readb(sabi + sabi_config->header_offsets.re_mem), port);
262
263         /* see if the command actually succeeded */
264         complete = readb(sabi_iface + SABI_IFACE_COMPLETE);
265         iface_data = readb(sabi_iface + SABI_IFACE_DATA);
266         if (complete != 0xaa || iface_data == 0xff) {
267                 pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
268                         command, complete, iface_data);
269                 retval = -EINVAL;
270                 goto exit;
271         }
272         /*
273          * Save off the data into a structure so the caller use it.
274          * Right now we only want the first 4 bytes,
275          * There are commands that need more, but not for the ones we
276          * currently care about.
277          */
278         sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA);
279         sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1);
280         sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2);
281         sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3);
282
283 exit:
284         mutex_unlock(&sabi_mutex);
285         return retval;
286
287 }
288
289 static int sabi_set_command(u8 command, u8 data)
290 {
291         int retval = 0;
292         u16 port = readw(sabi + sabi_config->header_offsets.port);
293         u8 complete, iface_data;
294
295         mutex_lock(&sabi_mutex);
296
297         /* enable memory to be able to write to it */
298         outb(readb(sabi + sabi_config->header_offsets.en_mem), port);
299
300         /* write out the command */
301         writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN);
302         writew(command, sabi_iface + SABI_IFACE_SUB);
303         writeb(0, sabi_iface + SABI_IFACE_COMPLETE);
304         writeb(data, sabi_iface + SABI_IFACE_DATA);
305         outb(readb(sabi + sabi_config->header_offsets.iface_func), port);
306
307         /* write protect memory to make it safe */
308         outb(readb(sabi + sabi_config->header_offsets.re_mem), port);
309
310         /* see if the command actually succeeded */
311         complete = readb(sabi_iface + SABI_IFACE_COMPLETE);
312         iface_data = readb(sabi_iface + SABI_IFACE_DATA);
313         if (complete != 0xaa || iface_data == 0xff) {
314                 pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
315                        command, complete, iface_data);
316                 retval = -EINVAL;
317         }
318
319         mutex_unlock(&sabi_mutex);
320         return retval;
321 }
322
323 static void test_backlight(void)
324 {
325         struct sabi_retval sretval;
326
327         sabi_get_command(sabi_config->commands.get_backlight, &sretval);
328         printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
329
330         sabi_set_command(sabi_config->commands.set_backlight, 0);
331         printk(KERN_DEBUG "backlight should be off\n");
332
333         sabi_get_command(sabi_config->commands.get_backlight, &sretval);
334         printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
335
336         msleep(1000);
337
338         sabi_set_command(sabi_config->commands.set_backlight, 1);
339         printk(KERN_DEBUG "backlight should be on\n");
340
341         sabi_get_command(sabi_config->commands.get_backlight, &sretval);
342         printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
343 }
344
345 static void test_wireless(void)
346 {
347         struct sabi_retval sretval;
348
349         sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
350         printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
351
352         sabi_set_command(sabi_config->commands.set_wireless_button, 0);
353         printk(KERN_DEBUG "wireless led should be off\n");
354
355         sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
356         printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
357
358         msleep(1000);
359
360         sabi_set_command(sabi_config->commands.set_wireless_button, 1);
361         printk(KERN_DEBUG "wireless led should be on\n");
362
363         sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
364         printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
365 }
366
367 static u8 read_brightness(void)
368 {
369         struct sabi_retval sretval;
370         int user_brightness = 0;
371         int retval;
372
373         retval = sabi_get_command(sabi_config->commands.get_brightness,
374                                   &sretval);
375         if (!retval) {
376                 user_brightness = sretval.retval[0];
377                 if (user_brightness > sabi_config->min_brightness)
378                         user_brightness -= sabi_config->min_brightness;
379                 else
380                         user_brightness = 0;
381         }
382         return user_brightness;
383 }
384
385 static void set_brightness(u8 user_brightness)
386 {
387         u8 user_level = user_brightness + sabi_config->min_brightness;
388
389         if (has_stepping_quirk && user_level != 0) {
390                 /*
391                  * short circuit if the specified level is what's already set
392                  * to prevent the screen from flickering needlessly
393                  */
394                 if (user_brightness == read_brightness())
395                         return;
396
397                 sabi_set_command(sabi_config->commands.set_brightness, 0);
398         }
399
400         sabi_set_command(sabi_config->commands.set_brightness, user_level);
401 }
402
403 static int get_brightness(struct backlight_device *bd)
404 {
405         return (int)read_brightness();
406 }
407
408 static void check_for_stepping_quirk(void)
409 {
410         u8 initial_level;
411         u8 check_level;
412         u8 orig_level = read_brightness();
413
414         /*
415          * Some laptops exhibit the strange behaviour of stepping toward
416          * (rather than setting) the brightness except when changing to/from
417          * brightness level 0. This behaviour is checked for here and worked
418          * around in set_brightness.
419          */
420
421         if (orig_level == 0)
422                 set_brightness(1);
423
424         initial_level = read_brightness();
425
426         if (initial_level <= 2)
427                 check_level = initial_level + 2;
428         else
429                 check_level = initial_level - 2;
430
431         has_stepping_quirk = false;
432         set_brightness(check_level);
433
434         if (read_brightness() != check_level) {
435                 has_stepping_quirk = true;
436                 pr_info("enabled workaround for brightness stepping quirk\n");
437         }
438
439         set_brightness(orig_level);
440 }
441
442 static int update_status(struct backlight_device *bd)
443 {
444         set_brightness(bd->props.brightness);
445
446         if (bd->props.power == FB_BLANK_UNBLANK)
447                 sabi_set_command(sabi_config->commands.set_backlight, 1);
448         else
449                 sabi_set_command(sabi_config->commands.set_backlight, 0);
450         return 0;
451 }
452
453 static const struct backlight_ops backlight_ops = {
454         .get_brightness = get_brightness,
455         .update_status  = update_status,
456 };
457
458 static int rfkill_set(void *data, bool blocked)
459 {
460         /* Do something with blocked...*/
461         /*
462          * blocked == false is on
463          * blocked == true is off
464          */
465         if (blocked)
466                 sabi_set_command(sabi_config->commands.set_wireless_button, 0);
467         else
468                 sabi_set_command(sabi_config->commands.set_wireless_button, 1);
469
470         return 0;
471 }
472
473 static struct rfkill_ops rfkill_ops = {
474         .set_block = rfkill_set,
475 };
476
477 static int init_wireless(struct platform_device *sdev)
478 {
479         int retval;
480
481         rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN,
482                            &rfkill_ops, NULL);
483         if (!rfk)
484                 return -ENOMEM;
485
486         retval = rfkill_register(rfk);
487         if (retval) {
488                 rfkill_destroy(rfk);
489                 return -ENODEV;
490         }
491
492         return 0;
493 }
494
495 static void destroy_wireless(void)
496 {
497         rfkill_unregister(rfk);
498         rfkill_destroy(rfk);
499 }
500
501 static ssize_t get_performance_level(struct device *dev,
502                                      struct device_attribute *attr, char *buf)
503 {
504         struct sabi_retval sretval;
505         int retval;
506         int i;
507
508         /* Read the state */
509         retval = sabi_get_command(sabi_config->commands.get_performance_level,
510                                   &sretval);
511         if (retval)
512                 return retval;
513
514         /* The logic is backwards, yeah, lots of fun... */
515         for (i = 0; sabi_config->performance_levels[i].name; ++i) {
516                 if (sretval.retval[0] == sabi_config->performance_levels[i].value)
517                         return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name);
518         }
519         return sprintf(buf, "%s\n", "unknown");
520 }
521
522 static ssize_t set_performance_level(struct device *dev,
523                                 struct device_attribute *attr, const char *buf,
524                                 size_t count)
525 {
526         if (count >= 1) {
527                 int i;
528                 for (i = 0; sabi_config->performance_levels[i].name; ++i) {
529                         const struct sabi_performance_level *level =
530                                 &sabi_config->performance_levels[i];
531                         if (!strncasecmp(level->name, buf, strlen(level->name))) {
532                                 sabi_set_command(sabi_config->commands.set_performance_level,
533                                                  level->value);
534                                 break;
535                         }
536                 }
537                 if (!sabi_config->performance_levels[i].name)
538                         return -EINVAL;
539         }
540         return count;
541 }
542 static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
543                    get_performance_level, set_performance_level);
544
545
546 static struct dmi_system_id __initdata samsung_dmi_table[] = {
547         {
548                 .matches = {
549                         DMI_MATCH(DMI_SYS_VENDOR,
550                                         "SAMSUNG ELECTRONICS CO., LTD."),
551                         DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
552                 },
553         },
554         {
555                 .matches = {
556                         DMI_MATCH(DMI_SYS_VENDOR,
557                                         "SAMSUNG ELECTRONICS CO., LTD."),
558                         DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
559                 },
560         },
561         {
562                 .matches = {
563                         DMI_MATCH(DMI_SYS_VENDOR,
564                                         "SAMSUNG ELECTRONICS CO., LTD."),
565                         DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
566                 },
567         },
568         {
569                 .matches = {
570                         DMI_MATCH(DMI_SYS_VENDOR,
571                                         "SAMSUNG ELECTRONICS CO., LTD."),
572                         DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
573                 },
574         },
575         { },
576 };
577 MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
578
579 static int find_signature(void __iomem *memcheck, const char *testStr)
580 {
581         int i = 0;
582         int loca;
583
584         for (loca = 0; loca < 0xffff; loca++) {
585                 char temp = readb(memcheck + loca);
586
587                 if (temp == testStr[i]) {
588                         if (i == strlen(testStr)-1)
589                                 break;
590                         ++i;
591                 } else {
592                         i = 0;
593                 }
594         }
595         return loca;
596 }
597
598 static int __init samsung_init(void)
599 {
600         struct backlight_properties props;
601         struct sabi_retval sretval;
602         unsigned int ifaceP;
603         int i;
604         int loca = 0xffff;
605         int retval;
606
607         if (efi_enabled(EFI_BOOT))
608                 return -ENODEV;
609
610         mutex_init(&sabi_mutex);
611         handle_backlight = true;
612
613 #ifdef CONFIG_ACPI
614         /* Don't handle backlight here if the acpi video already handle it */
615         if (acpi_video_backlight_support())
616                 handle_backlight = false;
617 #endif
618
619         if (!force && !dmi_check_system(samsung_dmi_table))
620                 return -ENODEV;
621
622         f0000_segment = ioremap_nocache(0xf0000, 0xffff);
623         if (!f0000_segment) {
624                 if (debug || force)
625                         pr_err("Can't map the segment at 0xf0000\n");
626                 return -EINVAL;
627         }
628
629         /* Try to find one of the signatures in memory to find the header */
630         for (i = 0; sabi_configs[i].test_string != 0; ++i) {
631                 sabi_config = &sabi_configs[i];
632                 loca = find_signature(f0000_segment, sabi_config->test_string);
633                 if (loca != 0xffff)
634                         break;
635         }
636
637         if (loca == 0xffff) {
638                 if (debug || force)
639                         pr_err("This computer does not support SABI\n");
640                 goto error_no_signature;
641         }
642
643         /* point to the SMI port Number */
644         loca += 1;
645         sabi = (f0000_segment + loca);
646
647         if (debug) {
648                 printk(KERN_DEBUG "This computer supports SABI==%x\n",
649                         loca + 0xf0000 - 6);
650                 printk(KERN_DEBUG "SABI header:\n");
651                 printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
652                         readw(sabi + sabi_config->header_offsets.port));
653                 printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
654                         readb(sabi + sabi_config->header_offsets.iface_func));
655                 printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
656                         readb(sabi + sabi_config->header_offsets.en_mem));
657                 printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
658                         readb(sabi + sabi_config->header_offsets.re_mem));
659                 printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
660                         readw(sabi + sabi_config->header_offsets.data_offset));
661                 printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
662                         readw(sabi + sabi_config->header_offsets.data_segment));
663         }
664
665         /* Get a pointer to the SABI Interface */
666         ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4;
667         ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff;
668         sabi_iface = ioremap_nocache(ifaceP, 16);
669         if (!sabi_iface) {
670                 pr_err("Can't remap %x\n", ifaceP);
671                 goto error_no_signature;
672         }
673         if (debug) {
674                 printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP);
675                 printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface);
676
677                 if (handle_backlight)
678                         test_backlight();
679                 test_wireless();
680
681                 retval = sabi_get_command(sabi_config->commands.get_brightness,
682                                           &sretval);
683                 printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]);
684         }
685
686         /* Turn on "Linux" mode in the BIOS */
687         if (sabi_config->commands.set_linux != 0xff) {
688                 retval = sabi_set_command(sabi_config->commands.set_linux,
689                                           0x81);
690                 if (retval) {
691                         pr_warn("Linux mode was not set!\n");
692                         goto error_no_platform;
693                 }
694         }
695
696         /* Check for stepping quirk */
697         if (handle_backlight)
698                 check_for_stepping_quirk();
699
700 #ifdef CONFIG_ACPI
701         /* Only log that if we are really on a sabi platform */
702         if (acpi_video_backlight_support())
703                 pr_info("Backlight controlled by ACPI video driver\n");
704 #endif
705
706         /* knock up a platform device to hang stuff off of */
707         sdev = platform_device_register_simple("samsung", -1, NULL, 0);
708         if (IS_ERR(sdev))
709                 goto error_no_platform;
710
711         if (!handle_backlight)
712                 goto skip_backlight;
713
714         /* create a backlight device to talk to this one */
715         memset(&props, 0, sizeof(struct backlight_properties));
716         props.type = BACKLIGHT_PLATFORM;
717         props.max_brightness = sabi_config->max_brightness -
718                                 sabi_config->min_brightness;
719         backlight_device = backlight_device_register("samsung", &sdev->dev,
720                                                      NULL, &backlight_ops,
721                                                      &props);
722         if (IS_ERR(backlight_device))
723                 goto error_no_backlight;
724
725         backlight_device->props.brightness = read_brightness();
726         backlight_device->props.power = FB_BLANK_UNBLANK;
727         backlight_update_status(backlight_device);
728
729 skip_backlight:
730         retval = init_wireless(sdev);
731         if (retval)
732                 goto error_no_rfk;
733
734         retval = device_create_file(&sdev->dev, &dev_attr_performance_level);
735         if (retval)
736                 goto error_file_create;
737
738         return 0;
739
740 error_file_create:
741         destroy_wireless();
742
743 error_no_rfk:
744         backlight_device_unregister(backlight_device);
745
746 error_no_backlight:
747         platform_device_unregister(sdev);
748
749 error_no_platform:
750         iounmap(sabi_iface);
751
752 error_no_signature:
753         iounmap(f0000_segment);
754         return -EINVAL;
755 }
756
757 static void __exit samsung_exit(void)
758 {
759         /* Turn off "Linux" mode in the BIOS */
760         if (sabi_config->commands.set_linux != 0xff)
761                 sabi_set_command(sabi_config->commands.set_linux, 0x80);
762
763         device_remove_file(&sdev->dev, &dev_attr_performance_level);
764         backlight_device_unregister(backlight_device);
765         destroy_wireless();
766         iounmap(sabi_iface);
767         iounmap(f0000_segment);
768         platform_device_unregister(sdev);
769 }
770
771 module_init(samsung_init);
772 module_exit(samsung_exit);
773
774 MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
775 MODULE_DESCRIPTION("Samsung Backlight driver");
776 MODULE_LICENSE("GPL");