dell-wmi: Add keys for Dell XPS L502X
[linux-2.6.git] / drivers / platform / x86 / dell-wmi.c
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
40 MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
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
48 MODULE_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
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
110 struct dell_bios_keymap_entry {
111         u16 scancode;
112         u16 keycode;
113 };
114
115 struct dell_bios_hotkey_table {
116         struct dmi_header header;
117         struct dell_bios_keymap_entry keymap[];
118
119 };
120
121 static const struct dell_bios_hotkey_table *dell_bios_hotkey_table;
122
123 static 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
146 static struct input_dev *dell_wmi_input_dev;
147
148 static 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
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;
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 }
310 module_init(dell_wmi_init);
311
312 static void __exit dell_wmi_exit(void)
313 {
314         wmi_remove_notify_handler(DELL_EVENT_GUID);
315         dell_wmi_input_destroy();
316 }
317 module_exit(dell_wmi_exit);