Re: Add INPUT support to toshiba_acpi

From: Richard Hughes
Date: Thu May 31 2007 - 12:00:39 EST


On Thu, 2007-05-31 at 14:44 +0100, Richard Hughes wrote:
> Nope, impossible AFAICS. The hardware is just broken. Windows XP has an
> toshiba supplied daemon that polls, so I think we have to just bite the
> bullet.

... adding in reply in different thread...

On Thu, 2007-05-31 at 10:37 -0400, Dmitry Torokhov wrote:
> Please use input-polldev to set up polled devices. It
> will create work queue for you and will make sure that polling is
> stopped when device is closed.

Okay, I had never heard of this. I've done the suggested changes in the
attached patch. Please can you have a look and make sure I'm being sane.

> Also I don't think you want to use
> KEY_BREAK. What is the expected function of that key?

It's a "lock" key, I really want KEY_LOCK added to input.h, but that
might prove difficult. For now I've used KEY_CLEAR, yell if you think
there's a better one to substitute or if you guys want me to add get a
constant added to input.h.

Thanks for the review.

Richard.

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 139f41f..38835b6 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -222,6 +222,7 @@ config ACPI_TOSHIBA
tristate "Toshiba Laptop Extras"
depends on X86
select BACKLIGHT_CLASS_DEVICE
+ select INPUT_POLLDEV
---help---
This driver adds support for access to certain system settings
on "legacy free" Toshiba laptops. These laptops can be recognized by
diff --git a/drivers/acpi/toshiba_acpi.c b/drivers/acpi/toshiba_acpi.c
index 3906d47..2b1a3d9 100644
--- a/drivers/acpi/toshiba_acpi.c
+++ b/drivers/acpi/toshiba_acpi.c
@@ -27,13 +27,13 @@
* engineering the Windows drivers
* Yasushi Nagato - changes for linux kernel 2.4 -> 2.5
* Rob Miller - TV out and hotkeys help
- *
+ * Richard Hughes - emit INPUT events for hotkeys
*
* TODO
*
*/

-#define TOSHIBA_ACPI_VERSION "0.18"
+#define TOSHIBA_ACPI_VERSION "0.19"
#define PROC_INTERFACE_VERSION 1

#include <linux/kernel.h>
@@ -42,6 +42,7 @@
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/backlight.h>
+#include <linux/input-polldev.h>

#include <asm/uaccess.h>

@@ -99,6 +100,16 @@ MODULE_LICENSE("GPL");
#define HCI_VIDEO_OUT_CRT 0x2
#define HCI_VIDEO_OUT_TV 0x4

+/* key definitions */
+#define HCI_HKEY_MUTE 0x0101
+#define HCI_HKEY_BREAK 0x013b
+#define HCI_HKEY_SEARCH 0x013c
+#define HCI_HKEY_SUSPEND 0x013d
+#define HCI_HKEY_HIBERNATE 0x013e
+#define HCI_HKEY_BRIGHTNESSDOWN 0x0140
+#define HCI_HKEY_BRIGHTNESSUP 0x0141
+#define HCI_HKEY_WLAN 0x0142
+
/* utility
*/

@@ -213,9 +224,15 @@ static acpi_status hci_read1(u32 reg, u32 * out1, u32 * result)

static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ;
static struct backlight_device *toshiba_backlight_device;
+static struct input_polled_dev *toshiba_poll_dev;
static int force_fan;
static int last_key_event;
static int key_event_valid;
+static int hotkeys_over_input = 1;
+static int hotkeys_check_per_sec = 2;
+
+module_param(hotkeys_over_input, uint, 0400);
+module_param(hotkeys_check_per_sec, uint, 0400);

typedef struct _ProcItem {
const char *name;
@@ -443,27 +460,33 @@ static char *read_keys(char *p)
u32 hci_result;
u32 value;

- if (!key_event_valid) {
- hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result);
- if (hci_result == HCI_SUCCESS) {
- key_event_valid = 1;
- last_key_event = value;
- } else if (hci_result == HCI_EMPTY) {
- /* better luck next time */
- } 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");
- } else {
- printk(MY_ERR "Error reading hotkey status\n");
- goto end;
+ if (!hotkeys_over_input) {
+ if (!key_event_valid) {
+ hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result);
+ if (hci_result == HCI_SUCCESS) {
+ key_event_valid = 1;
+ last_key_event = value;
+ } else if (hci_result == HCI_EMPTY) {
+ /* better luck next time */
+ } 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");
+ } else {
+ printk(MY_ERR "Error reading hotkey status\n");
+ goto end;
+ }
}
+ } else {
+ key_event_valid = 0;
+ last_key_event = 0;
}

p += sprintf(p, "hotkey_ready: %d\n", key_event_valid);
p += sprintf(p, "hotkey: 0x%04x\n", last_key_event);
+ p += sprintf(p, "hotkeys_via_input: %d\n", hotkeys_over_input);

end:
return p;
@@ -534,15 +557,127 @@ static acpi_status __exit remove_device(void)
}

static struct backlight_ops toshiba_backlight_data = {
- .get_brightness = get_lcd,
- .update_status = set_lcd_status,
+ .get_brightness = get_lcd,
+ .update_status = set_lcd_status,
};

