Loading...
1/*
2 * Dell WMI hotkeys
3 *
4 * Copyright (C) 2008 Red Hat <mjg@redhat.com>
5 * Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com>
6 *
7 * Portions based on wistron_btns.c:
8 * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
9 * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
10 * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 */
26
27#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
28
29#include <linux/kernel.h>
30#include <linux/module.h>
31#include <linux/init.h>
32#include <linux/slab.h>
33#include <linux/types.h>
34#include <linux/input.h>
35#include <linux/input/sparse-keymap.h>
36#include <linux/acpi.h>
37#include <linux/string.h>
38#include <linux/dmi.h>
39#include <acpi/video.h>
40#include "dell-smbios.h"
41
42MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
43MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
44MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
45MODULE_LICENSE("GPL");
46
47#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492"
48#define DELL_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492"
49
50static u32 dell_wmi_interface_version;
51static bool wmi_requires_smbios_request;
52
53MODULE_ALIAS("wmi:"DELL_EVENT_GUID);
54MODULE_ALIAS("wmi:"DELL_DESCRIPTOR_GUID);
55
56static int __init dmi_matched(const struct dmi_system_id *dmi)
57{
58 wmi_requires_smbios_request = 1;
59 return 1;
60}
61
62static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = {
63 {
64 .callback = dmi_matched,
65 .ident = "Dell Inspiron M5110",
66 .matches = {
67 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
68 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"),
69 },
70 },
71 {
72 .callback = dmi_matched,
73 .ident = "Dell Vostro V131",
74 .matches = {
75 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
76 DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"),
77 },
78 },
79 { }
80};
81
82/*
83 * Certain keys are flagged as KE_IGNORE. All of these are either
84 * notifications (rather than requests for change) or are also sent
85 * via the keyboard controller so should not be sent again.
86 */
87
88static const struct key_entry dell_wmi_legacy_keymap[] __initconst = {
89 { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } },
90
91 { KE_KEY, 0xe045, { KEY_PROG1 } },
92 { KE_KEY, 0xe009, { KEY_EJECTCD } },
93
94 /* These also contain the brightness level at offset 6 */
95 { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } },
96 { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } },
97
98 /* Battery health status button */
99 { KE_KEY, 0xe007, { KEY_BATTERY } },
100
101 /* Radio devices state change */
102 { KE_IGNORE, 0xe008, { KEY_RFKILL } },
103
104 /* The next device is at offset 6, the active devices are at
105 offset 8 and the attached devices at offset 10 */
106 { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } },
107
108 { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } },
109
110 /* BIOS error detected */
111 { KE_IGNORE, 0xe00d, { KEY_RESERVED } },
112
113 /* Wifi Catcher */
114 { KE_KEY, 0xe011, {KEY_PROG2 } },
115
116 /* Ambient light sensor toggle */
117 { KE_IGNORE, 0xe013, { KEY_RESERVED } },
118
119 { KE_IGNORE, 0xe020, { KEY_MUTE } },
120
121 /* Dell Instant Launch key */
122 { KE_KEY, 0xe025, { KEY_PROG4 } },
123 { KE_KEY, 0xe029, { KEY_PROG4 } },
124
125 /* Audio panel key */
126 { KE_IGNORE, 0xe026, { KEY_RESERVED } },
127
128 { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } },
129 { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } },
130 { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } },
131 { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } },
132 { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } },
133 { KE_IGNORE, 0xe045, { KEY_NUMLOCK } },
134 { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } },
135 { KE_IGNORE, 0xe0f7, { KEY_MUTE } },
136 { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } },
137 { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } },
138 { KE_END, 0 }
139};
140
141static bool dell_new_hk_type;
142
143struct dell_bios_keymap_entry {
144 u16 scancode;
145 u16 keycode;
146};
147
148struct dell_bios_hotkey_table {
149 struct dmi_header header;
150 struct dell_bios_keymap_entry keymap[];
151
152};
153
154struct dell_dmi_results {
155 int err;
156 struct key_entry *keymap;
157};
158
159/* Uninitialized entries here are KEY_RESERVED == 0. */
160static const u16 bios_to_linux_keycode[256] __initconst = {
161 [0] = KEY_MEDIA,
162 [1] = KEY_NEXTSONG,
163 [2] = KEY_PLAYPAUSE,
164 [3] = KEY_PREVIOUSSONG,
165 [4] = KEY_STOPCD,
166 [5] = KEY_UNKNOWN,
167 [6] = KEY_UNKNOWN,
168 [7] = KEY_UNKNOWN,
169 [8] = KEY_WWW,
170 [9] = KEY_UNKNOWN,
171 [10] = KEY_VOLUMEDOWN,
172 [11] = KEY_MUTE,
173 [12] = KEY_VOLUMEUP,
174 [13] = KEY_UNKNOWN,
175 [14] = KEY_BATTERY,
176 [15] = KEY_EJECTCD,
177 [16] = KEY_UNKNOWN,
178 [17] = KEY_SLEEP,
179 [18] = KEY_PROG1,
180 [19] = KEY_BRIGHTNESSDOWN,
181 [20] = KEY_BRIGHTNESSUP,
182 [21] = KEY_UNKNOWN,
183 [22] = KEY_KBDILLUMTOGGLE,
184 [23] = KEY_UNKNOWN,
185 [24] = KEY_SWITCHVIDEOMODE,
186 [25] = KEY_UNKNOWN,
187 [26] = KEY_UNKNOWN,
188 [27] = KEY_SWITCHVIDEOMODE,
189 [28] = KEY_UNKNOWN,
190 [29] = KEY_UNKNOWN,
191 [30] = KEY_PROG2,
192 [31] = KEY_UNKNOWN,
193 [32] = KEY_UNKNOWN,
194 [33] = KEY_UNKNOWN,
195 [34] = KEY_UNKNOWN,
196 [35] = KEY_UNKNOWN,
197 [36] = KEY_UNKNOWN,
198 [37] = KEY_UNKNOWN,
199 [38] = KEY_MICMUTE,
200 [255] = KEY_PROG3,
201};
202
203/*
204 * These are applied if the 0xB2 DMI hotkey table is present and doesn't
205 * override them.
206 */
207static const struct key_entry dell_wmi_extra_keymap[] __initconst = {
208 /* Fn-lock */
209 { KE_IGNORE, 0x151, { KEY_RESERVED } },
210
211 /* Change keyboard illumination */
212 { KE_IGNORE, 0x152, { KEY_KBDILLUMTOGGLE } },
213
214 /*
215 * Radio disable (notify only -- there is no model for which the
216 * WMI event is supposed to trigger an action).
217 */
218 { KE_IGNORE, 0x153, { KEY_RFKILL } },
219
220 /* RGB keyboard backlight control */
221 { KE_IGNORE, 0x154, { KEY_RESERVED } },
222
223 /* Stealth mode toggle */
224 { KE_IGNORE, 0x155, { KEY_RESERVED } },
225};
226
227static struct input_dev *dell_wmi_input_dev;
228
229static void dell_wmi_process_key(int reported_key)
230{
231 const struct key_entry *key;
232
233 key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
234 reported_key);
235 if (!key) {
236 pr_info("Unknown key with scancode 0x%x pressed\n",
237 reported_key);
238 return;
239 }
240
241 pr_debug("Key %x pressed\n", reported_key);
242
243 /* Don't report brightness notifications that will also come via ACPI */
244 if ((key->keycode == KEY_BRIGHTNESSUP ||
245 key->keycode == KEY_BRIGHTNESSDOWN) &&
246 acpi_video_handles_brightness_key_presses())
247 return;
248
249 if (reported_key == 0xe025 && !wmi_requires_smbios_request)
250 return;
251
252 sparse_keymap_report_entry(dell_wmi_input_dev, key, 1, true);
253}
254
255static void dell_wmi_notify(u32 value, void *context)
256{
257 struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
258 union acpi_object *obj;
259 acpi_status status;
260 acpi_size buffer_size;
261 u16 *buffer_entry, *buffer_end;
262 int len, i;
263
264 status = wmi_get_event_data(value, &response);
265 if (status != AE_OK) {
266 pr_warn("bad event status 0x%x\n", status);
267 return;
268 }
269
270 obj = (union acpi_object *)response.pointer;
271 if (!obj) {
272 pr_warn("no response\n");
273 return;
274 }
275
276 if (obj->type != ACPI_TYPE_BUFFER) {
277 pr_warn("bad response type %x\n", obj->type);
278 kfree(obj);
279 return;
280 }
281
282 pr_debug("Received WMI event (%*ph)\n",
283 obj->buffer.length, obj->buffer.pointer);
284
285 buffer_entry = (u16 *)obj->buffer.pointer;
286 buffer_size = obj->buffer.length/2;
287
288 if (!dell_new_hk_type) {
289 if (buffer_size >= 3 && buffer_entry[1] == 0x0)
290 dell_wmi_process_key(buffer_entry[2]);
291 else if (buffer_size >= 2)
292 dell_wmi_process_key(buffer_entry[1]);
293 else
294 pr_info("Received unknown WMI event\n");
295 kfree(obj);
296 return;
297 }
298
299 buffer_end = buffer_entry + buffer_size;
300
301 /*
302 * BIOS/ACPI on devices with WMI interface version 0 does not clear
303 * buffer before filling it. So next time when BIOS/ACPI send WMI event
304 * which is smaller as previous then it contains garbage in buffer from
305 * previous event.
306 *
307 * BIOS/ACPI on devices with WMI interface version 1 clears buffer and
308 * sometimes send more events in buffer at one call.
309 *
310 * So to prevent reading garbage from buffer we will process only first
311 * one event on devices with WMI interface version 0.
312 */
313 if (dell_wmi_interface_version == 0 && buffer_entry < buffer_end)
314 if (buffer_end > buffer_entry + buffer_entry[0] + 1)
315 buffer_end = buffer_entry + buffer_entry[0] + 1;
316
317 while (buffer_entry < buffer_end) {
318
319 len = buffer_entry[0];
320 if (len == 0)
321 break;
322
323 len++;
324
325 if (buffer_entry + len > buffer_end) {
326 pr_warn("Invalid length of WMI event\n");
327 break;
328 }
329
330 pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry);
331
332 switch (buffer_entry[1]) {
333 case 0x00:
334 for (i = 2; i < len; ++i) {
335 switch (buffer_entry[i]) {
336 case 0xe043:
337 /* NIC Link is Up */
338 pr_debug("NIC Link is Up\n");
339 break;
340 case 0xe044:
341 /* NIC Link is Down */
342 pr_debug("NIC Link is Down\n");
343 break;
344 case 0xe045:
345 /* Unknown event but defined in DSDT */
346 default:
347 /* Unknown event */
348 pr_info("Unknown WMI event type 0x00: "
349 "0x%x\n", (int)buffer_entry[i]);
350 break;
351 }
352 }
353 break;
354 case 0x10:
355 /* Keys pressed */
356 for (i = 2; i < len; ++i)
357 dell_wmi_process_key(buffer_entry[i]);
358 break;
359 case 0x11:
360 for (i = 2; i < len; ++i) {
361 switch (buffer_entry[i]) {
362 case 0xfff0:
363 /* Battery unplugged */
364 pr_debug("Battery unplugged\n");
365 break;
366 case 0xfff1:
367 /* Battery inserted */
368 pr_debug("Battery inserted\n");
369 break;
370 case 0x01e1:
371 case 0x02ea:
372 case 0x02eb:
373 case 0x02ec:
374 case 0x02f6:
375 /* Keyboard backlight level changed */
376 pr_debug("Keyboard backlight level "
377 "changed\n");
378 break;
379 default:
380 /* Unknown event */
381 pr_info("Unknown WMI event type 0x11: "
382 "0x%x\n", (int)buffer_entry[i]);
383 break;
384 }
385 }
386 break;
387 default:
388 /* Unknown event */
389 pr_info("Unknown WMI event type 0x%x\n",
390 (int)buffer_entry[1]);
391 break;
392 }
393
394 buffer_entry += len;
395
396 }
397
398 kfree(obj);
399}
400
401static bool have_scancode(u32 scancode, const struct key_entry *keymap, int len)
402{
403 int i;
404
405 for (i = 0; i < len; i++)
406 if (keymap[i].code == scancode)
407 return true;
408
409 return false;
410}
411
412static void __init handle_dmi_entry(const struct dmi_header *dm,
413
414 void *opaque)
415
416{
417 struct dell_dmi_results *results = opaque;
418 struct dell_bios_hotkey_table *table;
419 int hotkey_num, i, pos = 0;
420 struct key_entry *keymap;
421 int num_bios_keys;
422
423 if (results->err || results->keymap)
424 return; /* We already found the hotkey table. */
425
426 if (dm->type != 0xb2)
427 return;
428
429 table = container_of(dm, struct dell_bios_hotkey_table, header);
430
431 hotkey_num = (table->header.length -
432 sizeof(struct dell_bios_hotkey_table)) /
433 sizeof(struct dell_bios_keymap_entry);
434 if (hotkey_num < 1) {
435 /*
436 * Historically, dell-wmi would ignore a DMI entry of
437 * fewer than 7 bytes. Sizes between 4 and 8 bytes are
438 * nonsensical (both the header and all entries are 4
439 * bytes), so we approximate the old behavior by
440 * ignoring tables with fewer than one entry.
441 */
442 return;
443 }
444
445 keymap = kcalloc(hotkey_num + ARRAY_SIZE(dell_wmi_extra_keymap) + 1,
446 sizeof(struct key_entry), GFP_KERNEL);
447 if (!keymap) {
448 results->err = -ENOMEM;
449 return;
450 }
451
452 for (i = 0; i < hotkey_num; i++) {
453 const struct dell_bios_keymap_entry *bios_entry =
454 &table->keymap[i];
455
456 /* Uninitialized entries are 0 aka KEY_RESERVED. */
457 u16 keycode = (bios_entry->keycode <
458 ARRAY_SIZE(bios_to_linux_keycode)) ?
459 bios_to_linux_keycode[bios_entry->keycode] :
460 KEY_RESERVED;
461
462 /*
463 * Log if we find an entry in the DMI table that we don't
464 * understand. If this happens, we should figure out what
465 * the entry means and add it to bios_to_linux_keycode.
466 */
467 if (keycode == KEY_RESERVED) {
468 pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n",
469 bios_entry->scancode, bios_entry->keycode);
470 continue;
471 }
472
473 if (keycode == KEY_KBDILLUMTOGGLE)
474 keymap[pos].type = KE_IGNORE;
475 else
476 keymap[pos].type = KE_KEY;
477 keymap[pos].code = bios_entry->scancode;
478 keymap[pos].keycode = keycode;
479
480 pos++;
481 }
482
483 num_bios_keys = pos;
484
485 for (i = 0; i < ARRAY_SIZE(dell_wmi_extra_keymap); i++) {
486 const struct key_entry *entry = &dell_wmi_extra_keymap[i];
487
488 /*
489 * Check if we've already found this scancode. This takes
490 * quadratic time, but it doesn't matter unless the list
491 * of extra keys gets very long.
492 */
493 if (!have_scancode(entry->code, keymap, num_bios_keys)) {
494 keymap[pos] = *entry;
495 pos++;
496 }
497 }
498
499 keymap[pos].type = KE_END;
500
501 results->keymap = keymap;
502}
503
504static int __init dell_wmi_input_setup(void)
505{
506 struct dell_dmi_results dmi_results = {};
507 int err;
508
509 dell_wmi_input_dev = input_allocate_device();
510 if (!dell_wmi_input_dev)
511 return -ENOMEM;
512
513 dell_wmi_input_dev->name = "Dell WMI hotkeys";
514 dell_wmi_input_dev->phys = "wmi/input0";
515 dell_wmi_input_dev->id.bustype = BUS_HOST;
516
517 if (dmi_walk(handle_dmi_entry, &dmi_results)) {
518 /*
519 * Historically, dell-wmi ignored dmi_walk errors. A failure
520 * is certainly surprising, but it probably just indicates
521 * a very old laptop.
522 */
523 pr_warn("no DMI; using the old-style hotkey interface\n");
524 }
525
526 if (dmi_results.err) {
527 err = dmi_results.err;
528 goto err_free_dev;
529 }
530
531 if (dmi_results.keymap) {
532 dell_new_hk_type = true;
533
534 err = sparse_keymap_setup(dell_wmi_input_dev,
535 dmi_results.keymap, NULL);
536
537 /*
538 * Sparse keymap library makes a copy of keymap so we
539 * don't need the original one that was allocated.
540 */
541 kfree(dmi_results.keymap);
542 } else {
543 err = sparse_keymap_setup(dell_wmi_input_dev,
544 dell_wmi_legacy_keymap, NULL);
545 }
546 if (err)
547 goto err_free_dev;
548
549 err = input_register_device(dell_wmi_input_dev);
550 if (err)
551 goto err_free_keymap;
552
553 return 0;
554
555 err_free_keymap:
556 sparse_keymap_free(dell_wmi_input_dev);
557 err_free_dev:
558 input_free_device(dell_wmi_input_dev);
559 return err;
560}
561
562static void dell_wmi_input_destroy(void)
563{
564 sparse_keymap_free(dell_wmi_input_dev);
565 input_unregister_device(dell_wmi_input_dev);
566}
567
568/*
569 * Descriptor buffer is 128 byte long and contains:
570 *
571 * Name Offset Length Value
572 * Vendor Signature 0 4 "DELL"
573 * Object Signature 4 4 " WMI"
574 * WMI Interface Version 8 4 <version>
575 * WMI buffer length 12 4 4096
576 */
577static int __init dell_wmi_check_descriptor_buffer(void)
578{
579 struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
580 union acpi_object *obj;
581 acpi_status status;
582 u32 *buffer;
583
584 status = wmi_query_block(DELL_DESCRIPTOR_GUID, 0, &out);
585 if (ACPI_FAILURE(status)) {
586 pr_err("Cannot read Dell descriptor buffer - %d\n", status);
587 return status;
588 }
589
590 obj = (union acpi_object *)out.pointer;
591 if (!obj) {
592 pr_err("Dell descriptor buffer is empty\n");
593 return -EINVAL;
594 }
595
596 if (obj->type != ACPI_TYPE_BUFFER) {
597 pr_err("Cannot read Dell descriptor buffer\n");
598 kfree(obj);
599 return -EINVAL;
600 }
601
602 if (obj->buffer.length != 128) {
603 pr_err("Dell descriptor buffer has invalid length (%d)\n",
604 obj->buffer.length);
605 if (obj->buffer.length < 16) {
606 kfree(obj);
607 return -EINVAL;
608 }
609 }
610
611 buffer = (u32 *)obj->buffer.pointer;
612
613 if (buffer[0] != 0x4C4C4544 && buffer[1] != 0x494D5720)
614 pr_warn("Dell descriptor buffer has invalid signature (%*ph)\n",
615 8, buffer);
616
617 if (buffer[2] != 0 && buffer[2] != 1)
618 pr_warn("Dell descriptor buffer has unknown version (%d)\n",
619 buffer[2]);
620
621 if (buffer[3] != 4096)
622 pr_warn("Dell descriptor buffer has invalid buffer length (%d)\n",
623 buffer[3]);
624
625 dell_wmi_interface_version = buffer[2];
626
627 pr_info("Detected Dell WMI interface version %u\n",
628 dell_wmi_interface_version);
629
630 kfree(obj);
631 return 0;
632}
633
634/*
635 * According to Dell SMBIOS documentation:
636 *
637 * 17 3 Application Program Registration
638 *
639 * cbArg1 Application ID 1 = 0x00010000
640 * cbArg2 Application ID 2
641 * QUICKSET/DCP = 0x51534554 "QSET"
642 * ALS Driver = 0x416c7353 "AlsS"
643 * Latitude ON = 0x4c6f6e52 "LonR"
644 * cbArg3 Application version or revision number
645 * cbArg4 0 = Unregister application
646 * 1 = Register application
647 * cbRes1 Standard return codes (0, -1, -2)
648 */
649
650static int dell_wmi_events_set_enabled(bool enable)
651{
652 struct calling_interface_buffer *buffer;
653 int ret;
654
655 buffer = dell_smbios_get_buffer();
656 buffer->input[0] = 0x10000;
657 buffer->input[1] = 0x51534554;
658 buffer->input[3] = enable;
659 dell_smbios_send_request(17, 3);
660 ret = buffer->output[0];
661 dell_smbios_release_buffer();
662
663 return dell_smbios_error(ret);
664}
665
666static int __init dell_wmi_init(void)
667{
668 int err;
669 acpi_status status;
670
671 if (!wmi_has_guid(DELL_EVENT_GUID) ||
672 !wmi_has_guid(DELL_DESCRIPTOR_GUID)) {
673 pr_warn("Dell WMI GUID were not found\n");
674 return -ENODEV;
675 }
676
677 err = dell_wmi_check_descriptor_buffer();
678 if (err)
679 return err;
680
681 err = dell_wmi_input_setup();
682 if (err)
683 return err;
684
685 status = wmi_install_notify_handler(DELL_EVENT_GUID,
686 dell_wmi_notify, NULL);
687 if (ACPI_FAILURE(status)) {
688 dell_wmi_input_destroy();
689 pr_err("Unable to register notify handler - %d\n", status);
690 return -ENODEV;
691 }
692
693 dmi_check_system(dell_wmi_smbios_list);
694
695 if (wmi_requires_smbios_request) {
696 err = dell_wmi_events_set_enabled(true);
697 if (err) {
698 pr_err("Failed to enable WMI events\n");
699 wmi_remove_notify_handler(DELL_EVENT_GUID);
700 dell_wmi_input_destroy();
701 return err;
702 }
703 }
704
705 return 0;
706}
707module_init(dell_wmi_init);
708
709static void __exit dell_wmi_exit(void)
710{
711 if (wmi_requires_smbios_request)
712 dell_wmi_events_set_enabled(false);
713 wmi_remove_notify_handler(DELL_EVENT_GUID);
714 dell_wmi_input_destroy();
715}
716module_exit(dell_wmi_exit);
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);