From 3c0eb510697dbbb53674c72544350624a04ab5b4 Mon Sep 17 00:00:00 2001 From: Corentin Chary Date: Thu, 3 Dec 2009 07:44:52 +0000 Subject: eeepc-laptop: add touchpad led This led can be found on Eeepc 1005 series. Signed-off-by: Corentin Chary Signed-off-by: Len Brown --- drivers/platform/x86/Kconfig | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/platform/x86/Kconfig') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 55ca39dea42..e5e43121995 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -334,6 +334,8 @@ config EEEPC_LAPTOP depends on HOTPLUG_PCI select BACKLIGHT_CLASS_DEVICE select HWMON + select LEDS_CLASS + select NEW_LEDS ---help--- This driver supports the Fn-Fx keys on Eee PC laptops. -- cgit v1.2.3-70-g09d2 From 42b4e9ee3d1d3b691bcae37f217f08740320c58c Mon Sep 17 00:00:00 2001 From: Jes Sorensen Date: Wed, 16 Dec 2009 12:08:15 -0500 Subject: Toshiba Bluetooth Enabling driver (RFKill handler v3) This patch adds support for the ACPI events generated by the RFKill switch on modern Toshiba laptops, and re-enables the Bluetooth USB device when the switch is flipped back to the 'on' position. The RFKill switch brute force pulls out the USB device when flipped to 'off', but it doesn't automatically re-enable it. Without this driver, the Bluetooth is gone until after a reboot on my Portege R500. Signed-off-by: Jes Sorensen Signed-off-by: Len Brown --- drivers/platform/x86/Kconfig | 15 ++++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/toshiba_bluetooth.c | 144 +++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 drivers/platform/x86/toshiba_bluetooth.c (limited to 'drivers/platform/x86/Kconfig') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 55ca39dea42..8dd2b3a4c47 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -435,4 +435,19 @@ config ACPI_TOSHIBA If you have a legacy free Toshiba laptop (such as the Libretto L1 series), say Y. + +config TOSHIBA_BT_RFKILL + tristate "Toshiba Bluetooth RFKill switch support" + depends on ACPI + ---help--- + This driver adds support for Bluetooth events for the RFKill + switch on modern Toshiba laptops with full ACPI support and + an RFKill switch. + + This driver handles RFKill events for the TOS6205 Bluetooth, + and re-enables it when the switch is set back to the 'on' + position. + + If you have a modern Toshiba laptop with a Bluetooth and an + RFKill switch (such as the Portege R500), say Y. endif # X86_PLATFORM_DEVICES diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index d1c16210a51..394258bd77c 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o +obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c new file mode 100644 index 00000000000..a350418e87e --- /dev/null +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -0,0 +1,144 @@ +/* + * Toshiba Bluetooth Enable Driver + * + * Copyright (C) 2009 Jes Sorensen + * + * Thanks to Matthew Garrett for background info on ACPI innards which + * normal people aren't meant to understand :-) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Note the Toshiba Bluetooth RFKill switch seems to be a strange + * fish. It only provides a BT event when the switch is flipped to + * the 'on' position. When flipping it to 'off', the USB device is + * simply pulled away underneath us, without any BT event being + * delivered. + */ + +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jes Sorensen "); +MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver"); +MODULE_LICENSE("GPL"); + + +static int toshiba_bt_rfkill_add(struct acpi_device *device); +static int toshiba_bt_rfkill_remove(struct acpi_device *device, int type); +static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event); +static int toshiba_bt_resume(struct acpi_device *device); + +static const struct acpi_device_id bt_device_ids[] = { + { "TOS6205", 0}, + { "", 0}, +}; +MODULE_DEVICE_TABLE(acpi, bt_device_ids); + +static struct acpi_driver toshiba_bt_rfkill_driver = { + .name = "Toshiba BT", + .class = "Toshiba", + .ids = bt_device_ids, + .ops = { + .add = toshiba_bt_rfkill_add, + .remove = toshiba_bt_rfkill_remove, + .notify = toshiba_bt_rfkill_notify, + .resume = toshiba_bt_resume, + }, + .owner = THIS_MODULE, +}; + + +static int toshiba_bluetooth_enable(acpi_handle handle) +{ + acpi_status res1, res2; + acpi_integer result; + + /* + * Query ACPI to verify RFKill switch is set to 'on'. + * If not, we return silently, no need to report it as + * an error. + */ + res1 = acpi_evaluate_integer(handle, "BTST", NULL, &result); + if (ACPI_FAILURE(res1)) + return res1; + if (!(result & 0x01)) + return 0; + + printk(KERN_INFO "toshiba_bluetooth: Re-enabling Toshiba Bluetooth\n"); + res1 = acpi_evaluate_object(handle, "AUSB", NULL, NULL); + res2 = acpi_evaluate_object(handle, "BTPO", NULL, NULL); + if (!ACPI_FAILURE(res1) || !ACPI_FAILURE(res2)) + return 0; + + printk(KERN_WARNING "toshiba_bluetooth: Failed to re-enable " + "Toshiba Bluetooth\n"); + + return -ENODEV; +} + +static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) +{ + toshiba_bluetooth_enable(device->handle); +} + +static int toshiba_bt_resume(struct acpi_device *device) +{ + return toshiba_bluetooth_enable(device->handle); +} + +static int toshiba_bt_rfkill_add(struct acpi_device *device) +{ + acpi_status status; + acpi_integer bt_present; + int result = -ENODEV; + + /* + * Some Toshiba laptops may have a fake TOS6205 device in + * their ACPI BIOS, so query the _STA method to see if there + * is really anything there, before trying to enable it. + */ + status = acpi_evaluate_integer(device->handle, "_STA", NULL, + &bt_present); + + if (!ACPI_FAILURE(status) && bt_present) { + printk(KERN_INFO "Detected Toshiba ACPI Bluetooth device - " + "installing RFKill handler\n"); + result = toshiba_bluetooth_enable(device->handle); + } + + return result; +} + +static int __init toshiba_bt_rfkill_init(void) +{ + int result; + + result = acpi_bus_register_driver(&toshiba_bt_rfkill_driver); + if (result < 0) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Error registering driver\n")); + return result; + } + + return 0; +} + +static int toshiba_bt_rfkill_remove(struct acpi_device *device, int type) +{ + /* clean up */ + return 0; +} + +static void __exit toshiba_bt_rfkill_exit(void) +{ + acpi_bus_unregister_driver(&toshiba_bt_rfkill_driver); +} + +module_init(toshiba_bt_rfkill_init); +module_exit(toshiba_bt_rfkill_exit); -- cgit v1.2.3-70-g09d2 From d12d8baff927a31b7e13b72ed9549be6f296a6ef Mon Sep 17 00:00:00 2001 From: Thomas Renninger Date: Thu, 10 Dec 2009 14:18:13 +0100 Subject: 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 CC: Carlos Corbacho CC: Matthew Garrett Tested-by: Matt Chen Reviewed-by: Anisse Astier Signed-off-by: Len Brown --- drivers/platform/x86/Kconfig | 10 ++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/msi-wmi.c | 369 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 380 insertions(+) create mode 100644 drivers/platform/x86/msi-wmi.c (limited to 'drivers/platform/x86/Kconfig') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 55ca39dea42..98ec6bd9226 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -365,6 +365,16 @@ config ACPI_WMI It is safe to enable this driver even if your DSDT doesn't define any ACPI-WMI devices. +config MSI_WMI + tristate "MSI WMI extras" + depends on ACPI_WMI + depends on INPUT + help + Say Y here if you want to support WMI-based hotkeys on MSI laptops. + + To compile this driver as a module, choose M here: the module will + be called msi-wmi. + config ACPI_ASUS tristate "ASUS/Medion Laptop Extras (DEPRECATED)" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index d1c16210a51..13aa37310f3 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_ACPI_WMI) += wmi.o +obj-$(CONFIG_MSI_WMI) += msi-wmi.o obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o obj-$(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 00000000000..7e0dab65975 --- /dev/null +++ b/drivers/platform/x86/msi-wmi.c @@ -0,0 +1,369 @@ +/* + * MSI WMI hotkeys + * + * Copyright (C) 2009 Novell + * + * Most stuff taken over from hp-wmi + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Thomas Renninger "); +MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Set this to 1 to let the driver be more verbose"); + +MODULE_ALIAS("wmi:551A1F84-FBDD-4125-91DB-3EA8F44F1D45"); +MODULE_ALIAS("wmi:B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"); + +/* Temporary workaround until the WMI sysfs interface goes in + { "svn", DMI_SYS_VENDOR }, + { "pn", DMI_PRODUCT_NAME }, + { "pvr", DMI_PRODUCT_VERSION }, + { "rvn", DMI_BOARD_VENDOR }, + { "rn", DMI_BOARD_NAME }, +*/ + +MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-6638:*"); + +#define DRV_NAME "msi-wmi" +#define DRV_PFX DRV_NAME ": " + +#define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45" +#define MSIWMI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2" + +#define dprintk(msg...) do { \ + if (debug) \ + printk(KERN_INFO DRV_PFX msg); \ + } while (0) + +struct key_entry { + char type; /* See KE_* below */ + u16 code; + u16 keycode; + int instance; + ktime_t last_pressed; +}; + +/* + * KE_KEY the only used key type, but keep this, others might also + * show up in the future. Compare with hp-wmi.c + */ +enum { KE_KEY, KE_END }; + +static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, 0xd0, KEY_BRIGHTNESSUP, 0, {0, } }, + { KE_KEY, 0xd1, KEY_BRIGHTNESSDOWN, 1, {0, } }, + { KE_KEY, 0xd2, KEY_VOLUMEUP, 2, {0, } }, + { KE_KEY, 0xd3, KEY_VOLUMEDOWN, 3, {0, } }, + { KE_END, 0} +}; + +struct backlight_device *backlight; + +static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF }; + +static struct input_dev *msi_wmi_input_dev; + +static int msi_wmi_query_block(int instance, int *ret) +{ + acpi_status status; + union acpi_object *obj; + + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + + status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output); + + obj = output.pointer; + + if (!obj || obj->type != ACPI_TYPE_INTEGER) { + if (obj) { + printk(KERN_ERR DRV_PFX "query block returned object " + "type: %d - buffer length:%d\n", obj->type, + obj->type == ACPI_TYPE_BUFFER ? + obj->buffer.length : 0); + } + kfree(obj); + return -EINVAL; + } + *ret = obj->integer.value; + kfree(obj); + return 0; +} + +static int msi_wmi_set_block(int instance, int value) +{ + acpi_status status; + + struct acpi_buffer input = { sizeof(int), &value }; + + dprintk("Going to set block of instance: %d - value: %d\n", + instance, value); + + status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input); + + return ACPI_SUCCESS(status) ? 0 : 1; +} + +static int bl_get(struct backlight_device *bd) +{ + int level, err, ret = 0; + + /* Instance 1 is "get backlight", cmp with DSDT */ + err = msi_wmi_query_block(1, &ret); + if (err) + printk(KERN_ERR DRV_PFX "Could not query backlight: %d\n", err); + dprintk("Get: Query block returned: %d\n", ret); + for (level = 0; level < ARRAY_SIZE(backlight_map); level++) { + if (backlight_map[level] == ret) { + dprintk("Current backlight level: 0x%X - index: %d\n", + backlight_map[level], level); + break; + } + } + if (level == ARRAY_SIZE(backlight_map)) { + printk(KERN_ERR DRV_PFX "get: Invalid brightness value: 0x%X\n", + ret); + return -EINVAL; + } + return level; +} + +static int bl_set_status(struct backlight_device *bd) +{ + int bright = bd->props.brightness; + if (bright >= ARRAY_SIZE(backlight_map) || bright < 0) + return -EINVAL; + + /* Instance 0 is "set backlight" */ + return msi_wmi_set_block(0, backlight_map[bright]); +} + +static struct backlight_ops msi_backlight_ops = { + .get_brightness = bl_get, + .update_status = bl_set_status, +}; + +static struct key_entry *msi_wmi_get_entry_by_scancode(int code) +{ + struct key_entry *key; + + for (key = msi_wmi_keymap; key->type != KE_END; key++) + if (code == key->code) + return key; + + return NULL; +} + +static struct key_entry *msi_wmi_get_entry_by_keycode(int keycode) +{ + struct key_entry *key; + + for (key = msi_wmi_keymap; key->type != KE_END; key++) + if (key->type == KE_KEY && keycode == key->keycode) + return key; + + return NULL; +} + +static int msi_wmi_getkeycode(struct input_dev *dev, int scancode, int *keycode) +{ + struct key_entry *key = msi_wmi_get_entry_by_scancode(scancode); + + if (key && key->type == KE_KEY) { + *keycode = key->keycode; + return 0; + } + + return -EINVAL; +} + +static int msi_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 = msi_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 (!msi_wmi_get_entry_by_keycode(old_keycode)) + clear_bit(old_keycode, dev->keybit); + return 0; + } + + return -EINVAL; +} + +static void msi_wmi_notify(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + static struct key_entry *key; + union acpi_object *obj; + ktime_t cur; + + wmi_get_event_data(value, &response); + + obj = (union acpi_object *)response.pointer; + + if (obj && obj->type == ACPI_TYPE_INTEGER) { + int eventcode = obj->integer.value; + dprintk("Eventcode: 0x%x\n", eventcode); + key = msi_wmi_get_entry_by_scancode(eventcode); + if (key) { + cur = ktime_get_real(); + /* Ignore event if the same event happened in a 50 ms + timeframe -> Key press may result in 10-20 GPEs */ + if (ktime_to_us(ktime_sub(cur, key->last_pressed)) + < 1000 * 50) { + dprintk("Suppressed key event 0x%X - " + "Last press was %lld us ago\n", + key->code, + ktime_to_us(ktime_sub(cur, + key->last_pressed))); + return; + } + key->last_pressed = cur; + + switch (key->type) { + case KE_KEY: + /* Brightness is served via acpi video driver */ + if (!backlight && + (key->keycode == KEY_BRIGHTNESSUP || + key->keycode == KEY_BRIGHTNESSDOWN)) + break; + + dprintk("Send key: 0x%X - " + "Input layer keycode: %d\n", key->code, + key->keycode); + input_report_key(msi_wmi_input_dev, + key->keycode, 1); + input_sync(msi_wmi_input_dev); + input_report_key(msi_wmi_input_dev, + key->keycode, 0); + input_sync(msi_wmi_input_dev); + break; + } + } else + printk(KERN_INFO "Unknown key pressed - %x\n", + eventcode); + } else + printk(KERN_INFO DRV_PFX "Unknown event received\n"); + kfree(response.pointer); +} + +static int __init msi_wmi_input_setup(void) +{ + struct key_entry *key; + int err; + + msi_wmi_input_dev = input_allocate_device(); + + msi_wmi_input_dev->name = "MSI WMI hotkeys"; + msi_wmi_input_dev->phys = "wmi/input0"; + msi_wmi_input_dev->id.bustype = BUS_HOST; + msi_wmi_input_dev->getkeycode = msi_wmi_getkeycode; + msi_wmi_input_dev->setkeycode = msi_wmi_setkeycode; + + for (key = msi_wmi_keymap; key->type != KE_END; key++) { + switch (key->type) { + case KE_KEY: + set_bit(EV_KEY, msi_wmi_input_dev->evbit); + set_bit(key->keycode, msi_wmi_input_dev->keybit); + break; + } + } + + err = input_register_device(msi_wmi_input_dev); + + if (err) { + input_free_device(msi_wmi_input_dev); + return err; + } + + return 0; +} + +static int __init msi_wmi_init(void) +{ + int err; + + if (wmi_has_guid(MSIWMI_EVENT_GUID)) { + err = wmi_install_notify_handler(MSIWMI_EVENT_GUID, + msi_wmi_notify, NULL); + if (err) + return -EINVAL; + + err = msi_wmi_input_setup(); + if (err) { + wmi_remove_notify_handler(MSIWMI_EVENT_GUID); + return -EINVAL; + } + + if (!acpi_video_backlight_support()) { + backlight = backlight_device_register(DRV_NAME, + NULL, NULL, &msi_backlight_ops); + if (IS_ERR(backlight)) { + wmi_remove_notify_handler(MSIWMI_EVENT_GUID); + input_unregister_device(msi_wmi_input_dev); + return -EINVAL; + } + + backlight->props.max_brightness = ARRAY_SIZE(backlight_map) - 1; + err = bl_get(NULL); + if (err < 0) { + wmi_remove_notify_handler(MSIWMI_EVENT_GUID); + input_unregister_device(msi_wmi_input_dev); + backlight_device_unregister(backlight); + return -EINVAL; + } + backlight->props.brightness = err; + } + } + printk(KERN_INFO DRV_PFX "Event handler installed\n"); + return 0; +} + +static void __exit msi_wmi_exit(void) +{ + if (wmi_has_guid(MSIWMI_EVENT_GUID)) { + wmi_remove_notify_handler(MSIWMI_EVENT_GUID); + input_unregister_device(msi_wmi_input_dev); + backlight_device_unregister(backlight); + } +} + +module_init(msi_wmi_init); +module_exit(msi_wmi_exit); -- cgit v1.2.3-70-g09d2 From c30116c6f0d26cd6e46dfa578163d573ef4730b2 Mon Sep 17 00:00:00 2001 From: Anisse Astier Date: Thu, 10 Dec 2009 14:18:19 +0100 Subject: msi-wmi: switch to using input sparse keymap library Signed-off-by: Anisse Astier Signed-off-by: Len Brown --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/msi-wmi.c | 134 ++++++++++------------------------------- 2 files changed, 33 insertions(+), 102 deletions(-) (limited to 'drivers/platform/x86/Kconfig') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 98ec6bd9226..1f82d6df96e 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -369,6 +369,7 @@ config MSI_WMI tristate "MSI WMI extras" depends on ACPI_WMI depends on INPUT + select INPUT_SPARSEKMAP help Say Y here if you want to support WMI-based hotkeys on MSI laptops. diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 2c2afc1828b..e25b80c530f 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -21,9 +21,9 @@ */ - #include #include +#include #include #include @@ -52,26 +52,15 @@ MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-6638:*"); #define dprintk(msg...) pr_debug(DRV_PFX msg) -struct key_entry { - char type; /* See KE_* below */ - u16 code; - u16 keycode; - ktime_t last_pressed; -}; - -/* - * KE_KEY the only used key type, but keep this, others might also - * show up in the future. Compare with hp-wmi.c - */ -enum { KE_KEY, KE_END }; - +#define KEYCODE_BASE 0xD0 static struct key_entry msi_wmi_keymap[] = { - { KE_KEY, 0xd0, KEY_BRIGHTNESSUP, {0, } }, - { KE_KEY, 0xd1, KEY_BRIGHTNESSDOWN, {0, } }, - { KE_KEY, 0xd2, KEY_VOLUMEUP, {0, } }, - { KE_KEY, 0xd3, KEY_VOLUMEDOWN, {0, } }, + { KE_KEY, KEYCODE_BASE, {KEY_BRIGHTNESSUP} }, + { KE_KEY, KEYCODE_BASE + 1, {KEY_BRIGHTNESSDOWN} }, + { KE_KEY, KEYCODE_BASE + 2, {KEY_VOLUMEUP} }, + { KE_KEY, KEYCODE_BASE + 3, {KEY_VOLUMEDOWN} }, { KE_END, 0} }; +static ktime_t last_pressed[ARRAY_SIZE(msi_wmi_keymap) - 1]; struct backlight_device *backlight; @@ -158,61 +147,6 @@ static struct backlight_ops msi_backlight_ops = { .update_status = bl_set_status, }; -static struct key_entry *msi_wmi_get_entry_by_scancode(int code) -{ - struct key_entry *key; - - for (key = msi_wmi_keymap; key->type != KE_END; key++) - if (code == key->code) - return key; - - return NULL; -} - -static struct key_entry *msi_wmi_get_entry_by_keycode(int keycode) -{ - struct key_entry *key; - - for (key = msi_wmi_keymap; key->type != KE_END; key++) - if (key->type == KE_KEY && keycode == key->keycode) - return key; - - return NULL; -} - -static int msi_wmi_getkeycode(struct input_dev *dev, int scancode, int *keycode) -{ - struct key_entry *key = msi_wmi_get_entry_by_scancode(scancode); - - if (key && key->type == KE_KEY) { - *keycode = key->keycode; - return 0; - } - - return -EINVAL; -} - -static int msi_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 = msi_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 (!msi_wmi_get_entry_by_keycode(old_keycode)) - clear_bit(old_keycode, dev->keybit); - return 0; - } - - return -EINVAL; -} - static void msi_wmi_notify(u32 value, void *context) { struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -227,21 +161,22 @@ static void msi_wmi_notify(u32 value, void *context) if (obj && obj->type == ACPI_TYPE_INTEGER) { int eventcode = obj->integer.value; dprintk("Eventcode: 0x%x\n", eventcode); - key = msi_wmi_get_entry_by_scancode(eventcode); + key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, + eventcode); if (key) { + ktime_t diff; cur = ktime_get_real(); + diff = ktime_sub(cur, last_pressed[key->code - + KEYCODE_BASE]); /* Ignore event if the same event happened in a 50 ms timeframe -> Key press may result in 10-20 GPEs */ - if (ktime_to_us(ktime_sub(cur, key->last_pressed)) - < 1000 * 50) { + if (ktime_to_us(diff) < 1000 * 50) { dprintk("Suppressed key event 0x%X - " "Last press was %lld us ago\n", - key->code, - ktime_to_us(ktime_sub(cur, - key->last_pressed))); + key->code, ktime_to_us(diff)); return; } - key->last_pressed = cur; + last_pressed[key->code - KEYCODE_BASE] = cur; if (key->type == KE_KEY && /* Brightness is served via acpi video driver */ @@ -250,12 +185,8 @@ static void msi_wmi_notify(u32 value, void *context) dprintk("Send key: 0x%X - " "Input layer keycode: %d\n", key->code, key->keycode); - input_report_key(msi_wmi_input_dev, - key->keycode, 1); - input_sync(msi_wmi_input_dev); - input_report_key(msi_wmi_input_dev, - key->keycode, 0); - input_sync(msi_wmi_input_dev); + sparse_keymap_report_entry(msi_wmi_input_dev, + key, 1, true); } } else printk(KERN_INFO "Unknown key pressed - %x\n", @@ -267,7 +198,6 @@ static void msi_wmi_notify(u32 value, void *context) static int __init msi_wmi_input_setup(void) { - struct key_entry *key; int err; msi_wmi_input_dev = input_allocate_device(); @@ -277,26 +207,25 @@ static int __init msi_wmi_input_setup(void) msi_wmi_input_dev->name = "MSI WMI hotkeys"; msi_wmi_input_dev->phys = "wmi/input0"; msi_wmi_input_dev->id.bustype = BUS_HOST; - msi_wmi_input_dev->getkeycode = msi_wmi_getkeycode; - msi_wmi_input_dev->setkeycode = msi_wmi_setkeycode; - - for (key = msi_wmi_keymap; key->type != KE_END; key++) { - switch (key->type) { - case KE_KEY: - set_bit(EV_KEY, msi_wmi_input_dev->evbit); - set_bit(key->keycode, msi_wmi_input_dev->keybit); - break; - } - } + + err = sparse_keymap_setup(msi_wmi_input_dev, msi_wmi_keymap, NULL); + if (err) + goto err_free_dev; err = input_register_device(msi_wmi_input_dev); - if (err) { - input_free_device(msi_wmi_input_dev); - return err; - } + if (err) + goto err_free_keymap; + + memset(last_pressed, 0, sizeof(last_pressed)); return 0; + +err_free_keymap: + sparse_keymap_free(msi_wmi_input_dev); +err_free_dev: + input_free_device(msi_wmi_input_dev); + return err; } static int __init msi_wmi_init(void) @@ -347,6 +276,7 @@ static void __exit msi_wmi_exit(void) { if (wmi_has_guid(MSIWMI_EVENT_GUID)) { wmi_remove_notify_handler(MSIWMI_EVENT_GUID); + sparse_keymap_free(msi_wmi_input_dev); input_unregister_device(msi_wmi_input_dev); backlight_device_unregister(backlight); } -- cgit v1.2.3-70-g09d2 From de078e5747fa3a95efac04fd6725dcceb4520416 Mon Sep 17 00:00:00 2001 From: Anisse Astier Date: Mon, 14 Dec 2009 10:21:39 +0100 Subject: msi-wmi: depend on backlight and fix corner-cases problems Now depends on BACKLIGHT_CLASS_DEVICE. Driver will return an error if it can't get actual backlight value Fix remapping of brightness keys when backlight is not controlled by ACPI. Signed-off-by: Anisse Astier Signed-off-by: Len Brown --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/msi-wmi.c | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) (limited to 'drivers/platform/x86/Kconfig') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 1f82d6df96e..a006dec3b3e 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -369,6 +369,7 @@ config MSI_WMI tristate "MSI WMI extras" depends on ACPI_WMI depends on INPUT + depends on BACKLIGHT_CLASS_DEVICE select INPUT_SPARSEKMAP help Say Y here if you want to support WMI-based hotkeys on MSI laptops. diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index e25b80c530f..0c8fe145c4a 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -53,11 +53,15 @@ MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-6638:*"); #define dprintk(msg...) pr_debug(DRV_PFX msg) #define KEYCODE_BASE 0xD0 +#define MSI_WMI_BRIGHTNESSUP KEYCODE_BASE +#define MSI_WMI_BRIGHTNESSDOWN (KEYCODE_BASE + 1) +#define MSI_WMI_VOLUMEUP (KEYCODE_BASE + 2) +#define MSI_WMI_VOLUMEDOWN (KEYCODE_BASE + 3) static struct key_entry msi_wmi_keymap[] = { - { KE_KEY, KEYCODE_BASE, {KEY_BRIGHTNESSUP} }, - { KE_KEY, KEYCODE_BASE + 1, {KEY_BRIGHTNESSDOWN} }, - { KE_KEY, KEYCODE_BASE + 2, {KEY_VOLUMEUP} }, - { KE_KEY, KEYCODE_BASE + 3, {KEY_VOLUMEDOWN} }, + { KE_KEY, MSI_WMI_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, + { KE_KEY, MSI_WMI_BRIGHTNESSDOWN, {KEY_BRIGHTNESSDOWN} }, + { KE_KEY, MSI_WMI_VOLUMEUP, {KEY_VOLUMEUP} }, + { KE_KEY, MSI_WMI_VOLUMEDOWN, {KEY_VOLUMEDOWN} }, { KE_END, 0} }; static ktime_t last_pressed[ARRAY_SIZE(msi_wmi_keymap) - 1]; @@ -110,12 +114,14 @@ static int msi_wmi_set_block(int instance, int value) static int bl_get(struct backlight_device *bd) { - int level, err, ret = 0; + int level, err, ret; /* Instance 1 is "get backlight", cmp with DSDT */ err = msi_wmi_query_block(1, &ret); - if (err) + if (err) { printk(KERN_ERR DRV_PFX "Could not query backlight: %d\n", err); + return -EINVAL; + } dprintk("Get: Query block returned: %d\n", ret); for (level = 0; level < ARRAY_SIZE(backlight_map); level++) { if (backlight_map[level] == ret) { @@ -180,8 +186,9 @@ static void msi_wmi_notify(u32 value, void *context) if (key->type == KE_KEY && /* Brightness is served via acpi video driver */ - (backlight || (key->keycode != KEY_BRIGHTNESSUP && - key->keycode != KEY_BRIGHTNESSDOWN))) { + (!acpi_video_backlight_support() || + (key->code != MSI_WMI_BRIGHTNESSUP && + key->code != MSI_WMI_BRIGHTNESSDOWN))) { dprintk("Send key: 0x%X - " "Input layer keycode: %d\n", key->code, key->keycode); -- cgit v1.2.3-70-g09d2 From 529aa8cb0a59367d08883f818e8c47028e819d0d Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Mon, 21 Dec 2009 16:20:01 -0800 Subject: classmate-laptop: add support for Classmate PC ACPI devices This add supports for devices like keyboard, backlight, tablet and accelerometer. This work is supported by International Syst S/A. [randy.dunlap@oracle.com: cmpc_acpi: depends on ACPI] [akpm@linux-foundation.org: readability tweaks] [akpm@linux-foundation.org: coding-style fixes] Signed-off-by: Thadeu Lima de Souza Cascardo Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- MAINTAINERS | 6 + drivers/platform/x86/Kconfig | 12 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/classmate-laptop.c | 609 ++++++++++++++++++++++++++++++++ 4 files changed, 628 insertions(+) create mode 100644 drivers/platform/x86/classmate-laptop.c (limited to 'drivers/platform/x86/Kconfig') diff --git a/MAINTAINERS b/MAINTAINERS index efd2ef2c266..9ddfc97b1de 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1470,6 +1470,12 @@ L: linux-scsi@vger.kernel.org S: Supported F: drivers/scsi/fnic/ +CMPC ACPI DRIVER +M: Thadeu Lima de Souza Cascardo +M: Daniel Oliveira Nascimento +S: Supported +F: drivers/platform/x86/classmate-laptop.c + CODA FILE SYSTEM M: Jan Harkes M: coda@cs.cmu.edu diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index fc5bf9d2a3f..ec4faffe6b0 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -464,4 +464,16 @@ config TOSHIBA_BT_RFKILL If you have a modern Toshiba laptop with a Bluetooth and an RFKill switch (such as the Portege R500), say Y. + +config ACPI_CMPC + tristate "CMPC Laptop Extras" + depends on X86 && ACPI + select INPUT + select BACKLIGHT_CLASS_DEVICE + default n + help + Support for Intel Classmate PC ACPI devices, including some + keys as input device, backlight device, tablet and accelerometer + devices. + endif # X86_PLATFORM_DEVICES diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index b7474b6a8bf..9cd9fa0a27e 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o +obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c new file mode 100644 index 00000000000..ed90082cdf1 --- /dev/null +++ b/drivers/platform/x86/classmate-laptop.c @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2009 Thadeu Lima de Souza Cascardo + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); + + +struct cmpc_accel { + int sensitivity; +}; + +#define CMPC_ACCEL_SENSITIVITY_DEFAULT 5 + + +/* + * Generic input device code. + */ + +typedef void (*input_device_init)(struct input_dev *dev); + +static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name, + input_device_init idev_init) +{ + struct input_dev *inputdev; + int error; + + inputdev = input_allocate_device(); + if (!inputdev) + return -ENOMEM; + inputdev->name = name; + inputdev->dev.parent = &acpi->dev; + idev_init(inputdev); + error = input_register_device(inputdev); + if (error) { + input_free_device(inputdev); + return error; + } + dev_set_drvdata(&acpi->dev, inputdev); + return 0; +} + +static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi) +{ + struct input_dev *inputdev = dev_get_drvdata(&acpi->dev); + input_unregister_device(inputdev); + return 0; +} + +/* + * Accelerometer code. + */ +static acpi_status cmpc_start_accel(acpi_handle handle) +{ + union acpi_object param[2]; + struct acpi_object_list input; + acpi_status status; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x3; + param[1].type = ACPI_TYPE_INTEGER; + input.count = 2; + input.pointer = param; + status = acpi_evaluate_object(handle, "ACMD", &input, NULL); + return status; +} + +static acpi_status cmpc_stop_accel(acpi_handle handle) +{ + union acpi_object param[2]; + struct acpi_object_list input; + acpi_status status; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x4; + param[1].type = ACPI_TYPE_INTEGER; + input.count = 2; + input.pointer = param; + status = acpi_evaluate_object(handle, "ACMD", &input, NULL); + return status; +} + +static acpi_status cmpc_accel_set_sensitivity(acpi_handle handle, int val) +{ + union acpi_object param[2]; + struct acpi_object_list input; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x02; + param[1].type = ACPI_TYPE_INTEGER; + param[1].integer.value = val; + input.count = 2; + input.pointer = param; + return acpi_evaluate_object(handle, "ACMD", &input, NULL); +} + +static acpi_status cmpc_get_accel(acpi_handle handle, + unsigned char *x, + unsigned char *y, + unsigned char *z) +{ + union acpi_object param[2]; + struct acpi_object_list input; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, 0 }; + unsigned char *locs; + acpi_status status; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x01; + param[1].type = ACPI_TYPE_INTEGER; + input.count = 2; + input.pointer = param; + status = acpi_evaluate_object(handle, "ACMD", &input, &output); + if (ACPI_SUCCESS(status)) { + union acpi_object *obj; + obj = output.pointer; + locs = obj->buffer.pointer; + *x = locs[0]; + *y = locs[1]; + *z = locs[2]; + kfree(output.pointer); + } + return status; +} + +static void cmpc_accel_handler(struct acpi_device *dev, u32 event) +{ + if (event == 0x81) { + unsigned char x, y, z; + acpi_status status; + + status = cmpc_get_accel(dev->handle, &x, &y, &z); + if (ACPI_SUCCESS(status)) { + struct input_dev *inputdev = dev_get_drvdata(&dev->dev); + + input_report_abs(inputdev, ABS_X, x); + input_report_abs(inputdev, ABS_Y, y); + input_report_abs(inputdev, ABS_Z, z); + input_sync(inputdev); + } + } +} + +static ssize_t cmpc_accel_sensitivity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi; + struct input_dev *inputdev; + struct cmpc_accel *accel; + + acpi = to_acpi_device(dev); + inputdev = dev_get_drvdata(&acpi->dev); + accel = dev_get_drvdata(&inputdev->dev); + + return sprintf(buf, "%d\n", accel->sensitivity); +} + +static ssize_t cmpc_accel_sensitivity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi; + struct input_dev *inputdev; + struct cmpc_accel *accel; + unsigned long sensitivity; + int r; + + acpi = to_acpi_device(dev); + inputdev = dev_get_drvdata(&acpi->dev); + accel = dev_get_drvdata(&inputdev->dev); + + r = strict_strtoul(buf, 0, &sensitivity); + if (r) + return r; + + accel->sensitivity = sensitivity; + cmpc_accel_set_sensitivity(acpi->handle, sensitivity); + + return strnlen(buf, count); +} + +struct device_attribute cmpc_accel_sensitivity_attr = { + .attr = { .name = "sensitivity", .mode = 0660 }, + .show = cmpc_accel_sensitivity_show, + .store = cmpc_accel_sensitivity_store +}; + +static int cmpc_accel_open(struct input_dev *input) +{ + struct acpi_device *acpi; + + acpi = to_acpi_device(input->dev.parent); + if (ACPI_SUCCESS(cmpc_start_accel(acpi->handle))) + return 0; + return -EIO; +} + +static void cmpc_accel_close(struct input_dev *input) +{ + struct acpi_device *acpi; + + acpi = to_acpi_device(input->dev.parent); + cmpc_stop_accel(acpi->handle); +} + +static void cmpc_accel_idev_init(struct input_dev *inputdev) +{ + set_bit(EV_ABS, inputdev->evbit); + input_set_abs_params(inputdev, ABS_X, 0, 255, 8, 0); + input_set_abs_params(inputdev, ABS_Y, 0, 255, 8, 0); + input_set_abs_params(inputdev, ABS_Z, 0, 255, 8, 0); + inputdev->open = cmpc_accel_open; + inputdev->close = cmpc_accel_close; +} + +static int cmpc_accel_add(struct acpi_device *acpi) +{ + int error; + struct input_dev *inputdev; + struct cmpc_accel *accel; + + accel = kmalloc(sizeof(*accel), GFP_KERNEL); + if (!accel) + return -ENOMEM; + + accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; + cmpc_accel_set_sensitivity(acpi->handle, accel->sensitivity); + + error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr); + if (error) + goto failed_file; + + error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel", + cmpc_accel_idev_init); + if (error) + goto failed_input; + + inputdev = dev_get_drvdata(&acpi->dev); + dev_set_drvdata(&inputdev->dev, accel); + + return 0; + +failed_input: + device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); +failed_file: + kfree(accel); + return error; +} + +static int cmpc_accel_remove(struct acpi_device *acpi, int type) +{ + struct input_dev *inputdev; + struct cmpc_accel *accel; + + inputdev = dev_get_drvdata(&acpi->dev); + accel = dev_get_drvdata(&inputdev->dev); + + device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); + return cmpc_remove_acpi_notify_device(acpi); +} + +static const struct acpi_device_id cmpc_accel_device_ids[] = { + {"ACCE0000", 0}, + {"", 0} +}; +MODULE_DEVICE_TABLE(acpi, cmpc_accel_device_ids); + +static struct acpi_driver cmpc_accel_acpi_driver = { + .owner = THIS_MODULE, + .name = "cmpc_accel", + .class = "cmpc_accel", + .ids = cmpc_accel_device_ids, + .ops = { + .add = cmpc_accel_add, + .remove = cmpc_accel_remove, + .notify = cmpc_accel_handler, + } +}; + + +/* + * Tablet mode code. + */ +static acpi_status cmpc_get_tablet(acpi_handle handle, + unsigned long long *value) +{ + union acpi_object param; + struct acpi_object_list input; + unsigned long long output; + acpi_status status; + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = 0x01; + input.count = 1; + input.pointer = ¶m; + status = acpi_evaluate_integer(handle, "TCMD", &input, &output); + if (ACPI_SUCCESS(status)) + *value = output; + return status; +} + +static void cmpc_tablet_handler(struct acpi_device *dev, u32 event) +{ + unsigned long long val = 0; + struct input_dev *inputdev = dev_get_drvdata(&dev->dev); + + if (event == 0x81) { + if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) + input_report_switch(inputdev, SW_TABLET_MODE, !val); + } +} + +static void cmpc_tablet_idev_init(struct input_dev *inputdev) +{ + unsigned long long val = 0; + struct acpi_device *acpi; + + set_bit(EV_SW, inputdev->evbit); + set_bit(SW_TABLET_MODE, inputdev->swbit); + + acpi = to_acpi_device(inputdev->dev.parent); + if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) + input_report_switch(inputdev, SW_TABLET_MODE, !val); +} + +static int cmpc_tablet_add(struct acpi_device *acpi) +{ + return cmpc_add_acpi_notify_device(acpi, "cmpc_tablet", + cmpc_tablet_idev_init); +} + +static int cmpc_tablet_remove(struct acpi_device *acpi, int type) +{ + return cmpc_remove_acpi_notify_device(acpi); +} + +static int cmpc_tablet_resume(struct acpi_device *acpi) +{ + struct input_dev *inputdev = dev_get_drvdata(&acpi->dev); + unsigned long long val = 0; + if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) + input_report_switch(inputdev, SW_TABLET_MODE, !val); + return 0; +} + +static const struct acpi_device_id cmpc_tablet_device_ids[] = { + {"TBLT0000", 0}, + {"", 0} +}; +MODULE_DEVICE_TABLE(acpi, cmpc_tablet_device_ids); + +static struct acpi_driver cmpc_tablet_acpi_driver = { + .owner = THIS_MODULE, + .name = "cmpc_tablet", + .class = "cmpc_tablet", + .ids = cmpc_tablet_device_ids, + .ops = { + .add = cmpc_tablet_add, + .remove = cmpc_tablet_remove, + .resume = cmpc_tablet_resume, + .notify = cmpc_tablet_handler, + } +}; + + +/* + * Backlight code. + */ + +static acpi_status cmpc_get_brightness(acpi_handle handle, + unsigned long long *value) +{ + union acpi_object param; + struct acpi_object_list input; + unsigned long long output; + acpi_status status; + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = 0xC0; + input.count = 1; + input.pointer = ¶m; + status = acpi_evaluate_integer(handle, "GRDI", &input, &output); + if (ACPI_SUCCESS(status)) + *value = output; + return status; +} + +static acpi_status cmpc_set_brightness(acpi_handle handle, + unsigned long long value) +{ + union acpi_object param[2]; + struct acpi_object_list input; + acpi_status status; + unsigned long long output; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0xC0; + param[1].type = ACPI_TYPE_INTEGER; + param[1].integer.value = value; + input.count = 2; + input.pointer = param; + status = acpi_evaluate_integer(handle, "GWRI", &input, &output); + return status; +} + +static int cmpc_bl_get_brightness(struct backlight_device *bd) +{ + acpi_status status; + acpi_handle handle; + unsigned long long brightness; + + handle = bl_get_data(bd); + status = cmpc_get_brightness(handle, &brightness); + if (ACPI_SUCCESS(status)) + return brightness; + else + return -1; +} + +static int cmpc_bl_update_status(struct backlight_device *bd) +{ + acpi_status status; + acpi_handle handle; + + handle = bl_get_data(bd); + status = cmpc_set_brightness(handle, bd->props.brightness); + if (ACPI_SUCCESS(status)) + return 0; + else + return -1; +} + +static struct backlight_ops cmpc_bl_ops = { + .get_brightness = cmpc_bl_get_brightness, + .update_status = cmpc_bl_update_status +}; + +static int cmpc_bl_add(struct acpi_device *acpi) +{ + struct backlight_device *bd; + + bd = backlight_device_register("cmpc_bl", &acpi->dev, + acpi->handle, &cmpc_bl_ops); + bd->props.max_brightness = 7; + dev_set_drvdata(&acpi->dev, bd); + return 0; +} + +static int cmpc_bl_remove(struct acpi_device *acpi, int type) +{ + struct backlight_device *bd; + + bd = dev_get_drvdata(&acpi->dev); + backlight_device_unregister(bd); + return 0; +} + +static const struct acpi_device_id cmpc_device_ids[] = { + {"IPML200", 0}, + {"", 0} +}; +MODULE_DEVICE_TABLE(acpi, cmpc_device_ids); + +static struct acpi_driver cmpc_bl_acpi_driver = { + .owner = THIS_MODULE, + .name = "cmpc", + .class = "cmpc", + .ids = cmpc_device_ids, + .ops = { + .add = cmpc_bl_add, + .remove = cmpc_bl_remove + } +}; + + +/* + * Extra keys code. + */ +static int cmpc_keys_codes[] = { + KEY_UNKNOWN, + KEY_WLAN, + KEY_SWITCHVIDEOMODE, + KEY_BRIGHTNESSDOWN, + KEY_BRIGHTNESSUP, + KEY_VENDOR, + KEY_MAX +}; + +static void cmpc_keys_handler(struct acpi_device *dev, u32 event) +{ + struct input_dev *inputdev; + int code = KEY_MAX; + + if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes)) + code = cmpc_keys_codes[event & 0x0F]; + inputdev = dev_get_drvdata(&dev->dev);; + input_report_key(inputdev, code, !(event & 0x10)); +} + +static void cmpc_keys_idev_init(struct input_dev *inputdev) +{ + int i; + + set_bit(EV_KEY, inputdev->evbit); + for (i = 0; cmpc_keys_codes[i] != KEY_MAX; i++) + set_bit(cmpc_keys_codes[i], inputdev->keybit); +} + +static int cmpc_keys_add(struct acpi_device *acpi) +{ + return cmpc_add_acpi_notify_device(acpi, "cmpc_keys", + cmpc_keys_idev_init); +} + +static int cmpc_keys_remove(struct acpi_device *acpi, int type) +{ + return cmpc_remove_acpi_notify_device(acpi); +} + +static const struct acpi_device_id cmpc_keys_device_ids[] = { + {"FnBT0000", 0}, + {"", 0} +}; +MODULE_DEVICE_TABLE(acpi, cmpc_keys_device_ids); + +static struct acpi_driver cmpc_keys_acpi_driver = { + .owner = THIS_MODULE, + .name = "cmpc_keys", + .class = "cmpc_keys", + .ids = cmpc_keys_device_ids, + .ops = { + .add = cmpc_keys_add, + .remove = cmpc_keys_remove, + .notify = cmpc_keys_handler, + } +}; + + +/* + * General init/exit code. + */ + +static int cmpc_init(void) +{ + int r; + + r = acpi_bus_register_driver(&cmpc_keys_acpi_driver); + if (r) + goto failed_keys; + + r = acpi_bus_register_driver(&cmpc_bl_acpi_driver); + if (r) + goto failed_bl; + + r = acpi_bus_register_driver(&cmpc_tablet_acpi_driver); + if (r) + goto failed_tablet; + + r = acpi_bus_register_driver(&cmpc_accel_acpi_driver); + if (r) + goto failed_accel; + + return r; + +failed_accel: + acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); + +failed_tablet: + acpi_bus_unregister_driver(&cmpc_bl_acpi_driver); + +failed_bl: + acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); + +failed_keys: + return r; +} + +static void cmpc_exit(void) +{ + acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); + acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); + acpi_bus_unregister_driver(&cmpc_bl_acpi_driver); + acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); +} + +module_init(cmpc_init); +module_exit(cmpc_exit); -- cgit v1.2.3-70-g09d2 From ff850c339a1a6a7724537160c73cdc09a483fc5d Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 26 Dec 2009 22:52:15 -0200 Subject: thinkpad-acpi: make volume subdriver optional Allow the user to choose through Kconfig if the Console Audio Control interface (aka "volume subdriver") should be available or not. This not only saves some memory, but also allows the thinkpad-acpi driver to be built-in even if ALSA is modular when the console audio control interface is not wanted. This change fixes a build problem that is causing some annoyances, in a way that doesn't disable the entire driver on kernels without ALSA support. Signed-off-by: Henrique de Moraes Holschuh Cc: Ingo Molnar Cc: Amerigo Wang Cc: Helight Xu Cc: Takashi Iwai Signed-off-by: Len Brown --- drivers/platform/x86/Kconfig | 23 +++++++++++++++++++++++ drivers/platform/x86/thinkpad_acpi.c | 26 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) (limited to 'drivers/platform/x86/Kconfig') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ec4faffe6b0..2462dc30b39 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -233,6 +233,29 @@ config THINKPAD_ACPI If you have an IBM or Lenovo ThinkPad laptop, say Y or M here. +config THINKPAD_ACPI_ALSA_SUPPORT + bool "Console audio control ALSA interface" + depends on THINKPAD_ACPI + depends on SND + depends on SND = y || THINKPAD_ACPI = SND + default y + ---help--- + Enables monitoring of the built-in console audio output control + (headphone and speakers), which is operated by the mute and (in + some ThinkPad models) volume hotkeys. + + If this option is enabled, ThinkPad-ACPI will export an ALSA card + with a single read-only mixer control, which should be used for + on-screen-display feedback purposes by the Desktop Environment. + + Optionally, the driver will also allow software control (the + ALSA mixer will be made read-write). Please refer to the driver + documentation for details. + + All IBM models have both volume and mute control. Newer Lenovo + models only have mute control (the volume hotkeys are just normal + keys and volume control is done through the main HDA mixer). + config THINKPAD_ACPI_DEBUGFACILITIES bool "Maintainer debug facilities" depends on THINKPAD_ACPI diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 9b7da9c96e7..e67e4feb35c 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -6384,6 +6384,8 @@ static struct ibm_struct brightness_driver_data = { * and we leave them unchanged. */ +#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT + #define TPACPI_ALSA_DRVNAME "ThinkPad EC" #define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control" #define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME @@ -7021,6 +7023,28 @@ static struct ibm_struct volume_driver_data = { .shutdown = volume_shutdown, }; +#else /* !CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */ + +#define alsa_card NULL + +static void inline volume_alsa_notify_change(void) +{ +} + +static int __init volume_init(struct ibm_init_struct *iibm) +{ + printk(TPACPI_INFO + "volume: disabled as there is no ALSA support in this kernel\n"); + + return 1; +} + +static struct ibm_struct volume_driver_data = { + .name = "volume", +}; + +#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */ + /************************************************************************* * Fan subdriver */ @@ -8743,6 +8767,7 @@ MODULE_PARM_DESC(hotkey_report_mode, "used for backwards compatibility with userspace, " "see documentation"); +#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT module_param_named(volume_mode, volume_mode, uint, 0444); MODULE_PARM_DESC(volume_mode, "Selects volume control strategy: " @@ -8765,6 +8790,7 @@ module_param_named(id, alsa_id, charp, 0444); MODULE_PARM_DESC(id, "ALSA id for the ACPI EC Mixer"); module_param_named(enable, alsa_enable, bool, 0444); MODULE_PARM_DESC(enable, "Enable the ALSA interface for the ACPI EC Mixer"); +#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */ #define TPACPI_PARAM(feature) \ module_param_call(feature, set_ibm_param, NULL, NULL, 0); \ -- cgit v1.2.3-70-g09d2 From 6e5b08ee941af38cfc6456158e7e04c1bc49306f Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 26 Dec 2009 22:52:17 -0200 Subject: thinkpad-acpi: improve Kconfig help text Document that rfkill and ALSA functionality exists, but requires the subsystems to be available, and not modular if thinkpad-acpi is not modular. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/platform/x86/Kconfig | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/platform/x86/Kconfig') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 2462dc30b39..db32c25e360 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -231,6 +231,11 @@ config THINKPAD_ACPI This driver was formerly known as ibm-acpi. + Extra functionality will be available if the rfkill (CONFIG_RFKILL) + and/or ALSA (CONFIG_SND) subsystems are available in the kernel. + Note that if you want ThinkPad-ACPI to be built-in instead of + modular, ALSA and rfkill will also have to be built-in. + If you have an IBM or Lenovo ThinkPad laptop, say Y or M here. config THINKPAD_ACPI_ALSA_SUPPORT -- cgit v1.2.3-70-g09d2