+static void toshiba_deliver_button_event(struct input_dev *input, u32 value)
+{
+ int keycode = KEY_UNKNOWN;
+ int key_down = 1;
+
+ if (!input)
+ return;
+
+ /* translate MSB to key up */
+ if (value & 0x80) {
+ value &= ~0x80;
+ key_down = 0;
+ }
+
+ /* ignore FN on its own */
+ if (value == 0x0100)
+ return;
+
+ /* translate keys to keycodes */
+ switch (value) {
+ case HCI_HKEY_MUTE:
+ keycode = KEY_MUTE;
+ break;
+ case HCI_HKEY_BREAK:
+ keycode = KEY_BREAK;
+ break;
+ case HCI_HKEY_SEARCH:
+ keycode = KEY_SEARCH;
+ break;
+ case HCI_HKEY_SUSPEND:
+ keycode = KEY_SLEEP;
+ break;
+ case HCI_HKEY_HIBERNATE:
+ keycode = KEY_SUSPEND;
+ break;
+ case HCI_HKEY_BRIGHTNESSDOWN:
+ keycode = KEY_BRIGHTNESSDOWN;
+ break;
+ case HCI_HKEY_BRIGHTNESSUP:
+ keycode = KEY_BRIGHTNESSUP;
+ break;
+ case HCI_HKEY_WLAN:
+ keycode = KEY_WLAN;
+ break;
+ default:
+ keycode = KEY_UNKNOWN;
+ }
+
+ if (keycode != KEY_UNKNOWN) {
+ printk(MY_INFO "mapped hkey %i to keycode %i [%i]\n", value, keycode, key_down);
+ input_report_key(input, keycode, key_down);
+ input_sync(input);
+ }
+}
+
+static void toshiba_keys_clear(void)
+{
+ int dropped = 0;
+ int clear_queue = 0;
+ u32 hci_result, value;
+
+ do {
+ /* We don't want to get stuck here; older toshibas such as the
+ * A60 may load and then return junk during the hci_read */
+ if (clear_queue++ > 16)
+ break;
+
+ hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result);
+ if (hci_result == HCI_SUCCESS) {
+ dropped++;
+ } else if (hci_result == HCI_EMPTY) {
+ /* better luck next time */
+ } 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);
+
+ printk(MY_INFO "Dropped %d keys from the queue on startup\n", dropped);
+}
+
+static void handle_buttons(struct input_polled_dev *dev)
+{
+ u32 hci_result, value;
+ struct input_dev *input = dev->input;
+
+ do {
+ hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result);
+ if (hci_result == HCI_SUCCESS) {
+ /* we got a button event */
+ toshiba_deliver_button_event(input, value);
+ } else if (hci_result == HCI_EMPTY) {
+ /* better luck next time */
+ } 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_SUCCESS);
+}
+
static void __exit toshiba_acpi_exit(void)
{
if (toshiba_backlight_device)
backlight_device_unregister(toshiba_backlight_device);

+ if (toshiba_poll_dev) {
+ input_unregister_polled_device(toshiba_poll_dev);
+ input_free_polled_device(toshiba_poll_dev);
+ }
+
remove_device();

if (toshiba_proc_dir)
@@ -551,6 +686,49 @@ static void __exit toshiba_acpi_exit(void)
return;
}

+static int __init toshiba_input_polldev_init(void)
+{
+ int error;
+ struct input_dev *input;
+
+ /* use INPUT for key events */
+ toshiba_poll_dev = input_allocate_polled_device();;
+ if (!toshiba_poll_dev) {
+ error = -ENOMEM;
+ goto err_no_mem;
+ }
+ toshiba_poll_dev->poll = handle_buttons;
+ toshiba_poll_dev->poll_interval = HZ / hotkeys_check_per_sec;
+ input = toshiba_poll_dev->input;
+
+ /* create one 'keyboard' virtual input device for all the acpi events */
+ input->name = "Toshiba Extra Buttons";
+ input->phys = "toshiba/input0";
+ input->id.bustype = BUS_HOST;
+ input->id.product = 0x0001;
+ input->evbit[0] = BIT(EV_KEY);
+ set_bit(KEY_MUTE, input->keybit);
+ set_bit(KEY_BREAK, input->keybit); /* probably should be KEY_LOCK */
+ set_bit(KEY_SEARCH, input->keybit);
+ set_bit(KEY_SUSPEND, input->keybit);
+ set_bit(KEY_SLEEP, input->keybit);
+ set_bit(KEY_BRIGHTNESSDOWN, input->keybit);
+ set_bit(KEY_BRIGHTNESSUP, input->keybit);
+ set_bit(KEY_WLAN, input->keybit);
+
+ toshiba_keys_clear();
+
+ error = input_register_polled_device(toshiba_poll_dev);
+ if (error)
+ goto err_free_input;
+ return 0;
+
+err_free_input:
+ input_free_polled_device(toshiba_poll_dev);
+err_no_mem:
+ return error;
+}
+
static int __init toshiba_acpi_init(void)
{
acpi_status status = AE_OK;
@@ -590,12 +768,28 @@ static int __init toshiba_acpi_init(void)
toshiba_backlight_device = backlight_device_register("toshiba",NULL,
NULL,
&toshiba_backlight_data);
- if (IS_ERR(toshiba_backlight_device)) {
+ if (IS_ERR(toshiba_backlight_device)) {
printk(KERN_ERR "Could not register toshiba backlight device\n");
toshiba_backlight_device = NULL;
toshiba_acpi_exit();
}
- toshiba_backlight_device->props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1;
+ toshiba_backlight_device->props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1;
+
+ /* we have to poll the device as we do not get events */
+ if (hotkeys_over_input && hotkeys_check_per_sec > 0) {
+
+ /* sanitise to something sane */
+ if (hotkeys_check_per_sec > 10)
+ hotkeys_check_per_sec = 10;
+ printk(KERN_INFO "ktoshkeyd checks per second : %d\n", hotkeys_check_per_sec);
+
+ toshiba_input_polldev_init ();
+ /* just abort */
+ if (!toshiba_poll_dev) {
+ printk(KERN_ERR "could not allocate input device\n");
+ toshiba_acpi_exit();
+ }
+ }

return (ACPI_SUCCESS(status)) ? 0 : -ENODEV;
}