peaq-wmi.c (3284B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * PEAQ 2-in-1 WMI hotkey driver 4 * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> 5 */ 6 7#include <linux/acpi.h> 8#include <linux/dmi.h> 9#include <linux/input.h> 10#include <linux/kernel.h> 11#include <linux/module.h> 12 13#define PEAQ_DOLBY_BUTTON_GUID "ABBC0F6F-8EA1-11D1-00A0-C90629100000" 14#define PEAQ_DOLBY_BUTTON_METHOD_ID 5 15#define PEAQ_POLL_INTERVAL_MS 250 16#define PEAQ_POLL_IGNORE_MS 500 17#define PEAQ_POLL_MAX_MS 1000 18 19MODULE_ALIAS("wmi:"PEAQ_DOLBY_BUTTON_GUID); 20 21static struct input_dev *peaq_poll_dev; 22 23/* 24 * The Dolby button (yes really a Dolby button) causes an ACPI variable to get 25 * set on both press and release. The WMI method checks and clears that flag. 26 * So for a press + release we will get back One from the WMI method either once 27 * (if polling after the release) or twice (polling between press and release). 28 * We ignore events for 0.5s after the first event to avoid reporting 2 presses. 29 */ 30static void peaq_wmi_poll(struct input_dev *input_dev) 31{ 32 static unsigned long last_event_time; 33 static bool had_events; 34 union acpi_object obj; 35 acpi_status status; 36 u32 dummy = 0; 37 38 struct acpi_buffer input = { sizeof(dummy), &dummy }; 39 struct acpi_buffer output = { sizeof(obj), &obj }; 40 41 status = wmi_evaluate_method(PEAQ_DOLBY_BUTTON_GUID, 0, 42 PEAQ_DOLBY_BUTTON_METHOD_ID, 43 &input, &output); 44 if (ACPI_FAILURE(status)) 45 return; 46 47 if (obj.type != ACPI_TYPE_INTEGER) { 48 dev_err(&input_dev->dev, 49 "Error WMBC did not return an integer\n"); 50 return; 51 } 52 53 if (!obj.integer.value) 54 return; 55 56 if (had_events && time_before(jiffies, last_event_time + 57 msecs_to_jiffies(PEAQ_POLL_IGNORE_MS))) 58 return; 59 60 input_event(input_dev, EV_KEY, KEY_SOUND, 1); 61 input_sync(input_dev); 62 input_event(input_dev, EV_KEY, KEY_SOUND, 0); 63 input_sync(input_dev); 64 65 last_event_time = jiffies; 66 had_events = true; 67} 68 69/* Some other devices (Shuttle XS35) use the same WMI GUID for other purposes */ 70static const struct dmi_system_id peaq_dmi_table[] __initconst = { 71 { 72 .matches = { 73 DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"), 74 DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"), 75 }, 76 }, 77 {} 78}; 79 80static int __init peaq_wmi_init(void) 81{ 82 int err; 83 84 /* WMI GUID is not unique, also check for a DMI match */ 85 if (!dmi_check_system(peaq_dmi_table)) 86 return -ENODEV; 87 88 if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID)) 89 return -ENODEV; 90 91 peaq_poll_dev = input_allocate_device(); 92 if (!peaq_poll_dev) 93 return -ENOMEM; 94 95 peaq_poll_dev->name = "PEAQ WMI hotkeys"; 96 peaq_poll_dev->phys = "wmi/input0"; 97 peaq_poll_dev->id.bustype = BUS_HOST; 98 input_set_capability(peaq_poll_dev, EV_KEY, KEY_SOUND); 99 100 err = input_setup_polling(peaq_poll_dev, peaq_wmi_poll); 101 if (err) 102 goto err_out; 103 104 input_set_poll_interval(peaq_poll_dev, PEAQ_POLL_INTERVAL_MS); 105 input_set_max_poll_interval(peaq_poll_dev, PEAQ_POLL_MAX_MS); 106 107 err = input_register_device(peaq_poll_dev); 108 if (err) 109 goto err_out; 110 111 return 0; 112 113err_out: 114 input_free_device(peaq_poll_dev); 115 return err; 116} 117 118static void __exit peaq_wmi_exit(void) 119{ 120 input_unregister_device(peaq_poll_dev); 121} 122 123module_init(peaq_wmi_init); 124module_exit(peaq_wmi_exit); 125 126MODULE_DESCRIPTION("PEAQ 2-in-1 WMI hotkey driver"); 127MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 128MODULE_LICENSE("GPL");