linux/drivers/platform/x86/peaq-wmi.c
<<
>>
Prefs
   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");
 129