linux/drivers/platform/x86/dell-wmi.c
<<
>>
Prefs
   1/*
   2 * Dell WMI hotkeys
   3 *
   4 * Copyright (C) 2008 Red Hat <mjg@redhat.com>
   5 *
   6 * Portions based on wistron_btns.c:
   7 * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
   8 * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
   9 * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
  10 *
  11 *  This program is free software; you can redistribute it and/or modify
  12 *  it under the terms of the GNU General Public License as published by
  13 *  the Free Software Foundation; either version 2 of the License, or
  14 *  (at your option) any later version.
  15 *
  16 *  This program is distributed in the hope that it will be useful,
  17 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  18 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19 *  GNU General Public License for more details.
  20 *
  21 *  You should have received a copy of the GNU General Public License
  22 *  along with this program; if not, write to the Free Software
  23 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  24 */
  25
  26#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  27
  28#include <linux/kernel.h>
  29#include <linux/module.h>
  30#include <linux/init.h>
  31#include <linux/slab.h>
  32#include <linux/types.h>
  33#include <linux/input.h>
  34#include <linux/input/sparse-keymap.h>
  35#include <acpi/acpi_drivers.h>
  36#include <linux/acpi.h>
  37#include <linux/string.h>
  38#include <linux/dmi.h>
  39
  40MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
  41MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
  42MODULE_LICENSE("GPL");
  43
  44#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492"
  45
  46static int acpi_video;
  47
  48MODULE_ALIAS("wmi:"DELL_EVENT_GUID);
  49
  50/*
  51 * Certain keys are flagged as KE_IGNORE. All of these are either
  52 * notifications (rather than requests for change) or are also sent
  53 * via the keyboard controller so should not be sent again.
  54 */
  55
  56static const struct key_entry dell_wmi_legacy_keymap[] __initconst = {
  57        { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } },
  58
  59        { KE_KEY, 0xe045, { KEY_PROG1 } },
  60        { KE_KEY, 0xe009, { KEY_EJECTCD } },
  61
  62        /* These also contain the brightness level at offset 6 */
  63        { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } },
  64        { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } },
  65
  66        /* Battery health status button */
  67        { KE_KEY, 0xe007, { KEY_BATTERY } },
  68
  69        /* This is actually for all radios. Although physically a
  70         * switch, the notification does not provide an indication of
  71         * state and so it should be reported as a key */
  72        { KE_KEY, 0xe008, { KEY_WLAN } },
  73
  74        /* The next device is at offset 6, the active devices are at
  75           offset 8 and the attached devices at offset 10 */
  76        { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } },
  77
  78        { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } },
  79
  80        /* BIOS error detected */
  81        { KE_IGNORE, 0xe00d, { KEY_RESERVED } },
  82
  83        /* Wifi Catcher */
  84        { KE_KEY, 0xe011, {KEY_PROG2 } },
  85
  86        /* Ambient light sensor toggle */
  87        { KE_IGNORE, 0xe013, { KEY_RESERVED } },
  88
  89        { KE_IGNORE, 0xe020, { KEY_MUTE } },
  90
  91        /* Shortcut and audio panel keys */
  92        { KE_IGNORE, 0xe025, { KEY_RESERVED } },
  93        { KE_IGNORE, 0xe026, { KEY_RESERVED } },
  94
  95        { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } },
  96        { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } },
  97        { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } },
  98        { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } },
  99        { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } },
 100        { KE_IGNORE, 0xe045, { KEY_NUMLOCK } },
 101        { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } },
 102        { KE_IGNORE, 0xe0f7, { KEY_MUTE } },
 103        { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } },
 104        { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } },
 105        { KE_END, 0 }
 106};
 107
 108static bool dell_new_hk_type;
 109
 110struct dell_bios_keymap_entry {
 111        u16 scancode;
 112        u16 keycode;
 113};
 114
 115struct dell_bios_hotkey_table {
 116        struct dmi_header header;
 117        struct dell_bios_keymap_entry keymap[];
 118
 119};
 120
 121static const struct dell_bios_hotkey_table *dell_bios_hotkey_table;
 122
 123static const u16 bios_to_linux_keycode[256] __initconst = {
 124
 125        KEY_MEDIA,      KEY_NEXTSONG,   KEY_PLAYPAUSE, KEY_PREVIOUSSONG,
 126        KEY_STOPCD,     KEY_UNKNOWN,    KEY_UNKNOWN,    KEY_UNKNOWN,
 127        KEY_WWW,        KEY_UNKNOWN,    KEY_VOLUMEDOWN, KEY_MUTE,
 128        KEY_VOLUMEUP,   KEY_UNKNOWN,    KEY_BATTERY,    KEY_EJECTCD,
 129        KEY_UNKNOWN,    KEY_SLEEP,      KEY_PROG1, KEY_BRIGHTNESSDOWN,
 130        KEY_BRIGHTNESSUP,       KEY_UNKNOWN,    KEY_KBDILLUMTOGGLE,
 131        KEY_UNKNOWN,    KEY_SWITCHVIDEOMODE,    KEY_UNKNOWN, KEY_UNKNOWN,
 132        KEY_SWITCHVIDEOMODE,    KEY_UNKNOWN,    KEY_UNKNOWN, KEY_PROG2,
 133        KEY_UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 134        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 135        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 136        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 137        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 138        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 139        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 140        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 141        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 142        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 143        KEY_PROG3
 144};
 145
 146static struct input_dev *dell_wmi_input_dev;
 147
 148static void dell_wmi_notify(u32 value, void *context)
 149{
 150        struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
 151        union acpi_object *obj;
 152        acpi_status status;
 153
 154        status = wmi_get_event_data(value, &response);
 155        if (status != AE_OK) {
 156                pr_info("bad event status 0x%x\n", status);
 157                return;
 158        }
 159
 160        obj = (union acpi_object *)response.pointer;
 161
 162        if (obj && obj->type == ACPI_TYPE_BUFFER) {
 163                const struct key_entry *key;
 164                int reported_key;
 165                u16 *buffer_entry = (u16 *)obj->buffer.pointer;
 166
 167                if (dell_new_hk_type && (buffer_entry[1] != 0x10)) {
 168                        pr_info("Received unknown WMI event (0x%x)\n",
 169                                buffer_entry[1]);
 170                        kfree(obj);
 171                        return;
 172                }
 173
 174                if (dell_new_hk_type || buffer_entry[1] == 0x0)
 175                        reported_key = (int)buffer_entry[2];
 176                else
 177                        reported_key = (int)buffer_entry[1] & 0xffff;
 178
 179                key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
 180                                                        reported_key);
 181                if (!key) {
 182                        pr_info("Unknown key %x pressed\n", reported_key);
 183                } else if ((key->keycode == KEY_BRIGHTNESSUP ||
 184                            key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) {
 185                        /* Don't report brightness notifications that will also
 186                         * come via ACPI */
 187                        ;
 188                } else {
 189                        sparse_keymap_report_entry(dell_wmi_input_dev, key,
 190                                                   1, true);
 191                }
 192        }
 193        kfree(obj);
 194}
 195
 196static const struct key_entry * __init dell_wmi_prepare_new_keymap(void)
 197{
 198        int hotkey_num = (dell_bios_hotkey_table->header.length - 4) /
 199                                sizeof(struct dell_bios_keymap_entry);
 200        struct key_entry *keymap;
 201        int i;
 202
 203        keymap = kcalloc(hotkey_num + 1, sizeof(struct key_entry), GFP_KERNEL);
 204        if (!keymap)
 205                return NULL;
 206
 207        for (i = 0; i < hotkey_num; i++) {
 208                const struct dell_bios_keymap_entry *bios_entry =
 209                                        &dell_bios_hotkey_table->keymap[i];
 210                keymap[i].type = KE_KEY;
 211                keymap[i].code = bios_entry->scancode;
 212                keymap[i].keycode = bios_entry->keycode < 256 ?
 213                                    bios_to_linux_keycode[bios_entry->keycode] :
 214                                    KEY_RESERVED;
 215        }
 216
 217        keymap[hotkey_num].type = KE_END;
 218
 219        return keymap;
 220}
 221
 222static int __init dell_wmi_input_setup(void)
 223{
 224        int err;
 225
 226        dell_wmi_input_dev = input_allocate_device();
 227        if (!dell_wmi_input_dev)
 228                return -ENOMEM;
 229
 230        dell_wmi_input_dev->name = "Dell WMI hotkeys";
 231        dell_wmi_input_dev->phys = "wmi/input0";
 232        dell_wmi_input_dev->id.bustype = BUS_HOST;
 233
 234        if (dell_new_hk_type) {
 235                const struct key_entry *keymap = dell_wmi_prepare_new_keymap();
 236                if (!keymap) {
 237                        err = -ENOMEM;
 238                        goto err_free_dev;
 239                }
 240
 241                err = sparse_keymap_setup(dell_wmi_input_dev, keymap, NULL);
 242
 243                /*
 244                 * Sparse keymap library makes a copy of keymap so we
 245                 * don't need the original one that was allocated.
 246                 */
 247                kfree(keymap);
 248        } else {
 249                err = sparse_keymap_setup(dell_wmi_input_dev,
 250                                          dell_wmi_legacy_keymap, NULL);
 251        }
 252        if (err)
 253                goto err_free_dev;
 254
 255        err = input_register_device(dell_wmi_input_dev);
 256        if (err)
 257                goto err_free_keymap;
 258
 259        return 0;
 260
 261 err_free_keymap:
 262        sparse_keymap_free(dell_wmi_input_dev);
 263 err_free_dev:
 264        input_free_device(dell_wmi_input_dev);
 265        return err;
 266}
 267
 268static void dell_wmi_input_destroy(void)
 269{
 270        sparse_keymap_free(dell_wmi_input_dev);
 271        input_unregister_device(dell_wmi_input_dev);
 272}
 273
 274static void __init find_hk_type(const struct dmi_header *dm, void *dummy)
 275{
 276        if (dm->type == 0xb2 && dm->length > 6) {
 277                dell_new_hk_type = true;
 278                dell_bios_hotkey_table =
 279                        container_of(dm, struct dell_bios_hotkey_table, header);
 280        }
 281}
 282
 283static int __init dell_wmi_init(void)
 284{
 285        int err;
 286        acpi_status status;
 287
 288        if (!wmi_has_guid(DELL_EVENT_GUID)) {
 289                pr_warn("No known WMI GUID found\n");
 290                return -ENODEV;
 291        }
 292
 293        dmi_walk(find_hk_type, NULL);
 294        acpi_video = acpi_video_backlight_support();
 295
 296        err = dell_wmi_input_setup();
 297        if (err)
 298                return err;
 299
 300        status = wmi_install_notify_handler(DELL_EVENT_GUID,
 301                                         dell_wmi_notify, NULL);
 302        if (ACPI_FAILURE(status)) {
 303                dell_wmi_input_destroy();
 304                pr_err("Unable to register notify handler - %d\n", status);
 305                return -ENODEV;
 306        }
 307
 308        return 0;
 309}
 310module_init(dell_wmi_init);
 311
 312static void __exit dell_wmi_exit(void)
 313{
 314        wmi_remove_notify_handler(DELL_EVENT_GUID);
 315        dell_wmi_input_destroy();
 316}
 317module_exit(dell_wmi_exit);
 318