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 
39 MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
40 MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
41 MODULE_LICENSE("GPL");
42 
43 #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492"
44 
45 static int acpi_video;
46 
47 MODULE_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 
55 static 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 
105 static bool dell_new_hk_type;
106 
107 struct dell_bios_keymap_entry {
108 	u16 scancode;
109 	u16 keycode;
110 };
111 
112 struct dell_bios_hotkey_table {
113 	struct dmi_header header;
114 	struct dell_bios_keymap_entry keymap[];
115 
116 };
117 
118 static const struct dell_bios_hotkey_table *dell_bios_hotkey_table;
119 
120 static 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 
144 static struct input_dev *dell_wmi_input_dev;
145 
dell_wmi_process_key(int reported_key)146 static 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 
dell_wmi_notify(u32 value,void * context)167 static 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 
dell_wmi_prepare_new_keymap(void)297 static 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 
dell_wmi_input_setup(void)328 static 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 
dell_wmi_input_destroy(void)374 static 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 
find_hk_type(const struct dmi_header * dm,void * dummy)380 static 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 
dell_wmi_init(void)389 static 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 }
416 module_init(dell_wmi_init);
417 
dell_wmi_exit(void)418 static void __exit dell_wmi_exit(void)
419 {
420 	wmi_remove_notify_handler(DELL_EVENT_GUID);
421 	dell_wmi_input_destroy();
422 }
423 module_exit(dell_wmi_exit);
424