Blame view
drivers/platform/x86/dell-wmi.c
8.81 KB
0b3f6109f
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/* * Dell WMI hotkeys * * Copyright (C) 2008 Red Hat <mjg@redhat.com> * * Portions based on wistron_btns.c: * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ |
eb8895241
|
25 |
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
0b3f6109f
|
26 27 28 |
#include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> |
5a0e3ad6a
|
29 |
#include <linux/slab.h> |
0b3f6109f
|
30 31 |
#include <linux/types.h> #include <linux/input.h> |
890a7c8e8
|
32 |
#include <linux/input/sparse-keymap.h> |
0b3f6109f
|
33 34 35 |
#include <acpi/acpi_drivers.h> #include <linux/acpi.h> #include <linux/string.h> |
5ea255972
|
36 |
#include <linux/dmi.h> |
0b3f6109f
|
37 38 39 40 41 42 |
MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); MODULE_LICENSE("GPL"); #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" |
5ea255972
|
43 |
static int acpi_video; |
0b3f6109f
|
44 |
MODULE_ALIAS("wmi:"DELL_EVENT_GUID); |
5cab00981
|
45 46 47 48 49 |
/* * Certain keys are flagged as KE_IGNORE. All of these are either * notifications (rather than requests for change) or are also sent * via the keyboard controller so should not be sent again. */ |
0b3f6109f
|
50 |
|
890a7c8e8
|
51 |
static const struct key_entry dell_wmi_legacy_keymap[] __initconst = { |
f1566f0dc
|
52 |
{ KE_IGNORE, 0x003a, { KEY_CAPSLOCK } }, |
890a7c8e8
|
53 54 |
{ KE_KEY, 0xe045, { KEY_PROG1 } }, { KE_KEY, 0xe009, { KEY_EJECTCD } }, |
5cab00981
|
55 56 |
/* These also contain the brightness level at offset 6 */ |
890a7c8e8
|
57 58 |
{ KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, |
5cab00981
|
59 60 |
/* Battery health status button */ |
890a7c8e8
|
61 |
{ KE_KEY, 0xe007, { KEY_BATTERY } }, |
5cab00981
|
62 63 64 65 |
/* This is actually for all radios. Although physically a * switch, the notification does not provide an indication of * state and so it should be reported as a key */ |
890a7c8e8
|
66 |
{ KE_KEY, 0xe008, { KEY_WLAN } }, |
5cab00981
|
67 68 69 |
/* The next device is at offset 6, the active devices are at offset 8 and the attached devices at offset 10 */ |
890a7c8e8
|
70 |
{ KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, |
5cab00981
|
71 |
|
890a7c8e8
|
72 |
{ KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } }, |
5cab00981
|
73 74 |
/* BIOS error detected */ |
890a7c8e8
|
75 |
{ KE_IGNORE, 0xe00d, { KEY_RESERVED } }, |
5cab00981
|
76 77 |
/* Wifi Catcher */ |
890a7c8e8
|
78 |
{ KE_KEY, 0xe011, {KEY_PROG2 } }, |
5cab00981
|
79 80 |
/* Ambient light sensor toggle */ |
890a7c8e8
|
81 82 83 |
{ KE_IGNORE, 0xe013, { KEY_RESERVED } }, { KE_IGNORE, 0xe020, { KEY_MUTE } }, |
f1566f0dc
|
84 85 86 87 |
/* Shortcut and audio panel keys */ { KE_IGNORE, 0xe025, { KEY_RESERVED } }, { KE_IGNORE, 0xe026, { KEY_RESERVED } }, |
890a7c8e8
|
88 89 90 91 92 93 94 |
{ KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } }, { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } }, { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } }, { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } }, { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } }, { KE_IGNORE, 0xe045, { KEY_NUMLOCK } }, { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } }, |
f1566f0dc
|
95 96 97 |
{ KE_IGNORE, 0xe0f7, { KEY_MUTE } }, { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } }, { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } }, |
890a7c8e8
|
98 |
{ KE_END, 0 } |
0b3f6109f
|
99 |
}; |
5ea255972
|
100 |
static bool dell_new_hk_type; |
890a7c8e8
|
101 |
struct dell_bios_keymap_entry { |
5ea255972
|
102 103 104 |
u16 scancode; u16 keycode; }; |
890a7c8e8
|
105 |
struct dell_bios_hotkey_table { |
5ea255972
|
106 |
struct dmi_header header; |
890a7c8e8
|
107 |
struct dell_bios_keymap_entry keymap[]; |
5ea255972
|
108 109 |
}; |
890a7c8e8
|
110 |
static const struct dell_bios_hotkey_table *dell_bios_hotkey_table; |
5ea255972
|
111 |
|
890a7c8e8
|
112 |
static const u16 bios_to_linux_keycode[256] __initconst = { |
5ea255972
|
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
KEY_MEDIA, KEY_NEXTSONG, KEY_PLAYPAUSE, KEY_PREVIOUSSONG, KEY_STOPCD, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_WWW, KEY_UNKNOWN, KEY_VOLUMEDOWN, KEY_MUTE, KEY_VOLUMEUP, KEY_UNKNOWN, KEY_BATTERY, KEY_EJECTCD, KEY_UNKNOWN, KEY_SLEEP, KEY_PROG1, KEY_BRIGHTNESSDOWN, KEY_BRIGHTNESSUP, KEY_UNKNOWN, KEY_KBDILLUMTOGGLE, KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_PROG2, KEY_UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PROG3 }; |
0b3f6109f
|
134 |
static struct input_dev *dell_wmi_input_dev; |
0b3f6109f
|
135 136 137 |
static void dell_wmi_notify(u32 value, void *context) { struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; |
0b3f6109f
|
138 |
union acpi_object *obj; |
fda11e61f
|
139 |
acpi_status status; |
0b3f6109f
|
140 |
|
fda11e61f
|
141 142 |
status = wmi_get_event_data(value, &response); if (status != AE_OK) { |
eb8895241
|
143 144 |
pr_info("bad event status 0x%x ", status); |
fda11e61f
|
145 146 |
return; } |
0b3f6109f
|
147 148 149 150 |
obj = (union acpi_object *)response.pointer; if (obj && obj->type == ACPI_TYPE_BUFFER) { |
890a7c8e8
|
151 |
const struct key_entry *key; |
5ea255972
|
152 153 |
int reported_key; u16 *buffer_entry = (u16 *)obj->buffer.pointer; |
890a7c8e8
|
154 |
|
5ea255972
|
155 |
if (dell_new_hk_type && (buffer_entry[1] != 0x10)) { |
eb8895241
|
156 157 158 |
pr_info("Received unknown WMI event (0x%x) ", buffer_entry[1]); |
a0624a90a
|
159 |
kfree(obj); |
5ea255972
|
160 161 |
return; } |
d5164dbf1
|
162 |
if (dell_new_hk_type || buffer_entry[1] == 0x0) |
5ea255972
|
163 164 165 |
reported_key = (int)buffer_entry[2]; else reported_key = (int)buffer_entry[1] & 0xffff; |
890a7c8e8
|
166 167 |
key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev, reported_key); |
5ea255972
|
168 |
if (!key) { |
eb8895241
|
169 170 |
pr_info("Unknown key %x pressed ", reported_key); |
5ea255972
|
171 172 173 174 |
} else if ((key->keycode == KEY_BRIGHTNESSUP || key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) { /* Don't report brightness notifications that will also * come via ACPI */ |
a0624a90a
|
175 |
; |
5ea255972
|
176 |
} else { |
890a7c8e8
|
177 178 |
sparse_keymap_report_entry(dell_wmi_input_dev, key, 1, true); |
5ea255972
|
179 |
} |
0b3f6109f
|
180 |
} |
3e9b988e4
|
181 |
kfree(obj); |
0b3f6109f
|
182 |
} |
890a7c8e8
|
183 |
static const struct key_entry * __init dell_wmi_prepare_new_keymap(void) |
5ea255972
|
184 |
{ |
890a7c8e8
|
185 186 187 |
int hotkey_num = (dell_bios_hotkey_table->header.length - 4) / sizeof(struct dell_bios_keymap_entry); struct key_entry *keymap; |
5ea255972
|
188 |
int i; |
5ea255972
|
189 |
|
890a7c8e8
|
190 191 192 |
keymap = kcalloc(hotkey_num + 1, sizeof(struct key_entry), GFP_KERNEL); if (!keymap) return NULL; |
5ea255972
|
193 194 |
for (i = 0; i < hotkey_num; i++) { |
890a7c8e8
|
195 196 197 198 199 200 201 |
const struct dell_bios_keymap_entry *bios_entry = &dell_bios_hotkey_table->keymap[i]; keymap[i].type = KE_KEY; keymap[i].code = bios_entry->scancode; keymap[i].keycode = bios_entry->keycode < 256 ? bios_to_linux_keycode[bios_entry->keycode] : KEY_RESERVED; |
5ea255972
|
202 |
} |
890a7c8e8
|
203 |
keymap[hotkey_num].type = KE_END; |
5ea255972
|
204 |
|
890a7c8e8
|
205 |
return keymap; |
5ea255972
|
206 |
} |
0b3f6109f
|
207 208 |
static int __init dell_wmi_input_setup(void) { |
0b3f6109f
|
209 210 211 |
int err; dell_wmi_input_dev = input_allocate_device(); |
0b3f6109f
|
212 213 214 215 216 217 |
if (!dell_wmi_input_dev) return -ENOMEM; dell_wmi_input_dev->name = "Dell WMI hotkeys"; dell_wmi_input_dev->phys = "wmi/input0"; dell_wmi_input_dev->id.bustype = BUS_HOST; |
890a7c8e8
|
218 219 220 221 222 223 |
if (dell_new_hk_type) { const struct key_entry *keymap = dell_wmi_prepare_new_keymap(); if (!keymap) { err = -ENOMEM; goto err_free_dev; |
0b3f6109f
|
224 |
} |
0b3f6109f
|
225 |
|
890a7c8e8
|
226 |
err = sparse_keymap_setup(dell_wmi_input_dev, keymap, NULL); |
0b3f6109f
|
227 |
|
890a7c8e8
|
228 229 230 231 232 233 234 235 |
/* * Sparse keymap library makes a copy of keymap so we * don't need the original one that was allocated. */ kfree(keymap); } else { err = sparse_keymap_setup(dell_wmi_input_dev, dell_wmi_legacy_keymap, NULL); |
0b3f6109f
|
236 |
} |
890a7c8e8
|
237 238 239 240 241 242 |
if (err) goto err_free_dev; err = input_register_device(dell_wmi_input_dev); if (err) goto err_free_keymap; |
0b3f6109f
|
243 244 |
return 0; |
890a7c8e8
|
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
err_free_keymap: sparse_keymap_free(dell_wmi_input_dev); err_free_dev: input_free_device(dell_wmi_input_dev); return err; } static void dell_wmi_input_destroy(void) { sparse_keymap_free(dell_wmi_input_dev); input_unregister_device(dell_wmi_input_dev); } static void __init find_hk_type(const struct dmi_header *dm, void *dummy) { if (dm->type == 0xb2 && dm->length > 6) { dell_new_hk_type = true; dell_bios_hotkey_table = container_of(dm, struct dell_bios_hotkey_table, header); } |
0b3f6109f
|
266 267 268 269 270 |
} static int __init dell_wmi_init(void) { int err; |
abb631bfe
|
271 |
acpi_status status; |
0b3f6109f
|
272 |
|
7a9568f53
|
273 |
if (!wmi_has_guid(DELL_EVENT_GUID)) { |
eb8895241
|
274 275 |
pr_warn("No known WMI GUID found "); |
1fdd407f4
|
276 277 |
return -ENODEV; } |
5ea255972
|
278 |
|
1fdd407f4
|
279 280 |
dmi_walk(find_hk_type, NULL); acpi_video = acpi_video_backlight_support(); |
0b3f6109f
|
281 |
|
1fdd407f4
|
282 |
err = dell_wmi_input_setup(); |
890a7c8e8
|
283 |
if (err) |
1fdd407f4
|
284 |
return err; |
5ea255972
|
285 |
|
abb631bfe
|
286 |
status = wmi_install_notify_handler(DELL_EVENT_GUID, |
1fdd407f4
|
287 |
dell_wmi_notify, NULL); |
abb631bfe
|
288 |
if (ACPI_FAILURE(status)) { |
890a7c8e8
|
289 |
dell_wmi_input_destroy(); |
eb8895241
|
290 291 |
pr_err("Unable to register notify handler - %d ", status); |
abb631bfe
|
292 |
return -ENODEV; |
1fdd407f4
|
293 |
} |
0b3f6109f
|
294 295 296 |
return 0; } |
890a7c8e8
|
297 |
module_init(dell_wmi_init); |
0b3f6109f
|
298 299 300 |
static void __exit dell_wmi_exit(void) { |
1fdd407f4
|
301 |
wmi_remove_notify_handler(DELL_EVENT_GUID); |
890a7c8e8
|
302 |
dell_wmi_input_destroy(); |
0b3f6109f
|
303 |
} |
0b3f6109f
|
304 |
module_exit(dell_wmi_exit); |