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