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 
40 MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
41 MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
42 MODULE_LICENSE("GPL");
43 
44 #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492"
45 
46 static int acpi_video;
47 
48 MODULE_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 
56 static 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 
108 static bool dell_new_hk_type;
109 
110 struct dell_bios_keymap_entry {
111 	u16 scancode;
112 	u16 keycode;
113 };
114 
115 struct dell_bios_hotkey_table {
116 	struct dmi_header header;
117 	struct dell_bios_keymap_entry keymap[];
118 
119 };
120 
121 static const struct dell_bios_hotkey_table *dell_bios_hotkey_table;
122 
123 static 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 
146 static struct input_dev *dell_wmi_input_dev;
147 
dell_wmi_notify(u32 value,void * context)148 static 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 
dell_wmi_prepare_new_keymap(void)196 static 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 
dell_wmi_input_setup(void)222 static 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 
dell_wmi_input_destroy(void)268 static 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 
find_hk_type(const struct dmi_header * dm,void * dummy)274 static 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 
dell_wmi_init(void)283 static 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 }
310 module_init(dell_wmi_init);
311 
dell_wmi_exit(void)312 static void __exit dell_wmi_exit(void)
313 {
314 	wmi_remove_notify_handler(DELL_EVENT_GUID);
315 	dell_wmi_input_destroy();
316 }
317 module_exit(dell_wmi_exit);
318