162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * WMI hotkeys support for Dell All-In-One series 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/kernel.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/init.h> 1162306a36Sopenharmony_ci#include <linux/types.h> 1262306a36Sopenharmony_ci#include <linux/input.h> 1362306a36Sopenharmony_ci#include <linux/input/sparse-keymap.h> 1462306a36Sopenharmony_ci#include <linux/acpi.h> 1562306a36Sopenharmony_ci#include <linux/string.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ciMODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series"); 1862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4" 2162306a36Sopenharmony_ci#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistruct dell_wmi_event { 2462306a36Sopenharmony_ci u16 length; 2562306a36Sopenharmony_ci /* 0x000: A hot key pressed or an event occurred 2662306a36Sopenharmony_ci * 0x00F: A sequence of hot keys are pressed */ 2762306a36Sopenharmony_ci u16 type; 2862306a36Sopenharmony_ci u16 event[]; 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic const char *dell_wmi_aio_guids[] = { 3262306a36Sopenharmony_ci EVENT_GUID1, 3362306a36Sopenharmony_ci EVENT_GUID2, 3462306a36Sopenharmony_ci NULL 3562306a36Sopenharmony_ci}; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ciMODULE_ALIAS("wmi:"EVENT_GUID1); 3862306a36Sopenharmony_ciMODULE_ALIAS("wmi:"EVENT_GUID2); 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic const struct key_entry dell_wmi_aio_keymap[] = { 4162306a36Sopenharmony_ci { KE_KEY, 0xc0, { KEY_VOLUMEUP } }, 4262306a36Sopenharmony_ci { KE_KEY, 0xc1, { KEY_VOLUMEDOWN } }, 4362306a36Sopenharmony_ci { KE_KEY, 0xe030, { KEY_VOLUMEUP } }, 4462306a36Sopenharmony_ci { KE_KEY, 0xe02e, { KEY_VOLUMEDOWN } }, 4562306a36Sopenharmony_ci { KE_KEY, 0xe020, { KEY_MUTE } }, 4662306a36Sopenharmony_ci { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, 4762306a36Sopenharmony_ci { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, 4862306a36Sopenharmony_ci { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, 4962306a36Sopenharmony_ci { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, 5062306a36Sopenharmony_ci { KE_END, 0 } 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic struct input_dev *dell_wmi_aio_input_dev; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci/* 5662306a36Sopenharmony_ci * The new WMI event data format will follow the dell_wmi_event structure 5762306a36Sopenharmony_ci * So, we will check if the buffer matches the format 5862306a36Sopenharmony_ci */ 5962306a36Sopenharmony_cistatic bool dell_wmi_aio_event_check(u8 *buffer, int length) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci struct dell_wmi_event *event = (struct dell_wmi_event *)buffer; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (event == NULL || length < 6) 6462306a36Sopenharmony_ci return false; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci if ((event->type == 0 || event->type == 0xf) && 6762306a36Sopenharmony_ci event->length >= 2) 6862306a36Sopenharmony_ci return true; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci return false; 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic void dell_wmi_aio_notify(u32 value, void *context) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 7662306a36Sopenharmony_ci union acpi_object *obj; 7762306a36Sopenharmony_ci struct dell_wmi_event *event; 7862306a36Sopenharmony_ci acpi_status status; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci status = wmi_get_event_data(value, &response); 8162306a36Sopenharmony_ci if (status != AE_OK) { 8262306a36Sopenharmony_ci pr_info("bad event status 0x%x\n", status); 8362306a36Sopenharmony_ci return; 8462306a36Sopenharmony_ci } 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci obj = (union acpi_object *)response.pointer; 8762306a36Sopenharmony_ci if (obj) { 8862306a36Sopenharmony_ci unsigned int scancode = 0; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci switch (obj->type) { 9162306a36Sopenharmony_ci case ACPI_TYPE_INTEGER: 9262306a36Sopenharmony_ci /* Most All-In-One correctly return integer scancode */ 9362306a36Sopenharmony_ci scancode = obj->integer.value; 9462306a36Sopenharmony_ci sparse_keymap_report_event(dell_wmi_aio_input_dev, 9562306a36Sopenharmony_ci scancode, 1, true); 9662306a36Sopenharmony_ci break; 9762306a36Sopenharmony_ci case ACPI_TYPE_BUFFER: 9862306a36Sopenharmony_ci if (dell_wmi_aio_event_check(obj->buffer.pointer, 9962306a36Sopenharmony_ci obj->buffer.length)) { 10062306a36Sopenharmony_ci event = (struct dell_wmi_event *) 10162306a36Sopenharmony_ci obj->buffer.pointer; 10262306a36Sopenharmony_ci scancode = event->event[0]; 10362306a36Sopenharmony_ci } else { 10462306a36Sopenharmony_ci /* Broken machines return the scancode in a 10562306a36Sopenharmony_ci buffer */ 10662306a36Sopenharmony_ci if (obj->buffer.pointer && 10762306a36Sopenharmony_ci obj->buffer.length > 0) 10862306a36Sopenharmony_ci scancode = obj->buffer.pointer[0]; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci if (scancode) 11162306a36Sopenharmony_ci sparse_keymap_report_event( 11262306a36Sopenharmony_ci dell_wmi_aio_input_dev, 11362306a36Sopenharmony_ci scancode, 1, true); 11462306a36Sopenharmony_ci break; 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci } 11762306a36Sopenharmony_ci kfree(obj); 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic int __init dell_wmi_aio_input_setup(void) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci int err; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci dell_wmi_aio_input_dev = input_allocate_device(); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci if (!dell_wmi_aio_input_dev) 12762306a36Sopenharmony_ci return -ENOMEM; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys"; 13062306a36Sopenharmony_ci dell_wmi_aio_input_dev->phys = "wmi/input0"; 13162306a36Sopenharmony_ci dell_wmi_aio_input_dev->id.bustype = BUS_HOST; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci err = sparse_keymap_setup(dell_wmi_aio_input_dev, 13462306a36Sopenharmony_ci dell_wmi_aio_keymap, NULL); 13562306a36Sopenharmony_ci if (err) { 13662306a36Sopenharmony_ci pr_err("Unable to setup input device keymap\n"); 13762306a36Sopenharmony_ci goto err_free_dev; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci err = input_register_device(dell_wmi_aio_input_dev); 14062306a36Sopenharmony_ci if (err) { 14162306a36Sopenharmony_ci pr_info("Unable to register input device\n"); 14262306a36Sopenharmony_ci goto err_free_dev; 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci return 0; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cierr_free_dev: 14762306a36Sopenharmony_ci input_free_device(dell_wmi_aio_input_dev); 14862306a36Sopenharmony_ci return err; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic const char *dell_wmi_aio_find(void) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci int i; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci for (i = 0; dell_wmi_aio_guids[i] != NULL; i++) 15662306a36Sopenharmony_ci if (wmi_has_guid(dell_wmi_aio_guids[i])) 15762306a36Sopenharmony_ci return dell_wmi_aio_guids[i]; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci return NULL; 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic int __init dell_wmi_aio_init(void) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci int err; 16562306a36Sopenharmony_ci const char *guid; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci guid = dell_wmi_aio_find(); 16862306a36Sopenharmony_ci if (!guid) { 16962306a36Sopenharmony_ci pr_warn("No known WMI GUID found\n"); 17062306a36Sopenharmony_ci return -ENXIO; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci err = dell_wmi_aio_input_setup(); 17462306a36Sopenharmony_ci if (err) 17562306a36Sopenharmony_ci return err; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL); 17862306a36Sopenharmony_ci if (err) { 17962306a36Sopenharmony_ci pr_err("Unable to register notify handler - %d\n", err); 18062306a36Sopenharmony_ci input_unregister_device(dell_wmi_aio_input_dev); 18162306a36Sopenharmony_ci return err; 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci return 0; 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic void __exit dell_wmi_aio_exit(void) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci const char *guid; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci guid = dell_wmi_aio_find(); 19262306a36Sopenharmony_ci wmi_remove_notify_handler(guid); 19362306a36Sopenharmony_ci input_unregister_device(dell_wmi_aio_input_dev); 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cimodule_init(dell_wmi_aio_init); 19762306a36Sopenharmony_cimodule_exit(dell_wmi_aio_exit); 198