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