1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
52
53
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
63 { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } },
64 { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } },
65
66
67 { KE_KEY, 0xe007, { KEY_BATTERY } },
68
69
70
71
72 { KE_KEY, 0xe008, { KEY_WLAN } },
73
74
75
76 { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } },
77
78 { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } },
79
80
81 { KE_IGNORE, 0xe00d, { KEY_RESERVED } },
82
83
84 { KE_KEY, 0xe011, {KEY_PROG2 } },
85
86
87 { KE_IGNORE, 0xe013, { KEY_RESERVED } },
88
89 { KE_IGNORE, 0xe020, { KEY_MUTE } },
90
91
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, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
134 KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_MICMUTE,
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, 0, 0,
143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
144 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PROG3
145};
146
147static struct input_dev *dell_wmi_input_dev;
148
149static void dell_wmi_notify(u32 value, void *context)
150{
151 struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
152 union acpi_object *obj;
153 acpi_status status;
154
155 status = wmi_get_event_data(value, &response);
156 if (status != AE_OK) {
157 pr_info("bad event status 0x%x\n", status);
158 return;
159 }
160
161 obj = (union acpi_object *)response.pointer;
162
163 if (obj && obj->type == ACPI_TYPE_BUFFER) {
164 const struct key_entry *key;
165 int reported_key;
166 u16 *buffer_entry = (u16 *)obj->buffer.pointer;
167
168 if (dell_new_hk_type && (buffer_entry[1] != 0x10)) {
169 pr_info("Received unknown WMI event (0x%x)\n",
170 buffer_entry[1]);
171 kfree(obj);
172 return;
173 }
174
175 if (dell_new_hk_type || buffer_entry[1] == 0x0)
176 reported_key = (int)buffer_entry[2];
177 else
178 reported_key = (int)buffer_entry[1] & 0xffff;
179
180 key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
181 reported_key);
182 if (!key) {
183 pr_info("Unknown key %x pressed\n", reported_key);
184 } else if ((key->keycode == KEY_BRIGHTNESSUP ||
185 key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) {
186
187
188 ;
189 } else {
190 sparse_keymap_report_entry(dell_wmi_input_dev, key,
191 1, true);
192 }
193 }
194 kfree(obj);
195}
196
197static const struct key_entry * __init dell_wmi_prepare_new_keymap(void)
198{
199 int hotkey_num = (dell_bios_hotkey_table->header.length - 4) /
200 sizeof(struct dell_bios_keymap_entry);
201 struct key_entry *keymap;
202 int i;
203
204 keymap = kcalloc(hotkey_num + 1, sizeof(struct key_entry), GFP_KERNEL);
205 if (!keymap)
206 return NULL;
207
208 for (i = 0; i < hotkey_num; i++) {
209 const struct dell_bios_keymap_entry *bios_entry =
210 &dell_bios_hotkey_table->keymap[i];
211 keymap[i].type = KE_KEY;
212 keymap[i].code = bios_entry->scancode;
213 keymap[i].keycode = bios_entry->keycode < 256 ?
214 bios_to_linux_keycode[bios_entry->keycode] :
215 KEY_RESERVED;
216 }
217
218 keymap[hotkey_num].type = KE_END;
219
220 return keymap;
221}
222
223static int __init dell_wmi_input_setup(void)
224{
225 int err;
226
227 dell_wmi_input_dev = input_allocate_device();
228 if (!dell_wmi_input_dev)
229 return -ENOMEM;
230
231 dell_wmi_input_dev->name = "Dell WMI hotkeys";
232 dell_wmi_input_dev->phys = "wmi/input0";
233 dell_wmi_input_dev->id.bustype = BUS_HOST;
234
235 if (dell_new_hk_type) {
236 const struct key_entry *keymap = dell_wmi_prepare_new_keymap();
237 if (!keymap) {
238 err = -ENOMEM;
239 goto err_free_dev;
240 }
241
242 err = sparse_keymap_setup(dell_wmi_input_dev, keymap, NULL);
243
244
245
246
247
248 kfree(keymap);
249 } else {
250 err = sparse_keymap_setup(dell_wmi_input_dev,
251 dell_wmi_legacy_keymap, NULL);
252 }
253 if (err)
254 goto err_free_dev;
255
256 err = input_register_device(dell_wmi_input_dev);
257 if (err)
258 goto err_free_keymap;
259
260 return 0;
261
262 err_free_keymap:
263 sparse_keymap_free(dell_wmi_input_dev);
264 err_free_dev:
265 input_free_device(dell_wmi_input_dev);
266 return err;
267}
268
269static void dell_wmi_input_destroy(void)
270{
271 sparse_keymap_free(dell_wmi_input_dev);
272 input_unregister_device(dell_wmi_input_dev);
273}
274
275static void __init find_hk_type(const struct dmi_header *dm, void *dummy)
276{
277 if (dm->type == 0xb2 && dm->length > 6) {
278 dell_new_hk_type = true;
279 dell_bios_hotkey_table =
280 container_of(dm, struct dell_bios_hotkey_table, header);
281 }
282}
283
284static int __init dell_wmi_init(void)
285{
286 int err;
287 acpi_status status;
288
289 if (!wmi_has_guid(DELL_EVENT_GUID)) {
290 pr_warn("No known WMI GUID found\n");
291 return -ENODEV;
292 }
293
294 dmi_walk(find_hk_type, NULL);
295 acpi_video = acpi_video_backlight_support();
296
297 err = dell_wmi_input_setup();
298 if (err)
299 return err;
300
301 status = wmi_install_notify_handler(DELL_EVENT_GUID,
302 dell_wmi_notify, NULL);
303 if (ACPI_FAILURE(status)) {
304 dell_wmi_input_destroy();
305 pr_err("Unable to register notify handler - %d\n", status);
306 return -ENODEV;
307 }
308
309 return 0;
310}
311module_init(dell_wmi_init);
312
313static void __exit dell_wmi_exit(void)
314{
315 wmi_remove_notify_handler(DELL_EVENT_GUID);
316 dell_wmi_input_destroy();
317}
318module_exit(dell_wmi_exit);
319