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,    KEY_UNKNOWN,    KEY_UNKNOWN,    KEY_UNKNOWN,
 134        KEY_UNKNOWN,    KEY_UNKNOWN,    KEY_UNKNOWN,    KEY_MICMUTE,
 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, 0, 0,
 143        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 144        0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PROG3
 145};
 146
 147static struct input_dev *dell_wmi_input_dev;
 148
 149static void dell_wmi_notify(u32 value, void *context)
 150{
 151        struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
 152        union acpi_object *obj;
 153        acpi_status status;
 154
 155        status = wmi_get_event_data(value, &response);
 156        if (status != AE_OK) {
 157                pr_info("bad event status 0x%x\n", status);
 158                return;
 159        }
 160
 161        obj = (union acpi_object *)response.pointer;
 162
 163        if (obj && obj->type == ACPI_TYPE_BUFFER) {
 164                const struct key_entry *key;
 165                int reported_key;
 166                u16 *buffer_entry = (u16 *)obj->buffer.pointer;
 167
 168                if (dell_new_hk_type && (buffer_entry[1] != 0x10)) {
 169                        pr_info("Received unknown WMI event (0x%x)\n",
 170                                buffer_entry[1]);
 171                        kfree(obj);
 172                        return;
 173                }
 174
 175                if (dell_new_hk_type || buffer_entry[1] == 0x0)
 176                        reported_key = (int)buffer_entry[2];
 177                else
 178                        reported_key = (int)buffer_entry[1] & 0xffff;
 179
 180                key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
 181                                                        reported_key);
 182                if (!key) {
 183                        pr_info("Unknown key %x pressed\n", reported_key);
 184                } else if ((key->keycode == KEY_BRIGHTNESSUP ||
 185                            key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) {
 186                        /* Don't report brightness notifications that will also
 187                         * come via ACPI */
 188                        ;
 189                } else {
 190                        sparse_keymap_report_entry(dell_wmi_input_dev, key,
 191                                                   1, true);
 192                }
 193        }
 194        kfree(obj);
 195}
 196
 197static const struct key_entry * __init dell_wmi_prepare_new_keymap(void)
 198{
 199        int hotkey_num = (dell_bios_hotkey_table->header.length - 4) /
 200                                sizeof(struct dell_bios_keymap_entry);
 201        struct key_entry *keymap;
 202        int i;
 203
 204        keymap = kcalloc(hotkey_num + 1, sizeof(struct key_entry), GFP_KERNEL);
 205        if (!keymap)
 206                return NULL;
 207
 208        for (i = 0; i < hotkey_num; i++) {
 209                const struct dell_bios_keymap_entry *bios_entry =
 210                                        &dell_bios_hotkey_table->keymap[i];
 211                keymap[i].type = KE_KEY;
 212                keymap[i].code = bios_entry->scancode;
 213                keymap[i].keycode = bios_entry->keycode < 256 ?
 214                                    bios_to_linux_keycode[bios_entry->keycode] :
 215                                    KEY_RESERVED;
 216        }
 217
 218        keymap[hotkey_num].type = KE_END;
 219
 220        return keymap;
 221}
 222
 223static int __init dell_wmi_input_setup(void)
 224{
 225        int err;
 226
 227        dell_wmi_input_dev = input_allocate_device();
 228        if (!dell_wmi_input_dev)
 229                return -ENOMEM;
 230
 231        dell_wmi_input_dev->name = "Dell WMI hotkeys";
 232        dell_wmi_input_dev->phys = "wmi/input0";
 233        dell_wmi_input_dev->id.bustype = BUS_HOST;
 234
 235        if (dell_new_hk_type) {
 236                const struct key_entry *keymap = dell_wmi_prepare_new_keymap();
 237                if (!keymap) {
 238                        err = -ENOMEM;
 239                        goto err_free_dev;
 240                }
 241
 242                err = sparse_keymap_setup(dell_wmi_input_dev, keymap, NULL);
 243
 244                /*
 245                 * Sparse keymap library makes a copy of keymap so we
 246                 * don't need the original one that was allocated.
 247                 */
 248                kfree(keymap);
 249        } else {
 250                err = sparse_keymap_setup(dell_wmi_input_dev,
 251                                          dell_wmi_legacy_keymap, NULL);
 252        }
 253        if (err)
 254                goto err_free_dev;
 255
 256        err = input_register_device(dell_wmi_input_dev);
 257        if (err)
 258                goto err_free_keymap;
 259
 260        return 0;
 261
 262 err_free_keymap:
 263        sparse_keymap_free(dell_wmi_input_dev);
 264 err_free_dev:
 265        input_free_device(dell_wmi_input_dev);
 266        return err;
 267}
 268
 269static void dell_wmi_input_destroy(void)
 270{
 271        sparse_keymap_free(dell_wmi_input_dev);
 272        input_unregister_device(dell_wmi_input_dev);
 273}
 274
 275static void __init find_hk_type(const struct dmi_header *dm, void *dummy)
 276{
 277        if (dm->type == 0xb2 && dm->length > 6) {
 278                dell_new_hk_type = true;
 279                dell_bios_hotkey_table =
 280                        container_of(dm, struct dell_bios_hotkey_table, header);
 281        }
 282}
 283
 284static int __init dell_wmi_init(void)
 285{
 286        int err;
 287        acpi_status status;
 288
 289        if (!wmi_has_guid(DELL_EVENT_GUID)) {
 290                pr_warn("No known WMI GUID found\n");
 291                return -ENODEV;
 292        }
 293
 294        dmi_walk(find_hk_type, NULL);
 295        acpi_video = acpi_video_backlight_support();
 296
 297        err = dell_wmi_input_setup();
 298        if (err)
 299                return err;
 300
 301        status = wmi_install_notify_handler(DELL_EVENT_GUID,
 302                                         dell_wmi_notify, NULL);
 303        if (ACPI_FAILURE(status)) {
 304                dell_wmi_input_destroy();
 305                pr_err("Unable to register notify handler - %d\n", status);
 306                return -ENODEV;
 307        }
 308
 309        return 0;
 310}
 311module_init(dell_wmi_init);
 312
 313static void __exit dell_wmi_exit(void)
 314{
 315        wmi_remove_notify_handler(DELL_EVENT_GUID);
 316        dell_wmi_input_destroy();
 317}
 318module_exit(dell_wmi_exit);
 319