Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wirel...
[linux-drm-fsl-dcu.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,    KEY_UNKNOWN,    KEY_UNKNOWN,    KEY_UNKNOWN,
134         KEY_UNKNOWN,    KEY_UNKNOWN,    KEY_UNKNOWN,    KEY_MICMUTE,
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, 0, 0,
143         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
144         0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PROG3
145 };
146
147 static struct input_dev *dell_wmi_input_dev;
148
149 static void dell_wmi_notify(u32 value, void *context)
150 {
151         struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
152         union acpi_object *obj;
153         acpi_status status;
154
155         status = wmi_get_event_data(value, &response);
156         if (status != AE_OK) {
157                 pr_info("bad event status 0x%x\n", status);
158                 return;
159         }
160
161         obj = (union acpi_object *)response.pointer;
162
163         if (obj && obj->type == ACPI_TYPE_BUFFER) {
164                 const struct key_entry *key;
165                 int reported_key;
166                 u16 *buffer_entry = (u16 *)obj->buffer.pointer;
167
168                 if (dell_new_hk_type && (buffer_entry[1] != 0x10)) {
169                         pr_info("Received unknown WMI event (0x%x)\n",
170                                 buffer_entry[1]);
171                         kfree(obj);
172                         return;
173                 }
174
175                 if (dell_new_hk_type || buffer_entry[1] == 0x0)
176                         reported_key = (int)buffer_entry[2];
177                 else
178                         reported_key = (int)buffer_entry[1] & 0xffff;
179
180                 key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
181                                                         reported_key);
182                 if (!key) {
183                         pr_info("Unknown key %x pressed\n", reported_key);
184                 } else if ((key->keycode == KEY_BRIGHTNESSUP ||
185                             key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) {
186                         /* Don't report brightness notifications that will also
187                          * come via ACPI */
188                         ;
189                 } else {
190                         sparse_keymap_report_entry(dell_wmi_input_dev, key,
191                                                    1, true);
192                 }
193         }
194         kfree(obj);
195 }
196
197 static const struct key_entry * __init dell_wmi_prepare_new_keymap(void)
198 {
199         int hotkey_num = (dell_bios_hotkey_table->header.length - 4) /
200                                 sizeof(struct dell_bios_keymap_entry);
201         struct key_entry *keymap;
202         int i;
203
204         keymap = kcalloc(hotkey_num + 1, sizeof(struct key_entry), GFP_KERNEL);
205         if (!keymap)
206                 return NULL;
207
208         for (i = 0; i < hotkey_num; i++) {
209                 const struct dell_bios_keymap_entry *bios_entry =
210                                         &dell_bios_hotkey_table->keymap[i];
211                 keymap[i].type = KE_KEY;
212                 keymap[i].code = bios_entry->scancode;
213                 keymap[i].keycode = bios_entry->keycode < 256 ?
214                                     bios_to_linux_keycode[bios_entry->keycode] :
215                                     KEY_RESERVED;
216         }
217
218         keymap[hotkey_num].type = KE_END;
219
220         return keymap;
221 }
222
223 static int __init dell_wmi_input_setup(void)
224 {
225         int err;
226
227         dell_wmi_input_dev = input_allocate_device();
228         if (!dell_wmi_input_dev)
229                 return -ENOMEM;
230
231         dell_wmi_input_dev->name = "Dell WMI hotkeys";
232         dell_wmi_input_dev->phys = "wmi/input0";
233         dell_wmi_input_dev->id.bustype = BUS_HOST;
234
235         if (dell_new_hk_type) {
236                 const struct key_entry *keymap = dell_wmi_prepare_new_keymap();
237                 if (!keymap) {
238                         err = -ENOMEM;
239                         goto err_free_dev;
240                 }
241
242                 err = sparse_keymap_setup(dell_wmi_input_dev, keymap, NULL);
243
244                 /*
245                  * Sparse keymap library makes a copy of keymap so we
246                  * don't need the original one that was allocated.
247                  */
248                 kfree(keymap);
249         } else {
250                 err = sparse_keymap_setup(dell_wmi_input_dev,
251                                           dell_wmi_legacy_keymap, NULL);
252         }
253         if (err)
254                 goto err_free_dev;
255
256         err = input_register_device(dell_wmi_input_dev);
257         if (err)
258                 goto err_free_keymap;
259
260         return 0;
261
262  err_free_keymap:
263         sparse_keymap_free(dell_wmi_input_dev);
264  err_free_dev:
265         input_free_device(dell_wmi_input_dev);
266         return err;
267 }
268
269 static void dell_wmi_input_destroy(void)
270 {
271         sparse_keymap_free(dell_wmi_input_dev);
272         input_unregister_device(dell_wmi_input_dev);
273 }
274
275 static void __init find_hk_type(const struct dmi_header *dm, void *dummy)
276 {
277         if (dm->type == 0xb2 && dm->length > 6) {
278                 dell_new_hk_type = true;
279                 dell_bios_hotkey_table =
280                         container_of(dm, struct dell_bios_hotkey_table, header);
281         }
282 }
283
284 static int __init dell_wmi_init(void)
285 {
286         int err;
287         acpi_status status;
288
289         if (!wmi_has_guid(DELL_EVENT_GUID)) {
290                 pr_warn("No known WMI GUID found\n");
291                 return -ENODEV;
292         }
293
294         dmi_walk(find_hk_type, NULL);
295         acpi_video = acpi_video_backlight_support();
296
297         err = dell_wmi_input_setup();
298         if (err)
299                 return err;
300
301         status = wmi_install_notify_handler(DELL_EVENT_GUID,
302                                          dell_wmi_notify, NULL);
303         if (ACPI_FAILURE(status)) {
304                 dell_wmi_input_destroy();
305                 pr_err("Unable to register notify handler - %d\n", status);
306                 return -ENODEV;
307         }
308
309         return 0;
310 }
311 module_init(dell_wmi_init);
312
313 static void __exit dell_wmi_exit(void)
314 {
315         wmi_remove_notify_handler(DELL_EVENT_GUID);
316         dell_wmi_input_destroy();
317 }
318 module_exit(dell_wmi_exit);