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