• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-R7000-V1.0.7.12_1.2.5/components/opensource/linux/linux-2.6.36/drivers/platform/x86/
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#include <linux/kernel.h>
27#include <linux/module.h>
28#include <linux/init.h>
29#include <linux/slab.h>
30#include <linux/types.h>
31#include <linux/input.h>
32#include <acpi/acpi_drivers.h>
33#include <linux/acpi.h>
34#include <linux/string.h>
35#include <linux/dmi.h>
36
37MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
38MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
39MODULE_LICENSE("GPL");
40
41#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492"
42
43static int acpi_video;
44
45MODULE_ALIAS("wmi:"DELL_EVENT_GUID);
46
47struct key_entry {
48	char type;		/* See KE_* below */
49	u16 code;
50	u16 keycode;
51};
52
53enum { KE_KEY, KE_SW, KE_IGNORE, KE_END };
54
55/*
56 * Certain keys are flagged as KE_IGNORE. All of these are either
57 * notifications (rather than requests for change) or are also sent
58 * via the keyboard controller so should not be sent again.
59 */
60
61static struct key_entry dell_legacy_wmi_keymap[] = {
62	{KE_KEY, 0xe045, KEY_PROG1},
63	{KE_KEY, 0xe009, KEY_EJECTCD},
64
65	/* These also contain the brightness level at offset 6 */
66	{KE_KEY, 0xe006, KEY_BRIGHTNESSUP},
67	{KE_KEY, 0xe005, KEY_BRIGHTNESSDOWN},
68
69	/* Battery health status button */
70	{KE_KEY, 0xe007, KEY_BATTERY},
71
72	/* This is actually for all radios. Although physically a
73	 * switch, the notification does not provide an indication of
74	 * state and so it should be reported as a key */
75	{KE_KEY, 0xe008, KEY_WLAN},
76
77	/* The next device is at offset 6, the active devices are at
78	   offset 8 and the attached devices at offset 10 */
79	{KE_KEY, 0xe00b, KEY_SWITCHVIDEOMODE},
80
81	{KE_IGNORE, 0xe00c, KEY_KBDILLUMTOGGLE},
82
83	/* BIOS error detected */
84	{KE_IGNORE, 0xe00d, KEY_RESERVED},
85
86	/* Wifi Catcher */
87	{KE_KEY, 0xe011, KEY_PROG2},
88
89	/* Ambient light sensor toggle */
90	{KE_IGNORE, 0xe013, KEY_RESERVED},
91
92	{KE_IGNORE, 0xe020, KEY_MUTE},
93	{KE_IGNORE, 0xe02e, KEY_VOLUMEDOWN},
94	{KE_IGNORE, 0xe030, KEY_VOLUMEUP},
95	{KE_IGNORE, 0xe033, KEY_KBDILLUMUP},
96	{KE_IGNORE, 0xe034, KEY_KBDILLUMDOWN},
97	{KE_IGNORE, 0xe03a, KEY_CAPSLOCK},
98	{KE_IGNORE, 0xe045, KEY_NUMLOCK},
99	{KE_IGNORE, 0xe046, KEY_SCROLLLOCK},
100	{KE_END, 0}
101};
102
103static bool dell_new_hk_type;
104
105struct dell_new_keymap_entry {
106	u16 scancode;
107	u16 keycode;
108};
109
110struct dell_hotkey_table {
111	struct dmi_header header;
112	struct dell_new_keymap_entry keymap[];
113
114};
115
116static struct key_entry *dell_new_wmi_keymap;
117
118static u16 bios_to_linux_keycode[256] = {
119
120	KEY_MEDIA,	KEY_NEXTSONG,	KEY_PLAYPAUSE, KEY_PREVIOUSSONG,
121	KEY_STOPCD,	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_UNKNOWN,
122	KEY_WWW,	KEY_UNKNOWN,	KEY_VOLUMEDOWN, KEY_MUTE,
123	KEY_VOLUMEUP,	KEY_UNKNOWN,	KEY_BATTERY,	KEY_EJECTCD,
124	KEY_UNKNOWN,	KEY_SLEEP,	KEY_PROG1, KEY_BRIGHTNESSDOWN,
125	KEY_BRIGHTNESSUP,	KEY_UNKNOWN,	KEY_KBDILLUMTOGGLE,
126	KEY_UNKNOWN,	KEY_SWITCHVIDEOMODE,	KEY_UNKNOWN, KEY_UNKNOWN,
127	KEY_SWITCHVIDEOMODE,	KEY_UNKNOWN,	KEY_UNKNOWN, KEY_PROG2,
128	KEY_UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
129	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
130	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
131	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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,
138	KEY_PROG3
139};
140
141
142static struct key_entry *dell_wmi_keymap = dell_legacy_wmi_keymap;
143
144static struct input_dev *dell_wmi_input_dev;
145
146static struct key_entry *dell_wmi_get_entry_by_scancode(unsigned int code)
147{
148	struct key_entry *key;
149
150	for (key = dell_wmi_keymap; key->type != KE_END; key++)
151		if (code == key->code)
152			return key;
153
154	return NULL;
155}
156
157static struct key_entry *dell_wmi_get_entry_by_keycode(unsigned int keycode)
158{
159	struct key_entry *key;
160
161	for (key = dell_wmi_keymap; key->type != KE_END; key++)
162		if (key->type == KE_KEY && keycode == key->keycode)
163			return key;
164
165	return NULL;
166}
167
168static int dell_wmi_getkeycode(struct input_dev *dev,
169				unsigned int scancode, unsigned int *keycode)
170{
171	struct key_entry *key = dell_wmi_get_entry_by_scancode(scancode);
172
173	if (key && key->type == KE_KEY) {
174		*keycode = key->keycode;
175		return 0;
176	}
177
178	return -EINVAL;
179}
180
181static int dell_wmi_setkeycode(struct input_dev *dev,
182				unsigned int scancode, unsigned int keycode)
183{
184	struct key_entry *key;
185	unsigned int old_keycode;
186
187	key = dell_wmi_get_entry_by_scancode(scancode);
188	if (key && key->type == KE_KEY) {
189		old_keycode = key->keycode;
190		key->keycode = keycode;
191		set_bit(keycode, dev->keybit);
192		if (!dell_wmi_get_entry_by_keycode(old_keycode))
193			clear_bit(old_keycode, dev->keybit);
194		return 0;
195	}
196	return -EINVAL;
197}
198
199static void dell_wmi_notify(u32 value, void *context)
200{
201	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
202	static struct key_entry *key;
203	union acpi_object *obj;
204	acpi_status status;
205
206	status = wmi_get_event_data(value, &response);
207	if (status != AE_OK) {
208		printk(KERN_INFO "dell-wmi: bad event status 0x%x\n", status);
209		return;
210	}
211
212	obj = (union acpi_object *)response.pointer;
213
214	if (obj && obj->type == ACPI_TYPE_BUFFER) {
215		int reported_key;
216		u16 *buffer_entry = (u16 *)obj->buffer.pointer;
217		if (dell_new_hk_type && (buffer_entry[1] != 0x10)) {
218			printk(KERN_INFO "dell-wmi: Received unknown WMI event"
219					 " (0x%x)\n", buffer_entry[1]);
220			kfree(obj);
221			return;
222		}
223
224		if (dell_new_hk_type || buffer_entry[1] == 0x0)
225			reported_key = (int)buffer_entry[2];
226		else
227			reported_key = (int)buffer_entry[1] & 0xffff;
228
229		key = dell_wmi_get_entry_by_scancode(reported_key);
230
231		if (!key) {
232			printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n",
233				reported_key);
234		} else if ((key->keycode == KEY_BRIGHTNESSUP ||
235			    key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) {
236			/* Don't report brightness notifications that will also
237			 * come via ACPI */
238			;
239		} else {
240			input_report_key(dell_wmi_input_dev, key->keycode, 1);
241			input_sync(dell_wmi_input_dev);
242			input_report_key(dell_wmi_input_dev, key->keycode, 0);
243			input_sync(dell_wmi_input_dev);
244		}
245	}
246	kfree(obj);
247}
248
249
250static void setup_new_hk_map(const struct dmi_header *dm)
251{
252
253	int i;
254	int hotkey_num = (dm->length-4)/sizeof(struct dell_new_keymap_entry);
255	struct dell_hotkey_table *table =
256		container_of(dm, struct dell_hotkey_table, header);
257
258	dell_new_wmi_keymap = kzalloc((hotkey_num+1) *
259				      sizeof(struct key_entry), GFP_KERNEL);
260
261	for (i = 0; i < hotkey_num; i++) {
262		dell_new_wmi_keymap[i].type = KE_KEY;
263		dell_new_wmi_keymap[i].code = table->keymap[i].scancode;
264		dell_new_wmi_keymap[i].keycode =
265			(table->keymap[i].keycode > 255) ? 0 :
266			bios_to_linux_keycode[table->keymap[i].keycode];
267	}
268
269	dell_new_wmi_keymap[i].type = KE_END;
270	dell_new_wmi_keymap[i].code = 0;
271	dell_new_wmi_keymap[i].keycode = 0;
272
273	dell_wmi_keymap = dell_new_wmi_keymap;
274
275}
276
277
278static void find_hk_type(const struct dmi_header *dm, void *dummy)
279{
280
281	if ((dm->type == 0xb2) && (dm->length > 6)) {
282		dell_new_hk_type = true;
283		setup_new_hk_map(dm);
284	}
285
286}
287
288
289static int __init dell_wmi_input_setup(void)
290{
291	struct key_entry *key;
292	int err;
293
294	dell_wmi_input_dev = input_allocate_device();
295
296	if (!dell_wmi_input_dev)
297		return -ENOMEM;
298
299	dell_wmi_input_dev->name = "Dell WMI hotkeys";
300	dell_wmi_input_dev->phys = "wmi/input0";
301	dell_wmi_input_dev->id.bustype = BUS_HOST;
302	dell_wmi_input_dev->getkeycode = dell_wmi_getkeycode;
303	dell_wmi_input_dev->setkeycode = dell_wmi_setkeycode;
304
305	for (key = dell_wmi_keymap; key->type != KE_END; key++) {
306		switch (key->type) {
307		case KE_KEY:
308			set_bit(EV_KEY, dell_wmi_input_dev->evbit);
309			set_bit(key->keycode, dell_wmi_input_dev->keybit);
310			break;
311		case KE_SW:
312			set_bit(EV_SW, dell_wmi_input_dev->evbit);
313			set_bit(key->keycode, dell_wmi_input_dev->swbit);
314			break;
315		}
316	}
317
318	err = input_register_device(dell_wmi_input_dev);
319
320	if (err) {
321		input_free_device(dell_wmi_input_dev);
322		return err;
323	}
324
325	return 0;
326}
327
328static int __init dell_wmi_init(void)
329{
330	int err;
331	acpi_status status;
332
333	if (!wmi_has_guid(DELL_EVENT_GUID)) {
334		printk(KERN_WARNING "dell-wmi: No known WMI GUID found\n");
335		return -ENODEV;
336	}
337
338	dmi_walk(find_hk_type, NULL);
339	acpi_video = acpi_video_backlight_support();
340
341	err = dell_wmi_input_setup();
342	if (err) {
343		if (dell_new_hk_type)
344			kfree(dell_wmi_keymap);
345		return err;
346	}
347
348	status = wmi_install_notify_handler(DELL_EVENT_GUID,
349					 dell_wmi_notify, NULL);
350	if (ACPI_FAILURE(status)) {
351		input_unregister_device(dell_wmi_input_dev);
352		if (dell_new_hk_type)
353			kfree(dell_wmi_keymap);
354		printk(KERN_ERR
355			"dell-wmi: Unable to register notify handler - %d\n",
356			status);
357		return -ENODEV;
358	}
359
360	return 0;
361}
362
363static void __exit dell_wmi_exit(void)
364{
365	wmi_remove_notify_handler(DELL_EVENT_GUID);
366	input_unregister_device(dell_wmi_input_dev);
367	if (dell_new_hk_type)
368		kfree(dell_wmi_keymap);
369}
370
371module_init(dell_wmi_init);
372module_exit(dell_wmi_exit);
373