summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSachin Nikam <snikam@nvidia.com>2010-12-17 18:58:13 +0530
committerBharat Nihalani <bnihalani@nvidia.com>2010-12-28 23:07:18 -0800
commitbb9b07750cc166d0d7e19df2e0411043a8bebfdb (patch)
tree5e7e88affde3c228468b5136f97d42253d9acee8
parentf94020abac1d00cf0ab79f7b2e72ba57900017c1 (diff)
[ARM] tegra: gpio based scrollwheel driver
Reports Scroll keys: - Press - Scrollup - Scrolldown Change-Id: I47f6ca3d7b5bed4aa7441634850881df7066fce7 Reviewed-on: http://git-master/r/13618 Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com> Tested-by: Bharat Nihalani <bnihalani@nvidia.com>
-rw-r--r--drivers/input/misc/Kconfig16
-rw-r--r--drivers/input/misc/Makefile1
-rw-r--r--drivers/input/misc/alps_gpio_scrollwheel.c428
-rw-r--r--include/linux/gpio_scrollwheel.h46
4 files changed, 491 insertions, 0 deletions
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 263471a905f7..37f843e95af0 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -454,4 +454,20 @@ config INPUT_ADXL34X_SPI
To compile this driver as a module, choose M here: the
module will be called adxl34x-spi.
+config INPUT_ALPS_GPIO_SCROLLWHEEL
+ tristate "Alps GPIO Scrollwheel"
+ depends on GENERIC_GPIO
+ help
+ This driver implements support for Alps SRBE
+ ScrollWheel connected to GPIO pins of various
+ CPUs (and some other chips).
+
+ Say Y here if your device has ScrollWheel connected
+ directly to such GPIO pins. Your board-specific
+ setup logic must also provide a platform device,
+ with configuration data saying which GPIOs are used.
+
+ To compile this driver as a module, choose M here: the
+ module will be called alps_gpio_scrollwheel.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 7ac4ca759999..bc875ba892da 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_INPUT_AD714X_SPI) += ad714x-spi.o
obj-$(CONFIG_INPUT_ADXL34X) += adxl34x.o
obj-$(CONFIG_INPUT_ADXL34X_I2C) += adxl34x-i2c.o
obj-$(CONFIG_INPUT_ADXL34X_SPI) += adxl34x-spi.o
+obj-$(CONFIG_INPUT_ALPS_GPIO_SCROLLWHEEL) += alps_gpio_scrollwheel.o
obj-$(CONFIG_INPUT_APANEL) += apanel.o
obj-$(CONFIG_INPUT_ATI_REMOTE) += ati_remote.o
obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_remote2.o
diff --git a/drivers/input/misc/alps_gpio_scrollwheel.c b/drivers/input/misc/alps_gpio_scrollwheel.c
new file mode 100644
index 000000000000..4a789267c475
--- /dev/null
+++ b/drivers/input/misc/alps_gpio_scrollwheel.c
@@ -0,0 +1,428 @@
+/*
+ * kernel/drivers/input/misc/alps_gpio_scrollwheel.c
+ *
+ * Copyright (c) 2010, NVIDIA Corporation.
+ *
+ * Driver for ScrollWheel on GPIO lines capable of generating interrupts.
+ *
+ * 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/sysctl.h>
+#include <linux/proc_fs.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/gpio_scrollwheel.h>
+#include <linux/workqueue.h>
+#include <linux/gpio.h>
+
+struct scrollwheel_button_data {
+ struct gpio_scrollwheel_button *button;
+ struct input_dev *input;
+ struct timer_list timer;
+ struct work_struct work;
+ int timer_debounce; /* in msecs */
+ int rotgpio;
+ bool disabled;
+};
+
+struct gpio_scrollwheel_drvdata {
+ struct input_dev *input;
+ struct mutex disable_lock;
+ unsigned int n_buttons;
+ int (*enable)(struct device *dev);
+ void (*disable)(struct device *dev);
+ struct scrollwheel_button_data data[0];
+};
+
+static void scrollwheel_report_key(struct scrollwheel_button_data *bdata)
+{
+ struct gpio_scrollwheel_button *button = bdata->button;
+ struct input_dev *input = bdata->input;
+ int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ \
+ button->active_low;
+ int state2 = 0;
+
+ switch (button->pinaction) {
+ case GPIO_SCROLLWHEEL_PIN_PRESS:
+ input_report_key(input, KEY_ENTER, 1);
+ input_report_key(input, KEY_ENTER, 0);
+ input_sync(input);
+ break;
+
+ case GPIO_SCROLLWHEEL_PIN_ROT1:
+ case GPIO_SCROLLWHEEL_PIN_ROT2:
+ state2 = (gpio_get_value(bdata->rotgpio) ? 1 : 0) \
+ ^ button->active_low;
+ if (state != state2) {
+ input_report_key(input, KEY_DOWN, 1);
+ input_report_key(input, KEY_DOWN, 0);
+ } else {
+ input_report_key(input, KEY_UP, 1);
+ input_report_key(input, KEY_UP, 0);
+ }
+ input_sync(input);
+ break;
+
+ default:
+ pr_err("%s:Line=%d, Invalid Pinaction\n", __func__, __LINE__);
+ }
+}
+
+static void scrollwheel_work_func(struct work_struct *work)
+{
+ struct scrollwheel_button_data *bdata =
+ container_of(work, struct scrollwheel_button_data, work);
+
+ scrollwheel_report_key(bdata);
+}
+
+static void scrollwheel_timer(unsigned long _data)
+{
+ struct scrollwheel_button_data *data = \
+ (struct scrollwheel_button_data *)_data;
+
+ schedule_work(&data->work);
+}
+
+static irqreturn_t scrollwheel_isr(int irq, void *dev_id)
+{
+ struct scrollwheel_button_data *bdata = dev_id;
+ struct gpio_scrollwheel_button *button = bdata->button;
+
+ BUG_ON(irq != gpio_to_irq(button->gpio));
+
+ 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 gpio_scrollwheel_setup_key(struct platform_device *pdev,
+ struct scrollwheel_button_data *bdata,
+ struct gpio_scrollwheel_button *button)
+{
+ char *desc = button->desc ? button->desc : "gpio_scrollwheel";
+ struct device *dev = &pdev->dev;
+ unsigned long irqflags;
+ int irq, error;
+
+ setup_timer(&bdata->timer, scrollwheel_timer, (unsigned long)bdata);
+ INIT_WORK(&bdata->work, scrollwheel_work_func);
+
+ error = gpio_request(button->gpio, desc);
+ if (error < 0) {
+ dev_err(dev, "failed to request GPIO %d, error %d\n",
+ button->gpio, error);
+ return error;
+ }
+
+ error = gpio_direction_input(button->gpio);
+ if (error < 0) {
+ dev_err(dev, "failed to configure"
+ " direction for GPIO %d, error %d\n",
+ button->gpio, error);
+ goto fail;
+ }
+
+ if (button->debounce_interval) {
+ error = gpio_set_debounce(button->gpio,
+ button->debounce_interval * 1000);
+ /* use timer if gpiolib doesn't provide debounce */
+ if (error < 0)
+ bdata->timer_debounce = button->debounce_interval;
+ }
+
+ irq = gpio_to_irq(button->gpio);
+ if (irq < 0) {
+ error = irq;
+ dev_err(dev, "Unable to get irq no for GPIO %d, error %d\n",
+ button->gpio, error);
+ goto fail;
+ }
+
+ irqflags = IRQF_TRIGGER_FALLING;
+
+ error = request_irq(irq, scrollwheel_isr, irqflags, desc, bdata);
+ if (error) {
+ dev_err(dev, "Unable to claim irq %d; error %d\n",
+ irq, error);
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ return error;
+}
+
+static int gpio_scrollwheel_open(struct input_dev *input)
+{
+ struct gpio_scrollwheel_drvdata *ddata = input_get_drvdata(input);
+
+ return ddata->enable ? ddata->enable(input->dev.parent) : 0;
+}
+
+static void gpio_scrollwheel_close(struct input_dev *input)
+{
+ struct gpio_scrollwheel_drvdata *ddata = input_get_drvdata(input);
+
+ if (ddata->disable)
+ ddata->disable(input->dev.parent);
+}
+
+static int __devinit gpio_scrollwheel_probe(struct platform_device *pdev)
+{
+ struct gpio_scrollwheel_platform_data *pdata = pdev->dev.platform_data;
+ struct gpio_scrollwheel_drvdata *ddata;
+ struct device *dev = &pdev->dev;
+ struct input_dev *input;
+ int i, error;
+
+ ddata = kzalloc(sizeof(struct gpio_scrollwheel_drvdata) +
+ pdata->nbuttons * sizeof(struct scrollwheel_button_data),
+ GFP_KERNEL);
+ if (ddata == NULL) {
+ dev_err(dev, "failed to allocate memory\n");
+ error = -ENOMEM;
+ return error;
+ }
+
+ input = input_allocate_device();
+ if (input == NULL) {
+ dev_err(dev, "failed to allocate input device\n");
+ error = -ENOMEM;
+ kfree(ddata);
+ return error;
+ }
+
+ ddata->input = input;
+ ddata->n_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 = "gpio-scrollwheel/input0";
+ input->dev.parent = &pdev->dev;
+ input->open = gpio_scrollwheel_open;
+ input->close = gpio_scrollwheel_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 gpio_scrollwheel_button *button = &pdata->buttons[i];
+ struct scrollwheel_button_data *bdata = &ddata->data[i];
+
+ bdata->input = input;
+ bdata->button = button;
+
+ if (button->pinaction == GPIO_SCROLLWHEEL_PIN_PRESS ||
+ button->pinaction == GPIO_SCROLLWHEEL_PIN_ROT1) {
+ error = gpio_scrollwheel_setup_key(pdev, bdata, button);
+ if (error)
+ goto fail;
+ } else {
+ if (button->pinaction == GPIO_SCROLLWHEEL_PIN_ONOFF) {
+ gpio_request(button->gpio, button->desc);
+ gpio_direction_output(button->gpio, 0);
+ }
+
+ if (button->pinaction == GPIO_SCROLLWHEEL_PIN_ROT2) {
+ gpio_request(button->gpio, button->desc);
+ gpio_direction_input(button->gpio);
+ /* Save rot2 gpio number in rot1 context */
+ ddata->data[2].rotgpio = button->gpio;
+ }
+ }
+ }
+
+ /* set input capability */
+ __set_bit(EV_KEY, input->evbit);
+ __set_bit(KEY_ENTER, input->keybit);
+ __set_bit(KEY_UP, input->keybit);
+ __set_bit(KEY_DOWN, input->keybit);
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(dev, "Unable to register input device, error: %d\n",
+ error);
+ goto fail;
+ }
+
+ input_sync(input);
+
+ return 0;
+
+fail:
+ while (--i >= 0) {
+ if (pdata->buttons[i].pinaction == GPIO_SCROLLWHEEL_PIN_PRESS ||
+ pdata->buttons[i].pinaction == GPIO_SCROLLWHEEL_PIN_ROT1) {
+ free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]);
+ if (ddata->data[i].timer_debounce)
+ del_timer_sync(&ddata->data[i].timer);
+ cancel_work_sync(&ddata->data[i].work);
+ }
+ gpio_free(pdata->buttons[i].gpio);
+ }
+
+ platform_set_drvdata(pdev, NULL);
+ input_free_device(input);
+ kfree(ddata);
+ return error;
+}
+
+static int __devexit gpio_scrollwheel_remove(struct platform_device *pdev)
+{
+ struct gpio_scrollwheel_platform_data *pdata = pdev->dev.platform_data;
+ struct gpio_scrollwheel_drvdata *ddata = platform_get_drvdata(pdev);
+ struct input_dev *input = ddata->input;
+ int i;
+
+ for (i = 0; i < pdata->nbuttons; i++) {
+ if (pdata->buttons[i].pinaction == GPIO_SCROLLWHEEL_PIN_PRESS ||
+ pdata->buttons[i].pinaction == GPIO_SCROLLWHEEL_PIN_ROT1) {
+ int irq = gpio_to_irq(pdata->buttons[i].gpio);
+ free_irq(irq, &ddata->data[i]);
+ if (ddata->data[i].timer_debounce)
+ del_timer_sync(&ddata->data[i].timer);
+ cancel_work_sync(&ddata->data[i].work);
+ }
+ gpio_free(pdata->buttons[i].gpio);
+ }
+
+ input_unregister_device(input);
+
+ return 0;
+}
+
+
+#ifdef CONFIG_PM
+static int gpio_scrollwheel_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct gpio_scrollwheel_platform_data *pdata = pdev->dev.platform_data;
+ struct gpio_scrollwheel_drvdata *ddata = platform_get_drvdata(pdev);
+ int i, irq;
+
+ for (i = 0; i < pdata->nbuttons; i++) {
+ if (pdata->buttons[i].pinaction == GPIO_SCROLLWHEEL_PIN_PRESS ||
+ pdata->buttons[i].pinaction == GPIO_SCROLLWHEEL_PIN_ROT1) {
+ irq = gpio_to_irq(pdata->buttons[i].gpio);
+ disable_irq(irq);
+ if (ddata->data[i].timer_debounce)
+ del_timer_sync(&ddata->data[i].timer);
+ cancel_work_sync(&ddata->data[i].work);
+ } else {
+ if (pdata->buttons[i].pinaction == GPIO_SCROLLWHEEL_PIN_ONOFF)
+ gpio_direction_output(pdata->buttons[i].gpio, 1);
+ else {
+ irq = gpio_to_irq(pdata->buttons[i].gpio);
+ disable_irq(irq);
+ }
+ }
+ }
+ return 0;
+}
+
+static int gpio_scrollwheel_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct gpio_scrollwheel_platform_data *pdata = pdev->dev.platform_data;
+ struct gpio_scrollwheel_drvdata *ddata = platform_get_drvdata(pdev);
+ int i, irq;
+
+ for (i = 0; i < pdata->nbuttons; i++) {
+ if (pdata->buttons[i].pinaction == GPIO_SCROLLWHEEL_PIN_PRESS ||
+ pdata->buttons[i].pinaction == GPIO_SCROLLWHEEL_PIN_ROT1) {
+ irq = gpio_to_irq(pdata->buttons[i].gpio);
+ enable_irq(irq);
+ if (ddata->data[i].timer_debounce)
+ setup_timer(&ddata->data[i].timer,\
+ scrollwheel_timer, (unsigned long)&ddata->data[i]);
+
+ INIT_WORK(&ddata->data[i].work, scrollwheel_work_func);
+ } else {
+ if (pdata->buttons[i].pinaction == GPIO_SCROLLWHEEL_PIN_ONOFF)
+ gpio_direction_output(pdata->buttons[i].gpio, 0);
+ else {
+ irq = gpio_to_irq(pdata->buttons[i].gpio);
+ enable_irq(irq);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops gpio_scrollwheel_pm_ops = {
+ .suspend = gpio_scrollwheel_suspend,
+ .resume = gpio_scrollwheel_resume,
+};
+#endif
+
+static struct platform_driver gpio_scrollwheel_device_driver = {
+ .probe = gpio_scrollwheel_probe,
+ .remove = __devexit_p(gpio_scrollwheel_remove),
+ .driver = {
+ .name = "alps-gpio-scrollwheel",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &gpio_scrollwheel_pm_ops,
+#endif
+ }
+};
+
+static int __init gpio_scrollwheel_init(void)
+{
+ return platform_driver_register(&gpio_scrollwheel_device_driver);
+}
+
+static void __exit gpio_scrollwheel_exit(void)
+{
+ platform_driver_unregister(&gpio_scrollwheel_device_driver);
+}
+
+module_init(gpio_scrollwheel_init);
+module_exit(gpio_scrollwheel_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("NVIDIA Corporation");
+MODULE_DESCRIPTION("Alps SRBE ScrollWheel driver");
+
+MODULE_ALIAS("platform:alps-gpio-scrollwheel");
diff --git a/include/linux/gpio_scrollwheel.h b/include/linux/gpio_scrollwheel.h
new file mode 100644
index 000000000000..33d17a0199ea
--- /dev/null
+++ b/include/linux/gpio_scrollwheel.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010, 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.
+ */
+
+#ifndef _GPIO_SCROLLWHEEL_H
+#define _GPIO_SCROLLWHEEL_H
+
+#define GPIO_SCROLLWHEEL_PIN_ONOFF 0
+#define GPIO_SCROLLWHEEL_PIN_PRESS 1
+#define GPIO_SCROLLWHEEL_PIN_ROT1 2
+#define GPIO_SCROLLWHEEL_PIN_ROT2 3
+#define GPIO_SCROLLWHEEL_PIN_MAX 4
+
+struct gpio_scrollwheel_button {
+ /* Configuration parameters */
+ int pinaction; /* GPIO_SCROLLWHEEL_PIN_* */
+ int gpio;
+ char *desc;
+ int active_low;
+ int debounce_interval; /* debounce ticks interval in msecs */
+};
+
+struct gpio_scrollwheel_platform_data {
+ struct gpio_scrollwheel_button *buttons;
+ int nbuttons;
+ unsigned int rep:1; /* enable input subsystem auto repeat */
+ int (*enable)(struct device *dev);
+ void (*disable)(struct device *dev);
+};
+
+#endif
+