| commit 61a2aa30877a6e2be1d3fb3a71385e1f741819d7 |
| Author: Matthew Garrett <mjg@redhat.com> |
| Date: Fri Mar 6 00:25:45 2009 +0000 |
| |
| toshiba-acpi: Add support for hotkey notifications |
| |
| Calling the ENAB method on Toshiba laptops results in notifications being |
| sent when laptop hotkeys are pressed. This patch simply calls that method |
| and sets up an input device if it's successful. |
| |
| Signed-off-by: Matthew Garrett <mjg@redhat.com> |
| |
| diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c |
| index 40e60fc..604f9fa 100644 |
| |
| |
| @@ -46,6 +46,7 @@ |
| #include <linux/platform_device.h> |
| #include <linux/rfkill.h> |
| #include <linux/input-polldev.h> |
| +#include <linux/input.h> |
| |
| #include <asm/uaccess.h> |
| |
| @@ -62,9 +63,10 @@ MODULE_LICENSE("GPL"); |
| |
| /* Toshiba ACPI method paths */ |
| #define METHOD_LCD_BRIGHTNESS "\\_SB_.PCI0.VGA_.LCD_._BCM" |
| -#define METHOD_HCI_1 "\\_SB_.VALD.GHCI" |
| -#define METHOD_HCI_2 "\\_SB_.VALZ.GHCI" |
| +#define TOSH_INTERFACE_1 "\\_SB_.VALD" |
| +#define TOSH_INTERFACE_2 "\\_SB_.VALZ" |
| #define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX" |
| +#define GHCI_METHOD ".GHCI" |
| |
| /* Toshiba HCI interface definitions |
| * |
| @@ -116,6 +118,36 @@ static const struct acpi_device_id toshiba_device_ids[] = { |
| }; |
| MODULE_DEVICE_TABLE(acpi, toshiba_device_ids); |
| |
| +struct key_entry { |
| + char type; |
| + u16 code; |
| + u16 keycode; |
| +}; |
| + |
| +enum {KE_KEY, KE_END}; |
| + |
| +static struct key_entry toshiba_acpi_keymap[] = { |
| + {KE_KEY, 0x101, KEY_MUTE}, |
| + {KE_KEY, 0x13b, KEY_COFFEE}, |
| + {KE_KEY, 0x13c, KEY_BATTERY}, |
| + {KE_KEY, 0x13d, KEY_SLEEP}, |
| + {KE_KEY, 0x13e, KEY_SUSPEND}, |
| + {KE_KEY, 0x13f, KEY_SWITCHVIDEOMODE}, |
| + {KE_KEY, 0x140, KEY_BRIGHTNESSDOWN}, |
| + {KE_KEY, 0x141, KEY_BRIGHTNESSUP}, |
| + {KE_KEY, 0x142, KEY_WLAN}, |
| + {KE_KEY, 0x143, KEY_PROG1}, |
| + {KE_KEY, 0xb05, KEY_PROG2}, |
| + {KE_KEY, 0xb06, KEY_WWW}, |
| + {KE_KEY, 0xb07, KEY_MAIL}, |
| + {KE_KEY, 0xb30, KEY_STOP}, |
| + {KE_KEY, 0xb31, KEY_PREVIOUSSONG}, |
| + {KE_KEY, 0xb32, KEY_NEXTSONG}, |
| + {KE_KEY, 0xb33, KEY_PLAYPAUSE}, |
| + {KE_KEY, 0xb5a, KEY_MEDIA}, |
| + {KE_END, 0, 0}, |
| +}; |
| + |
| /* utility |
| */ |
| |
| @@ -252,6 +284,8 @@ struct toshiba_acpi_dev { |
| struct platform_device *p_dev; |
| struct rfkill *rfk_dev; |
| struct input_polled_dev *poll_dev; |
| + struct input_dev *hotkey_dev; |
| + acpi_handle handle; |
| |
| const char *bt_name; |
| const char *rfk_name; |
| @@ -702,6 +736,154 @@ static struct backlight_ops toshiba_backlight_data = { |
| .update_status = set_lcd_status, |
| }; |
| |
| +static struct key_entry *toshiba_acpi_get_entry_by_scancode(int code) |
| +{ |
| + struct key_entry *key; |
| + |
| + for (key = toshiba_acpi_keymap; key->type != KE_END; key++) |
| + if (code == key->code) |
| + return key; |
| + |
| + return NULL; |
| +} |
| + |
| +static struct key_entry *toshiba_acpi_get_entry_by_keycode(int code) |
| +{ |
| + struct key_entry *key; |
| + |
| + for (key = toshiba_acpi_keymap; key->type != KE_END; key++) |
| + if (code == key->keycode && key->type == KE_KEY) |
| + return key; |
| + |
| + return NULL; |
| +} |
| + |
| +static int toshiba_acpi_getkeycode(struct input_dev *dev, int scancode, |
| + int *keycode) |
| +{ |
| + struct key_entry *key = toshiba_acpi_get_entry_by_scancode(scancode); |
| + |
| + if (key && key->type == KE_KEY) { |
| + *keycode = key->keycode; |
| + return 0; |
| + } |
| + |
| + return -EINVAL; |
| +} |
| + |
| +static int toshiba_acpi_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 = toshiba_acpi_get_entry_by_scancode(scancode); |
| + if (key && key->type == KE_KEY) { |
| + old_keycode = key->keycode; |
| + key->keycode = keycode; |
| + set_bit(keycode, dev->keybit); |
| + if (!toshiba_acpi_get_entry_by_keycode(old_keycode)) |
| + clear_bit(old_keycode, dev->keybit); |
| + return 0; |
| + } |
| + |
| + return -EINVAL; |
| +} |
| + |
| +static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *data) |
| +{ |
| + u32 hci_result, value; |
| + struct key_entry *key; |
| + |
| + if (event != 0x80) |
| + return; |
| + do { |
| + hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); |
| + if (hci_result == HCI_SUCCESS) { |
| + if (value == 0x100) |
| + continue; |
| + else if (value & 0x80) { |
| + key = toshiba_acpi_get_entry_by_scancode |
| + (value & ~0x80); |
| + if (!key) { |
| + printk(MY_INFO "Unknown key %x\n", |
| + value & ~0x80); |
| + continue; |
| + } |
| + input_report_key(toshiba_acpi.hotkey_dev, |
| + key->keycode, 1); |
| + input_sync(toshiba_acpi.hotkey_dev); |
| + input_report_key(toshiba_acpi.hotkey_dev, |
| + key->keycode, 0); |
| + input_sync(toshiba_acpi.hotkey_dev); |
| + } |
| + } else if (hci_result == HCI_NOT_SUPPORTED) { |
| + /* This is a workaround for an unresolved issue on |
| + * some machines where system events sporadically |
| + * become disabled. */ |
| + hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); |
| + printk(MY_NOTICE "Re-enabled hotkeys\n"); |
| + } |
| + } while (hci_result != HCI_EMPTY); |
| +} |
| + |
| +static int toshiba_acpi_setup_keyboard(char *device) |
| +{ |
| + acpi_status status; |
| + acpi_handle handle; |
| + int result; |
| + const struct key_entry *key; |
| + |
| + status = acpi_get_handle(NULL, device, &handle); |
| + if (ACPI_FAILURE(status)) { |
| + printk(MY_INFO "Unable to get notification device\n"); |
| + return -ENODEV; |
| + } |
| + |
| + toshiba_acpi.handle = handle; |
| + |
| + status = acpi_evaluate_object(handle, "ENAB", NULL, NULL); |
| + if (ACPI_FAILURE(status)) { |
| + printk(MY_INFO "Unable to enable hotkeys\n"); |
| + return -ENODEV; |
| + } |
| + |
| + status = acpi_install_notify_handler (handle, ACPI_DEVICE_NOTIFY, |
| + toshiba_acpi_notify, NULL); |
| + if (ACPI_FAILURE(status)) { |
| + printk(MY_INFO "Unable to install hotkey notification\n"); |
| + return -ENODEV; |
| + } |
| + |
| + toshiba_acpi.hotkey_dev = input_allocate_device(); |
| + if (!toshiba_acpi.hotkey_dev) { |
| + printk(MY_INFO "Unable to register input device\n"); |
| + return -ENOMEM; |
| + } |
| + |
| + toshiba_acpi.hotkey_dev->name = "Toshiba input device"; |
| + toshiba_acpi.hotkey_dev->phys = device; |
| + toshiba_acpi.hotkey_dev->id.bustype = BUS_HOST; |
| + toshiba_acpi.hotkey_dev->getkeycode = toshiba_acpi_getkeycode; |
| + toshiba_acpi.hotkey_dev->setkeycode = toshiba_acpi_setkeycode; |
| + |
| + for (key = toshiba_acpi_keymap; key->type != KE_END; key++) { |
| + set_bit(EV_KEY, toshiba_acpi.hotkey_dev->evbit); |
| + set_bit(key->keycode, toshiba_acpi.hotkey_dev->keybit); |
| + } |
| + |
| + result = input_register_device(toshiba_acpi.hotkey_dev); |
| + if (result) { |
| + printk(MY_INFO "Unable to register input device\n"); |
| + return result; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| static void toshiba_acpi_exit(void) |
| { |
| if (toshiba_acpi.poll_dev) { |
| @@ -709,12 +891,18 @@ static void toshiba_acpi_exit(void) |
| input_free_polled_device(toshiba_acpi.poll_dev); |
| } |
| |
| + if (toshiba_acpi.hotkey_dev) |
| + input_unregister_device(toshiba_acpi.hotkey_dev); |
| + |
| if (toshiba_acpi.rfk_dev) |
| rfkill_unregister(toshiba_acpi.rfk_dev); |
| |
| if (toshiba_backlight_device) |
| backlight_device_unregister(toshiba_backlight_device); |
| |
| + acpi_remove_notify_handler(toshiba_acpi.handle, ACPI_DEVICE_NOTIFY, |
| + toshiba_acpi_notify); |
| + |
| remove_device(); |
| |
| if (toshiba_proc_dir) |
| @@ -738,11 +926,15 @@ static int __init toshiba_acpi_init(void) |
| return -ENODEV; |
| |
| /* simple device detection: look for HCI method */ |
| - if (is_valid_acpi_path(METHOD_HCI_1)) |
| - method_hci = METHOD_HCI_1; |
| - else if (is_valid_acpi_path(METHOD_HCI_2)) |
| - method_hci = METHOD_HCI_2; |
| - else |
| + if (is_valid_acpi_path(TOSH_INTERFACE_1 GHCI_METHOD)) { |
| + method_hci = TOSH_INTERFACE_1 GHCI_METHOD; |
| + if (toshiba_acpi_setup_keyboard(TOSH_INTERFACE_1)) |
| + printk(MY_INFO "Unable to activate hotkeys\n"); |
| + } else if (is_valid_acpi_path(TOSH_INTERFACE_2 GHCI_METHOD)) { |
| + method_hci = TOSH_INTERFACE_2 GHCI_METHOD; |
| + if (toshiba_acpi_setup_keyboard(TOSH_INTERFACE_2)) |
| + printk(MY_INFO "Unable to activate hotkeys\n"); |
| + } else |
| return -ENODEV; |
| |
| printk(MY_INFO "Toshiba Laptop ACPI Extras version %s\n", |