Merge branch 'drm-radeon-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git...
[pandora-kernel.git] / drivers / platform / x86 / dell-laptop.c
1 /*
2  *  Driver for Dell laptop extras
3  *
4  *  Copyright (c) Red Hat <mjg@redhat.com>
5  *
6  *  Based on documentation in the libsmbios package, Copyright (C) 2005 Dell
7  *  Inc.
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License version 2 as
11  *  published by the Free Software Foundation.
12  */
13
14 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
15
16 #include <linux/module.h>
17 #include <linux/kernel.h>
18 #include <linux/init.h>
19 #include <linux/platform_device.h>
20 #include <linux/backlight.h>
21 #include <linux/err.h>
22 #include <linux/dmi.h>
23 #include <linux/io.h>
24 #include <linux/rfkill.h>
25 #include <linux/power_supply.h>
26 #include <linux/acpi.h>
27 #include <linux/mm.h>
28 #include <linux/i8042.h>
29 #include <linux/slab.h>
30 #include <linux/debugfs.h>
31 #include <linux/seq_file.h>
32 #include "../../firmware/dcdbas.h"
33
34 #define BRIGHTNESS_TOKEN 0x7d
35
36 /* This structure will be modified by the firmware when we enter
37  * system management mode, hence the volatiles */
38
39 struct calling_interface_buffer {
40         u16 class;
41         u16 select;
42         volatile u32 input[4];
43         volatile u32 output[4];
44 } __packed;
45
46 struct calling_interface_token {
47         u16 tokenID;
48         u16 location;
49         union {
50                 u16 value;
51                 u16 stringlength;
52         };
53 };
54
55 struct calling_interface_structure {
56         struct dmi_header header;
57         u16 cmdIOAddress;
58         u8 cmdIOCode;
59         u32 supportedCmds;
60         struct calling_interface_token tokens[];
61 } __packed;
62
63 static int da_command_address;
64 static int da_command_code;
65 static int da_num_tokens;
66 static struct calling_interface_token *da_tokens;
67
68 static struct platform_driver platform_driver = {
69         .driver = {
70                 .name = "dell-laptop",
71                 .owner = THIS_MODULE,
72         }
73 };
74
75 static struct platform_device *platform_device;
76 static struct backlight_device *dell_backlight_device;
77 static struct rfkill *wifi_rfkill;
78 static struct rfkill *bluetooth_rfkill;
79 static struct rfkill *wwan_rfkill;
80
81 static const struct dmi_system_id __initdata dell_device_table[] = {
82         {
83                 .ident = "Dell laptop",
84                 .matches = {
85                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
86                         DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
87                 },
88         },
89         {
90                 .matches = {
91                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
92                         DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/
93                 },
94         },
95         {
96                 .ident = "Dell Computer Corporation",
97                 .matches = {
98                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
99                         DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
100                 },
101         },
102         { }
103 };
104
105 static struct dmi_system_id __devinitdata dell_blacklist[] = {
106         /* Supported by compal-laptop */
107         {
108                 .ident = "Dell Mini 9",
109                 .matches = {
110                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
111                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 910"),
112                 },
113         },
114         {
115                 .ident = "Dell Mini 10",
116                 .matches = {
117                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
118                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1010"),
119                 },
120         },
121         {
122                 .ident = "Dell Mini 10v",
123                 .matches = {
124                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
125                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1011"),
126                 },
127         },
128         {
129                 .ident = "Dell Mini 1012",
130                 .matches = {
131                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
132                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"),
133                 },
134         },
135         {
136                 .ident = "Dell Inspiron 11z",
137                 .matches = {
138                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
139                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1110"),
140                 },
141         },
142         {
143                 .ident = "Dell Mini 12",
144                 .matches = {
145                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
146                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1210"),
147                 },
148         },
149         {}
150 };
151
152 static struct calling_interface_buffer *buffer;
153 static struct page *bufferpage;
154 static DEFINE_MUTEX(buffer_mutex);
155
156 static int hwswitch_state;
157
158 static void get_buffer(void)
159 {
160         mutex_lock(&buffer_mutex);
161         memset(buffer, 0, sizeof(struct calling_interface_buffer));
162 }
163
164 static void release_buffer(void)
165 {
166         mutex_unlock(&buffer_mutex);
167 }
168
169 static void __init parse_da_table(const struct dmi_header *dm)
170 {
171         /* Final token is a terminator, so we don't want to copy it */
172         int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1;
173         struct calling_interface_structure *table =
174                 container_of(dm, struct calling_interface_structure, header);
175
176         /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least
177            6 bytes of entry */
178
179         if (dm->length < 17)
180                 return;
181
182         da_command_address = table->cmdIOAddress;
183         da_command_code = table->cmdIOCode;
184
185         da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) *
186                              sizeof(struct calling_interface_token),
187                              GFP_KERNEL);
188
189         if (!da_tokens)
190                 return;
191
192         memcpy(da_tokens+da_num_tokens, table->tokens,
193                sizeof(struct calling_interface_token) * tokens);
194
195         da_num_tokens += tokens;
196 }
197
198 static void __init find_tokens(const struct dmi_header *dm, void *dummy)
199 {
200         switch (dm->type) {
201         case 0xd4: /* Indexed IO */
202                 break;
203         case 0xd5: /* Protected Area Type 1 */
204                 break;
205         case 0xd6: /* Protected Area Type 2 */
206                 break;
207         case 0xda: /* Calling interface */
208                 parse_da_table(dm);
209                 break;
210         }
211 }
212
213 static int find_token_location(int tokenid)
214 {
215         int i;
216         for (i = 0; i < da_num_tokens; i++) {
217                 if (da_tokens[i].tokenID == tokenid)
218                         return da_tokens[i].location;
219         }
220
221         return -1;
222 }
223
224 static struct calling_interface_buffer *
225 dell_send_request(struct calling_interface_buffer *buffer, int class,
226                   int select)
227 {
228         struct smi_cmd command;
229
230         command.magic = SMI_CMD_MAGIC;
231         command.command_address = da_command_address;
232         command.command_code = da_command_code;
233         command.ebx = virt_to_phys(buffer);
234         command.ecx = 0x42534931;
235
236         buffer->class = class;
237         buffer->select = select;
238
239         dcdbas_smi_request(&command);
240
241         return buffer;
242 }
243
244 /* Derived from information in DellWirelessCtl.cpp:
245    Class 17, select 11 is radio control. It returns an array of 32-bit values.
246
247    Input byte 0 = 0: Wireless information
248
249    result[0]: return code
250    result[1]:
251      Bit 0:      Hardware switch supported
252      Bit 1:      Wifi locator supported
253      Bit 2:      Wifi is supported
254      Bit 3:      Bluetooth is supported
255      Bit 4:      WWAN is supported
256      Bit 5:      Wireless keyboard supported
257      Bits 6-7:   Reserved
258      Bit 8:      Wifi is installed
259      Bit 9:      Bluetooth is installed
260      Bit 10:     WWAN is installed
261      Bits 11-15: Reserved
262      Bit 16:     Hardware switch is on
263      Bit 17:     Wifi is blocked
264      Bit 18:     Bluetooth is blocked
265      Bit 19:     WWAN is blocked
266      Bits 20-31: Reserved
267    result[2]: NVRAM size in bytes
268    result[3]: NVRAM format version number
269
270    Input byte 0 = 2: Wireless switch configuration
271    result[0]: return code
272    result[1]:
273      Bit 0:      Wifi controlled by switch
274      Bit 1:      Bluetooth controlled by switch
275      Bit 2:      WWAN controlled by switch
276      Bits 3-6:   Reserved
277      Bit 7:      Wireless switch config locked
278      Bit 8:      Wifi locator enabled
279      Bits 9-14:  Reserved
280      Bit 15:     Wifi locator setting locked
281      Bits 16-31: Reserved
282 */
283
284 static int dell_rfkill_set(void *data, bool blocked)
285 {
286         int disable = blocked ? 1 : 0;
287         unsigned long radio = (unsigned long)data;
288         int hwswitch_bit = (unsigned long)data - 1;
289         int ret = 0;
290
291         get_buffer();
292         dell_send_request(buffer, 17, 11);
293
294         /* If the hardware switch controls this radio, and the hardware
295            switch is disabled, don't allow changing the software state.
296            If the hardware switch is reported as not supported, always
297            fire the SMI to toggle the killswitch. */
298         if ((hwswitch_state & BIT(hwswitch_bit)) &&
299             !(buffer->output[1] & BIT(16)) &&
300             (buffer->output[1] & BIT(0))) {
301                 ret = -EINVAL;
302                 goto out;
303         }
304
305         buffer->input[0] = (1 | (radio<<8) | (disable << 16));
306         dell_send_request(buffer, 17, 11);
307
308 out:
309         release_buffer();
310         return ret;
311 }
312
313 static void dell_rfkill_query(struct rfkill *rfkill, void *data)
314 {
315         int status;
316         int bit = (unsigned long)data + 16;
317         int hwswitch_bit = (unsigned long)data - 1;
318
319         get_buffer();
320         dell_send_request(buffer, 17, 11);
321         status = buffer->output[1];
322         release_buffer();
323
324         rfkill_set_sw_state(rfkill, !!(status & BIT(bit)));
325
326         if (hwswitch_state & (BIT(hwswitch_bit)))
327                 rfkill_set_hw_state(rfkill, !(status & BIT(16)));
328 }
329
330 static const struct rfkill_ops dell_rfkill_ops = {
331         .set_block = dell_rfkill_set,
332         .query = dell_rfkill_query,
333 };
334
335 static struct dentry *dell_laptop_dir;
336
337 static int dell_debugfs_show(struct seq_file *s, void *data)
338 {
339         int status;
340
341         get_buffer();
342         dell_send_request(buffer, 17, 11);
343         status = buffer->output[1];
344         release_buffer();
345
346         seq_printf(s, "status:\t0x%X\n", status);
347         seq_printf(s, "Bit 0 : Hardware switch supported:   %lu\n",
348                    status & BIT(0));
349         seq_printf(s, "Bit 1 : Wifi locator supported:      %lu\n",
350                   (status & BIT(1)) >> 1);
351         seq_printf(s, "Bit 2 : Wifi is supported:           %lu\n",
352                   (status & BIT(2)) >> 2);
353         seq_printf(s, "Bit 3 : Bluetooth is supported:      %lu\n",
354                   (status & BIT(3)) >> 3);
355         seq_printf(s, "Bit 4 : WWAN is supported:           %lu\n",
356                   (status & BIT(4)) >> 4);
357         seq_printf(s, "Bit 5 : Wireless keyboard supported: %lu\n",
358                   (status & BIT(5)) >> 5);
359         seq_printf(s, "Bit 8 : Wifi is installed:           %lu\n",
360                   (status & BIT(8)) >> 8);
361         seq_printf(s, "Bit 9 : Bluetooth is installed:      %lu\n",
362                   (status & BIT(9)) >> 9);
363         seq_printf(s, "Bit 10: WWAN is installed:           %lu\n",
364                   (status & BIT(10)) >> 10);
365         seq_printf(s, "Bit 16: Hardware switch is on:       %lu\n",
366                   (status & BIT(16)) >> 16);
367         seq_printf(s, "Bit 17: Wifi is blocked:             %lu\n",
368                   (status & BIT(17)) >> 17);
369         seq_printf(s, "Bit 18: Bluetooth is blocked:        %lu\n",
370                   (status & BIT(18)) >> 18);
371         seq_printf(s, "Bit 19: WWAN is blocked:             %lu\n",
372                   (status & BIT(19)) >> 19);
373
374         seq_printf(s, "\nhwswitch_state:\t0x%X\n", hwswitch_state);
375         seq_printf(s, "Bit 0 : Wifi controlled by switch:      %lu\n",
376                    hwswitch_state & BIT(0));
377         seq_printf(s, "Bit 1 : Bluetooth controlled by switch: %lu\n",
378                    (hwswitch_state & BIT(1)) >> 1);
379         seq_printf(s, "Bit 2 : WWAN controlled by switch:      %lu\n",
380                    (hwswitch_state & BIT(2)) >> 2);
381         seq_printf(s, "Bit 7 : Wireless switch config locked:  %lu\n",
382                    (hwswitch_state & BIT(7)) >> 7);
383         seq_printf(s, "Bit 8 : Wifi locator enabled:           %lu\n",
384                    (hwswitch_state & BIT(8)) >> 8);
385         seq_printf(s, "Bit 15: Wifi locator setting locked:    %lu\n",
386                    (hwswitch_state & BIT(15)) >> 15);
387
388         return 0;
389 }
390
391 static int dell_debugfs_open(struct inode *inode, struct file *file)
392 {
393         return single_open(file, dell_debugfs_show, inode->i_private);
394 }
395
396 static const struct file_operations dell_debugfs_fops = {
397         .owner = THIS_MODULE,
398         .open = dell_debugfs_open,
399         .read = seq_read,
400         .llseek = seq_lseek,
401         .release = single_release,
402 };
403
404 static void dell_update_rfkill(struct work_struct *ignored)
405 {
406         int status;
407
408         get_buffer();
409         dell_send_request(buffer, 17, 11);
410         status = buffer->output[1];
411         release_buffer();
412
413         /* if hardware rfkill is not supported, set it explicitly */
414         if (!(status & BIT(0))) {
415                 if (wifi_rfkill)
416                         dell_rfkill_set((void *)1, !((status & BIT(17)) >> 17));
417                 if (bluetooth_rfkill)
418                         dell_rfkill_set((void *)2, !((status & BIT(18)) >> 18));
419                 if (wwan_rfkill)
420                         dell_rfkill_set((void *)3, !((status & BIT(19)) >> 19));
421         }
422
423         if (wifi_rfkill)
424                 dell_rfkill_query(wifi_rfkill, (void *)1);
425         if (bluetooth_rfkill)
426                 dell_rfkill_query(bluetooth_rfkill, (void *)2);
427         if (wwan_rfkill)
428                 dell_rfkill_query(wwan_rfkill, (void *)3);
429 }
430 static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill);
431
432
433 static int __init dell_setup_rfkill(void)
434 {
435         int status;
436         int ret;
437
438         if (dmi_check_system(dell_blacklist)) {
439                 pr_info("Blacklisted hardware detected - not enabling rfkill\n");
440                 return 0;
441         }
442
443         get_buffer();
444         dell_send_request(buffer, 17, 11);
445         status = buffer->output[1];
446         buffer->input[0] = 0x2;
447         dell_send_request(buffer, 17, 11);
448         hwswitch_state = buffer->output[1];
449         release_buffer();
450
451         if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) {
452                 wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev,
453                                            RFKILL_TYPE_WLAN,
454                                            &dell_rfkill_ops, (void *) 1);
455                 if (!wifi_rfkill) {
456                         ret = -ENOMEM;
457                         goto err_wifi;
458                 }
459                 ret = rfkill_register(wifi_rfkill);
460                 if (ret)
461                         goto err_wifi;
462         }
463
464         if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) {
465                 bluetooth_rfkill = rfkill_alloc("dell-bluetooth",
466                                                 &platform_device->dev,
467                                                 RFKILL_TYPE_BLUETOOTH,
468                                                 &dell_rfkill_ops, (void *) 2);
469                 if (!bluetooth_rfkill) {
470                         ret = -ENOMEM;
471                         goto err_bluetooth;
472                 }
473                 ret = rfkill_register(bluetooth_rfkill);
474                 if (ret)
475                         goto err_bluetooth;
476         }
477
478         if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) {
479                 wwan_rfkill = rfkill_alloc("dell-wwan",
480                                            &platform_device->dev,
481                                            RFKILL_TYPE_WWAN,
482                                            &dell_rfkill_ops, (void *) 3);
483                 if (!wwan_rfkill) {
484                         ret = -ENOMEM;
485                         goto err_wwan;
486                 }
487                 ret = rfkill_register(wwan_rfkill);
488                 if (ret)
489                         goto err_wwan;
490         }
491
492         return 0;
493 err_wwan:
494         rfkill_destroy(wwan_rfkill);
495         if (bluetooth_rfkill)
496                 rfkill_unregister(bluetooth_rfkill);
497 err_bluetooth:
498         rfkill_destroy(bluetooth_rfkill);
499         if (wifi_rfkill)
500                 rfkill_unregister(wifi_rfkill);
501 err_wifi:
502         rfkill_destroy(wifi_rfkill);
503
504         return ret;
505 }
506
507 static void dell_cleanup_rfkill(void)
508 {
509         if (wifi_rfkill) {
510                 rfkill_unregister(wifi_rfkill);
511                 rfkill_destroy(wifi_rfkill);
512         }
513         if (bluetooth_rfkill) {
514                 rfkill_unregister(bluetooth_rfkill);
515                 rfkill_destroy(bluetooth_rfkill);
516         }
517         if (wwan_rfkill) {
518                 rfkill_unregister(wwan_rfkill);
519                 rfkill_destroy(wwan_rfkill);
520         }
521 }
522
523 static int dell_send_intensity(struct backlight_device *bd)
524 {
525         int ret = 0;
526
527         get_buffer();
528         buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
529         buffer->input[1] = bd->props.brightness;
530
531         if (buffer->input[0] == -1) {
532                 ret = -ENODEV;
533                 goto out;
534         }
535
536         if (power_supply_is_system_supplied() > 0)
537                 dell_send_request(buffer, 1, 2);
538         else
539                 dell_send_request(buffer, 1, 1);
540
541 out:
542         release_buffer();
543         return 0;
544 }
545
546 static int dell_get_intensity(struct backlight_device *bd)
547 {
548         int ret = 0;
549
550         get_buffer();
551         buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
552
553         if (buffer->input[0] == -1) {
554                 ret = -ENODEV;
555                 goto out;
556         }
557
558         if (power_supply_is_system_supplied() > 0)
559                 dell_send_request(buffer, 0, 2);
560         else
561                 dell_send_request(buffer, 0, 1);
562
563 out:
564         release_buffer();
565         if (ret)
566                 return ret;
567         return buffer->output[1];
568 }
569
570 static const struct backlight_ops dell_ops = {
571         .get_brightness = dell_get_intensity,
572         .update_status  = dell_send_intensity,
573 };
574
575 static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str,
576                               struct serio *port)
577 {
578         static bool extended;
579
580         if (str & 0x20)
581                 return false;
582
583         if (unlikely(data == 0xe0)) {
584                 extended = true;
585                 return false;
586         } else if (unlikely(extended)) {
587                 switch (data) {
588                 case 0x8:
589                         schedule_delayed_work(&dell_rfkill_work,
590                                               round_jiffies_relative(HZ));
591                         break;
592                 }
593                 extended = false;
594         }
595
596         return false;
597 }
598
599 static int __init dell_init(void)
600 {
601         int max_intensity = 0;
602         int ret;
603
604         if (!dmi_check_system(dell_device_table))
605                 return -ENODEV;
606
607         dmi_walk(find_tokens, NULL);
608
609         if (!da_tokens)  {
610                 pr_info("Unable to find dmi tokens\n");
611                 return -ENODEV;
612         }
613
614         ret = platform_driver_register(&platform_driver);
615         if (ret)
616                 goto fail_platform_driver;
617         platform_device = platform_device_alloc("dell-laptop", -1);
618         if (!platform_device) {
619                 ret = -ENOMEM;
620                 goto fail_platform_device1;
621         }
622         ret = platform_device_add(platform_device);
623         if (ret)
624                 goto fail_platform_device2;
625
626         /*
627          * Allocate buffer below 4GB for SMI data--only 32-bit physical addr
628          * is passed to SMI handler.
629          */
630         bufferpage = alloc_page(GFP_KERNEL | GFP_DMA32);
631
632         if (!bufferpage)
633                 goto fail_buffer;
634         buffer = page_address(bufferpage);
635         mutex_init(&buffer_mutex);
636
637         ret = dell_setup_rfkill();
638
639         if (ret) {
640                 pr_warn("Unable to setup rfkill\n");
641                 goto fail_rfkill;
642         }
643
644         ret = i8042_install_filter(dell_laptop_i8042_filter);
645         if (ret) {
646                 pr_warn("Unable to install key filter\n");
647                 goto fail_filter;
648         }
649
650         dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL);
651         if (dell_laptop_dir != NULL)
652                 debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL,
653                                     &dell_debugfs_fops);
654
655 #ifdef CONFIG_ACPI
656         /* In the event of an ACPI backlight being available, don't
657          * register the platform controller.
658          */
659         if (acpi_video_backlight_support())
660                 return 0;
661 #endif
662
663         get_buffer();
664         buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
665         if (buffer->input[0] != -1) {
666                 dell_send_request(buffer, 0, 2);
667                 max_intensity = buffer->output[3];
668         }
669         release_buffer();
670
671         if (max_intensity) {
672                 struct backlight_properties props;
673                 memset(&props, 0, sizeof(struct backlight_properties));
674                 props.type = BACKLIGHT_PLATFORM;
675                 props.max_brightness = max_intensity;
676                 dell_backlight_device = backlight_device_register("dell_backlight",
677                                                                   &platform_device->dev,
678                                                                   NULL,
679                                                                   &dell_ops,
680                                                                   &props);
681
682                 if (IS_ERR(dell_backlight_device)) {
683                         ret = PTR_ERR(dell_backlight_device);
684                         dell_backlight_device = NULL;
685                         goto fail_backlight;
686                 }
687
688                 dell_backlight_device->props.brightness =
689                         dell_get_intensity(dell_backlight_device);
690                 backlight_update_status(dell_backlight_device);
691         }
692
693         return 0;
694
695 fail_backlight:
696         i8042_remove_filter(dell_laptop_i8042_filter);
697         cancel_delayed_work_sync(&dell_rfkill_work);
698 fail_filter:
699         dell_cleanup_rfkill();
700 fail_rfkill:
701         free_page((unsigned long)bufferpage);
702 fail_buffer:
703         platform_device_del(platform_device);
704 fail_platform_device2:
705         platform_device_put(platform_device);
706 fail_platform_device1:
707         platform_driver_unregister(&platform_driver);
708 fail_platform_driver:
709         kfree(da_tokens);
710         return ret;
711 }
712
713 static void __exit dell_exit(void)
714 {
715         debugfs_remove_recursive(dell_laptop_dir);
716         i8042_remove_filter(dell_laptop_i8042_filter);
717         cancel_delayed_work_sync(&dell_rfkill_work);
718         backlight_device_unregister(dell_backlight_device);
719         dell_cleanup_rfkill();
720         if (platform_device) {
721                 platform_device_unregister(platform_device);
722                 platform_driver_unregister(&platform_driver);
723         }
724         kfree(da_tokens);
725         free_page((unsigned long)buffer);
726 }
727
728 module_init(dell_init);
729 module_exit(dell_exit);
730
731 MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
732 MODULE_DESCRIPTION("Dell laptop driver");
733 MODULE_LICENSE("GPL");
734 MODULE_ALIAS("dmi:*svnDellInc.:*:ct8:*");
735 MODULE_ALIAS("dmi:*svnDellInc.:*:ct9:*");
736 MODULE_ALIAS("dmi:*svnDellComputerCorporation.:*:ct8:*");