Loading...
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
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 * 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
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 /* 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
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, 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
146static struct input_dev *dell_wmi_input_dev;
147
148static 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
196static 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
222static 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
268static 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
274static 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
283static 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}
310module_init(dell_wmi_init);
311
312static void __exit dell_wmi_exit(void)
313{
314 wmi_remove_notify_handler(DELL_EVENT_GUID);
315 dell_wmi_input_destroy();
316}
317module_exit(dell_wmi_exit);
1/*
2 * Dell WMI hotkeys
3 *
4 * Copyright (C) 2008 Red Hat <mjg@redhat.com>
5 * Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com>
6 *
7 * Portions based on wistron_btns.c:
8 * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
9 * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
10 * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 */
26
27#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
28
29#include <linux/kernel.h>
30#include <linux/module.h>
31#include <linux/init.h>
32#include <linux/slab.h>
33#include <linux/types.h>
34#include <linux/input.h>
35#include <linux/input/sparse-keymap.h>
36#include <linux/acpi.h>
37#include <linux/string.h>
38#include <linux/dmi.h>
39#include <linux/wmi.h>
40#include <acpi/video.h>
41#include "dell-smbios.h"
42#include "dell-wmi-descriptor.h"
43
44MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
45MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
46MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
47MODULE_LICENSE("GPL");
48
49#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492"
50
51static bool wmi_requires_smbios_request;
52
53MODULE_ALIAS("wmi:"DELL_EVENT_GUID);
54
55struct dell_wmi_priv {
56 struct input_dev *input_dev;
57 u32 interface_version;
58};
59
60static int __init dmi_matched(const struct dmi_system_id *dmi)
61{
62 wmi_requires_smbios_request = 1;
63 return 1;
64}
65
66static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = {
67 {
68 .callback = dmi_matched,
69 .ident = "Dell Inspiron M5110",
70 .matches = {
71 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
72 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"),
73 },
74 },
75 {
76 .callback = dmi_matched,
77 .ident = "Dell Vostro V131",
78 .matches = {
79 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
80 DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"),
81 },
82 },
83 { }
84};
85
86/*
87 * Keymap for WMI events of type 0x0000
88 *
89 * Certain keys are flagged as KE_IGNORE. All of these are either
90 * notifications (rather than requests for change) or are also sent
91 * via the keyboard controller so should not be sent again.
92 */
93static const struct key_entry dell_wmi_keymap_type_0000[] = {
94 { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } },
95
96 /* Key code is followed by brightness level */
97 { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } },
98 { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } },
99
100 /* Battery health status button */
101 { KE_KEY, 0xe007, { KEY_BATTERY } },
102
103 /* Radio devices state change, key code is followed by other values */
104 { KE_IGNORE, 0xe008, { KEY_RFKILL } },
105
106 { KE_KEY, 0xe009, { KEY_EJECTCD } },
107
108 /* Key code is followed by: next, active and attached devices */
109 { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } },
110
111 /* Key code is followed by keyboard illumination level */
112 { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } },
113
114 /* BIOS error detected */
115 { KE_IGNORE, 0xe00d, { KEY_RESERVED } },
116
117 /* Battery was removed or inserted */
118 { KE_IGNORE, 0xe00e, { KEY_RESERVED } },
119
120 /* Wifi Catcher */
121 { KE_KEY, 0xe011, { KEY_WLAN } },
122
123 /* Ambient light sensor toggle */
124 { KE_IGNORE, 0xe013, { KEY_RESERVED } },
125
126 { KE_IGNORE, 0xe020, { KEY_MUTE } },
127
128 /* Unknown, defined in ACPI DSDT */
129 /* { KE_IGNORE, 0xe023, { KEY_RESERVED } }, */
130
131 /* Untested, Dell Instant Launch key on Inspiron 7520 */
132 /* { KE_IGNORE, 0xe024, { KEY_RESERVED } }, */
133
134 /* Dell Instant Launch key */
135 { KE_KEY, 0xe025, { KEY_PROG4 } },
136
137 /* Audio panel key */
138 { KE_IGNORE, 0xe026, { KEY_RESERVED } },
139
140 /* LCD Display On/Off Control key */
141 { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } },
142
143 /* Untested, Multimedia key on Dell Vostro 3560 */
144 /* { KE_IGNORE, 0xe028, { KEY_RESERVED } }, */
145
146 /* Dell Instant Launch key */
147 { KE_KEY, 0xe029, { KEY_PROG4 } },
148
149 /* Untested, Windows Mobility Center button on Inspiron 7520 */
150 /* { KE_IGNORE, 0xe02a, { KEY_RESERVED } }, */
151
152 /* Unknown, defined in ACPI DSDT */
153 /* { KE_IGNORE, 0xe02b, { KEY_RESERVED } }, */
154
155 /* Untested, Dell Audio With Preset Switch button on Inspiron 7520 */
156 /* { KE_IGNORE, 0xe02c, { KEY_RESERVED } }, */
157
158 { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } },
159 { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } },
160 { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } },
161 { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } },
162 { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } },
163
164 /* NIC Link is Up */
165 { KE_IGNORE, 0xe043, { KEY_RESERVED } },
166
167 /* NIC Link is Down */
168 { KE_IGNORE, 0xe044, { KEY_RESERVED } },
169
170 /*
171 * This entry is very suspicious!
172 * Originally Matthew Garrett created this dell-wmi driver specially for
173 * "button with a picture of a battery" which has event code 0xe045.
174 * Later Mario Limonciello from Dell told us that event code 0xe045 is
175 * reported by Num Lock and should be ignored because key is send also
176 * by keyboard controller.
177 * So for now we will ignore this event to prevent potential double
178 * Num Lock key press.
179 */
180 { KE_IGNORE, 0xe045, { KEY_NUMLOCK } },
181
182 /* Scroll lock and also going to tablet mode on portable devices */
183 { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } },
184
185 /* Untested, going from tablet mode on portable devices */
186 /* { KE_IGNORE, 0xe047, { KEY_RESERVED } }, */
187
188 /* Dell Support Center key */
189 { KE_IGNORE, 0xe06e, { KEY_RESERVED } },
190
191 { KE_IGNORE, 0xe0f7, { KEY_MUTE } },
192 { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } },
193 { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } },
194};
195
196struct dell_bios_keymap_entry {
197 u16 scancode;
198 u16 keycode;
199};
200
201struct dell_bios_hotkey_table {
202 struct dmi_header header;
203 struct dell_bios_keymap_entry keymap[];
204
205};
206
207struct dell_dmi_results {
208 int err;
209 int keymap_size;
210 struct key_entry *keymap;
211};
212
213/* Uninitialized entries here are KEY_RESERVED == 0. */
214static const u16 bios_to_linux_keycode[256] = {
215 [0] = KEY_MEDIA,
216 [1] = KEY_NEXTSONG,
217 [2] = KEY_PLAYPAUSE,
218 [3] = KEY_PREVIOUSSONG,
219 [4] = KEY_STOPCD,
220 [5] = KEY_UNKNOWN,
221 [6] = KEY_UNKNOWN,
222 [7] = KEY_UNKNOWN,
223 [8] = KEY_WWW,
224 [9] = KEY_UNKNOWN,
225 [10] = KEY_VOLUMEDOWN,
226 [11] = KEY_MUTE,
227 [12] = KEY_VOLUMEUP,
228 [13] = KEY_UNKNOWN,
229 [14] = KEY_BATTERY,
230 [15] = KEY_EJECTCD,
231 [16] = KEY_UNKNOWN,
232 [17] = KEY_SLEEP,
233 [18] = KEY_PROG1,
234 [19] = KEY_BRIGHTNESSDOWN,
235 [20] = KEY_BRIGHTNESSUP,
236 [21] = KEY_UNKNOWN,
237 [22] = KEY_KBDILLUMTOGGLE,
238 [23] = KEY_UNKNOWN,
239 [24] = KEY_SWITCHVIDEOMODE,
240 [25] = KEY_UNKNOWN,
241 [26] = KEY_UNKNOWN,
242 [27] = KEY_SWITCHVIDEOMODE,
243 [28] = KEY_UNKNOWN,
244 [29] = KEY_UNKNOWN,
245 [30] = KEY_PROG2,
246 [31] = KEY_UNKNOWN,
247 [32] = KEY_UNKNOWN,
248 [33] = KEY_UNKNOWN,
249 [34] = KEY_UNKNOWN,
250 [35] = KEY_UNKNOWN,
251 [36] = KEY_UNKNOWN,
252 [37] = KEY_UNKNOWN,
253 [38] = KEY_MICMUTE,
254 [255] = KEY_PROG3,
255};
256
257/*
258 * Keymap for WMI events of type 0x0010
259 *
260 * These are applied if the 0xB2 DMI hotkey table is present and doesn't
261 * override them.
262 */
263static const struct key_entry dell_wmi_keymap_type_0010[] = {
264 /* Mic mute */
265 { KE_KEY, 0x150, { KEY_MICMUTE } },
266
267 /* Fn-lock */
268 { KE_IGNORE, 0x151, { KEY_RESERVED } },
269
270 /* Change keyboard illumination */
271 { KE_IGNORE, 0x152, { KEY_KBDILLUMTOGGLE } },
272
273 /*
274 * Radio disable (notify only -- there is no model for which the
275 * WMI event is supposed to trigger an action).
276 */
277 { KE_IGNORE, 0x153, { KEY_RFKILL } },
278
279 /* RGB keyboard backlight control */
280 { KE_IGNORE, 0x154, { KEY_RESERVED } },
281
282 /*
283 * Stealth mode toggle. This will "disable all lights and sounds".
284 * The action is performed by the BIOS and EC; the WMI event is just
285 * a notification. On the XPS 13 9350, this is Fn+F7, and there's
286 * a BIOS setting to enable and disable the hotkey.
287 */
288 { KE_IGNORE, 0x155, { KEY_RESERVED } },
289
290 /* Rugged magnetic dock attach/detach events */
291 { KE_IGNORE, 0x156, { KEY_RESERVED } },
292 { KE_IGNORE, 0x157, { KEY_RESERVED } },
293
294 /* Rugged programmable (P1/P2/P3 keys) */
295 { KE_KEY, 0x850, { KEY_PROG1 } },
296 { KE_KEY, 0x851, { KEY_PROG2 } },
297 { KE_KEY, 0x852, { KEY_PROG3 } },
298
299};
300
301/*
302 * Keymap for WMI events of type 0x0011
303 */
304static const struct key_entry dell_wmi_keymap_type_0011[] = {
305 /* Battery unplugged */
306 { KE_IGNORE, 0xfff0, { KEY_RESERVED } },
307
308 /* Battery inserted */
309 { KE_IGNORE, 0xfff1, { KEY_RESERVED } },
310
311 /* Keyboard backlight level changed */
312 { KE_IGNORE, 0x01e1, { KEY_RESERVED } },
313 { KE_IGNORE, 0x02ea, { KEY_RESERVED } },
314 { KE_IGNORE, 0x02eb, { KEY_RESERVED } },
315 { KE_IGNORE, 0x02ec, { KEY_RESERVED } },
316 { KE_IGNORE, 0x02f6, { KEY_RESERVED } },
317};
318
319static void dell_wmi_process_key(struct wmi_device *wdev, int type, int code)
320{
321 struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
322 const struct key_entry *key;
323
324 key = sparse_keymap_entry_from_scancode(priv->input_dev,
325 (type << 16) | code);
326 if (!key) {
327 pr_info("Unknown key with type 0x%04x and code 0x%04x pressed\n",
328 type, code);
329 return;
330 }
331
332 pr_debug("Key with type 0x%04x and code 0x%04x pressed\n", type, code);
333
334 /* Don't report brightness notifications that will also come via ACPI */
335 if ((key->keycode == KEY_BRIGHTNESSUP ||
336 key->keycode == KEY_BRIGHTNESSDOWN) &&
337 acpi_video_handles_brightness_key_presses())
338 return;
339
340 if (type == 0x0000 && code == 0xe025 && !wmi_requires_smbios_request)
341 return;
342
343 if (key->keycode == KEY_KBDILLUMTOGGLE)
344 dell_laptop_call_notifier(
345 DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, NULL);
346
347 sparse_keymap_report_entry(priv->input_dev, key, 1, true);
348}
349
350static void dell_wmi_notify(struct wmi_device *wdev,
351 union acpi_object *obj)
352{
353 struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
354 u16 *buffer_entry, *buffer_end;
355 acpi_size buffer_size;
356 int len, i;
357
358 if (obj->type != ACPI_TYPE_BUFFER) {
359 pr_warn("bad response type %x\n", obj->type);
360 return;
361 }
362
363 pr_debug("Received WMI event (%*ph)\n",
364 obj->buffer.length, obj->buffer.pointer);
365
366 buffer_entry = (u16 *)obj->buffer.pointer;
367 buffer_size = obj->buffer.length/2;
368 buffer_end = buffer_entry + buffer_size;
369
370 /*
371 * BIOS/ACPI on devices with WMI interface version 0 does not clear
372 * buffer before filling it. So next time when BIOS/ACPI send WMI event
373 * which is smaller as previous then it contains garbage in buffer from
374 * previous event.
375 *
376 * BIOS/ACPI on devices with WMI interface version 1 clears buffer and
377 * sometimes send more events in buffer at one call.
378 *
379 * So to prevent reading garbage from buffer we will process only first
380 * one event on devices with WMI interface version 0.
381 */
382 if (priv->interface_version == 0 && buffer_entry < buffer_end)
383 if (buffer_end > buffer_entry + buffer_entry[0] + 1)
384 buffer_end = buffer_entry + buffer_entry[0] + 1;
385
386 while (buffer_entry < buffer_end) {
387
388 len = buffer_entry[0];
389 if (len == 0)
390 break;
391
392 len++;
393
394 if (buffer_entry + len > buffer_end) {
395 pr_warn("Invalid length of WMI event\n");
396 break;
397 }
398
399 pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry);
400
401 switch (buffer_entry[1]) {
402 case 0x0000: /* One key pressed or event occurred */
403 if (len > 2)
404 dell_wmi_process_key(wdev, 0x0000,
405 buffer_entry[2]);
406 /* Other entries could contain additional information */
407 break;
408 case 0x0010: /* Sequence of keys pressed */
409 case 0x0011: /* Sequence of events occurred */
410 for (i = 2; i < len; ++i)
411 dell_wmi_process_key(wdev, buffer_entry[1],
412 buffer_entry[i]);
413 break;
414 default: /* Unknown event */
415 pr_info("Unknown WMI event type 0x%x\n",
416 (int)buffer_entry[1]);
417 break;
418 }
419
420 buffer_entry += len;
421
422 }
423
424}
425
426static bool have_scancode(u32 scancode, const struct key_entry *keymap, int len)
427{
428 int i;
429
430 for (i = 0; i < len; i++)
431 if (keymap[i].code == scancode)
432 return true;
433
434 return false;
435}
436
437static void handle_dmi_entry(const struct dmi_header *dm, void *opaque)
438{
439 struct dell_dmi_results *results = opaque;
440 struct dell_bios_hotkey_table *table;
441 int hotkey_num, i, pos = 0;
442 struct key_entry *keymap;
443
444 if (results->err || results->keymap)
445 return; /* We already found the hotkey table. */
446
447 /* The Dell hotkey table is type 0xB2. Scan until we find it. */
448 if (dm->type != 0xb2)
449 return;
450
451 table = container_of(dm, struct dell_bios_hotkey_table, header);
452
453 hotkey_num = (table->header.length -
454 sizeof(struct dell_bios_hotkey_table)) /
455 sizeof(struct dell_bios_keymap_entry);
456 if (hotkey_num < 1) {
457 /*
458 * Historically, dell-wmi would ignore a DMI entry of
459 * fewer than 7 bytes. Sizes between 4 and 8 bytes are
460 * nonsensical (both the header and all entries are 4
461 * bytes), so we approximate the old behavior by
462 * ignoring tables with fewer than one entry.
463 */
464 return;
465 }
466
467 keymap = kcalloc(hotkey_num, sizeof(struct key_entry), GFP_KERNEL);
468 if (!keymap) {
469 results->err = -ENOMEM;
470 return;
471 }
472
473 for (i = 0; i < hotkey_num; i++) {
474 const struct dell_bios_keymap_entry *bios_entry =
475 &table->keymap[i];
476
477 /* Uninitialized entries are 0 aka KEY_RESERVED. */
478 u16 keycode = (bios_entry->keycode <
479 ARRAY_SIZE(bios_to_linux_keycode)) ?
480 bios_to_linux_keycode[bios_entry->keycode] :
481 KEY_RESERVED;
482
483 /*
484 * Log if we find an entry in the DMI table that we don't
485 * understand. If this happens, we should figure out what
486 * the entry means and add it to bios_to_linux_keycode.
487 */
488 if (keycode == KEY_RESERVED) {
489 pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n",
490 bios_entry->scancode, bios_entry->keycode);
491 continue;
492 }
493
494 if (keycode == KEY_KBDILLUMTOGGLE)
495 keymap[pos].type = KE_IGNORE;
496 else
497 keymap[pos].type = KE_KEY;
498 keymap[pos].code = bios_entry->scancode;
499 keymap[pos].keycode = keycode;
500
501 pos++;
502 }
503
504 results->keymap = keymap;
505 results->keymap_size = pos;
506}
507
508static int dell_wmi_input_setup(struct wmi_device *wdev)
509{
510 struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
511 struct dell_dmi_results dmi_results = {};
512 struct key_entry *keymap;
513 int err, i, pos = 0;
514
515 priv->input_dev = input_allocate_device();
516 if (!priv->input_dev)
517 return -ENOMEM;
518
519 priv->input_dev->name = "Dell WMI hotkeys";
520 priv->input_dev->id.bustype = BUS_HOST;
521 priv->input_dev->dev.parent = &wdev->dev;
522
523 if (dmi_walk(handle_dmi_entry, &dmi_results)) {
524 /*
525 * Historically, dell-wmi ignored dmi_walk errors. A failure
526 * is certainly surprising, but it probably just indicates
527 * a very old laptop.
528 */
529 pr_warn("no DMI; using the old-style hotkey interface\n");
530 }
531
532 if (dmi_results.err) {
533 err = dmi_results.err;
534 goto err_free_dev;
535 }
536
537 keymap = kcalloc(dmi_results.keymap_size +
538 ARRAY_SIZE(dell_wmi_keymap_type_0000) +
539 ARRAY_SIZE(dell_wmi_keymap_type_0010) +
540 ARRAY_SIZE(dell_wmi_keymap_type_0011) +
541 1,
542 sizeof(struct key_entry), GFP_KERNEL);
543 if (!keymap) {
544 kfree(dmi_results.keymap);
545 err = -ENOMEM;
546 goto err_free_dev;
547 }
548
549 /* Append table with events of type 0x0010 which comes from DMI */
550 for (i = 0; i < dmi_results.keymap_size; i++) {
551 keymap[pos] = dmi_results.keymap[i];
552 keymap[pos].code |= (0x0010 << 16);
553 pos++;
554 }
555
556 kfree(dmi_results.keymap);
557
558 /* Append table with extra events of type 0x0010 which are not in DMI */
559 for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0010); i++) {
560 const struct key_entry *entry = &dell_wmi_keymap_type_0010[i];
561
562 /*
563 * Check if we've already found this scancode. This takes
564 * quadratic time, but it doesn't matter unless the list
565 * of extra keys gets very long.
566 */
567 if (dmi_results.keymap_size &&
568 have_scancode(entry->code | (0x0010 << 16),
569 keymap, dmi_results.keymap_size)
570 )
571 continue;
572
573 keymap[pos] = *entry;
574 keymap[pos].code |= (0x0010 << 16);
575 pos++;
576 }
577
578 /* Append table with events of type 0x0011 */
579 for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0011); i++) {
580 keymap[pos] = dell_wmi_keymap_type_0011[i];
581 keymap[pos].code |= (0x0011 << 16);
582 pos++;
583 }
584
585 /*
586 * Now append also table with "legacy" events of type 0x0000. Some of
587 * them are reported also on laptops which have scancodes in DMI.
588 */
589 for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0000); i++) {
590 keymap[pos] = dell_wmi_keymap_type_0000[i];
591 pos++;
592 }
593
594 keymap[pos].type = KE_END;
595
596 err = sparse_keymap_setup(priv->input_dev, keymap, NULL);
597 /*
598 * Sparse keymap library makes a copy of keymap so we don't need the
599 * original one that was allocated.
600 */
601 kfree(keymap);
602 if (err)
603 goto err_free_dev;
604
605 err = input_register_device(priv->input_dev);
606 if (err)
607 goto err_free_dev;
608
609 return 0;
610
611 err_free_dev:
612 input_free_device(priv->input_dev);
613 return err;
614}
615
616static void dell_wmi_input_destroy(struct wmi_device *wdev)
617{
618 struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
619
620 input_unregister_device(priv->input_dev);
621}
622
623/*
624 * According to Dell SMBIOS documentation:
625 *
626 * 17 3 Application Program Registration
627 *
628 * cbArg1 Application ID 1 = 0x00010000
629 * cbArg2 Application ID 2
630 * QUICKSET/DCP = 0x51534554 "QSET"
631 * ALS Driver = 0x416c7353 "AlsS"
632 * Latitude ON = 0x4c6f6e52 "LonR"
633 * cbArg3 Application version or revision number
634 * cbArg4 0 = Unregister application
635 * 1 = Register application
636 * cbRes1 Standard return codes (0, -1, -2)
637 */
638
639static int dell_wmi_events_set_enabled(bool enable)
640{
641 struct calling_interface_buffer *buffer;
642 int ret;
643
644 buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL);
645 if (!buffer)
646 return -ENOMEM;
647 buffer->cmd_class = CLASS_INFO;
648 buffer->cmd_select = SELECT_APP_REGISTRATION;
649 buffer->input[0] = 0x10000;
650 buffer->input[1] = 0x51534554;
651 buffer->input[3] = enable;
652 ret = dell_smbios_call(buffer);
653 if (ret == 0)
654 ret = buffer->output[0];
655 kfree(buffer);
656
657 return dell_smbios_error(ret);
658}
659
660static int dell_wmi_probe(struct wmi_device *wdev)
661{
662 struct dell_wmi_priv *priv;
663 int ret;
664
665 ret = dell_wmi_get_descriptor_valid();
666 if (ret)
667 return ret;
668
669 priv = devm_kzalloc(
670 &wdev->dev, sizeof(struct dell_wmi_priv), GFP_KERNEL);
671 if (!priv)
672 return -ENOMEM;
673 dev_set_drvdata(&wdev->dev, priv);
674
675 if (!dell_wmi_get_interface_version(&priv->interface_version))
676 return -EPROBE_DEFER;
677
678 return dell_wmi_input_setup(wdev);
679}
680
681static int dell_wmi_remove(struct wmi_device *wdev)
682{
683 dell_wmi_input_destroy(wdev);
684 return 0;
685}
686static const struct wmi_device_id dell_wmi_id_table[] = {
687 { .guid_string = DELL_EVENT_GUID },
688 { },
689};
690
691static struct wmi_driver dell_wmi_driver = {
692 .driver = {
693 .name = "dell-wmi",
694 },
695 .id_table = dell_wmi_id_table,
696 .probe = dell_wmi_probe,
697 .remove = dell_wmi_remove,
698 .notify = dell_wmi_notify,
699};
700
701static int __init dell_wmi_init(void)
702{
703 int err;
704
705 dmi_check_system(dell_wmi_smbios_list);
706
707 if (wmi_requires_smbios_request) {
708 err = dell_wmi_events_set_enabled(true);
709 if (err) {
710 pr_err("Failed to enable WMI events\n");
711 return err;
712 }
713 }
714
715 return wmi_driver_register(&dell_wmi_driver);
716}
717late_initcall(dell_wmi_init);
718
719static void __exit dell_wmi_exit(void)
720{
721 if (wmi_requires_smbios_request)
722 dell_wmi_events_set_enabled(false);
723
724 wmi_driver_unregister(&dell_wmi_driver);
725}
726module_exit(dell_wmi_exit);