diff options
author | Laxman Dewangan <ldewangan@nvidia.com> | 2011-03-08 20:45:10 +0530 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:41:53 -0800 |
commit | e70f83346e66351796ec54c569027fb19cf38c1c (patch) | |
tree | 19bde075cfaa8abbd07ada9edd6b64bb812a650c /drivers/input | |
parent | 6a17252e8158a7dba2243e50c9213d073b708466 (diff) |
input: keyboard: Adding keys support through interrupt lines
Adding keys support which are directly connected to interrupt lines.
Original-Change-Id: Ib26c06b170b82f4745e758be80b3e04122ad1d6c
Reviewed-on: http://git-master/r/22069
Tested-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
Original-Change-Id: I3921cc88f32558282654fbd36243b4b0b3574133
Rebase-Id: Rd7954ff7c2e0167a6a4765ff2f08494b9d0eba14
Diffstat (limited to 'drivers/input')
-rw-r--r-- | drivers/input/keyboard/Kconfig | 17 | ||||
-rw-r--r-- | drivers/input/keyboard/Makefile | 1 | ||||
-rwxr-xr-x | drivers/input/keyboard/interrupt_keys.c | 395 |
3 files changed, 413 insertions, 0 deletions
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index b4dee9d5a055..9433f4e5516c 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -203,6 +203,23 @@ config KEYBOARD_GPIO_POLLED To compile this driver as a module, choose M here: the module will be called gpio_keys_polled. +config KEYBOARD_INTERRUPT + tristate "Interrupt Buttons" + default n + help + This driver implements support for buttons connected + directly to interrupt pins of peripheral and peripheral + does not support any othe functionality like gpio + through that pins. + + Say Y here if your device has buttons connected + directly to such interrupt pins. Your board-specific + setup logic must also provide a platform device, + with configuration data saying which interrupts are used. + + To compile this driver as a module, choose M here: the + module will be called interrupt_keys. + config KEYBOARD_TCA6416 tristate "TCA6416/TCA6408A Keypad Support" depends on I2C diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index ddde0fd476f7..252fee12bf95 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_KEYBOARD_DAVINCI) += davinci_keyscan.o obj-$(CONFIG_KEYBOARD_EP93XX) += ep93xx_keypad.o obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o obj-$(CONFIG_KEYBOARD_GPIO_POLLED) += gpio_keys_polled.o +obj-$(CONFIG_KEYBOARD_INTERRUPT) += interrupt_keys.o obj-$(CONFIG_KEYBOARD_TCA6416) += tca6416-keypad.o obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o diff --git a/drivers/input/keyboard/interrupt_keys.c b/drivers/input/keyboard/interrupt_keys.c new file mode 100755 index 000000000000..2876bce2792f --- /dev/null +++ b/drivers/input/keyboard/interrupt_keys.c @@ -0,0 +1,395 @@ +/* + * drivers/input/keyboard/interrupt_keys.c + * Key driver for keys directly connected to interrupt lines. + * + * Copyright (c) 2011, NVIDIA Corporation. + * + * 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 <linux/module.h> + +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/sched.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt_keys.h> +#include <linux/workqueue.h> + +struct interrupt_button_data { + struct interrupt_keys_button *button; + struct input_dev *input; + struct timer_list timer; + struct work_struct work; + int timer_debounce; /* in msecs */ + bool disabled; +}; + +struct interrupt_keys_drvdata { + struct input_dev *input; + struct mutex disable_lock; + unsigned int n_int_buttons; + int (*enable)(struct device *dev); + void (*disable)(struct device *dev); + struct interrupt_button_data data[0]; +}; + +/** + * get_n_events_by_type() - returns maximum number of events per @type + * @type: type of button (%EV_KEY, %EV_SW) + * + * Return value of this function can be used to allocate bitmap + * large enough to hold all bits for given type. + */ +static inline int get_n_events_by_type(int type) +{ + BUG_ON(type != EV_SW && type != EV_KEY); + return (type == EV_KEY) ? KEY_CNT : SW_CNT; +} + +/** + * interrupt_keys_disable_button() - disables given interrupt button + * @bdata: button data for button to be disabled + * + * Disables button pointed by @bdata. This is done by masking + * IRQ line. After this function is called, button won't generate + * input events anymore. Note that one can only disable buttons + * that don't share IRQs. + * + * Make sure that @bdata->disable_lock is locked when entering + * this function to avoid races when concurrent threads are + * disabling buttons at the same time. + */ +static void interrupt_keys_disable_button(struct interrupt_button_data *bdata) +{ + if (!bdata->disabled) { + /* + * Disable IRQ and possible debouncing timer. + */ + disable_irq(bdata->button->irq); + if (bdata->timer_debounce) + del_timer_sync(&bdata->timer); + + bdata->disabled = true; + } +} + +/** + * interrupt_keys_enable_button() - enables given interrupt button + * @bdata: button data for button to be disabled + * + * Enables given button pointed by @bdata. + * + * Make sure that @bdata->disable_lock is locked when entering + * this function to avoid races with concurrent threads trying + * to enable the same button at the same time. + */ +static void interrupt_keys_enable_button(struct interrupt_button_data *bdata) +{ + if (bdata->disabled) { + enable_irq(bdata->button->irq); + bdata->disabled = false; + } +} + +static void interrupt_keys_report_event(struct interrupt_button_data *bdata) +{ + struct interrupt_keys_button *button = bdata->button; + struct input_dev *input = bdata->input; + unsigned int type = button->type ?: EV_KEY; + + input_event(input, type, button->code, 1); + input_sync(input); + input_event(input, type, button->code, 0); + input_sync(input); +} + +static void interrupt_keys_work_func(struct work_struct *work) +{ + struct interrupt_button_data *bdata = + container_of(work, struct interrupt_button_data, work); + + interrupt_keys_report_event(bdata); +} + +static void interrupt_keys_timer(unsigned long _data) +{ + struct interrupt_button_data *data = + (struct interrupt_button_data *)_data; + + schedule_work(&data->work); +} + +static irqreturn_t interrupt_keys_isr(int irq, void *dev_id) +{ + struct interrupt_button_data *bdata = dev_id; + struct interrupt_keys_button *button = bdata->button; + + BUG_ON(irq != button->irq); + + if (bdata->timer_debounce) + mod_timer(&bdata->timer, + jiffies + msecs_to_jiffies(bdata->timer_debounce)); + else + schedule_work(&bdata->work); + + return IRQ_HANDLED; +} + +static int __devinit interrupt_keys_setup_key(struct platform_device *pdev, + struct interrupt_button_data *bdata, + struct interrupt_keys_button *button) +{ + char *desc = button->desc ? button->desc : "int_keys"; + struct device *dev = &pdev->dev; + unsigned long irqflags; + int irq, error; + + setup_timer(&bdata->timer, interrupt_keys_timer, (unsigned long)bdata); + INIT_WORK(&bdata->work, interrupt_keys_work_func); + + irq = button->irq; + if (irq < 0) { + error = irq; + dev_err(dev, "Invalid irq number %d\n", button->irq); + goto fail; + } + + irqflags = 0; + /* + * If platform has specified that the button can be disabled, + * we don't want it to share the interrupt line. + */ + if (!button->can_disable) + irqflags |= IRQF_SHARED; + + error = request_threaded_irq(irq, NULL, interrupt_keys_isr, + irqflags, desc, bdata); + if (error) { + dev_err(dev, "Unable to register irq %d; error %d\n", + irq, error); + goto fail; + } + return 0; + +fail: + return error; +} + +static int interrupt_keys_open(struct input_dev *input) +{ + struct interrupt_keys_drvdata *ddata = input_get_drvdata(input); + + return ddata->enable ? ddata->enable(input->dev.parent) : 0; +} + +static void interrupt_keys_close(struct input_dev *input) +{ + struct interrupt_keys_drvdata *ddata = input_get_drvdata(input); + + if (ddata->disable) + ddata->disable(input->dev.parent); +} + +static int __devinit interrupt_keys_probe(struct platform_device *pdev) +{ + struct interrupt_keys_platform_data *pdata = pdev->dev.platform_data; + struct interrupt_keys_drvdata *ddata; + struct device *dev = &pdev->dev; + struct input_dev *input; + int i, error; + int wakeup = 0; + + ddata = kzalloc(sizeof(struct interrupt_keys_drvdata) + + pdata->nbuttons * sizeof(struct interrupt_button_data), + GFP_KERNEL); + input = input_allocate_device(); + if (!ddata || !input) { + dev_err(dev, "failed to allocate state\n"); + error = -ENOMEM; + goto fail1; + } + + ddata->input = input; + ddata->n_int_buttons = pdata->nbuttons; + ddata->enable = pdata->enable; + ddata->disable = pdata->disable; + mutex_init(&ddata->disable_lock); + + platform_set_drvdata(pdev, ddata); + input_set_drvdata(input, ddata); + + input->name = pdev->name; + input->phys = "int-keys/input0"; + input->dev.parent = &pdev->dev; + input->open = interrupt_keys_open; + input->close = interrupt_keys_close; + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + /* Enable auto repeat feature of Linux input subsystem */ + if (pdata->rep) + __set_bit(EV_REP, input->evbit); + + for (i = 0; i < pdata->nbuttons; i++) { + struct interrupt_keys_button *button = &pdata->int_buttons[i]; + struct interrupt_button_data *bdata = &ddata->data[i]; + unsigned int type = button->type ?: EV_KEY; + + bdata->input = input; + bdata->button = button; + + error = interrupt_keys_setup_key(pdev, bdata, button); + if (error) + goto fail2; + + if (button->wakeup) + wakeup = 1; + + input_set_capability(input, type, button->code); + } + error = input_register_device(input); + if (error) { + dev_err(dev, "Unable to register input device, error: %d\n", + error); + goto fail2; + } + + /* get current state of buttons */ + for (i = 0; i < pdata->nbuttons; i++) + interrupt_keys_report_event(&ddata->data[i]); + input_sync(input); + + device_init_wakeup(&pdev->dev, wakeup); + + return 0; + +fail2: + while (--i >= 0) { + free_irq(pdata->int_buttons[i].irq, &ddata->data[i]); + if (ddata->data[i].timer_debounce) + del_timer_sync(&ddata->data[i].timer); + cancel_work_sync(&ddata->data[i].work); + } + + platform_set_drvdata(pdev, NULL); +fail1: + input_free_device(input); + kfree(ddata); + + return error; +} + +static int __devexit interrupt_keys_remove(struct platform_device *pdev) +{ + struct interrupt_keys_platform_data *pdata = pdev->dev.platform_data; + struct interrupt_keys_drvdata *ddata = platform_get_drvdata(pdev); + struct input_dev *input = ddata->input; + int i; + + device_init_wakeup(&pdev->dev, 0); + + for (i = 0; i < pdata->nbuttons; i++) { + free_irq(pdata->int_buttons[i].irq, &ddata->data[i]); + if (ddata->data[i].timer_debounce) + del_timer_sync(&ddata->data[i].timer); + cancel_work_sync(&ddata->data[i].work); + } + + input_unregister_device(input); + + return 0; +} + + +#ifdef CONFIG_PM +static int interrupt_keys_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct interrupt_keys_platform_data *pdata = pdev->dev.platform_data; + struct interrupt_keys_button *button; + int i; + + if (device_may_wakeup(&pdev->dev)) { + for (i = 0; i < pdata->nbuttons; i++) { + button = &pdata->int_buttons[i]; + if (button->wakeup) + enable_irq_wake(button->irq); + } + } + return 0; +} + +static int interrupt_keys_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct interrupt_keys_platform_data *pdata = pdev->dev.platform_data; + struct interrupt_keys_button *button; + int i; + + for (i = 0; i < pdata->nbuttons; i++) { + button = &pdata->int_buttons[i]; + if (button->wakeup && device_may_wakeup(&pdev->dev)) { + int irq = button->irq; + disable_irq_wake(irq); + } + } + return 0; +} + +static const struct dev_pm_ops interrupt_keys_pm_ops = { + .suspend = interrupt_keys_suspend, + .resume = interrupt_keys_resume, +}; +#endif + +static struct platform_driver interrupt_keys_device_driver = { + .probe = interrupt_keys_probe, + .remove = __devexit_p(interrupt_keys_remove), + .driver = { + .name = "interrupt-keys", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &interrupt_keys_pm_ops, +#endif + } +}; + +static int __init interrupt_keys_init(void) +{ + return platform_driver_register(&interrupt_keys_device_driver); +} + +static void __exit interrupt_keys_exit(void) +{ + platform_driver_unregister(&interrupt_keys_device_driver); +} + +module_init(interrupt_keys_init); +module_exit(interrupt_keys_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Keyboard driver for CPU interrupts"); +MODULE_ALIAS("platform:interrupt-keys"); |