Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 56eb471..aaee448 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -329,4 +329,15 @@
To compile this as a module choose M here: the module will be called
maplecontrol.
+config TOUCHDISC_VTD518_SHINETSU
+ tristate "ShinEtsu VTD518 TouchDisc"
+ depends on I2C
+ default n
+ help
+ Say Y here if you have the ShinEtsu VTD518 Touchdisc connected. It
+ provides the detection of absolute and relative motions and dpad
+ like buttons.
+
+ To compile this as a module choose M here: the module will be called
+ tdisc_vtd518_shinetsu.
endif
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 92dc0de..7009c38 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -32,4 +32,4 @@
obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o
obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o
obj-$(CONFIG_JOYSTICK_WALKERA0701) += walkera0701.o
-
+obj-$(CONFIG_TOUCHDISC_VTD518_SHINETSU) += tdisc_vtd518_shinetsu.o
\ No newline at end of file
diff --git a/drivers/input/joystick/tdisc_vtd518_shinetsu.c b/drivers/input/joystick/tdisc_vtd518_shinetsu.c
new file mode 100644
index 0000000..efbe974
--- /dev/null
+++ b/drivers/input/joystick/tdisc_vtd518_shinetsu.c
@@ -0,0 +1,528 @@
+/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/workqueue.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/input/tdisc_shinetsu.h>
+
+#if defined(CONFIG_HAS_EARLYSUSPEND)
+#include <linux/earlysuspend.h>
+/* Early-suspend level */
+#define TDISC_SUSPEND_LEVEL 1
+#endif
+
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("0.1");
+MODULE_DESCRIPTION("Shinetsu Touchdisc driver");
+MODULE_ALIAS("platform:tdisc-shinetsu");
+
+#define TDSIC_BLK_READ_CMD 0x00
+#define TDISC_READ_DELAY msecs_to_jiffies(25)
+#define X_MAX (32)
+#define X_MIN (-32)
+#define Y_MAX (32)
+#define Y_MIN (-32)
+#define PRESSURE_MAX (32)
+#define PRESSURE_MIN (0)
+#define TDISC_USER_ACTIVE_MASK 0x40
+#define TDISC_NORTH_SWITCH_MASK 0x20
+#define TDISC_SOUTH_SWITCH_MASK 0x10
+#define TDISC_EAST_SWITCH_MASK 0x08
+#define TDISC_WEST_SWITCH_MASK 0x04
+#define TDISC_CENTER_SWITCH 0x01
+#define TDISC_BUTTON_PRESS_MASK 0x3F
+
+#define DRIVER_NAME "tdisc-shinetsu"
+#define DEVICE_NAME "vtd518"
+#define TDISC_NAME "tdisc_shinetsu"
+#define TDISC_INT "tdisc_interrupt"
+
+struct tdisc_data {
+ struct input_dev *tdisc_device;
+ struct i2c_client *clientp;
+ struct tdisc_platform_data *pdata;
+ struct delayed_work tdisc_work;
+#if defined(CONFIG_HAS_EARLYSUSPEND)
+ struct early_suspend tdisc_early_suspend;
+#endif
+};
+
+static void process_tdisc_data(struct tdisc_data *dd, u8 *data)
+{
+ int i;
+ static bool button_press;
+ s8 x, y;
+
+ /* Check if the user is actively navigating */
+ if (!(data[7] & TDISC_USER_ACTIVE_MASK)) {
+ pr_debug(" TDISC ! No Data to report ! False positive \n");
+ return;
+ }
+
+ for (i = 0; i < 8 ; i++)
+ pr_debug(" Data[%d] = %x\n", i, data[i]);
+
+ /* Check if there is a button press */
+ if (dd->pdata->tdisc_report_keys)
+ if (data[7] & TDISC_BUTTON_PRESS_MASK || button_press == true) {
+ input_report_key(dd->tdisc_device, KEY_UP,
+ (data[7] & TDISC_NORTH_SWITCH_MASK));
+
+ input_report_key(dd->tdisc_device, KEY_DOWN,
+ (data[7] & TDISC_SOUTH_SWITCH_MASK));
+
+ input_report_key(dd->tdisc_device, KEY_RIGHT,
+ (data[7] & TDISC_EAST_SWITCH_MASK));
+
+ input_report_key(dd->tdisc_device, KEY_LEFT,
+ (data[7] & TDISC_WEST_SWITCH_MASK));
+
+ input_report_key(dd->tdisc_device, KEY_ENTER,
+ (data[7] & TDISC_CENTER_SWITCH));
+
+ if (data[7] & TDISC_BUTTON_PRESS_MASK)
+ button_press = true;
+ else
+ button_press = false;
+ }
+
+ if (dd->pdata->tdisc_report_relative) {
+ /* Report relative motion values */
+ x = (s8) data[0];
+ y = (s8) data[1];
+
+ if (dd->pdata->tdisc_reverse_x)
+ x *= -1;
+ if (dd->pdata->tdisc_reverse_y)
+ y *= -1;
+
+ input_report_rel(dd->tdisc_device, REL_X, x);
+ input_report_rel(dd->tdisc_device, REL_Y, y);
+ }
+
+ if (dd->pdata->tdisc_report_absolute) {
+ input_report_abs(dd->tdisc_device, ABS_X, data[2]);
+ input_report_abs(dd->tdisc_device, ABS_Y, data[3]);
+ input_report_abs(dd->tdisc_device, ABS_PRESSURE, data[4]);
+ }
+
+ if (dd->pdata->tdisc_report_wheel)
+ input_report_rel(dd->tdisc_device, REL_WHEEL, (s8) data[6]);
+
+ input_sync(dd->tdisc_device);
+}
+
+static void tdisc_work_f(struct work_struct *work)
+{
+ int rc;
+ u8 data[8];
+ struct tdisc_data *dd =
+ container_of(work, struct tdisc_data, tdisc_work.work);
+
+ /*
+ * Read the value of the interrupt pin. If low, perform
+ * an I2C read of 8 bytes to get the touch values and then
+ * reschedule the work after 25ms. If pin is high, exit
+ * and wait for next interrupt.
+ */
+ rc = gpio_get_value_cansleep(dd->pdata->tdisc_gpio);
+ if (rc < 0) {
+ rc = pm_runtime_put_sync(&dd->clientp->dev);
+ if (rc < 0)
+ dev_dbg(&dd->clientp->dev, "%s: pm_runtime_put_sync"
+ " failed\n", __func__);
+ enable_irq(dd->clientp->irq);
+ return;
+ }
+
+ pr_debug("%s: TDISC gpio_get_value = %d\n", __func__, rc);
+ if (rc == 0) {
+ /* We have data to read */
+ rc = i2c_smbus_read_i2c_block_data(dd->clientp,
+ TDSIC_BLK_READ_CMD, 8, data);
+ if (rc < 0) {
+ pr_debug("%s:I2C read failed,trying again\n", __func__);
+ rc = i2c_smbus_read_i2c_block_data(dd->clientp,
+ TDSIC_BLK_READ_CMD, 8, data);
+ if (rc < 0) {
+ pr_err("%s:I2C read failed again, exiting\n",
+ __func__);
+ goto fail_i2c_read;
+ }
+ }
+ pr_debug("%s: TDISC: I2C read success\n", __func__);
+ process_tdisc_data(dd, data);
+ } else {
+ /*
+ * We have no data to read.
+ * Enable the IRQ to receive further interrupts.
+ */
+ enable_irq(dd->clientp->irq);
+
+ rc = pm_runtime_put_sync(&dd->clientp->dev);
+ if (rc < 0)
+ dev_dbg(&dd->clientp->dev, "%s: pm_runtime_put_sync"
+ " failed\n", __func__);
+ return;
+ }
+
+fail_i2c_read:
+ schedule_delayed_work(&dd->tdisc_work, TDISC_READ_DELAY);
+}
+
+static irqreturn_t tdisc_interrupt(int irq, void *dev_id)
+{
+ /*
+ * The touch disc intially generates an interrupt on any
+ * touch. The interrupt line is pulled low and remains low
+ * untill there are touch operations being performed. In case
+ * there are no further touch operations, the line goes high. The
+ * same process repeats again the next time,when the disc is touched.
+ *
+ * We do the following operations once we receive an interrupt.
+ * 1. Disable the IRQ for any further interrutps.
+ * 2. Schedule work every 25ms if the GPIO is still low.
+ * 3. In the work queue do a I2C read to get the touch data.
+ * 4. If the GPIO is pulled high, enable the IRQ and cancel the work.
+ */
+ struct tdisc_data *dd = dev_id;
+ int rc;
+
+ rc = pm_runtime_get(&dd->clientp->dev);
+ if (rc < 0)
+ dev_dbg(&dd->clientp->dev, "%s: pm_runtime_get"
+ " failed\n", __func__);
+ pr_debug("%s: TDISC IRQ ! :-)\n", __func__);
+
+ /* Schedule the work immediately */
+ disable_irq_nosync(dd->clientp->irq);
+ schedule_delayed_work(&dd->tdisc_work, 0);
+ return IRQ_HANDLED;
+}
+
+static int tdisc_open(struct input_dev *dev)
+{
+ int rc;
+ struct tdisc_data *dd = input_get_drvdata(dev);
+
+ if (!dd->clientp) {
+ /* Check if a valid i2c client is present */
+ pr_err("%s: no i2c adapter present \n", __func__);
+ return -ENODEV;
+ }
+
+ /* Enable the device */
+ if (dd->pdata->tdisc_enable != NULL) {
+ rc = dd->pdata->tdisc_enable();
+ if (rc)
+ goto fail_open;
+ }
+ rc = request_any_context_irq(dd->clientp->irq, tdisc_interrupt,
+ IRQF_TRIGGER_FALLING, TDISC_INT, dd);
+ if (rc < 0) {
+ pr_err("%s: request IRQ failed\n", __func__);
+ goto fail_irq_open;
+ }
+
+ return 0;
+
+fail_irq_open:
+ if (dd->pdata->tdisc_disable != NULL)
+ dd->pdata->tdisc_disable();
+fail_open:
+ return rc;
+}
+
+static void tdisc_close(struct input_dev *dev)
+{
+ struct tdisc_data *dd = input_get_drvdata(dev);
+
+ free_irq(dd->clientp->irq, dd);
+ cancel_delayed_work_sync(&dd->tdisc_work);
+ if (dd->pdata->tdisc_disable != NULL)
+ dd->pdata->tdisc_disable();
+}
+
+static int __devexit tdisc_remove(struct i2c_client *client)
+{
+ struct tdisc_data *dd;
+
+ pm_runtime_disable(&client->dev);
+ dd = i2c_get_clientdata(client);
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ unregister_early_suspend(&dd->tdisc_early_suspend);
+#endif
+ input_unregister_device(dd->tdisc_device);
+ if (dd->pdata->tdisc_release != NULL)
+ dd->pdata->tdisc_release();
+ i2c_set_clientdata(client, NULL);
+ kfree(dd);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int tdisc_suspend(struct device *dev)
+{
+ int rc;
+ struct tdisc_data *dd;
+
+ dd = dev_get_drvdata(dev);
+ if (device_may_wakeup(&dd->clientp->dev))
+ enable_irq_wake(dd->clientp->irq);
+ else {
+ disable_irq(dd->clientp->irq);
+
+ if (cancel_delayed_work_sync(&dd->tdisc_work))
+ enable_irq(dd->clientp->irq);
+
+ if (dd->pdata->tdisc_disable) {
+ rc = dd->pdata->tdisc_disable();
+ if (rc) {
+ pr_err("%s: Suspend failed\n", __func__);
+ return rc;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int tdisc_resume(struct device *dev)
+{
+ int rc;
+ struct tdisc_data *dd;
+
+ dd = dev_get_drvdata(dev);
+ if (device_may_wakeup(&dd->clientp->dev))
+ disable_irq_wake(dd->clientp->irq);
+ else {
+ if (dd->pdata->tdisc_enable) {
+ rc = dd->pdata->tdisc_enable();
+ if (rc) {
+ pr_err("%s: Resume failed\n", __func__);
+ return rc;
+ }
+ }
+ enable_irq(dd->clientp->irq);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void tdisc_early_suspend(struct early_suspend *h)
+{
+ struct tdisc_data *dd = container_of(h, struct tdisc_data,
+ tdisc_early_suspend);
+
+ tdisc_suspend(&dd->clientp->dev);
+}
+
+static void tdisc_late_resume(struct early_suspend *h)
+{
+ struct tdisc_data *dd = container_of(h, struct tdisc_data,
+ tdisc_early_suspend);
+
+ tdisc_resume(&dd->clientp->dev);
+}
+#endif
+
+static struct dev_pm_ops tdisc_pm_ops = {
+#ifndef CONFIG_HAS_EARLYSUSPEND
+ .suspend = tdisc_suspend,
+ .resume = tdisc_resume,
+#endif
+};
+#endif
+
+static const struct i2c_device_id tdisc_id[] = {
+ { DEVICE_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tdisc_id);
+
+static int __devinit tdisc_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int rc = -1;
+ int x_max, x_min, y_max, y_min, pressure_min, pressure_max;
+ struct tdisc_platform_data *pd;
+ struct tdisc_data *dd;
+
+ /* Check if the I2C adapter supports the BLOCK READ functionality */
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK))
+ return -ENODEV;
+
+ /* Enable runtime PM ops, start in ACTIVE mode */
+ rc = pm_runtime_set_active(&client->dev);
+ if (rc < 0)
+ dev_dbg(&client->dev, "unable to set runtime pm state\n");
+ pm_runtime_enable(&client->dev);
+
+ dd = kzalloc(sizeof *dd, GFP_KERNEL);
+ if (!dd) {
+ rc = -ENOMEM;
+ goto probe_exit;
+ }
+
+ i2c_set_clientdata(client, dd);
+ dd->clientp = client;
+ pd = client->dev.platform_data;
+ if (!pd) {
+ pr_err("%s: platform data not set \n", __func__);
+ rc = -EFAULT;
+ goto probe_free_exit;
+ }
+
+ dd->pdata = pd;
+
+ dd->tdisc_device = input_allocate_device();
+ if (!dd->tdisc_device) {
+ rc = -ENOMEM;
+ goto probe_free_exit;
+ }
+
+ input_set_drvdata(dd->tdisc_device, dd);
+ dd->tdisc_device->open = tdisc_open;
+ dd->tdisc_device->close = tdisc_close;
+ dd->tdisc_device->name = TDISC_NAME;
+ dd->tdisc_device->id.bustype = BUS_I2C;
+ dd->tdisc_device->id.product = 1;
+ dd->tdisc_device->id.version = 1;
+
+ if (pd->tdisc_abs) {
+ x_max = pd->tdisc_abs->x_max;
+ x_min = pd->tdisc_abs->x_min;
+ y_max = pd->tdisc_abs->y_max;
+ y_min = pd->tdisc_abs->y_min;
+ pressure_max = pd->tdisc_abs->pressure_max;
+ pressure_min = pd->tdisc_abs->pressure_min;
+ } else {
+ x_max = X_MAX;
+ x_min = X_MIN;
+ y_max = Y_MAX;
+ y_min = Y_MIN;
+ pressure_max = PRESSURE_MAX;
+ pressure_min = PRESSURE_MIN;
+ }
+
+ /* Device capablities for relative motion */
+ input_set_capability(dd->tdisc_device, EV_REL, REL_X);
+ input_set_capability(dd->tdisc_device, EV_REL, REL_Y);
+ input_set_capability(dd->tdisc_device, EV_KEY, BTN_MOUSE);
+
+ /* Device capablities for absolute motion */
+ input_set_capability(dd->tdisc_device, EV_ABS, ABS_X);
+ input_set_capability(dd->tdisc_device, EV_ABS, ABS_Y);
+ input_set_capability(dd->tdisc_device, EV_ABS, ABS_PRESSURE);
+
+ input_set_abs_params(dd->tdisc_device, ABS_X, x_min, x_max, 0, 0);
+ input_set_abs_params(dd->tdisc_device, ABS_Y, y_min, y_max, 0, 0);
+ input_set_abs_params(dd->tdisc_device, ABS_PRESSURE, pressure_min,
+ pressure_max, 0, 0);
+
+ /* Device capabilities for scroll and buttons */
+ input_set_capability(dd->tdisc_device, EV_REL, REL_WHEEL);
+ input_set_capability(dd->tdisc_device, EV_KEY, KEY_LEFT);
+ input_set_capability(dd->tdisc_device, EV_KEY, KEY_RIGHT);
+ input_set_capability(dd->tdisc_device, EV_KEY, KEY_UP);
+ input_set_capability(dd->tdisc_device, EV_KEY, KEY_DOWN);
+ input_set_capability(dd->tdisc_device, EV_KEY, KEY_ENTER);
+
+ /* Setup the device for operation */
+ if (dd->pdata->tdisc_setup != NULL) {
+ rc = dd->pdata->tdisc_setup();
+ if (rc) {
+ pr_err("%s: Setup failed \n", __func__);
+ goto probe_unreg_free_exit;
+ }
+ }
+
+ /* Setup wakeup capability */
+ device_init_wakeup(&dd->clientp->dev, dd->pdata->tdisc_wakeup);
+
+ INIT_DELAYED_WORK(&dd->tdisc_work, tdisc_work_f);
+
+ rc = input_register_device(dd->tdisc_device);
+ if (rc) {
+ pr_err("%s: input register device failed \n", __func__);
+ rc = -EINVAL;
+ goto probe_register_fail;
+ }
+
+ pm_runtime_set_suspended(&client->dev);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ dd->tdisc_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN +
+ TDISC_SUSPEND_LEVEL;
+ dd->tdisc_early_suspend.suspend = tdisc_early_suspend;
+ dd->tdisc_early_suspend.resume = tdisc_late_resume;
+ register_early_suspend(&dd->tdisc_early_suspend);
+#endif
+ return 0;
+
+probe_register_fail:
+ if (dd->pdata->tdisc_release != NULL)
+ dd->pdata->tdisc_release();
+probe_unreg_free_exit:
+ input_free_device(dd->tdisc_device);
+probe_free_exit:
+ i2c_set_clientdata(client, NULL);
+ kfree(dd);
+probe_exit:
+ pm_runtime_set_suspended(&client->dev);
+ pm_runtime_disable(&client->dev);
+ return rc;
+}
+
+static struct i2c_driver tdisc_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &tdisc_pm_ops,
+#endif
+ },
+ .probe = tdisc_probe,
+ .remove = __devexit_p(tdisc_remove),
+ .id_table = tdisc_id,
+};
+
+static int __init tdisc_init(void)
+{
+ int rc;
+
+ rc = i2c_add_driver(&tdisc_driver);
+ if (rc)
+ pr_err("%s: i2c add driver failed \n", __func__);
+ return rc;
+}
+
+static void __exit tdisc_exit(void)
+{
+ i2c_del_driver(&tdisc_driver);
+}
+
+module_init(tdisc_init);
+module_exit(tdisc_exit);