aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorThomas Renninger <trenn@suse.de>2009-12-10 08:18:13 -0500
committerLen Brown <len.brown@intel.com>2009-12-16 12:40:53 -0500
commitd12d8baff927a31b7e13b72ed9549be6f296a6ef (patch)
tree7134afdc75a228c14e0ad40d07f452317bf4a407 /drivers
parent8bea8672edfca7ec5f661cafb218f1205863b343 (diff)
X86 drivers: Introduce msi-wmi driver
This driver serves backlight (including switching) and volume up/down keys for MSI machines providing a specific wmi interface: 551A1F84-FBDD-4125-91DB-3EA8F44F1D45 B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2 Signed-off-by: Thomas Renninger <trenn@suse.de> CC: Carlos Corbacho <carlos@strangeworlds.co.uk> CC: Matthew Garrett <mjg59@srcf.ucam.org> Tested-by: Matt Chen <machen@novell.com> Reviewed-by: Anisse Astier <anisse@astier.eu> Signed-off-by: Len Brown <len.brown@intel.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/platform/x86/Kconfig10
-rw-r--r--drivers/platform/x86/Makefile1
-rw-r--r--drivers/platform/x86/msi-wmi.c369
3 files changed, 380 insertions, 0 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 55ca39dea42e..98ec6bd9226e 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -365,6 +365,16 @@ config ACPI_WMI
365 It is safe to enable this driver even if your DSDT doesn't define 365 It is safe to enable this driver even if your DSDT doesn't define
366 any ACPI-WMI devices. 366 any ACPI-WMI devices.
367 367
368config MSI_WMI
369 tristate "MSI WMI extras"
370 depends on ACPI_WMI
371 depends on INPUT
372 help
373 Say Y here if you want to support WMI-based hotkeys on MSI laptops.
374
375 To compile this driver as a module, choose M here: the module will
376 be called msi-wmi.
377
368config ACPI_ASUS 378config ACPI_ASUS
369 tristate "ASUS/Medion Laptop Extras (DEPRECATED)" 379 tristate "ASUS/Medion Laptop Extras (DEPRECATED)"
370 depends on ACPI 380 depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index d1c16210a512..13aa37310f33 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o
18obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o 18obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o
19obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o 19obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o
20obj-$(CONFIG_ACPI_WMI) += wmi.o 20obj-$(CONFIG_ACPI_WMI) += wmi.o
21obj-$(CONFIG_MSI_WMI) += msi-wmi.o
21obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o 22obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o
22obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o 23obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o
23obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o 24obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c
new file mode 100644
index 000000000000..7e0dab659752
--- /dev/null
+++ b/drivers/platform/x86/msi-wmi.c
@@ -0,0 +1,369 @@
1/*
2 * MSI WMI hotkeys
3 *
4 * Copyright (C) 2009 Novell <trenn@suse.de>
5 *
6 * Most stuff taken over from hp-wmi
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 */
22
23
24
25#include <linux/kernel.h>
26#include <linux/module.h>
27#include <linux/init.h>
28#include <linux/types.h>
29#include <linux/input.h>
30#include <acpi/acpi_drivers.h>
31#include <linux/acpi.h>
32#include <linux/string.h>
33#include <linux/hrtimer.h>
34#include <linux/backlight.h>
35
36MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>");
37MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver");
38MODULE_LICENSE("GPL");
39
40static int debug;
41module_param(debug, int, 0);
42MODULE_PARM_DESC(debug, "Set this to 1 to let the driver be more verbose");
43
44MODULE_ALIAS("wmi:551A1F84-FBDD-4125-91DB-3EA8F44F1D45");
45MODULE_ALIAS("wmi:B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2");
46
47/* Temporary workaround until the WMI sysfs interface goes in
48 { "svn", DMI_SYS_VENDOR },
49 { "pn", DMI_PRODUCT_NAME },
50 { "pvr", DMI_PRODUCT_VERSION },
51 { "rvn", DMI_BOARD_VENDOR },
52 { "rn", DMI_BOARD_NAME },
53*/
54
55MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-6638:*");
56
57#define DRV_NAME "msi-wmi"
58#define DRV_PFX DRV_NAME ": "
59
60#define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45"
61#define MSIWMI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"
62
63#define dprintk(msg...) do { \
64 if (debug) \
65 printk(KERN_INFO DRV_PFX msg); \
66 } while (0)
67
68struct key_entry {
69 char type; /* See KE_* below */
70 u16 code;
71 u16 keycode;
72 int instance;
73 ktime_t last_pressed;
74};
75
76/*
77 * KE_KEY the only used key type, but keep this, others might also
78 * show up in the future. Compare with hp-wmi.c
79 */
80enum { KE_KEY, KE_END };
81
82static struct key_entry msi_wmi_keymap[] = {
83 { KE_KEY, 0xd0, KEY_BRIGHTNESSUP, 0, {0, } },
84 { KE_KEY, 0xd1, KEY_BRIGHTNESSDOWN, 1, {0, } },
85 { KE_KEY, 0xd2, KEY_VOLUMEUP, 2, {0, } },
86 { KE_KEY, 0xd3, KEY_VOLUMEDOWN, 3, {0, } },
87 { KE_END, 0}
88};
89
90struct backlight_device *backlight;
91
92static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF };
93
94static struct input_dev *msi_wmi_input_dev;
95
96static int msi_wmi_query_block(int instance, int *ret)
97{
98 acpi_status status;
99 union acpi_object *obj;
100
101 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
102
103 status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output);
104
105 obj = output.pointer;
106
107 if (!obj || obj->type != ACPI_TYPE_INTEGER) {
108 if (obj) {
109 printk(KERN_ERR DRV_PFX "query block returned object "
110 "type: %d - buffer length:%d\n", obj->type,
111 obj->type == ACPI_TYPE_BUFFER ?
112 obj->buffer.length : 0);
113 }
114 kfree(obj);
115 return -EINVAL;
116 }
117 *ret = obj->integer.value;
118 kfree(obj);
119 return 0;
120}
121
122static int msi_wmi_set_block(int instance, int value)
123{
124 acpi_status status;
125
126 struct acpi_buffer input = { sizeof(int), &value };
127
128 dprintk("Going to set block of instance: %d - value: %d\n",
129 instance, value);
130
131 status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input);
132
133 return ACPI_SUCCESS(status) ? 0 : 1;
134}
135
136static int bl_get(struct backlight_device *bd)
137{
138 int level, err, ret = 0;
139
140 /* Instance 1 is "get backlight", cmp with DSDT */
141 err = msi_wmi_query_block(1, &ret);
142 if (err)
143 printk(KERN_ERR DRV_PFX "Could not query backlight: %d\n", err);
144 dprintk("Get: Query block returned: %d\n", ret);
145 for (level = 0; level < ARRAY_SIZE(backlight_map); level++) {
146 if (backlight_map[level] == ret) {
147 dprintk("Current backlight level: 0x%X - index: %d\n",
148 backlight_map[level], level);
149 break;
150 }
151 }
152 if (level == ARRAY_SIZE(backlight_map)) {
153 printk(KERN_ERR DRV_PFX "get: Invalid brightness value: 0x%X\n",
154 ret);
155 return -EINVAL;
156 }
157 return level;
158}
159
160static int bl_set_status(struct backlight_device *bd)
161{
162 int bright = bd->props.brightness;
163 if (bright >= ARRAY_SIZE(backlight_map) || bright < 0)
164 return -EINVAL;
165
166 /* Instance 0 is "set backlight" */
167 return msi_wmi_set_block(0, backlight_map[bright]);
168}
169
170static struct backlight_ops msi_backlight_ops = {
171 .get_brightness = bl_get,
172 .update_status = bl_set_status,
173};
174
175static struct key_entry *msi_wmi_get_entry_by_scancode(int code)
176{
177 struct key_entry *key;
178
179 for (key = msi_wmi_keymap; key->type != KE_END; key++)
180 if (code == key->code)
181 return key;
182
183 return NULL;
184}
185
186static struct key_entry *msi_wmi_get_entry_by_keycode(int keycode)
187{
188 struct key_entry *key;
189
190 for (key = msi_wmi_keymap; key->type != KE_END; key++)
191 if (key->type == KE_KEY && keycode == key->keycode)
192 return key;
193
194 return NULL;
195}
196
197static int msi_wmi_getkeycode(struct input_dev *dev, int scancode, int *keycode)
198{
199 struct key_entry *key = msi_wmi_get_entry_by_scancode(scancode);
200
201 if (key && key->type == KE_KEY) {
202 *keycode = key->keycode;
203 return 0;
204 }
205
206 return -EINVAL;
207}
208
209static int msi_wmi_setkeycode(struct input_dev *dev, int scancode, int keycode)
210{
211 struct key_entry *key;
212 int old_keycode;
213
214 if (keycode < 0 || keycode > KEY_MAX)
215 return -EINVAL;
216
217 key = msi_wmi_get_entry_by_scancode(scancode);
218 if (key && key->type == KE_KEY) {
219 old_keycode = key->keycode;
220 key->keycode = keycode;
221 set_bit(keycode, dev->keybit);
222 if (!msi_wmi_get_entry_by_keycode(old_keycode))
223 clear_bit(old_keycode, dev->keybit);
224 return 0;
225 }
226
227 return -EINVAL;
228}
229
230static void msi_wmi_notify(u32 value, void *context)
231{
232 struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
233 static struct key_entry *key;
234 union acpi_object *obj;
235 ktime_t cur;
236
237 wmi_get_event_data(value, &response);
238
239 obj = (union acpi_object *)response.pointer;
240
241 if (obj && obj->type == ACPI_TYPE_INTEGER) {
242 int eventcode = obj->integer.value;
243 dprintk("Eventcode: 0x%x\n", eventcode);
244 key = msi_wmi_get_entry_by_scancode(eventcode);
245 if (key) {
246 cur = ktime_get_real();
247 /* Ignore event if the same event happened in a 50 ms
248 timeframe -> Key press may result in 10-20 GPEs */
249 if (ktime_to_us(ktime_sub(cur, key->last_pressed))
250 < 1000 * 50) {
251 dprintk("Suppressed key event 0x%X - "
252 "Last press was %lld us ago\n",
253 key->code,
254 ktime_to_us(ktime_sub(cur,
255 key->last_pressed)));
256 return;
257 }
258 key->last_pressed = cur;
259
260 switch (key->type) {
261 case KE_KEY:
262 /* Brightness is served via acpi video driver */
263 if (!backlight &&
264 (key->keycode == KEY_BRIGHTNESSUP ||
265 key->keycode == KEY_BRIGHTNESSDOWN))
266 break;
267
268 dprintk("Send key: 0x%X - "
269 "Input layer keycode: %d\n", key->code,
270 key->keycode);
271 input_report_key(msi_wmi_input_dev,
272 key->keycode, 1);
273 input_sync(msi_wmi_input_dev);
274 input_report_key(msi_wmi_input_dev,
275 key->keycode, 0);
276 input_sync(msi_wmi_input_dev);
277 break;
278 }
279 } else
280 printk(KERN_INFO "Unknown key pressed - %x\n",
281 eventcode);
282 } else
283 printk(KERN_INFO DRV_PFX "Unknown event received\n");
284 kfree(response.pointer);
285}
286
287static int __init msi_wmi_input_setup(void)
288{
289 struct key_entry *key;
290 int err;
291
292 msi_wmi_input_dev = input_allocate_device();
293
294 msi_wmi_input_dev->name = "MSI WMI hotkeys";
295 msi_wmi_input_dev->phys = "wmi/input0";
296 msi_wmi_input_dev->id.bustype = BUS_HOST;
297 msi_wmi_input_dev->getkeycode = msi_wmi_getkeycode;
298 msi_wmi_input_dev->setkeycode = msi_wmi_setkeycode;
299
300 for (key = msi_wmi_keymap; key->type != KE_END; key++) {
301 switch (key->type) {
302 case KE_KEY:
303 set_bit(EV_KEY, msi_wmi_input_dev->evbit);
304 set_bit(key->keycode, msi_wmi_input_dev->keybit);
305 break;
306 }
307 }
308
309 err = input_register_device(msi_wmi_input_dev);
310
311 if (err) {
312 input_free_device(msi_wmi_input_dev);
313 return err;
314 }
315
316 return 0;
317}
318
319static int __init msi_wmi_init(void)
320{
321 int err;
322
323 if (wmi_has_guid(MSIWMI_EVENT_GUID)) {
324 err = wmi_install_notify_handler(MSIWMI_EVENT_GUID,
325 msi_wmi_notify, NULL);
326 if (err)
327 return -EINVAL;
328
329 err = msi_wmi_input_setup();
330 if (err) {
331 wmi_remove_notify_handler(MSIWMI_EVENT_GUID);
332 return -EINVAL;
333 }
334
335 if (!acpi_video_backlight_support()) {
336 backlight = backlight_device_register(DRV_NAME,
337 NULL, NULL, &msi_backlight_ops);
338 if (IS_ERR(backlight)) {
339 wmi_remove_notify_handler(MSIWMI_EVENT_GUID);
340 input_unregister_device(msi_wmi_input_dev);
341 return -EINVAL;
342 }
343
344 backlight->props.max_brightness = ARRAY_SIZE(backlight_map) - 1;
345 err = bl_get(NULL);
346 if (err < 0) {
347 wmi_remove_notify_handler(MSIWMI_EVENT_GUID);
348 input_unregister_device(msi_wmi_input_dev);
349 backlight_device_unregister(backlight);
350 return -EINVAL;
351 }
352 backlight->props.brightness = err;
353 }
354 }
355 printk(KERN_INFO DRV_PFX "Event handler installed\n");
356 return 0;
357}
358
359static void __exit msi_wmi_exit(void)
360{
361 if (wmi_has_guid(MSIWMI_EVENT_GUID)) {
362 wmi_remove_notify_handler(MSIWMI_EVENT_GUID);
363 input_unregister_device(msi_wmi_input_dev);
364 backlight_device_unregister(backlight);
365 }
366}
367
368module_init(msi_wmi_init);
369module_exit(msi_wmi_exit);