msi-wmi.c revision fedda8e7385f5fb01acb8897beca90b6256fc7bd
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#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
24
25#include <linux/kernel.h>
26#include <linux/input.h>
27#include <linux/input/sparse-keymap.h>
28#include <linux/acpi.h>
29#include <linux/backlight.h>
30#include <linux/slab.h>
31#include <linux/module.h>
32
33MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>");
34MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver");
35MODULE_LICENSE("GPL");
36
37#define DRV_NAME "msi-wmi"
38
39#define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45"
40#define MSIWMI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"
41
42MODULE_ALIAS("wmi:" MSIWMI_BIOS_GUID);
43MODULE_ALIAS("wmi:" MSIWMI_EVENT_GUID);
44
45enum msi_scancodes {
46	MSI_KEY_BRIGHTNESSUP	= 0xD0,
47	MSI_KEY_BRIGHTNESSDOWN,
48	MSI_KEY_VOLUMEUP,
49	MSI_KEY_VOLUMEDOWN,
50	MSI_KEY_MUTE,
51};
52static struct key_entry msi_wmi_keymap[] = {
53	{ KE_KEY, MSI_KEY_BRIGHTNESSUP,		{KEY_BRIGHTNESSUP} },
54	{ KE_KEY, MSI_KEY_BRIGHTNESSDOWN,	{KEY_BRIGHTNESSDOWN} },
55	{ KE_KEY, MSI_KEY_VOLUMEUP,		{KEY_VOLUMEUP} },
56	{ KE_KEY, MSI_KEY_VOLUMEDOWN,		{KEY_VOLUMEDOWN} },
57	{ KE_KEY, MSI_KEY_MUTE,			{KEY_MUTE} },
58	{ KE_END, 0 }
59};
60
61static ktime_t last_pressed;
62static bool quirk_last_pressed;
63
64static const char *event_wmi_guid;
65
66static struct backlight_device *backlight;
67
68static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF };
69
70static struct input_dev *msi_wmi_input_dev;
71
72static int msi_wmi_query_block(int instance, int *ret)
73{
74	acpi_status status;
75	union acpi_object *obj;
76
77	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
78
79	status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output);
80
81	obj = output.pointer;
82
83	if (!obj || obj->type != ACPI_TYPE_INTEGER) {
84		if (obj) {
85			pr_err("query block returned object "
86			       "type: %d - buffer length:%d\n", obj->type,
87			       obj->type == ACPI_TYPE_BUFFER ?
88			       obj->buffer.length : 0);
89		}
90		kfree(obj);
91		return -EINVAL;
92	}
93	*ret = obj->integer.value;
94	kfree(obj);
95	return 0;
96}
97
98static int msi_wmi_set_block(int instance, int value)
99{
100	acpi_status status;
101
102	struct acpi_buffer input = { sizeof(int), &value };
103
104	pr_debug("Going to set block of instance: %d - value: %d\n",
105		 instance, value);
106
107	status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input);
108
109	return ACPI_SUCCESS(status) ? 0 : 1;
110}
111
112static int bl_get(struct backlight_device *bd)
113{
114	int level, err, ret;
115
116	/* Instance 1 is "get backlight", cmp with DSDT */
117	err = msi_wmi_query_block(1, &ret);
118	if (err) {
119		pr_err("Could not query backlight: %d\n", err);
120		return -EINVAL;
121	}
122	pr_debug("Get: Query block returned: %d\n", ret);
123	for (level = 0; level < ARRAY_SIZE(backlight_map); level++) {
124		if (backlight_map[level] == ret) {
125			pr_debug("Current backlight level: 0x%X - index: %d\n",
126				 backlight_map[level], level);
127			break;
128		}
129	}
130	if (level == ARRAY_SIZE(backlight_map)) {
131		pr_err("get: Invalid brightness value: 0x%X\n", ret);
132		return -EINVAL;
133	}
134	return level;
135}
136
137static int bl_set_status(struct backlight_device *bd)
138{
139	int bright = bd->props.brightness;
140	if (bright >= ARRAY_SIZE(backlight_map) || bright < 0)
141		return -EINVAL;
142
143	/* Instance 0 is "set backlight" */
144	return msi_wmi_set_block(0, backlight_map[bright]);
145}
146
147static const struct backlight_ops msi_backlight_ops = {
148	.get_brightness	= bl_get,
149	.update_status	= bl_set_status,
150};
151
152static void msi_wmi_notify(u32 value, void *context)
153{
154	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
155	static struct key_entry *key;
156	union acpi_object *obj;
157	acpi_status status;
158
159	status = wmi_get_event_data(value, &response);
160	if (status != AE_OK) {
161		pr_info("bad event status 0x%x\n", status);
162		return;
163	}
164
165	obj = (union acpi_object *)response.pointer;
166
167	if (obj && obj->type == ACPI_TYPE_INTEGER) {
168		int eventcode = obj->integer.value;
169		pr_debug("Eventcode: 0x%x\n", eventcode);
170		key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev,
171				eventcode);
172		if (!key) {
173			pr_info("Unknown key pressed - %x\n", eventcode);
174			goto msi_wmi_notify_exit;
175		}
176
177		if (quirk_last_pressed) {
178			ktime_t cur = ktime_get_real();
179			ktime_t diff = ktime_sub(cur, last_pressed);
180			/* Ignore event if any event happened in a 50 ms
181			   timeframe -> Key press may result in 10-20 GPEs */
182			if (ktime_to_us(diff) < 1000 * 50) {
183				pr_debug("Suppressed key event 0x%X - "
184					 "Last press was %lld us ago\n",
185					 key->code, ktime_to_us(diff));
186				goto msi_wmi_notify_exit;
187			}
188			last_pressed = cur;
189		}
190
191		if (key->type == KE_KEY &&
192		/* Brightness is served via acpi video driver */
193		(backlight ||
194		(key->code != MSI_KEY_BRIGHTNESSUP &&
195		key->code != MSI_KEY_BRIGHTNESSDOWN))) {
196			pr_debug("Send key: 0x%X - Input layer keycode: %d\n",
197				 key->code, key->keycode);
198			sparse_keymap_report_entry(msi_wmi_input_dev, key, 1,
199						   true);
200		}
201	} else
202		pr_info("Unknown event received\n");
203
204msi_wmi_notify_exit:
205	kfree(response.pointer);
206}
207
208static int __init msi_wmi_backlight_setup(void)
209{
210	int err;
211	struct backlight_properties props;
212
213	memset(&props, 0, sizeof(struct backlight_properties));
214	props.type = BACKLIGHT_PLATFORM;
215	props.max_brightness = ARRAY_SIZE(backlight_map) - 1;
216	backlight = backlight_device_register(DRV_NAME, NULL, NULL,
217					      &msi_backlight_ops,
218					      &props);
219	if (IS_ERR(backlight))
220		return PTR_ERR(backlight);
221
222	err = bl_get(NULL);
223	if (err < 0) {
224		backlight_device_unregister(backlight);
225		return err;
226	}
227
228	backlight->props.brightness = err;
229
230	return 0;
231}
232
233static int __init msi_wmi_input_setup(void)
234{
235	int err;
236
237	msi_wmi_input_dev = input_allocate_device();
238	if (!msi_wmi_input_dev)
239		return -ENOMEM;
240
241	msi_wmi_input_dev->name = "MSI WMI hotkeys";
242	msi_wmi_input_dev->phys = "wmi/input0";
243	msi_wmi_input_dev->id.bustype = BUS_HOST;
244
245	err = sparse_keymap_setup(msi_wmi_input_dev, msi_wmi_keymap, NULL);
246	if (err)
247		goto err_free_dev;
248
249	err = input_register_device(msi_wmi_input_dev);
250
251	if (err)
252		goto err_free_keymap;
253
254	last_pressed = ktime_set(0, 0);
255
256	return 0;
257
258err_free_keymap:
259	sparse_keymap_free(msi_wmi_input_dev);
260err_free_dev:
261	input_free_device(msi_wmi_input_dev);
262	return err;
263}
264
265static int __init msi_wmi_init(void)
266{
267	int err;
268
269	if (wmi_has_guid(MSIWMI_EVENT_GUID)) {
270		err = msi_wmi_input_setup();
271		if (err) {
272			pr_err("Unable to setup input device\n");
273			return err;
274		}
275
276		err = wmi_install_notify_handler(MSIWMI_EVENT_GUID,
277			msi_wmi_notify, NULL);
278		if (ACPI_FAILURE(err)) {
279			pr_err("Unable to setup WMI notify handler\n");
280			goto err_free_input;
281		}
282
283		pr_debug("Event handler installed\n");
284		event_wmi_guid = MSIWMI_EVENT_GUID;
285		quirk_last_pressed = true;
286	}
287
288	if (wmi_has_guid(MSIWMI_BIOS_GUID) && !acpi_video_backlight_support()) {
289		err = msi_wmi_backlight_setup();
290		if (err) {
291			pr_err("Unable to setup backlight device\n");
292			goto err_uninstall_handler;
293		}
294		pr_debug("Backlight device created\n");
295	}
296
297	if (!event_wmi_guid && !backlight) {
298		pr_err("This machine doesn't have neither MSI-hotkeys nor backlight through WMI\n");
299		return -ENODEV;
300	}
301
302	return 0;
303
304err_uninstall_handler:
305	if (event_wmi_guid)
306		wmi_remove_notify_handler(event_wmi_guid);
307err_free_input:
308	if (event_wmi_guid) {
309		sparse_keymap_free(msi_wmi_input_dev);
310		input_unregister_device(msi_wmi_input_dev);
311	}
312	return err;
313}
314
315static void __exit msi_wmi_exit(void)
316{
317	if (event_wmi_guid) {
318		wmi_remove_notify_handler(event_wmi_guid);
319		sparse_keymap_free(msi_wmi_input_dev);
320		input_unregister_device(msi_wmi_input_dev);
321	}
322	if (backlight)
323		backlight_device_unregister(backlight);
324}
325
326module_init(msi_wmi_init);
327module_exit(msi_wmi_exit);
328