diff options
Diffstat (limited to 'drivers/platform/x86/dell-laptop.c')
-rw-r--r-- | drivers/platform/x86/dell-laptop.c | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index bb77e18b3dd4..c608b1d33f4a 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c | |||
@@ -21,6 +21,7 @@ | |||
21 | #include <linux/err.h> | 21 | #include <linux/err.h> |
22 | #include <linux/dmi.h> | 22 | #include <linux/dmi.h> |
23 | #include <linux/io.h> | 23 | #include <linux/io.h> |
24 | #include <linux/rfkill.h> | ||
24 | #include <linux/power_supply.h> | 25 | #include <linux/power_supply.h> |
25 | #include <linux/acpi.h> | 26 | #include <linux/acpi.h> |
26 | #include <linux/mm.h> | 27 | #include <linux/mm.h> |
@@ -89,6 +90,13 @@ static struct platform_driver platform_driver = { | |||
89 | 90 | ||
90 | static struct platform_device *platform_device; | 91 | static struct platform_device *platform_device; |
91 | static struct backlight_device *dell_backlight_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 | static bool force_rfkill; | ||
97 | |||
98 | module_param(force_rfkill, bool, 0444); | ||
99 | MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); | ||
92 | 100 | ||
93 | static const struct dmi_system_id dell_device_table[] __initconst = { | 101 | static const struct dmi_system_id dell_device_table[] __initconst = { |
94 | { | 102 | { |
@@ -355,6 +363,108 @@ dell_send_request(struct calling_interface_buffer *buffer, int class, | |||
355 | return buffer; | 363 | return buffer; |
356 | } | 364 | } |
357 | 365 | ||
366 | /* Derived from information in DellWirelessCtl.cpp: | ||
367 | Class 17, select 11 is radio control. It returns an array of 32-bit values. | ||
368 | |||
369 | Input byte 0 = 0: Wireless information | ||
370 | |||
371 | result[0]: return code | ||
372 | result[1]: | ||
373 | Bit 0: Hardware switch supported | ||
374 | Bit 1: Wifi locator supported | ||
375 | Bit 2: Wifi is supported | ||
376 | Bit 3: Bluetooth is supported | ||
377 | Bit 4: WWAN is supported | ||
378 | Bit 5: Wireless keyboard supported | ||
379 | Bits 6-7: Reserved | ||
380 | Bit 8: Wifi is installed | ||
381 | Bit 9: Bluetooth is installed | ||
382 | Bit 10: WWAN is installed | ||
383 | Bits 11-15: Reserved | ||
384 | Bit 16: Hardware switch is on | ||
385 | Bit 17: Wifi is blocked | ||
386 | Bit 18: Bluetooth is blocked | ||
387 | Bit 19: WWAN is blocked | ||
388 | Bits 20-31: Reserved | ||
389 | result[2]: NVRAM size in bytes | ||
390 | result[3]: NVRAM format version number | ||
391 | |||
392 | Input byte 0 = 2: Wireless switch configuration | ||
393 | result[0]: return code | ||
394 | result[1]: | ||
395 | Bit 0: Wifi controlled by switch | ||
396 | Bit 1: Bluetooth controlled by switch | ||
397 | Bit 2: WWAN controlled by switch | ||
398 | Bits 3-6: Reserved | ||
399 | Bit 7: Wireless switch config locked | ||
400 | Bit 8: Wifi locator enabled | ||
401 | Bits 9-14: Reserved | ||
402 | Bit 15: Wifi locator setting locked | ||
403 | Bits 16-31: Reserved | ||
404 | */ | ||
405 | |||
406 | static int dell_rfkill_set(void *data, bool blocked) | ||
407 | { | ||
408 | int disable = blocked ? 1 : 0; | ||
409 | unsigned long radio = (unsigned long)data; | ||
410 | int hwswitch_bit = (unsigned long)data - 1; | ||
411 | |||
412 | get_buffer(); | ||
413 | dell_send_request(buffer, 17, 11); | ||
414 | |||
415 | /* If the hardware switch controls this radio, and the hardware | ||
416 | switch is disabled, always disable the radio */ | ||
417 | if ((hwswitch_state & BIT(hwswitch_bit)) && | ||
418 | !(buffer->output[1] & BIT(16))) | ||
419 | disable = 1; | ||
420 | |||
421 | buffer->input[0] = (1 | (radio<<8) | (disable << 16)); | ||
422 | dell_send_request(buffer, 17, 11); | ||
423 | |||
424 | release_buffer(); | ||
425 | return 0; | ||
426 | } | ||
427 | |||
428 | /* Must be called with the buffer held */ | ||
429 | static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio, | ||
430 | int status) | ||
431 | { | ||
432 | if (status & BIT(0)) { | ||
433 | /* Has hw-switch, sync sw_state to BIOS */ | ||
434 | int block = rfkill_blocked(rfkill); | ||
435 | buffer->input[0] = (1 | (radio << 8) | (block << 16)); | ||
436 | dell_send_request(buffer, 17, 11); | ||
437 | } else { | ||
438 | /* No hw-switch, sync BIOS state to sw_state */ | ||
439 | rfkill_set_sw_state(rfkill, !!(status & BIT(radio + 16))); | ||
440 | } | ||
441 | } | ||
442 | |||
443 | static void dell_rfkill_update_hw_state(struct rfkill *rfkill, int radio, | ||
444 | int status) | ||
445 | { | ||
446 | if (hwswitch_state & (BIT(radio - 1))) | ||
447 | rfkill_set_hw_state(rfkill, !(status & BIT(16))); | ||
448 | } | ||
449 | |||
450 | static void dell_rfkill_query(struct rfkill *rfkill, void *data) | ||
451 | { | ||
452 | int status; | ||
453 | |||
454 | get_buffer(); | ||
455 | dell_send_request(buffer, 17, 11); | ||
456 | status = buffer->output[1]; | ||
457 | |||
458 | dell_rfkill_update_hw_state(rfkill, (unsigned long)data, status); | ||
459 | |||
460 | release_buffer(); | ||
461 | } | ||
462 | |||
463 | static const struct rfkill_ops dell_rfkill_ops = { | ||
464 | .set_block = dell_rfkill_set, | ||
465 | .query = dell_rfkill_query, | ||
466 | }; | ||
467 | |||
358 | static struct dentry *dell_laptop_dir; | 468 | static struct dentry *dell_laptop_dir; |
359 | 469 | ||
360 | static int dell_debugfs_show(struct seq_file *s, void *data) | 470 | static int dell_debugfs_show(struct seq_file *s, void *data) |
@@ -424,6 +534,136 @@ static const struct file_operations dell_debugfs_fops = { | |||
424 | .release = single_release, | 534 | .release = single_release, |
425 | }; | 535 | }; |
426 | 536 | ||
537 | static void dell_update_rfkill(struct work_struct *ignored) | ||
538 | { | ||
539 | int status; | ||
540 | |||
541 | get_buffer(); | ||
542 | dell_send_request(buffer, 17, 11); | ||
543 | status = buffer->output[1]; | ||
544 | |||
545 | if (wifi_rfkill) { | ||
546 | dell_rfkill_update_hw_state(wifi_rfkill, 1, status); | ||
547 | dell_rfkill_update_sw_state(wifi_rfkill, 1, status); | ||
548 | } | ||
549 | if (bluetooth_rfkill) { | ||
550 | dell_rfkill_update_hw_state(bluetooth_rfkill, 2, status); | ||
551 | dell_rfkill_update_sw_state(bluetooth_rfkill, 2, status); | ||
552 | } | ||
553 | if (wwan_rfkill) { | ||
554 | dell_rfkill_update_hw_state(wwan_rfkill, 3, status); | ||
555 | dell_rfkill_update_sw_state(wwan_rfkill, 3, status); | ||
556 | } | ||
557 | |||
558 | release_buffer(); | ||
559 | } | ||
560 | static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill); | ||
561 | |||
562 | |||
563 | static int __init dell_setup_rfkill(void) | ||
564 | { | ||
565 | int status; | ||
566 | int ret; | ||
567 | const char *product; | ||
568 | |||
569 | /* | ||
570 | * rfkill causes trouble on various non Latitudes, according to Dell | ||
571 | * actually testing the rfkill functionality is only done on Latitudes. | ||
572 | */ | ||
573 | product = dmi_get_system_info(DMI_PRODUCT_NAME); | ||
574 | if (!force_rfkill && (!product || strncmp(product, "Latitude", 8))) | ||
575 | return 0; | ||
576 | |||
577 | get_buffer(); | ||
578 | dell_send_request(buffer, 17, 11); | ||
579 | status = buffer->output[1]; | ||
580 | buffer->input[0] = 0x2; | ||
581 | dell_send_request(buffer, 17, 11); | ||
582 | hwswitch_state = buffer->output[1]; | ||
583 | release_buffer(); | ||
584 | |||
585 | if (!(status & BIT(0))) { | ||
586 | if (force_rfkill) { | ||
587 | /* No hwsitch, clear all hw-controlled bits */ | ||
588 | hwswitch_state &= ~7; | ||
589 | } else { | ||
590 | /* rfkill is only tested on laptops with a hwswitch */ | ||
591 | return 0; | ||
592 | } | ||
593 | } | ||
594 | |||
595 | if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) { | ||
596 | wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev, | ||
597 | RFKILL_TYPE_WLAN, | ||
598 | &dell_rfkill_ops, (void *) 1); | ||
599 | if (!wifi_rfkill) { | ||
600 | ret = -ENOMEM; | ||
601 | goto err_wifi; | ||
602 | } | ||
603 | ret = rfkill_register(wifi_rfkill); | ||
604 | if (ret) | ||
605 | goto err_wifi; | ||
606 | } | ||
607 | |||
608 | if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) { | ||
609 | bluetooth_rfkill = rfkill_alloc("dell-bluetooth", | ||
610 | &platform_device->dev, | ||
611 | RFKILL_TYPE_BLUETOOTH, | ||
612 | &dell_rfkill_ops, (void *) 2); | ||
613 | if (!bluetooth_rfkill) { | ||
614 | ret = -ENOMEM; | ||
615 | goto err_bluetooth; | ||
616 | } | ||
617 | ret = rfkill_register(bluetooth_rfkill); | ||
618 | if (ret) | ||
619 | goto err_bluetooth; | ||
620 | } | ||
621 | |||
622 | if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) { | ||
623 | wwan_rfkill = rfkill_alloc("dell-wwan", | ||
624 | &platform_device->dev, | ||
625 | RFKILL_TYPE_WWAN, | ||
626 | &dell_rfkill_ops, (void *) 3); | ||
627 | if (!wwan_rfkill) { | ||
628 | ret = -ENOMEM; | ||
629 | goto err_wwan; | ||
630 | } | ||
631 | ret = rfkill_register(wwan_rfkill); | ||
632 | if (ret) | ||
633 | goto err_wwan; | ||
634 | } | ||
635 | |||
636 | return 0; | ||
637 | err_wwan: | ||
638 | rfkill_destroy(wwan_rfkill); | ||
639 | if (bluetooth_rfkill) | ||
640 | rfkill_unregister(bluetooth_rfkill); | ||
641 | err_bluetooth: | ||
642 | rfkill_destroy(bluetooth_rfkill); | ||
643 | if (wifi_rfkill) | ||
644 | rfkill_unregister(wifi_rfkill); | ||
645 | err_wifi: | ||
646 | rfkill_destroy(wifi_rfkill); | ||
647 | |||
648 | return ret; | ||
649 | } | ||
650 | |||
651 | static void dell_cleanup_rfkill(void) | ||
652 | { | ||
653 | if (wifi_rfkill) { | ||
654 | rfkill_unregister(wifi_rfkill); | ||
655 | rfkill_destroy(wifi_rfkill); | ||
656 | } | ||
657 | if (bluetooth_rfkill) { | ||
658 | rfkill_unregister(bluetooth_rfkill); | ||
659 | rfkill_destroy(bluetooth_rfkill); | ||
660 | } | ||
661 | if (wwan_rfkill) { | ||
662 | rfkill_unregister(wwan_rfkill); | ||
663 | rfkill_destroy(wwan_rfkill); | ||
664 | } | ||
665 | } | ||
666 | |||
427 | static int dell_send_intensity(struct backlight_device *bd) | 667 | static int dell_send_intensity(struct backlight_device *bd) |
428 | { | 668 | { |
429 | int ret = 0; | 669 | int ret = 0; |
@@ -515,6 +755,30 @@ static void touchpad_led_exit(void) | |||
515 | led_classdev_unregister(&touchpad_led); | 755 | led_classdev_unregister(&touchpad_led); |
516 | } | 756 | } |
517 | 757 | ||
758 | static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, | ||
759 | struct serio *port) | ||
760 | { | ||
761 | static bool extended; | ||
762 | |||
763 | if (str & 0x20) | ||
764 | return false; | ||
765 | |||
766 | if (unlikely(data == 0xe0)) { | ||
767 | extended = true; | ||
768 | return false; | ||
769 | } else if (unlikely(extended)) { | ||
770 | switch (data) { | ||
771 | case 0x8: | ||
772 | schedule_delayed_work(&dell_rfkill_work, | ||
773 | round_jiffies_relative(HZ / 4)); | ||
774 | break; | ||
775 | } | ||
776 | extended = false; | ||
777 | } | ||
778 | |||
779 | return false; | ||
780 | } | ||
781 | |||
518 | static int __init dell_init(void) | 782 | static int __init dell_init(void) |
519 | { | 783 | { |
520 | int max_intensity = 0; | 784 | int max_intensity = 0; |
@@ -557,10 +821,26 @@ static int __init dell_init(void) | |||
557 | } | 821 | } |
558 | buffer = page_address(bufferpage); | 822 | buffer = page_address(bufferpage); |
559 | 823 | ||
824 | ret = dell_setup_rfkill(); | ||
825 | |||
826 | if (ret) { | ||
827 | pr_warn("Unable to setup rfkill\n"); | ||
828 | goto fail_rfkill; | ||
829 | } | ||
830 | |||
831 | ret = i8042_install_filter(dell_laptop_i8042_filter); | ||
832 | if (ret) { | ||
833 | pr_warn("Unable to install key filter\n"); | ||
834 | goto fail_filter; | ||
835 | } | ||
836 | |||
560 | if (quirks && quirks->touchpad_led) | 837 | if (quirks && quirks->touchpad_led) |
561 | touchpad_led_init(&platform_device->dev); | 838 | touchpad_led_init(&platform_device->dev); |
562 | 839 | ||
563 | dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); | 840 | dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); |
841 | if (dell_laptop_dir != NULL) | ||
842 | debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, | ||
843 | &dell_debugfs_fops); | ||
564 | 844 | ||
565 | #ifdef CONFIG_ACPI | 845 | #ifdef CONFIG_ACPI |
566 | /* In the event of an ACPI backlight being available, don't | 846 | /* In the event of an ACPI backlight being available, don't |
@@ -603,6 +883,11 @@ static int __init dell_init(void) | |||
603 | return 0; | 883 | return 0; |
604 | 884 | ||
605 | fail_backlight: | 885 | fail_backlight: |
886 | i8042_remove_filter(dell_laptop_i8042_filter); | ||
887 | cancel_delayed_work_sync(&dell_rfkill_work); | ||
888 | fail_filter: | ||
889 | dell_cleanup_rfkill(); | ||
890 | fail_rfkill: | ||
606 | free_page((unsigned long)bufferpage); | 891 | free_page((unsigned long)bufferpage); |
607 | fail_buffer: | 892 | fail_buffer: |
608 | platform_device_del(platform_device); | 893 | platform_device_del(platform_device); |
@@ -620,7 +905,10 @@ static void __exit dell_exit(void) | |||
620 | debugfs_remove_recursive(dell_laptop_dir); | 905 | debugfs_remove_recursive(dell_laptop_dir); |
621 | if (quirks && quirks->touchpad_led) | 906 | if (quirks && quirks->touchpad_led) |
622 | touchpad_led_exit(); | 907 | touchpad_led_exit(); |
908 | i8042_remove_filter(dell_laptop_i8042_filter); | ||
909 | cancel_delayed_work_sync(&dell_rfkill_work); | ||
623 | backlight_device_unregister(dell_backlight_device); | 910 | backlight_device_unregister(dell_backlight_device); |
911 | dell_cleanup_rfkill(); | ||
624 | if (platform_device) { | 912 | if (platform_device) { |
625 | platform_device_unregister(platform_device); | 913 | platform_device_unregister(platform_device); |
626 | platform_driver_unregister(&platform_driver); | 914 | platform_driver_unregister(&platform_driver); |