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