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