Add a WMI driver for Eee PC laptops. Currently it only supports hotkeys. Signed-off-by: Yong Wang <yong.y.wang@xxxxxxxxx> --- drivers/platform/x86/Kconfig | 10 ++ drivers/platform/x86/Makefile | 1 drivers/platform/x86/eeepc-wmi.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index e631dbe..7bec458 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -385,6 +385,16 @@ config EEEPC_LAPTOP If you have an Eee PC laptop, say Y or M here. +config EEEPC_WMI + tristate "Eee PC WMI Hotkey Driver (EXPERIMENTAL)" + depends on ACPI_WMI + depends on INPUT + depends on EXPERIMENTAL + ---help--- + Say Y here if you want to support WMI-based hotkeys on Eee PC laptops. + + To compile this driver as a module, choose M here: the module will + be called eeepc-wmi. config ACPI_WMI tristate "WMI" diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 9cd9fa0..a906490 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -4,6 +4,7 @@ # obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o +obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o diff --git a/drivers/platform/x86/eeepc-wmi.c b/drivers/platform/x86/eeepc-wmi.c new file mode 100644 index 0000000..cd9b4b5 --- /dev/null +++ b/drivers/platform/x86/eeepc-wmi.c @@ -0,0 +1,217 @@ +/* + * Eee PC WMI hotkey driver + * + * Copyright(C) 2010 Intel Corporation. + * + * Portions based on wistron_btns.c: + * Copyright (C) 2005 Miloslav Trmac <mitr@xxxxxxxx> + * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@xxxxxxxxxxxx> + * Copyright (C) 2005 Dmitry Torokhov <dtor@xxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/input.h> +#include <acpi/acpi_bus.h> +#include <acpi/acpi_drivers.h> + +MODULE_AUTHOR("Yong Wang <yong.y.wang@xxxxxxxxx>"); +MODULE_DESCRIPTION("Eee PC WMI Hotkey Driver"); +MODULE_LICENSE("GPL"); + +#define EEEPC_WMI_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" + +#define NOTIFY_BRNUP_MIN 0x11 +#define NOTIFY_BRNUP_MAX 0x1f +#define NOTIFY_BRNDOWN_MIN 0x20 +#define NOTIFY_BRNDOWN_MAX 0x2e + +struct key_entry { + char type; + u8 code; + u16 keycode; +}; + +enum { KE_KEY, KE_END }; + +static struct key_entry eeepc_wmi_keymap[] = { + /* Sleep already handled via generic ACPI code */ + {KE_KEY, 0x5d, KEY_WLAN }, + {KE_KEY, 0x32, KEY_MUTE }, + {KE_KEY, 0x31, KEY_VOLUMEDOWN }, + {KE_KEY, 0x30, KEY_VOLUMEUP }, + {KE_KEY, NOTIFY_BRNDOWN_MIN, KEY_BRIGHTNESSDOWN }, + {KE_KEY, NOTIFY_BRNUP_MIN, KEY_BRIGHTNESSUP }, + {KE_KEY, 0xcc, KEY_SWITCHVIDEOMODE }, + {KE_END, 0}, +}; + +static struct input_dev *eeepc_wmi_input_dev; + +static struct key_entry *eeepc_wmi_get_entry_by_scancode(int code) +{ + struct key_entry *key; + + for (key = eeepc_wmi_keymap; key->type != KE_END; key++) + if (code == key->code) + return key; + + return NULL; +} + +static struct key_entry *eeepc_wmi_get_entry_by_keycode(int code) +{ + struct key_entry *key; + + for (key = eeepc_wmi_keymap; key->type != KE_END; key++) + if (code == key->keycode && key->type == KE_KEY) + return key; + + return NULL; +} + +static int eeepc_wmi_getkeycode(struct input_dev *dev, int scancode, int *keycode) +{ + struct key_entry *key = eeepc_wmi_get_entry_by_scancode(scancode); + + if (key && key->type == KE_KEY) { + *keycode = key->keycode; + return 0; + } + + return -EINVAL; +} + +static int eeepc_wmi_setkeycode(struct input_dev *dev, int scancode, int keycode) +{ + struct key_entry *key; + int old_keycode; + + if (keycode < 0 || keycode > KEY_MAX) + return -EINVAL; + + key = eeepc_wmi_get_entry_by_scancode(scancode); + if (key && key->type == KE_KEY) { + old_keycode = key->keycode; + key->keycode = keycode; + set_bit(keycode, dev->keybit); + if (!eeepc_wmi_get_entry_by_keycode(old_keycode)) + clear_bit(old_keycode, dev->keybit); + return 0; + } + + return -EINVAL; +} + +static void eeepc_wmi_notify(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + static struct key_entry *key; + union acpi_object *obj; + int code; + + wmi_get_event_data(value, &response); + + obj = (union acpi_object *)response.pointer; + + if (obj && obj->type == ACPI_TYPE_INTEGER) { + code = obj->integer.value; + + if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX) + code = NOTIFY_BRNUP_MIN; + else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX) + code = NOTIFY_BRNDOWN_MIN; + + key = eeepc_wmi_get_entry_by_scancode(code); + + if (key) { + input_report_key(eeepc_wmi_input_dev, key->keycode, 1); + input_sync(eeepc_wmi_input_dev); + input_report_key(eeepc_wmi_input_dev, key->keycode, 0); + input_sync(eeepc_wmi_input_dev); + } else + printk(KERN_INFO "EEEPC WMI: Unknown key %x pressed\n", code); + } +} + +static int eeepc_wmi_input_setup(void) +{ + struct key_entry *key; + int err; + + eeepc_wmi_input_dev = input_allocate_device(); + + if (!eeepc_wmi_input_dev) + return -ENOMEM; + + eeepc_wmi_input_dev->name = "Eee PC WMI hotkeys"; + eeepc_wmi_input_dev->phys = "wmi/input0"; + eeepc_wmi_input_dev->id.bustype = BUS_HOST; + eeepc_wmi_input_dev->getkeycode = eeepc_wmi_getkeycode; + eeepc_wmi_input_dev->setkeycode = eeepc_wmi_setkeycode; + + for (key = eeepc_wmi_keymap; key->type != KE_END; key++) { + switch (key->type) { + case KE_KEY: + set_bit(EV_KEY, eeepc_wmi_input_dev->evbit); + set_bit(key->keycode, eeepc_wmi_input_dev->keybit); + break; + } + } + + err = input_register_device(eeepc_wmi_input_dev); + + if (err) { + input_free_device(eeepc_wmi_input_dev); + return err; + } + + return 0; +} + +static int __init eeepc_wmi_init(void) +{ + int err; + + if (wmi_has_guid(EEEPC_WMI_EVENT_GUID)) { + err = wmi_install_notify_handler(EEEPC_WMI_EVENT_GUID, + eeepc_wmi_notify, NULL); + if (ACPI_FAILURE(err)) + return -EINVAL; + + err = eeepc_wmi_input_setup(); + if (err) { + wmi_remove_notify_handler(EEEPC_WMI_EVENT_GUID); + return err; + } + } + + return 0; +} + +static void __exit eeepc_wmi_exit(void) +{ + if (wmi_has_guid(EEEPC_WMI_EVENT_GUID)) { + wmi_remove_notify_handler(EEEPC_WMI_EVENT_GUID); + input_unregister_device(eeepc_wmi_input_dev); + } +} + +module_init(eeepc_wmi_init); +module_exit(eeepc_wmi_exit); -- To unsubscribe from this list: send the line "unsubscribe platform-driver-x86" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html