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