blob: c6c6d09b376e9a23f0b4895ebb3c9d6963bb80fb [file] [log] [blame]
Matthew Garrett62ec30d2008-07-25 01:45:39 -07001/*
2 * HP 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#include <linux/kernel.h>
27#include <linux/module.h>
28#include <linux/init.h>
29#include <linux/types.h>
30#include <linux/input.h>
31#include <acpi/acpi_drivers.h>
32#include <linux/platform_device.h>
33#include <linux/acpi.h>
34#include <linux/rfkill.h>
35#include <linux/string.h>
36
37MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
38MODULE_DESCRIPTION("HP laptop WMI hotkeys driver");
39MODULE_LICENSE("GPL");
40
41MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C");
42MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");
43
44#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
45#define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
46
47#define HPWMI_DISPLAY_QUERY 0x1
48#define HPWMI_HDDTEMP_QUERY 0x2
49#define HPWMI_ALS_QUERY 0x3
50#define HPWMI_DOCK_QUERY 0x4
51#define HPWMI_WIRELESS_QUERY 0x5
52
53static int __init hp_wmi_bios_setup(struct platform_device *device);
54static int __exit hp_wmi_bios_remove(struct platform_device *device);
55
56struct bios_args {
57 u32 signature;
58 u32 command;
59 u32 commandtype;
60 u32 datasize;
61 u32 data;
62};
63
64struct bios_return {
65 u32 sigpass;
66 u32 return_code;
67 u32 value;
68};
69
70struct key_entry {
71 char type; /* See KE_* below */
72 u8 code;
73 u16 keycode;
74};
75
76enum { KE_KEY, KE_SW, KE_END };
77
78static struct key_entry hp_wmi_keymap[] = {
79 {KE_SW, 0x01, SW_DOCK},
80 {KE_KEY, 0x02, KEY_BRIGHTNESSUP},
81 {KE_KEY, 0x03, KEY_BRIGHTNESSDOWN},
82 {KE_KEY, 0x04, KEY_HELP},
83 {KE_END, 0}
84};
85
86static struct input_dev *hp_wmi_input_dev;
87static struct platform_device *hp_wmi_platform_dev;
88
89static struct rfkill *wifi_rfkill;
90static struct rfkill *bluetooth_rfkill;
91static struct rfkill *wwan_rfkill;
92
93static struct platform_driver hp_wmi_driver = {
94 .driver = {
95 .name = "hp-wmi",
96 .owner = THIS_MODULE,
97 },
98 .probe = hp_wmi_bios_setup,
99 .remove = hp_wmi_bios_remove,
100};
101
102static int hp_wmi_perform_query(int query, int write, int value)
103{
104 struct bios_return bios_return;
105 acpi_status status;
106 union acpi_object *obj;
107 struct bios_args args = {
108 .signature = 0x55434553,
109 .command = write ? 0x2 : 0x1,
110 .commandtype = query,
111 .datasize = write ? 0x4 : 0,
112 .data = value,
113 };
114 struct acpi_buffer input = { sizeof(struct bios_args), &args };
115 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
116
117 status = wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output);
118
119 obj = output.pointer;
120
121 if (!obj || obj->type != ACPI_TYPE_BUFFER)
122 return -EINVAL;
123
124 bios_return = *((struct bios_return *)obj->buffer.pointer);
125 if (bios_return.return_code > 0)
126 return bios_return.return_code * -1;
127 else
128 return bios_return.value;
129}
130
131static int hp_wmi_display_state(void)
132{
133 return hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, 0);
134}
135
136static int hp_wmi_hddtemp_state(void)
137{
138 return hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, 0);
139}
140
141static int hp_wmi_als_state(void)
142{
143 return hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, 0);
144}
145
146static int hp_wmi_dock_state(void)
147{
148 return hp_wmi_perform_query(HPWMI_DOCK_QUERY, 0, 0);
149}
150
151static int hp_wmi_wifi_set(void *data, enum rfkill_state state)
152{
153 if (state)
154 return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x101);
155 else
156 return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x100);
157}
158
159static int hp_wmi_bluetooth_set(void *data, enum rfkill_state state)
160{
161 if (state)
162 return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x202);
163 else
164 return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x200);
165}
166
167static int hp_wmi_wwan_set(void *data, enum rfkill_state state)
168{
169 if (state)
170 return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x404);
171 else
172 return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x400);
173}
174
175static int hp_wmi_wifi_state(void)
176{
177 int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);
178
179 if (wireless & 0x100)
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700180 return RFKILL_STATE_UNBLOCKED;
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700181 else
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700182 return RFKILL_STATE_SOFT_BLOCKED;
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700183}
184
185static int hp_wmi_bluetooth_state(void)
186{
187 int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);
188
189 if (wireless & 0x10000)
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700190 return RFKILL_STATE_UNBLOCKED;
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700191 else
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700192 return RFKILL_STATE_SOFT_BLOCKED;
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700193}
194
195static int hp_wmi_wwan_state(void)
196{
197 int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);
198
199 if (wireless & 0x1000000)
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700200 return RFKILL_STATE_UNBLOCKED;
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700201 else
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700202 return RFKILL_STATE_SOFT_BLOCKED;
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700203}
204
205static ssize_t show_display(struct device *dev, struct device_attribute *attr,
206 char *buf)
207{
208 int value = hp_wmi_display_state();
209 if (value < 0)
210 return -EINVAL;
211 return sprintf(buf, "%d\n", value);
212}
213
214static ssize_t show_hddtemp(struct device *dev, struct device_attribute *attr,
215 char *buf)
216{
217 int value = hp_wmi_hddtemp_state();
218 if (value < 0)
219 return -EINVAL;
220 return sprintf(buf, "%d\n", value);
221}
222
223static ssize_t show_als(struct device *dev, struct device_attribute *attr,
224 char *buf)
225{
226 int value = hp_wmi_als_state();
227 if (value < 0)
228 return -EINVAL;
229 return sprintf(buf, "%d\n", value);
230}
231
232static ssize_t show_dock(struct device *dev, struct device_attribute *attr,
233 char *buf)
234{
235 int value = hp_wmi_dock_state();
236 if (value < 0)
237 return -EINVAL;
238 return sprintf(buf, "%d\n", value);
239}
240
241static ssize_t set_als(struct device *dev, struct device_attribute *attr,
242 const char *buf, size_t count)
243{
244 u32 tmp = simple_strtoul(buf, NULL, 10);
245 hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, tmp);
246 return count;
247}
248
249static DEVICE_ATTR(display, S_IRUGO, show_display, NULL);
250static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL);
251static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als);
252static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL);
253
254static struct key_entry *hp_wmi_get_entry_by_scancode(int code)
255{
256 struct key_entry *key;
257
258 for (key = hp_wmi_keymap; key->type != KE_END; key++)
259 if (code == key->code)
260 return key;
261
262 return NULL;
263}
264
265static struct key_entry *hp_wmi_get_entry_by_keycode(int keycode)
266{
267 struct key_entry *key;
268
269 for (key = hp_wmi_keymap; key->type != KE_END; key++)
270 if (key->type == KE_KEY && keycode == key->keycode)
271 return key;
272
273 return NULL;
274}
275
276static int hp_wmi_getkeycode(struct input_dev *dev, int scancode, int *keycode)
277{
278 struct key_entry *key = hp_wmi_get_entry_by_scancode(scancode);
279
280 if (key && key->type == KE_KEY) {
281 *keycode = key->keycode;
282 return 0;
283 }
284
285 return -EINVAL;
286}
287
288static int hp_wmi_setkeycode(struct input_dev *dev, int scancode, int keycode)
289{
290 struct key_entry *key;
291 int old_keycode;
292
293 if (keycode < 0 || keycode > KEY_MAX)
294 return -EINVAL;
295
296 key = hp_wmi_get_entry_by_scancode(scancode);
297 if (key && key->type == KE_KEY) {
298 old_keycode = key->keycode;
299 key->keycode = keycode;
300 set_bit(keycode, dev->keybit);
301 if (!hp_wmi_get_entry_by_keycode(old_keycode))
302 clear_bit(old_keycode, dev->keybit);
303 return 0;
304 }
305
306 return -EINVAL;
307}
308
309void hp_wmi_notify(u32 value, void *context)
310{
311 struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
312 static struct key_entry *key;
313 union acpi_object *obj;
314
315 wmi_get_event_data(value, &response);
316
317 obj = (union acpi_object *)response.pointer;
318
319 if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == 8) {
320 int eventcode = *((u8 *) obj->buffer.pointer);
321 key = hp_wmi_get_entry_by_scancode(eventcode);
322 if (key) {
323 switch (key->type) {
324 case KE_KEY:
325 input_report_key(hp_wmi_input_dev,
326 key->keycode, 1);
327 input_sync(hp_wmi_input_dev);
328 input_report_key(hp_wmi_input_dev,
329 key->keycode, 0);
330 input_sync(hp_wmi_input_dev);
331 break;
332 case KE_SW:
333 input_report_switch(hp_wmi_input_dev,
334 key->keycode,
335 hp_wmi_dock_state());
336 input_sync(hp_wmi_input_dev);
337 break;
338 }
339 } else if (eventcode == 0x5) {
340 if (wifi_rfkill)
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700341 rfkill_force_state(wifi_rfkill,
342 hp_wmi_wifi_state());
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700343 if (bluetooth_rfkill)
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700344 rfkill_force_state(bluetooth_rfkill,
345 hp_wmi_bluetooth_state());
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700346 if (wwan_rfkill)
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700347 rfkill_force_state(wwan_rfkill,
348 hp_wmi_wwan_state());
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700349 } else
350 printk(KERN_INFO "HP WMI: Unknown key pressed - %x\n",
351 eventcode);
352 } else
353 printk(KERN_INFO "HP WMI: Unknown response received\n");
354}
355
356static int __init hp_wmi_input_setup(void)
357{
358 struct key_entry *key;
359 int err;
360
361 hp_wmi_input_dev = input_allocate_device();
362
363 hp_wmi_input_dev->name = "HP WMI hotkeys";
364 hp_wmi_input_dev->phys = "wmi/input0";
365 hp_wmi_input_dev->id.bustype = BUS_HOST;
366 hp_wmi_input_dev->getkeycode = hp_wmi_getkeycode;
367 hp_wmi_input_dev->setkeycode = hp_wmi_setkeycode;
368
369 for (key = hp_wmi_keymap; key->type != KE_END; key++) {
370 switch (key->type) {
371 case KE_KEY:
372 set_bit(EV_KEY, hp_wmi_input_dev->evbit);
373 set_bit(key->keycode, hp_wmi_input_dev->keybit);
374 break;
375 case KE_SW:
376 set_bit(EV_SW, hp_wmi_input_dev->evbit);
377 set_bit(key->keycode, hp_wmi_input_dev->swbit);
378 break;
379 }
380 }
381
382 err = input_register_device(hp_wmi_input_dev);
383
384 if (err) {
385 input_free_device(hp_wmi_input_dev);
386 return err;
387 }
388
389 return 0;
390}
391
392static void cleanup_sysfs(struct platform_device *device)
393{
394 device_remove_file(&device->dev, &dev_attr_display);
395 device_remove_file(&device->dev, &dev_attr_hddtemp);
396 device_remove_file(&device->dev, &dev_attr_als);
397 device_remove_file(&device->dev, &dev_attr_dock);
398}
399
400static int __init hp_wmi_bios_setup(struct platform_device *device)
401{
402 int err;
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700403 int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700404
405 err = device_create_file(&device->dev, &dev_attr_display);
406 if (err)
407 goto add_sysfs_error;
408 err = device_create_file(&device->dev, &dev_attr_hddtemp);
409 if (err)
410 goto add_sysfs_error;
411 err = device_create_file(&device->dev, &dev_attr_als);
412 if (err)
413 goto add_sysfs_error;
414 err = device_create_file(&device->dev, &dev_attr_dock);
415 if (err)
416 goto add_sysfs_error;
417
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700418 if (wireless & 0x1) {
419 wifi_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WLAN);
420 wifi_rfkill->name = "hp-wifi";
421 wifi_rfkill->state = hp_wmi_wifi_state();
422 wifi_rfkill->toggle_radio = hp_wmi_wifi_set;
423 wifi_rfkill->user_claim_unsupported = 1;
424 rfkill_register(wifi_rfkill);
425 }
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700426
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700427 if (wireless & 0x2) {
428 bluetooth_rfkill = rfkill_allocate(&device->dev,
429 RFKILL_TYPE_BLUETOOTH);
430 bluetooth_rfkill->name = "hp-bluetooth";
431 bluetooth_rfkill->state = hp_wmi_bluetooth_state();
432 bluetooth_rfkill->toggle_radio = hp_wmi_bluetooth_set;
433 bluetooth_rfkill->user_claim_unsupported = 1;
434 rfkill_register(bluetooth_rfkill);
435 }
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700436
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700437 if (wireless & 0x4) {
438 wwan_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WWAN);
439 wwan_rfkill->name = "hp-wwan";
440 wwan_rfkill->state = hp_wmi_wwan_state();
441 wwan_rfkill->toggle_radio = hp_wmi_wwan_set;
442 wwan_rfkill->user_claim_unsupported = 1;
443 rfkill_register(wwan_rfkill);
444 }
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700445
446 return 0;
447add_sysfs_error:
448 cleanup_sysfs(device);
449 return err;
450}
451
452static int __exit hp_wmi_bios_remove(struct platform_device *device)
453{
454 cleanup_sysfs(device);
455
Matthew Garrett3f6e2f12008-09-02 14:36:00 -0700456 if (wifi_rfkill)
457 rfkill_unregister(wifi_rfkill);
458 if (bluetooth_rfkill)
459 rfkill_unregister(bluetooth_rfkill);
460 if (wwan_rfkill)
461 rfkill_unregister(wwan_rfkill);
Matthew Garrett62ec30d2008-07-25 01:45:39 -0700462
463 return 0;
464}
465
466static int __init hp_wmi_init(void)
467{
468 int err;
469
470 if (wmi_has_guid(HPWMI_EVENT_GUID)) {
471 err = wmi_install_notify_handler(HPWMI_EVENT_GUID,
472 hp_wmi_notify, NULL);
473 if (!err)
474 hp_wmi_input_setup();
475 }
476
477 if (wmi_has_guid(HPWMI_BIOS_GUID)) {
478 err = platform_driver_register(&hp_wmi_driver);
479 if (err)
480 return 0;
481 hp_wmi_platform_dev = platform_device_alloc("hp-wmi", -1);
482 if (!hp_wmi_platform_dev) {
483 platform_driver_unregister(&hp_wmi_driver);
484 return 0;
485 }
486 platform_device_add(hp_wmi_platform_dev);
487 }
488
489 return 0;
490}
491
492static void __exit hp_wmi_exit(void)
493{
494 if (wmi_has_guid(HPWMI_EVENT_GUID)) {
495 wmi_remove_notify_handler(HPWMI_EVENT_GUID);
496 input_unregister_device(hp_wmi_input_dev);
497 }
498 if (hp_wmi_platform_dev) {
499 platform_device_del(hp_wmi_platform_dev);
500 platform_driver_unregister(&hp_wmi_driver);
501 }
502}
503
504module_init(hp_wmi_init);
505module_exit(hp_wmi_exit);