linux/drivers/platform/x86/dell-wmi-aio.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 *  WMI hotkeys support for Dell All-In-One series
   4 */
   5
   6#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
   7
   8#include <linux/kernel.h>
   9#include <linux/module.h>
  10#include <linux/init.h>
  11#include <linux/types.h>
  12#include <linux/input.h>
  13#include <linux/input/sparse-keymap.h>
  14#include <linux/acpi.h>
  15#include <linux/string.h>
  16
  17MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series");
  18MODULE_LICENSE("GPL");
  19
  20#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4"
  21#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8"
  22
  23struct dell_wmi_event {
  24        u16     length;
  25        /* 0x000: A hot key pressed or an event occurred
  26         * 0x00F: A sequence of hot keys are pressed */
  27        u16     type;
  28        u16     event[];
  29};
  30
  31static const char *dell_wmi_aio_guids[] = {
  32        EVENT_GUID1,
  33        EVENT_GUID2,
  34        NULL
  35};
  36
  37MODULE_ALIAS("wmi:"EVENT_GUID1);
  38MODULE_ALIAS("wmi:"EVENT_GUID2);
  39
  40static const struct key_entry dell_wmi_aio_keymap[] = {
  41        { KE_KEY, 0xc0, { KEY_VOLUMEUP } },
  42        { KE_KEY, 0xc1, { KEY_VOLUMEDOWN } },
  43        { KE_KEY, 0xe030, { KEY_VOLUMEUP } },
  44        { KE_KEY, 0xe02e, { KEY_VOLUMEDOWN } },
  45        { KE_KEY, 0xe020, { KEY_MUTE } },
  46        { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } },
  47        { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } },
  48        { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } },
  49        { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } },
  50        { KE_END, 0 }
  51};
  52
  53static struct input_dev *dell_wmi_aio_input_dev;
  54
  55/*
  56 * The new WMI event data format will follow the dell_wmi_event structure
  57 * So, we will check if the buffer matches the format
  58 */
  59static bool dell_wmi_aio_event_check(u8 *buffer, int length)
  60{
  61        struct dell_wmi_event *event = (struct dell_wmi_event *)buffer;
  62
  63        if (event == NULL || length < 6)
  64                return false;
  65
  66        if ((event->type == 0 || event->type == 0xf) &&
  67                        event->length >= 2)
  68                return true;
  69
  70        return false;
  71}
  72
  73static void dell_wmi_aio_notify(u32 value, void *context)
  74{
  75        struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
  76        union acpi_object *obj;
  77        struct dell_wmi_event *event;
  78        acpi_status status;
  79
  80        status = wmi_get_event_data(value, &response);
  81        if (status != AE_OK) {
  82                pr_info("bad event status 0x%x\n", status);
  83                return;
  84        }
  85
  86        obj = (union acpi_object *)response.pointer;
  87        if (obj) {
  88                unsigned int scancode = 0;
  89
  90                switch (obj->type) {
  91                case ACPI_TYPE_INTEGER:
  92                        /* Most All-In-One correctly return integer scancode */
  93                        scancode = obj->integer.value;
  94                        sparse_keymap_report_event(dell_wmi_aio_input_dev,
  95                                scancode, 1, true);
  96                        break;
  97                case ACPI_TYPE_BUFFER:
  98                        if (dell_wmi_aio_event_check(obj->buffer.pointer,
  99                                                obj->buffer.length)) {
 100                                event = (struct dell_wmi_event *)
 101                                        obj->buffer.pointer;
 102                                scancode = event->event[0];
 103                        } else {
 104                                /* Broken machines return the scancode in a
 105                                   buffer */
 106                                if (obj->buffer.pointer &&
 107                                                obj->buffer.length > 0)
 108                                        scancode = obj->buffer.pointer[0];
 109                        }
 110                        if (scancode)
 111                                sparse_keymap_report_event(
 112                                        dell_wmi_aio_input_dev,
 113                                        scancode, 1, true);
 114                        break;
 115                }
 116        }
 117        kfree(obj);
 118}
 119
 120static int __init dell_wmi_aio_input_setup(void)
 121{
 122        int err;
 123
 124        dell_wmi_aio_input_dev = input_allocate_device();
 125
 126        if (!dell_wmi_aio_input_dev)
 127                return -ENOMEM;
 128
 129        dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys";
 130        dell_wmi_aio_input_dev->phys = "wmi/input0";
 131        dell_wmi_aio_input_dev->id.bustype = BUS_HOST;
 132
 133        err = sparse_keymap_setup(dell_wmi_aio_input_dev,
 134                        dell_wmi_aio_keymap, NULL);
 135        if (err) {
 136                pr_err("Unable to setup input device keymap\n");
 137                goto err_free_dev;
 138        }
 139        err = input_register_device(dell_wmi_aio_input_dev);
 140        if (err) {
 141                pr_info("Unable to register input device\n");
 142                goto err_free_dev;
 143        }
 144        return 0;
 145
 146err_free_dev:
 147        input_free_device(dell_wmi_aio_input_dev);
 148        return err;
 149}
 150
 151static const char *dell_wmi_aio_find(void)
 152{
 153        int i;
 154
 155        for (i = 0; dell_wmi_aio_guids[i] != NULL; i++)
 156                if (wmi_has_guid(dell_wmi_aio_guids[i]))
 157                        return dell_wmi_aio_guids[i];
 158
 159        return NULL;
 160}
 161
 162static int __init dell_wmi_aio_init(void)
 163{
 164        int err;
 165        const char *guid;
 166
 167        guid = dell_wmi_aio_find();
 168        if (!guid) {
 169                pr_warn("No known WMI GUID found\n");
 170                return -ENXIO;
 171        }
 172
 173        err = dell_wmi_aio_input_setup();
 174        if (err)
 175                return err;
 176
 177        err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL);
 178        if (err) {
 179                pr_err("Unable to register notify handler - %d\n", err);
 180                input_unregister_device(dell_wmi_aio_input_dev);
 181                return err;
 182        }
 183
 184        return 0;
 185}
 186
 187static void __exit dell_wmi_aio_exit(void)
 188{
 189        const char *guid;
 190
 191        guid = dell_wmi_aio_find();
 192        wmi_remove_notify_handler(guid);
 193        input_unregister_device(dell_wmi_aio_input_dev);
 194}
 195
 196module_init(dell_wmi_aio_init);
 197module_exit(dell_wmi_aio_exit);
 198