Commit afa7c886578ce264d9b66d4bcb1fea51fac47925
Committed by
Matthew Garrett
1 parent
bc40cce201
Exists in
master
and in
4 other branches
eeepc-wmi: add hotplug code for Eeepc 1000H
Implement wireless like hotplug handling (code stolen from eeepc-laptop). Reminder: on some models rfkill is implemented by logically unplugging the wireless card from the PCI bus. Despite sending ACPI notifications, this does not appear to be implemented using standard ACPI hotplug - nor does the firmware provide the _OSC method required to support native PCIe hotplug. The only sensible choice appears to be to handle the hotplugging directly in the platform driver. Signed-off-by: Corentin Chary <corentincj@iksaif.net> Signed-off-by: Matthew Garrett <mjg@redhat.com>
Showing 1 changed file with 273 additions and 1 deletions Side-by-side Diff
drivers/platform/x86/eeepc-wmi.c
... | ... | @@ -37,9 +37,12 @@ |
37 | 37 | #include <linux/backlight.h> |
38 | 38 | #include <linux/leds.h> |
39 | 39 | #include <linux/rfkill.h> |
40 | +#include <linux/pci.h> | |
41 | +#include <linux/pci_hotplug.h> | |
40 | 42 | #include <linux/debugfs.h> |
41 | 43 | #include <linux/seq_file.h> |
42 | 44 | #include <linux/platform_device.h> |
45 | +#include <linux/dmi.h> | |
43 | 46 | #include <acpi/acpi_bus.h> |
44 | 47 | #include <acpi/acpi_drivers.h> |
45 | 48 | |
... | ... | @@ -72,6 +75,14 @@ |
72 | 75 | #define EEEPC_WMI_DEVID_BLUETOOTH 0x00010013 |
73 | 76 | #define EEEPC_WMI_DEVID_WWAN3G 0x00010019 |
74 | 77 | |
78 | +static bool hotplug_wireless; | |
79 | + | |
80 | +module_param(hotplug_wireless, bool, 0444); | |
81 | +MODULE_PARM_DESC(hotplug_wireless, | |
82 | + "Enable hotplug for wireless device. " | |
83 | + "If your laptop needs that, please report to " | |
84 | + "acpi4asus-user@lists.sourceforge.net."); | |
85 | + | |
75 | 86 | static const struct key_entry eeepc_wmi_keymap[] = { |
76 | 87 | /* Sleep already handled via generic ACPI code */ |
77 | 88 | { KE_IGNORE, NOTIFY_BRNDOWN_MIN, { KEY_BRIGHTNESSDOWN } }, |
... | ... | @@ -109,6 +120,8 @@ |
109 | 120 | }; |
110 | 121 | |
111 | 122 | struct eeepc_wmi { |
123 | + bool hotplug_wireless; | |
124 | + | |
112 | 125 | struct input_dev *inputdev; |
113 | 126 | struct backlight_device *backlight_device; |
114 | 127 | struct platform_device *platform_device; |
... | ... | @@ -122,6 +135,9 @@ |
122 | 135 | struct rfkill *bluetooth_rfkill; |
123 | 136 | struct rfkill *wwan3g_rfkill; |
124 | 137 | |
138 | + struct hotplug_slot *hotplug_slot; | |
139 | + struct mutex hotplug_lock; | |
140 | + | |
125 | 141 | struct eeepc_wmi_debug debug; |
126 | 142 | }; |
127 | 143 | |
... | ... | @@ -177,7 +193,8 @@ |
177 | 193 | u32 tmp; |
178 | 194 | |
179 | 195 | status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID, |
180 | - 1, EEEPC_WMI_METHODID_DSTS, &input, &output); | |
196 | + 1, EEEPC_WMI_METHODID_DSTS, | |
197 | + &input, &output); | |
181 | 198 | |
182 | 199 | if (ACPI_FAILURE(status)) |
183 | 200 | return status; |
... | ... | @@ -334,6 +351,206 @@ |
334 | 351 | } |
335 | 352 | |
336 | 353 | /* |
354 | + * PCI hotplug (for wlan rfkill) | |
355 | + */ | |
356 | +static bool eeepc_wlan_rfkill_blocked(struct eeepc_wmi *eeepc) | |
357 | +{ | |
358 | + u32 retval; | |
359 | + acpi_status status; | |
360 | + | |
361 | + status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_WLAN, &retval); | |
362 | + | |
363 | + if (ACPI_FAILURE(status)) | |
364 | + return false; | |
365 | + | |
366 | + return !(retval & 0x1); | |
367 | +} | |
368 | + | |
369 | +static void eeepc_rfkill_hotplug(struct eeepc_wmi *eeepc) | |
370 | +{ | |
371 | + struct pci_dev *dev; | |
372 | + struct pci_bus *bus; | |
373 | + bool blocked = eeepc_wlan_rfkill_blocked(eeepc); | |
374 | + bool absent; | |
375 | + u32 l; | |
376 | + | |
377 | + if (eeepc->wlan_rfkill) | |
378 | + rfkill_set_sw_state(eeepc->wlan_rfkill, blocked); | |
379 | + | |
380 | + mutex_lock(&eeepc->hotplug_lock); | |
381 | + | |
382 | + if (eeepc->hotplug_slot) { | |
383 | + bus = pci_find_bus(0, 1); | |
384 | + if (!bus) { | |
385 | + pr_warning("Unable to find PCI bus 1?\n"); | |
386 | + goto out_unlock; | |
387 | + } | |
388 | + | |
389 | + if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) { | |
390 | + pr_err("Unable to read PCI config space?\n"); | |
391 | + goto out_unlock; | |
392 | + } | |
393 | + absent = (l == 0xffffffff); | |
394 | + | |
395 | + if (blocked != absent) { | |
396 | + pr_warning("BIOS says wireless lan is %s, " | |
397 | + "but the pci device is %s\n", | |
398 | + blocked ? "blocked" : "unblocked", | |
399 | + absent ? "absent" : "present"); | |
400 | + pr_warning("skipped wireless hotplug as probably " | |
401 | + "inappropriate for this model\n"); | |
402 | + goto out_unlock; | |
403 | + } | |
404 | + | |
405 | + if (!blocked) { | |
406 | + dev = pci_get_slot(bus, 0); | |
407 | + if (dev) { | |
408 | + /* Device already present */ | |
409 | + pci_dev_put(dev); | |
410 | + goto out_unlock; | |
411 | + } | |
412 | + dev = pci_scan_single_device(bus, 0); | |
413 | + if (dev) { | |
414 | + pci_bus_assign_resources(bus); | |
415 | + if (pci_bus_add_device(dev)) | |
416 | + pr_err("Unable to hotplug wifi\n"); | |
417 | + } | |
418 | + } else { | |
419 | + dev = pci_get_slot(bus, 0); | |
420 | + if (dev) { | |
421 | + pci_remove_bus_device(dev); | |
422 | + pci_dev_put(dev); | |
423 | + } | |
424 | + } | |
425 | + } | |
426 | + | |
427 | +out_unlock: | |
428 | + mutex_unlock(&eeepc->hotplug_lock); | |
429 | +} | |
430 | + | |
431 | +static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data) | |
432 | +{ | |
433 | + struct eeepc_wmi *eeepc = data; | |
434 | + | |
435 | + if (event != ACPI_NOTIFY_BUS_CHECK) | |
436 | + return; | |
437 | + | |
438 | + eeepc_rfkill_hotplug(eeepc); | |
439 | +} | |
440 | + | |
441 | +static int eeepc_register_rfkill_notifier(struct eeepc_wmi *eeepc, | |
442 | + char *node) | |
443 | +{ | |
444 | + acpi_status status; | |
445 | + acpi_handle handle; | |
446 | + | |
447 | + status = acpi_get_handle(NULL, node, &handle); | |
448 | + | |
449 | + if (ACPI_SUCCESS(status)) { | |
450 | + status = acpi_install_notify_handler(handle, | |
451 | + ACPI_SYSTEM_NOTIFY, | |
452 | + eeepc_rfkill_notify, | |
453 | + eeepc); | |
454 | + if (ACPI_FAILURE(status)) | |
455 | + pr_warning("Failed to register notify on %s\n", node); | |
456 | + } else | |
457 | + return -ENODEV; | |
458 | + | |
459 | + return 0; | |
460 | +} | |
461 | + | |
462 | +static void eeepc_unregister_rfkill_notifier(struct eeepc_wmi *eeepc, | |
463 | + char *node) | |
464 | +{ | |
465 | + acpi_status status = AE_OK; | |
466 | + acpi_handle handle; | |
467 | + | |
468 | + status = acpi_get_handle(NULL, node, &handle); | |
469 | + | |
470 | + if (ACPI_SUCCESS(status)) { | |
471 | + status = acpi_remove_notify_handler(handle, | |
472 | + ACPI_SYSTEM_NOTIFY, | |
473 | + eeepc_rfkill_notify); | |
474 | + if (ACPI_FAILURE(status)) | |
475 | + pr_err("Error removing rfkill notify handler %s\n", | |
476 | + node); | |
477 | + } | |
478 | +} | |
479 | + | |
480 | +static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot, | |
481 | + u8 *value) | |
482 | +{ | |
483 | + u32 retval; | |
484 | + acpi_status status; | |
485 | + | |
486 | + status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_WLAN, &retval); | |
487 | + | |
488 | + if (ACPI_FAILURE(status)) | |
489 | + return -EIO; | |
490 | + | |
491 | + if (!retval || retval == 0x00060000) | |
492 | + return -ENODEV; | |
493 | + else | |
494 | + *value = (retval & 0x1); | |
495 | + | |
496 | + return 0; | |
497 | +} | |
498 | + | |
499 | +static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot) | |
500 | +{ | |
501 | + kfree(hotplug_slot->info); | |
502 | + kfree(hotplug_slot); | |
503 | +} | |
504 | + | |
505 | +static struct hotplug_slot_ops eeepc_hotplug_slot_ops = { | |
506 | + .owner = THIS_MODULE, | |
507 | + .get_adapter_status = eeepc_get_adapter_status, | |
508 | + .get_power_status = eeepc_get_adapter_status, | |
509 | +}; | |
510 | + | |
511 | +static int eeepc_setup_pci_hotplug(struct eeepc_wmi *eeepc) | |
512 | +{ | |
513 | + int ret = -ENOMEM; | |
514 | + struct pci_bus *bus = pci_find_bus(0, 1); | |
515 | + | |
516 | + if (!bus) { | |
517 | + pr_err("Unable to find wifi PCI bus\n"); | |
518 | + return -ENODEV; | |
519 | + } | |
520 | + | |
521 | + eeepc->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL); | |
522 | + if (!eeepc->hotplug_slot) | |
523 | + goto error_slot; | |
524 | + | |
525 | + eeepc->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info), | |
526 | + GFP_KERNEL); | |
527 | + if (!eeepc->hotplug_slot->info) | |
528 | + goto error_info; | |
529 | + | |
530 | + eeepc->hotplug_slot->private = eeepc; | |
531 | + eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug; | |
532 | + eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops; | |
533 | + eeepc_get_adapter_status(eeepc->hotplug_slot, | |
534 | + &eeepc->hotplug_slot->info->adapter_status); | |
535 | + | |
536 | + ret = pci_hp_register(eeepc->hotplug_slot, bus, 0, "eeepc-wifi"); | |
537 | + if (ret) { | |
538 | + pr_err("Unable to register hotplug slot - %d\n", ret); | |
539 | + goto error_register; | |
540 | + } | |
541 | + | |
542 | + return 0; | |
543 | + | |
544 | +error_register: | |
545 | + kfree(eeepc->hotplug_slot->info); | |
546 | +error_info: | |
547 | + kfree(eeepc->hotplug_slot); | |
548 | + eeepc->hotplug_slot = NULL; | |
549 | +error_slot: | |
550 | + return ret; | |
551 | +} | |
552 | + | |
553 | +/* | |
337 | 554 | * Rfkill devices |
338 | 555 | */ |
339 | 556 | static int eeepc_rfkill_set(void *data, bool blocked) |
340 | 557 | |
... | ... | @@ -404,11 +621,22 @@ |
404 | 621 | |
405 | 622 | static void eeepc_wmi_rfkill_exit(struct eeepc_wmi *eeepc) |
406 | 623 | { |
624 | + eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5"); | |
625 | + eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6"); | |
626 | + eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7"); | |
407 | 627 | if (eeepc->wlan_rfkill) { |
408 | 628 | rfkill_unregister(eeepc->wlan_rfkill); |
409 | 629 | rfkill_destroy(eeepc->wlan_rfkill); |
410 | 630 | eeepc->wlan_rfkill = NULL; |
411 | 631 | } |
632 | + /* | |
633 | + * Refresh pci hotplug in case the rfkill state was changed after | |
634 | + * eeepc_unregister_rfkill_notifier() | |
635 | + */ | |
636 | + eeepc_rfkill_hotplug(eeepc); | |
637 | + if (eeepc->hotplug_slot) | |
638 | + pci_hp_deregister(eeepc->hotplug_slot); | |
639 | + | |
412 | 640 | if (eeepc->bluetooth_rfkill) { |
413 | 641 | rfkill_unregister(eeepc->bluetooth_rfkill); |
414 | 642 | rfkill_destroy(eeepc->bluetooth_rfkill); |
... | ... | @@ -425,6 +653,8 @@ |
425 | 653 | { |
426 | 654 | int result = 0; |
427 | 655 | |
656 | + mutex_init(&eeepc->hotplug_lock); | |
657 | + | |
428 | 658 | result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill, |
429 | 659 | "eeepc-wlan", RFKILL_TYPE_WLAN, |
430 | 660 | EEEPC_WMI_DEVID_WLAN); |
... | ... | @@ -446,6 +676,23 @@ |
446 | 676 | if (result && result != -ENODEV) |
447 | 677 | goto exit; |
448 | 678 | |
679 | + result = eeepc_setup_pci_hotplug(eeepc); | |
680 | + /* | |
681 | + * If we get -EBUSY then something else is handling the PCI hotplug - | |
682 | + * don't fail in this case | |
683 | + */ | |
684 | + if (result == -EBUSY) | |
685 | + result = 0; | |
686 | + | |
687 | + eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5"); | |
688 | + eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6"); | |
689 | + eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7"); | |
690 | + /* | |
691 | + * Refresh pci hotplug in case the rfkill state was changed during | |
692 | + * setup. | |
693 | + */ | |
694 | + eeepc_rfkill_hotplug(eeepc); | |
695 | + | |
449 | 696 | exit: |
450 | 697 | if (result && result != -ENODEV) |
451 | 698 | eeepc_wmi_rfkill_exit(eeepc); |
... | ... | @@ -771,6 +1018,28 @@ |
771 | 1018 | /* |
772 | 1019 | * WMI Driver |
773 | 1020 | */ |
1021 | +static void eeepc_dmi_check(struct eeepc_wmi *eeepc) | |
1022 | +{ | |
1023 | + const char *model; | |
1024 | + | |
1025 | + model = dmi_get_system_info(DMI_PRODUCT_NAME); | |
1026 | + if (!model) | |
1027 | + return; | |
1028 | + | |
1029 | + /* | |
1030 | + * Whitelist for wlan hotplug | |
1031 | + * | |
1032 | + * Eeepc 1000H needs the current hotplug code to handle | |
1033 | + * Fn+F2 correctly. We may add other Eeepc here later, but | |
1034 | + * it seems that most of the laptops supported by eeepc-wmi | |
1035 | + * don't need to be on this list | |
1036 | + */ | |
1037 | + if (strcmp(model, "1000H") == 0) { | |
1038 | + eeepc->hotplug_wireless = true; | |
1039 | + pr_info("wlan hotplug enabled\n"); | |
1040 | + } | |
1041 | +} | |
1042 | + | |
774 | 1043 | static struct platform_device * __init eeepc_wmi_add(void) |
775 | 1044 | { |
776 | 1045 | struct eeepc_wmi *eeepc; |
... | ... | @@ -780,6 +1049,9 @@ |
780 | 1049 | eeepc = kzalloc(sizeof(struct eeepc_wmi), GFP_KERNEL); |
781 | 1050 | if (!eeepc) |
782 | 1051 | return ERR_PTR(-ENOMEM); |
1052 | + | |
1053 | + eeepc->hotplug_wireless = hotplug_wireless; | |
1054 | + eeepc_dmi_check(eeepc); | |
783 | 1055 | |
784 | 1056 | /* |
785 | 1057 | * Register the platform device first. It is used as a parent for the |