1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Airplane mode button for AMD, HP & Xiaomi laptops 4 * 5 * Copyright (C) 2014-2017 Alex Hung <alex.hung@canonical.com> 6 * Copyright (C) 2021 Advanced Micro Devices 7 */ 8 9 #include <linux/kernel.h> 10 #include <linux/module.h> 11 #include <linux/init.h> 12 #include <linux/input.h> 13 #include <linux/platform_device.h> 14 #include <linux/acpi.h> 15 #include <acpi/acpi_bus.h> 16 17 MODULE_DESCRIPTION("Airplane mode button for AMD, HP & Xiaomi laptops"); 18 MODULE_LICENSE("GPL"); 19 MODULE_AUTHOR("Alex Hung"); 20 MODULE_ALIAS("acpi*:HPQ6001:*"); 21 MODULE_ALIAS("acpi*:WSTADEF:*"); 22 MODULE_ALIAS("acpi*:AMDI0051:*"); 23 MODULE_ALIAS("acpi*:LGEX0815:*"); 24 25 struct wl_button { 26 struct input_dev *input_dev; 27 char phys[32]; 28 }; 29 30 static const struct acpi_device_id wl_ids[] = { 31 {"HPQ6001", 0}, 32 {"WSTADEF", 0}, 33 {"AMDI0051", 0}, 34 {"LGEX0815", 0}, 35 {"", 0}, 36 }; 37 38 static int wireless_input_setup(struct device *dev) 39 { 40 struct wl_button *button = dev_get_drvdata(dev); 41 int err; 42 43 button->input_dev = input_allocate_device(); 44 if (!button->input_dev) 45 return -ENOMEM; 46 47 snprintf(button->phys, sizeof(button->phys), "%s/input0", 48 acpi_device_hid(ACPI_COMPANION(dev))); 49 50 button->input_dev->name = "Wireless hotkeys"; 51 button->input_dev->phys = button->phys; 52 button->input_dev->id.bustype = BUS_HOST; 53 button->input_dev->evbit[0] = BIT(EV_KEY); 54 set_bit(KEY_RFKILL, button->input_dev->keybit); 55 56 err = input_register_device(button->input_dev); 57 if (err) 58 goto err_free_dev; 59 60 return 0; 61 62 err_free_dev: 63 input_free_device(button->input_dev); 64 return err; 65 } 66 67 static void wireless_input_destroy(struct device *dev) 68 { 69 struct wl_button *button = dev_get_drvdata(dev); 70 71 input_unregister_device(button->input_dev); 72 kfree(button); 73 } 74 75 static void wl_notify(acpi_handle handle, u32 event, void *data) 76 { 77 struct wl_button *button = data; 78 79 if (event != 0x80) { 80 pr_info("Received unknown event (0x%x)\n", event); 81 return; 82 } 83 84 input_report_key(button->input_dev, KEY_RFKILL, 1); 85 input_sync(button->input_dev); 86 input_report_key(button->input_dev, KEY_RFKILL, 0); 87 input_sync(button->input_dev); 88 } 89 90 static int wl_probe(struct platform_device *pdev) 91 { 92 struct acpi_device *adev; 93 struct wl_button *button; 94 int err; 95 96 adev = ACPI_COMPANION(&pdev->dev); 97 if (!adev) 98 return -ENODEV; 99 100 button = kzalloc_obj(struct wl_button); 101 if (!button) 102 return -ENOMEM; 103 104 platform_set_drvdata(pdev, button); 105 106 err = wireless_input_setup(&pdev->dev); 107 if (err) { 108 pr_err("Failed to setup wireless hotkeys\n"); 109 kfree(button); 110 return err; 111 } 112 err = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, 113 wl_notify, button); 114 if (err) { 115 pr_err("Failed to install ACPI notify handler\n"); 116 wireless_input_destroy(&pdev->dev); 117 } 118 119 return err; 120 } 121 122 static void wl_remove(struct platform_device *pdev) 123 { 124 acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), 125 ACPI_DEVICE_NOTIFY, wl_notify); 126 wireless_input_destroy(&pdev->dev); 127 } 128 129 static struct platform_driver wl_driver = { 130 .probe = wl_probe, 131 .remove = wl_remove, 132 .driver = { 133 .name = "wireless-hotkey", 134 .acpi_match_table = wl_ids, 135 }, 136 }; 137 138 module_platform_driver(wl_driver); 139