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 <linux/acpi.h>
36#include <linux/string.h>
37#include <linux/dmi.h>
38
39MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
40MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
41MODULE_LICENSE("GPL");
42
43#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492"
44
45static int acpi_video;
46
47MODULE_ALIAS("wmi:"DELL_EVENT_GUID);
48
49/*
50 * Certain keys are flagged as KE_IGNORE. All of these are either
51 * notifications (rather than requests for change) or are also sent
52 * via the keyboard controller so should not be sent again.
53 */
54
55static const struct key_entry dell_wmi_legacy_keymap[] __initconst = {
56	{ KE_IGNORE, 0x003a, { KEY_CAPSLOCK } },
57
58	{ KE_KEY, 0xe045, { KEY_PROG1 } },
59	{ KE_KEY, 0xe009, { KEY_EJECTCD } },
60
61	/* These also contain the brightness level at offset 6 */
62	{ KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } },
63	{ KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } },
64
65	/* Battery health status button */
66	{ KE_KEY, 0xe007, { KEY_BATTERY } },
67
68	/* Radio devices state change */
69	{ KE_IGNORE, 0xe008, { KEY_RFKILL } },
70
71	/* The next device is at offset 6, the active devices are at
72	   offset 8 and the attached devices at offset 10 */
73	{ KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } },
74
75	{ KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } },
76
77	/* BIOS error detected */
78	{ KE_IGNORE, 0xe00d, { KEY_RESERVED } },
79
80	/* Wifi Catcher */
81	{ KE_KEY, 0xe011, {KEY_PROG2 } },
82
83	/* Ambient light sensor toggle */
84	{ KE_IGNORE, 0xe013, { KEY_RESERVED } },
85
86	{ KE_IGNORE, 0xe020, { KEY_MUTE } },
87
88	/* Shortcut and audio panel keys */
89	{ KE_IGNORE, 0xe025, { KEY_RESERVED } },
90	{ KE_IGNORE, 0xe026, { KEY_RESERVED } },
91
92	{ KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } },
93	{ KE_IGNORE, 0xe030, { KEY_VOLUMEUP } },
94	{ KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } },
95	{ KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } },
96	{ KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } },
97	{ KE_IGNORE, 0xe045, { KEY_NUMLOCK } },
98	{ KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } },
99	{ KE_IGNORE, 0xe0f7, { KEY_MUTE } },
100	{ KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } },
101	{ KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } },
102	{ KE_END, 0 }
103};
104
105static bool dell_new_hk_type;
106
107struct dell_bios_keymap_entry {
108	u16 scancode;
109	u16 keycode;
110};
111
112struct dell_bios_hotkey_table {
113	struct dmi_header header;
114	struct dell_bios_keymap_entry keymap[];
115
116};
117
118static const struct dell_bios_hotkey_table *dell_bios_hotkey_table;
119
120static const u16 bios_to_linux_keycode[256] __initconst = {
121
122	KEY_MEDIA,	KEY_NEXTSONG,	KEY_PLAYPAUSE, KEY_PREVIOUSSONG,
123	KEY_STOPCD,	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_UNKNOWN,
124	KEY_WWW,	KEY_UNKNOWN,	KEY_VOLUMEDOWN, KEY_MUTE,
125	KEY_VOLUMEUP,	KEY_UNKNOWN,	KEY_BATTERY,	KEY_EJECTCD,
126	KEY_UNKNOWN,	KEY_SLEEP,	KEY_PROG1, KEY_BRIGHTNESSDOWN,
127	KEY_BRIGHTNESSUP,	KEY_UNKNOWN,	KEY_KBDILLUMTOGGLE,
128	KEY_UNKNOWN,	KEY_SWITCHVIDEOMODE,	KEY_UNKNOWN, KEY_UNKNOWN,
129	KEY_SWITCHVIDEOMODE,	KEY_UNKNOWN,	KEY_UNKNOWN, KEY_PROG2,
130	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_UNKNOWN,
131	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_MICMUTE,
132	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
133	0, 0, 0, 0, 0, 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, KEY_PROG3
142};
143
144static struct input_dev *dell_wmi_input_dev;
145
146static void dell_wmi_process_key(int reported_key)
147{
148	const struct key_entry *key;
149
150	key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
151						reported_key);
152	if (!key) {
153		pr_info("Unknown key %x pressed\n", reported_key);
154		return;
155	}
156
157	pr_debug("Key %x pressed\n", reported_key);
158
159	/* Don't report brightness notifications that will also come via ACPI */
160	if ((key->keycode == KEY_BRIGHTNESSUP ||
161	     key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video)
162		return;
163
164	sparse_keymap_report_entry(dell_wmi_input_dev, key, 1, true);
165}
166
167static void dell_wmi_notify(u32 value, void *context)
168{
169	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
170	union acpi_object *obj;
171	acpi_status status;
172	acpi_size buffer_size;
173	u16 *buffer_entry, *buffer_end;
174	int len, i;
175
176	status = wmi_get_event_data(value, &response);
177	if (status != AE_OK) {
178		pr_warn("bad event status 0x%x\n", status);
179		return;
180	}
181
182	obj = (union acpi_object *)response.pointer;
183	if (!obj) {
184		pr_warn("no response\n");
185		return;
186	}
187
188	if (obj->type != ACPI_TYPE_BUFFER) {
189		pr_warn("bad response type %x\n", obj->type);
190		kfree(obj);
191		return;
192	}
193
194	pr_debug("Received WMI event (%*ph)\n",
195		obj->buffer.length, obj->buffer.pointer);
196
197	buffer_entry = (u16 *)obj->buffer.pointer;
198	buffer_size = obj->buffer.length/2;
199
200	if (!dell_new_hk_type) {
201		if (buffer_size >= 3 && buffer_entry[1] == 0x0)
202			dell_wmi_process_key(buffer_entry[2]);
203		else if (buffer_size >= 2)
204			dell_wmi_process_key(buffer_entry[1]);
205		else
206			pr_info("Received unknown WMI event\n");
207		kfree(obj);
208		return;
209	}
210
211	buffer_end = buffer_entry + buffer_size;
212
213	while (buffer_entry < buffer_end) {
214
215		len = buffer_entry[0];
216		if (len == 0)
217			break;
218
219		len++;
220
221		if (buffer_entry + len > buffer_end) {
222			pr_warn("Invalid length of WMI event\n");
223			break;
224		}
225
226		pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry);
227
228		switch (buffer_entry[1]) {
229		case 0x00:
230			for (i = 2; i < len; ++i) {
231				switch (buffer_entry[i]) {
232				case 0xe043:
233					/* NIC Link is Up */
234					pr_debug("NIC Link is Up\n");
235					break;
236				case 0xe044:
237					/* NIC Link is Down */
238					pr_debug("NIC Link is Down\n");
239					break;
240				case 0xe045:
241					/* Unknown event but defined in DSDT */
242				default:
243					/* Unknown event */
244					pr_info("Unknown WMI event type 0x00: "
245						"0x%x\n", (int)buffer_entry[i]);
246					break;
247				}
248			}
249			break;
250		case 0x10:
251			/* Keys pressed */
252			for (i = 2; i < len; ++i)
253				dell_wmi_process_key(buffer_entry[i]);
254			break;
255		case 0x11:
256			for (i = 2; i < len; ++i) {
257				switch (buffer_entry[i]) {
258				case 0xfff0:
259					/* Battery unplugged */
260					pr_debug("Battery unplugged\n");
261					break;
262				case 0xfff1:
263					/* Battery inserted */
264					pr_debug("Battery inserted\n");
265					break;
266				case 0x01e1:
267				case 0x02ea:
268				case 0x02eb:
269				case 0x02ec:
270				case 0x02f6:
271					/* Keyboard backlight level changed */
272					pr_debug("Keyboard backlight level "
273						 "changed\n");
274					break;
275				default:
276					/* Unknown event */
277					pr_info("Unknown WMI event type 0x11: "
278						"0x%x\n", (int)buffer_entry[i]);
279					break;
280				}
281			}
282			break;
283		default:
284			/* Unknown event */
285			pr_info("Unknown WMI event type 0x%x\n",
286				(int)buffer_entry[1]);
287			break;
288		}
289
290		buffer_entry += len;
291
292	}
293
294	kfree(obj);
295}
296
297static const struct key_entry * __init dell_wmi_prepare_new_keymap(void)
298{
299	int hotkey_num = (dell_bios_hotkey_table->header.length - 4) /
300				sizeof(struct dell_bios_keymap_entry);
301	struct key_entry *keymap;
302	int i;
303
304	keymap = kcalloc(hotkey_num + 1, sizeof(struct key_entry), GFP_KERNEL);
305	if (!keymap)
306		return NULL;
307
308	for (i = 0; i < hotkey_num; i++) {
309		const struct dell_bios_keymap_entry *bios_entry =
310					&dell_bios_hotkey_table->keymap[i];
311		u16 keycode = bios_entry->keycode < 256 ?
312				    bios_to_linux_keycode[bios_entry->keycode] :
313				    KEY_RESERVED;
314
315		if (keycode == KEY_KBDILLUMTOGGLE)
316			keymap[i].type = KE_IGNORE;
317		else
318			keymap[i].type = KE_KEY;
319		keymap[i].code = bios_entry->scancode;
320		keymap[i].keycode = keycode;
321	}
322
323	keymap[hotkey_num].type = KE_END;
324
325	return keymap;
326}
327
328static int __init dell_wmi_input_setup(void)
329{
330	int err;
331
332	dell_wmi_input_dev = input_allocate_device();
333	if (!dell_wmi_input_dev)
334		return -ENOMEM;
335
336	dell_wmi_input_dev->name = "Dell WMI hotkeys";
337	dell_wmi_input_dev->phys = "wmi/input0";
338	dell_wmi_input_dev->id.bustype = BUS_HOST;
339
340	if (dell_new_hk_type) {
341		const struct key_entry *keymap = dell_wmi_prepare_new_keymap();
342		if (!keymap) {
343			err = -ENOMEM;
344			goto err_free_dev;
345		}
346
347		err = sparse_keymap_setup(dell_wmi_input_dev, keymap, NULL);
348
349		/*
350		 * Sparse keymap library makes a copy of keymap so we
351		 * don't need the original one that was allocated.
352		 */
353		kfree(keymap);
354	} else {
355		err = sparse_keymap_setup(dell_wmi_input_dev,
356					  dell_wmi_legacy_keymap, NULL);
357	}
358	if (err)
359		goto err_free_dev;
360
361	err = input_register_device(dell_wmi_input_dev);
362	if (err)
363		goto err_free_keymap;
364
365	return 0;
366
367 err_free_keymap:
368	sparse_keymap_free(dell_wmi_input_dev);
369 err_free_dev:
370	input_free_device(dell_wmi_input_dev);
371	return err;
372}
373
374static void dell_wmi_input_destroy(void)
375{
376	sparse_keymap_free(dell_wmi_input_dev);
377	input_unregister_device(dell_wmi_input_dev);
378}
379
380static void __init find_hk_type(const struct dmi_header *dm, void *dummy)
381{
382	if (dm->type == 0xb2 && dm->length > 6) {
383		dell_new_hk_type = true;
384		dell_bios_hotkey_table =
385			container_of(dm, struct dell_bios_hotkey_table, header);
386	}
387}
388
389static int __init dell_wmi_init(void)
390{
391	int err;
392	acpi_status status;
393
394	if (!wmi_has_guid(DELL_EVENT_GUID)) {
395		pr_warn("No known WMI GUID found\n");
396		return -ENODEV;
397	}
398
399	dmi_walk(find_hk_type, NULL);
400	acpi_video = acpi_video_backlight_support();
401
402	err = dell_wmi_input_setup();
403	if (err)
404		return err;
405
406	status = wmi_install_notify_handler(DELL_EVENT_GUID,
407					 dell_wmi_notify, NULL);
408	if (ACPI_FAILURE(status)) {
409		dell_wmi_input_destroy();
410		pr_err("Unable to register notify handler - %d\n", status);
411		return -ENODEV;
412	}
413
414	return 0;
415}
416module_init(dell_wmi_init);
417
418static void __exit dell_wmi_exit(void)
419{
420	wmi_remove_notify_handler(DELL_EVENT_GUID);
421	dell_wmi_input_destroy();
422}
423module_exit(dell_wmi_exit);
424