Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
dell-wmi.c
Go to the documentation of this file.
1 /*
2  * Dell WMI hotkeys
3  *
4  * Copyright (C) 2008 Red Hat <[email protected]>
5  *
6  * Portions based on wistron_btns.c:
7  * Copyright (C) 2005 Miloslav Trmac <[email protected]>
8  * Copyright (C) 2005 Bernhard Rosenkraenzer <[email protected]>
9  * Copyright (C) 2005 Dmitry Torokhov <[email protected]>
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>
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 <[email protected]>");
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 
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 
113 };
114 
118 
119 };
120 
122 
123 static const u16 bios_to_linux_keycode[256] __initconst = {
124 
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 
148 static void dell_wmi_notify(u32 value, void *context)
149 {
151  union acpi_object *obj;
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 
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 
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 
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 
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 
283 static int __init dell_wmi_init(void)
284 {
285  int err;
287 
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 
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 
312 static void __exit dell_wmi_exit(void)
313 {
315  dell_wmi_input_destroy();
316 }
317 module_exit(dell_wmi_exit);