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