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_LICENSE("GPL"); 18 MODULE_AUTHOR("Alex Hung"); 19 MODULE_ALIAS("acpi*:HPQ6001:*"); 20 MODULE_ALIAS("acpi*:WSTADEF:*"); 21 MODULE_ALIAS("acpi*:AMDI0051:*"); 22 MODULE_ALIAS("acpi*:LGEX0815:*"); 23 24 struct wl_button { 25 struct input_dev *input_dev; 26 char phys[32]; 27 }; 28 29 static const struct acpi_device_id wl_ids[] = { 30 {"HPQ6001", 0}, 31 {"WSTADEF", 0}, 32 {"AMDI0051", 0}, 33 {"LGEX0815", 0}, 34 {"", 0}, 35 }; 36 37 static int wireless_input_setup(struct acpi_device *device) 38 { 39 struct wl_button *button = acpi_driver_data(device); 40 int err; 41 42 button->input_dev = input_allocate_device(); 43 if (!button->input_dev) 44 return -ENOMEM; 45 46 snprintf(button->phys, sizeof(button->phys), "%s/input0", acpi_device_hid(device)); 47 48 button->input_dev->name = "Wireless hotkeys"; 49 button->input_dev->phys = button->phys; 50 button->input_dev->id.bustype = BUS_HOST; 51 button->input_dev->evbit[0] = BIT(EV_KEY); 52 set_bit(KEY_RFKILL, button->input_dev->keybit); 53 54 err = input_register_device(button->input_dev); 55 if (err) 56 goto err_free_dev; 57 58 return 0; 59 60 err_free_dev: 61 input_free_device(button->input_dev); 62 return err; 63 } 64 65 static void wireless_input_destroy(struct acpi_device *device) 66 { 67 struct wl_button *button = acpi_driver_data(device); 68 69 input_unregister_device(button->input_dev); 70 kfree(button); 71 } 72 73 static void wl_notify(struct acpi_device *acpi_dev, u32 event) 74 { 75 struct wl_button *button = acpi_driver_data(acpi_dev); 76 77 if (event != 0x80) { 78 pr_info("Received unknown event (0x%x)\n", event); 79 return; 80 } 81 82 input_report_key(button->input_dev, KEY_RFKILL, 1); 83 input_sync(button->input_dev); 84 input_report_key(button->input_dev, KEY_RFKILL, 0); 85 input_sync(button->input_dev); 86 } 87 88 static int wl_add(struct acpi_device *device) 89 { 90 struct wl_button *button; 91 int err; 92 93 button = kzalloc(sizeof(struct wl_button), GFP_KERNEL); 94 if (!button) 95 return -ENOMEM; 96 97 device->driver_data = button; 98 99 err = wireless_input_setup(device); 100 if (err) { 101 pr_err("Failed to setup wireless hotkeys\n"); 102 kfree(button); 103 } 104 105 return err; 106 } 107 108 static void wl_remove(struct acpi_device *device) 109 { 110 wireless_input_destroy(device); 111 } 112 113 static struct acpi_driver wl_driver = { 114 .name = "wireless-hotkey", 115 .owner = THIS_MODULE, 116 .ids = wl_ids, 117 .ops = { 118 .add = wl_add, 119 .remove = wl_remove, 120 .notify = wl_notify, 121 }, 122 }; 123 124 module_acpi_driver(wl_driver); 125