summaryrefslogtreecommitdiff
path: root/drivers/input
diff options
context:
space:
mode:
authorLaxman Dewangan <ldewangan@nvidia.com>2011-03-08 20:45:10 +0530
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:41:53 -0800
commite70f83346e66351796ec54c569027fb19cf38c1c (patch)
tree19bde075cfaa8abbd07ada9edd6b64bb812a650c /drivers/input
parent6a17252e8158a7dba2243e50c9213d073b708466 (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/Kconfig17
-rw-r--r--drivers/input/keyboard/Makefile1
-rwxr-xr-xdrivers/input/keyboard/interrupt_keys.c395
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");