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