aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/platform
diff options
context:
space:
mode:
authorLen Brown <len.brown@intel.com>2009-12-16 13:57:12 -0500
committerLen Brown <len.brown@intel.com>2009-12-16 13:57:12 -0500
commit7d8c22060555a51c23b9c6a76b97a5e75b5db882 (patch)
tree0cb2785c79acb7751ba5466d83e5eedcd59d6e3d /drivers/platform
parentf02f465b1cdcdf7485f89ec019e6cceaf80cadd5 (diff)
parentde078e5747fa3a95efac04fd6725dcceb4520416 (diff)
Merge branch 'msi-wmi' into release
Diffstat (limited to 'drivers/platform')
-rw-r--r--drivers/platform/x86/Kconfig12
-rw-r--r--drivers/platform/x86/Makefile1
-rw-r--r--drivers/platform/x86/msi-wmi.c293
3 files changed, 306 insertions, 0 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index e5e43121995d..ba3638fdf0ce 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -367,6 +367,18 @@ config ACPI_WMI
367 It is safe to enable this driver even if your DSDT doesn't define 367 It is safe to enable this driver even if your DSDT doesn't define
368 any ACPI-WMI devices. 368 any ACPI-WMI devices.
369 369
370config MSI_WMI
371 tristate "MSI WMI extras"
372 depends on ACPI_WMI
373 depends on INPUT
374 depends on BACKLIGHT_CLASS_DEVICE
375 select INPUT_SPARSEKMAP
376 help
377 Say Y here if you want to support WMI-based hotkeys on MSI laptops.
378
379 To compile this driver as a module, choose M here: the module will
380 be called msi-wmi.
381
370config ACPI_ASUS 382config ACPI_ASUS
371 tristate "ASUS/Medion Laptop Extras (DEPRECATED)" 383 tristate "ASUS/Medion Laptop Extras (DEPRECATED)"
372 depends on ACPI 384 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..0c8fe145c4af
--- /dev/null
+++ b/drivers/platform/x86/msi-wmi.c
@@ -0,0 +1,293 @@
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#include <linux/kernel.h>
25#include <linux/input.h>
26#include <linux/input/sparse-keymap.h>
27#include <linux/acpi.h>
28#include <linux/backlight.h>
29
30MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>");
31MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver");
32MODULE_LICENSE("GPL");
33
34MODULE_ALIAS("wmi:551A1F84-FBDD-4125-91DB-3EA8F44F1D45");
35MODULE_ALIAS("wmi:B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2");
36
37/* Temporary workaround until the WMI sysfs interface goes in
38 { "svn", DMI_SYS_VENDOR },
39 { "pn", DMI_PRODUCT_NAME },
40 { "pvr", DMI_PRODUCT_VERSION },
41 { "rvn", DMI_BOARD_VENDOR },
42 { "rn", DMI_BOARD_NAME },
43*/
44
45MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-6638:*");
46
47#define DRV_NAME "msi-wmi"
48#define DRV_PFX DRV_NAME ": "
49
50#define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45"
51#define MSIWMI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"
52
53#define dprintk(msg...) pr_debug(DRV_PFX msg)
54
55#define KEYCODE_BASE 0xD0
56#define MSI_WMI_BRIGHTNESSUP KEYCODE_BASE
57#define MSI_WMI_BRIGHTNESSDOWN (KEYCODE_BASE + 1)
58#define MSI_WMI_VOLUMEUP (KEYCODE_BASE + 2)
59#define MSI_WMI_VOLUMEDOWN (KEYCODE_BASE + 3)
60static struct key_entry msi_wmi_keymap[] = {
61 { KE_KEY, MSI_WMI_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} },
62 { KE_KEY, MSI_WMI_BRIGHTNESSDOWN, {KEY_BRIGHTNESSDOWN} },
63 { KE_KEY, MSI_WMI_VOLUMEUP, {KEY_VOLUMEUP} },
64 { KE_KEY, MSI_WMI_VOLUMEDOWN, {KEY_VOLUMEDOWN} },
65 { KE_END, 0}
66};
67static ktime_t last_pressed[ARRAY_SIZE(msi_wmi_keymap) - 1];
68
69struct backlight_device *backlight;
70
71static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF };
72
73static struct input_dev *msi_wmi_input_dev;
74
75static int msi_wmi_query_block(int instance, int *ret)
76{
77 acpi_status status;
78 union acpi_object *obj;
79
80 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
81
82 status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output);
83
84 obj = output.pointer;
85
86 if (!obj || obj->type != ACPI_TYPE_INTEGER) {
87 if (obj) {
88 printk(KERN_ERR DRV_PFX "query block returned object "
89 "type: %d - buffer length:%d\n", obj->type,
90 obj->type == ACPI_TYPE_BUFFER ?
91 obj->buffer.length : 0);
92 }
93 kfree(obj);
94 return -EINVAL;
95 }
96 *ret = obj->integer.value;
97 kfree(obj);
98 return 0;
99}
100
101static int msi_wmi_set_block(int instance, int value)
102{
103 acpi_status status;
104
105 struct acpi_buffer input = { sizeof(int), &value };
106
107 dprintk("Going to set block of instance: %d - value: %d\n",
108 instance, value);
109
110 status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input);
111
112 return ACPI_SUCCESS(status) ? 0 : 1;
113}
114
115static int bl_get(struct backlight_device *bd)
116{
117 int level, err, ret;
118
119 /* Instance 1 is "get backlight", cmp with DSDT */
120 err = msi_wmi_query_block(1, &ret);
121 if (err) {
122 printk(KERN_ERR DRV_PFX "Could not query backlight: %d\n", err);
123 return -EINVAL;
124 }
125 dprintk("Get: Query block returned: %d\n", ret);
126 for (level = 0; level < ARRAY_SIZE(backlight_map); level++) {
127 if (backlight_map[level] == ret) {
128 dprintk("Current backlight level: 0x%X - index: %d\n",
129 backlight_map[level], level);
130 break;
131 }
132 }
133 if (level == ARRAY_SIZE(backlight_map)) {
134 printk(KERN_ERR DRV_PFX "get: Invalid brightness value: 0x%X\n",
135 ret);
136 return -EINVAL;
137 }
138 return level;
139}
140
141static int bl_set_status(struct backlight_device *bd)
142{
143 int bright = bd->props.brightness;
144 if (bright >= ARRAY_SIZE(backlight_map) || bright < 0)
145 return -EINVAL;
146
147 /* Instance 0 is "set backlight" */
148 return msi_wmi_set_block(0, backlight_map[bright]);
149}
150
151static struct backlight_ops msi_backlight_ops = {
152 .get_brightness = bl_get,
153 .update_status = bl_set_status,
154};
155
156static void msi_wmi_notify(u32 value, void *context)
157{
158 struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
159 static struct key_entry *key;
160 union acpi_object *obj;
161 ktime_t cur;
162
163 wmi_get_event_data(value, &response);
164
165 obj = (union acpi_object *)response.pointer;
166
167 if (obj && obj->type == ACPI_TYPE_INTEGER) {
168 int eventcode = obj->integer.value;
169 dprintk("Eventcode: 0x%x\n", eventcode);
170 key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev,
171 eventcode);
172 if (key) {
173 ktime_t diff;
174 cur = ktime_get_real();
175 diff = ktime_sub(cur, last_pressed[key->code -
176 KEYCODE_BASE]);
177 /* Ignore event if the same event happened in a 50 ms
178 timeframe -> Key press may result in 10-20 GPEs */
179 if (ktime_to_us(diff) < 1000 * 50) {
180 dprintk("Suppressed key event 0x%X - "
181 "Last press was %lld us ago\n",
182 key->code, ktime_to_us(diff));
183 return;
184 }
185 last_pressed[key->code - KEYCODE_BASE] = cur;
186
187 if (key->type == KE_KEY &&
188 /* Brightness is served via acpi video driver */
189 (!acpi_video_backlight_support() ||
190 (key->code != MSI_WMI_BRIGHTNESSUP &&
191 key->code != MSI_WMI_BRIGHTNESSDOWN))) {
192 dprintk("Send key: 0x%X - "
193 "Input layer keycode: %d\n", key->code,
194 key->keycode);
195 sparse_keymap_report_entry(msi_wmi_input_dev,
196 key, 1, true);
197 }
198 } else
199 printk(KERN_INFO "Unknown key pressed - %x\n",
200 eventcode);
201 } else
202 printk(KERN_INFO DRV_PFX "Unknown event received\n");
203 kfree(response.pointer);
204}
205
206static int __init msi_wmi_input_setup(void)
207{
208 int err;
209
210 msi_wmi_input_dev = input_allocate_device();
211 if (!msi_wmi_input_dev)
212 return -ENOMEM;
213
214 msi_wmi_input_dev->name = "MSI WMI hotkeys";
215 msi_wmi_input_dev->phys = "wmi/input0";
216 msi_wmi_input_dev->id.bustype = BUS_HOST;
217
218 err = sparse_keymap_setup(msi_wmi_input_dev, msi_wmi_keymap, NULL);
219 if (err)
220 goto err_free_dev;
221
222 err = input_register_device(msi_wmi_input_dev);
223
224 if (err)
225 goto err_free_keymap;
226
227 memset(last_pressed, 0, sizeof(last_pressed));
228
229 return 0;
230
231err_free_keymap:
232 sparse_keymap_free(msi_wmi_input_dev);
233err_free_dev:
234 input_free_device(msi_wmi_input_dev);
235 return err;
236}
237
238static int __init msi_wmi_init(void)
239{
240 int err;
241
242 if (!wmi_has_guid(MSIWMI_EVENT_GUID)) {
243 printk(KERN_ERR
244 "This machine doesn't have MSI-hotkeys through WMI\n");
245 return -ENODEV;
246 }
247 err = wmi_install_notify_handler(MSIWMI_EVENT_GUID,
248 msi_wmi_notify, NULL);
249 if (err)
250 return -EINVAL;
251
252 err = msi_wmi_input_setup();
253 if (err)
254 goto err_uninstall_notifier;
255
256 if (!acpi_video_backlight_support()) {
257 backlight = backlight_device_register(DRV_NAME,
258 NULL, NULL, &msi_backlight_ops);
259 if (IS_ERR(backlight))
260 goto err_free_input;
261
262 backlight->props.max_brightness = ARRAY_SIZE(backlight_map) - 1;
263 err = bl_get(NULL);
264 if (err < 0)
265 goto err_free_backlight;
266
267 backlight->props.brightness = err;
268 }
269 dprintk("Event handler installed\n");
270
271 return 0;
272
273err_free_backlight:
274 backlight_device_unregister(backlight);
275err_free_input:
276 input_unregister_device(msi_wmi_input_dev);
277err_uninstall_notifier:
278 wmi_remove_notify_handler(MSIWMI_EVENT_GUID);
279 return err;
280}
281
282static void __exit msi_wmi_exit(void)
283{
284 if (wmi_has_guid(MSIWMI_EVENT_GUID)) {
285 wmi_remove_notify_handler(MSIWMI_EVENT_GUID);
286 sparse_keymap_free(msi_wmi_input_dev);
287 input_unregister_device(msi_wmi_input_dev);
288 backlight_device_unregister(backlight);
289 }
290}
291
292module_init(msi_wmi_init);
293module_exit(msi_wmi_exit);