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);