diff mbox

[2/3] Input:TouchScreen: Introduce TSC2004 driver support

Message ID 1258642000-2271-1-git-send-email-hvaibhav@ti.com (mailing list archive)
State New, archived
Headers show

Commit Message

Vaibhav Hiremath Nov. 19, 2009, 2:46 p.m. UTC
None
diff mbox

Patch

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 8cc453c..08aba0b 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -512,6 +512,17 @@  config TOUCHSCREEN_TSC2007
 	  To compile this driver as a module, choose M here: the
 	  module will be called tsc2007.
 
+config TOUCHSCREEN_TSC2004
+	tristate "TSC2004 based touchscreens"
+	depends on I2C
+	help
+	  Say Y here if you have a TSC2004 based touchscreen.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called tsc2004.
+
 config TOUCHSCREEN_W90X900
 	tristate "W90P910 touchscreen driver"
 	depends on HAVE_CLK
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 15fa62c..4ac5b81 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -30,6 +30,7 @@  obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
 obj-$(CONFIG_TOUCHSCREEN_TSC2007)	+= tsc2007.o
+obj-$(CONFIG_TOUCHSCREEN_TSC2004)	+= tsc2004.o
 obj-$(CONFIG_TOUCHSCREEN_UCB1400)	+= ucb1400_ts.o
 obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001)	+= wacom_w8001.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX)	+= wm97xx-ts.o
diff --git a/drivers/input/touchscreen/tsc2004.c b/drivers/input/touchscreen/tsc2004.c
new file mode 100644
index 0000000..0bba2e6
--- /dev/null
+++ b/drivers/input/touchscreen/tsc2004.c
@@ -0,0 +1,525 @@ 
+/*
+ * drivers/input/touchscreen/tsc2004.c
+ *
+ * Copyright (C) 2009 Texas Instruments Inc
+ * Author: Vaibhav Hiremath <hvaibhav@ti.com>
+ *
+ * Using code from:
+ *  - tsc2007.c
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/i2c/tsc2004.h>
+
+
+#define TS_POLL_DELAY			1 /* ms delay between samples */
+#define TS_POLL_PERIOD			1 /* ms delay between samples */
+
+/* Control byte 0 */
+#define TSC2004_CMD0(addr, pnd, rw) ((addr<<3)|(pnd<<1)|rw)
+/* Control byte 1 */
+#define TSC2004_CMD1(cmd, mode, rst) ((1<<7)|(cmd<<4)|(mode<<2)|(rst<<1))
+
+/* Command Bits */
+#define READ_REG	1
+#define WRITE_REG	0
+#define SWRST_TRUE	1
+#define SWRST_FALSE	0
+#define PND0_TRUE	1
+#define PND0_FALSE	0
+
+/* Converter function mapping */
+enum convertor_function {
+	MEAS_X_Y_Z1_Z2,	/* Measure X,Y,z1 and Z2:	0x0 */
+	MEAS_X_Y,	/* Measure X and Y only:	0x1 */
+	MEAS_X,		/* Measure X only:		0x2 */
+	MEAS_Y,		/* Measure Y only:		0x3 */
+	MEAS_Z1_Z2,	/* Measure Z1 and Z2 only:	0x4 */
+	MEAS_AUX,	/* Measure Auxillary input:	0x5 */
+	MEAS_TEMP1,	/* Measure Temparature1:	0x6 */
+	MEAS_TEMP2,	/* Measure Temparature2:	0x7 */
+	MEAS_AUX_CONT,	/* Continuously measure Auxillary input: 0x8 */
+	X_DRV_TEST,	/* X-Axis drivers tested 	0x9 */
+	Y_DRV_TEST,	/* Y-Axis drivers tested 	0xA */
+	/*Command Reserved*/
+	SHORT_CKT_TST = 0xC,	/* Short circuit test:	0xC */
+	XP_XN_DRV_STAT,	/* X+,Y- drivers status:	0xD */
+	YP_YN_DRV_STAT,	/* X+,Y- drivers status:	0xE */
+	YP_XN_DRV_STAT	/* Y+,X- drivers status:	0xF */
+};
+
+/* Register address mapping */
+enum register_address {
+	X_REG,		/* X register:		0x0 */
+	Y_REG,		/* Y register:		0x1 */
+	Z1_REG,		/* Z1 register:		0x2 */
+	Z2_REG,		/* Z2 register:		0x3 */
+	AUX_REG,	/* AUX register:	0x4 */
+	TEMP1_REG,	/* Temp1 register:	0x5 */
+	TEMP2_REG,	/* Temp2 register:	0x6 */
+	STAT_REG,	/* Status Register:	0x7 */
+	AUX_HGH_TH_REG,	/* AUX high threshold register:	0x8 */
+	AUX_LOW_TH_REG,	/* AUX low threshold register:	0x9 */
+	TMP_HGH_TH_REG,	/* Temp high threshold register:0xA */
+	TMP_LOW_TH_REG,	/* Temp low threshold register:	0xB */
+	CFR0_REG,	/* Configuration register 0:	0xC */
+	CFR1_REG,	/* Configuration register 1:	0xD */
+	CFR2_REG,	/* Configuration register 2:	0xE */
+	CONV_FN_SEL_STAT	/* Convertor function select register:	0xF */
+};
+
+/* Supported Resolution modes */
+enum resolution_mode {
+	MODE_10BIT,	/* 10 bit resolution */
+	MODE_12BIT		/* 12 bit resolution */
+};
+
+/* Configuraton register bit fields */
+/* CFR0 */
+#define PEN_STS_CTRL_MODE	(1<<15)
+#define ADC_STS			(1<<14)
+#define RES_CTRL		(1<<13)
+#define ADC_CLK_4MHZ		(0<<11)
+#define ADC_CLK_2MHZ		(1<<11)
+#define ADC_CLK_1MHZ		(2<<11)
+#define PANEL_VLTG_STB_TIME_0US		(0<<8)
+#define PANEL_VLTG_STB_TIME_100US	(1<<8)
+#define PANEL_VLTG_STB_TIME_500US	(2<<8)
+#define PANEL_VLTG_STB_TIME_1MS		(3<<8)
+#define PANEL_VLTG_STB_TIME_5MS		(4<<8)
+#define PANEL_VLTG_STB_TIME_10MS	(5<<8)
+#define PANEL_VLTG_STB_TIME_50MS	(6<<8)
+#define PANEL_VLTG_STB_TIME_100MS	(7<<8)
+
+/* CFR2 */
+#define PINTS1			(1<<15)
+#define PINTS0			(1<<14)
+#define MEDIAN_VAL_FLTR_SIZE_1	(0<<12)
+#define MEDIAN_VAL_FLTR_SIZE_3	(1<<12)
+#define MEDIAN_VAL_FLTR_SIZE_7	(2<<12)
+#define MEDIAN_VAL_FLTR_SIZE_15	(3<<12)
+#define AVRG_VAL_FLTR_SIZE_1	(0<<10)
+#define AVRG_VAL_FLTR_SIZE_3_4	(1<<10)
+#define AVRG_VAL_FLTR_SIZE_7_8	(2<<10)
+#define AVRG_VAL_FLTR_SIZE_16	(3<<10)
+#define MAV_FLTR_EN_X		(1<<4)
+#define MAV_FLTR_EN_Y		(1<<3)
+#define MAV_FLTR_EN_Z		(1<<2)
+
+#define	MAX_12BIT		((1 << 12) - 1)
+#define MEAS_MASK		0xFFF
+
+struct ts_event {
+	u16	x;
+	u16	y;
+	u16	z1, z2;
+};
+
+struct tsc2004 {
+	struct input_dev	*input;
+	char			phys[32];
+	struct delayed_work	work;
+
+	struct i2c_client	*client;
+
+	u16			model;
+	u16			x_plate_ohms;
+
+	bool			pendown;
+	int			irq;
+
+	int			(*get_pendown_state)(void);
+	void			(*clear_penirq)(void);
+};
+
+static inline int tsc2004_read_word_data(struct tsc2004 *tsc, u8 cmd)
+{
+	s32 data;
+	u16 val;
+
+	data = i2c_smbus_read_word_data(tsc->client, cmd);
+	if (data < 0) {
+		dev_err(&tsc->client->dev, "i2c io (read) error: %d\n", data);
+		return data;
+	}
+
+	/* The protocol and raw data format from i2c interface:
+	 * S Addr Wr [A] Comm [A] S Addr Rd [A] [DataLow] A [DataHigh] NA P
+	 * Where DataLow has [D11-D4], DataHigh has [D3-D0 << 4 | Dummy 4bit].
+	 */
+	val = swab16(data) >> 4;
+
+	dev_dbg(&tsc->client->dev, "data: 0x%x, val: 0x%x\n", data, val);
+
+	return val;
+}
+
+static inline int tsc2004_write_word_data(struct tsc2004 *tsc, u8 cmd, u16 data)
+{
+	u16 val;
+
+	val = swab16(data);
+	return i2c_smbus_write_word_data(tsc->client, cmd, val);
+}
+
+static inline int tsc2004_write_cmd(struct tsc2004 *tsc, u8 value)
+{
+	return i2c_smbus_write_byte(tsc->client, value);
+}
+
+static int tsc2004_prepare_for_reading(struct tsc2004 *ts)
+{
+	int err;
+	int cmd, data;
+
+	/* Reset the TSC, configure for 12 bit */
+	cmd = TSC2004_CMD1(MEAS_X_Y_Z1_Z2, MODE_12BIT, SWRST_TRUE);
+	err = tsc2004_write_cmd(ts, cmd);
+	if (err < 0)
+		return err;
+
+	/* Enable interrupt for PENIRQ and DAV */
+	cmd = TSC2004_CMD0(CFR2_REG, PND0_FALSE, WRITE_REG);
+	data = PINTS1 | PINTS0 | MEDIAN_VAL_FLTR_SIZE_15 |
+		AVRG_VAL_FLTR_SIZE_7_8 | MAV_FLTR_EN_X | MAV_FLTR_EN_Y |
+		MAV_FLTR_EN_Z;
+	err = tsc2004_write_word_data(ts, cmd, data);
+	if (err < 0)
+		return err;
+
+	/* Configure the TSC in TSMode 1 */
+	cmd = TSC2004_CMD0(CFR0_REG, PND0_FALSE, WRITE_REG);
+	data = PEN_STS_CTRL_MODE | ADC_CLK_2MHZ | PANEL_VLTG_STB_TIME_1MS;
+	err = tsc2004_write_word_data(ts, cmd, data);
+	if (err < 0)
+		return err;
+
+	/* Enable x, y, z1 and z2 conversion functions */
+	cmd = TSC2004_CMD1(MEAS_X_Y_Z1_Z2, MODE_12BIT, SWRST_FALSE);
+	err = tsc2004_write_cmd(ts, cmd);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static void tsc2004_read_values(struct tsc2004 *tsc, struct ts_event *tc)
+{
+	int cmd;
+
+	/* Read X Measurement */
+	cmd = TSC2004_CMD0(X_REG, PND0_FALSE, READ_REG);
+	tc->x = tsc2004_read_word_data(tsc, cmd);
+
+	/* Read Y Measurement */
+	cmd = TSC2004_CMD0(Y_REG, PND0_FALSE, READ_REG);
+	tc->y = tsc2004_read_word_data(tsc, cmd);
+
+	/* Read Z1 Measurement */
+	cmd = TSC2004_CMD0(Z1_REG, PND0_FALSE, READ_REG);
+	tc->z1 = tsc2004_read_word_data(tsc, cmd);
+
+	/* Read Z2 Measurement */
+	cmd = TSC2004_CMD0(Z2_REG, PND0_FALSE, READ_REG);
+	tc->z2 = tsc2004_read_word_data(tsc, cmd);
+
+
+	tc->x &= MEAS_MASK;
+	tc->y &= MEAS_MASK;
+	tc->z1 &= MEAS_MASK;
+	tc->z2 &= MEAS_MASK;
+
+	/* Prepare for touch readings */
+	if (tsc2004_prepare_for_reading(tsc) < 0)
+		dev_dbg(&tsc->client->dev, "Failed to prepare TSC for next"
+				"reading\n");
+}
+
+static u32 tsc2004_calculate_pressure(struct tsc2004 *tsc, struct ts_event *tc)
+{
+	u32 rt = 0;
+
+	/* range filtering */
+	if (tc->x == MAX_12BIT)
+		tc->x = 0;
+
+	if (likely(tc->x && tc->z1)) {
+		/* compute touch pressure resistance using equation #1 */
+		rt = tc->z2 - tc->z1;
+		rt *= tc->x;
+		rt *= tsc->x_plate_ohms;
+		rt /= tc->z1;
+		rt = (rt + 2047) >> 12;
+	}
+
+	return rt;
+}
+
+static void tsc2004_send_up_event(struct tsc2004 *tsc)
+{
+	struct input_dev *input = tsc->input;
+
+	dev_dbg(&tsc->client->dev, "UP\n");
+
+	input_report_key(input, BTN_TOUCH, 0);
+	input_report_abs(input, ABS_PRESSURE, 0);
+	input_sync(input);
+}
+
+static void tsc2004_work(struct work_struct *work)
+{
+	struct tsc2004 *ts =
+		container_of(to_delayed_work(work), struct tsc2004, work);
+	struct ts_event tc;
+	u32 rt;
+
+	/*
+	 * NOTE: We can't rely on the pressure to determine the pen down
+	 * state, even though this controller has a pressure sensor.
+	 * The pressure value can fluctuate for quite a while after
+	 * lifting the pen and in some cases may not even settle at the
+	 * expected value.
+	 *
+	 * The only safe way to check for the pen up condition is in the
+	 * work function by reading the pen signal state (it's a GPIO
+	 * and IRQ). Unfortunately such callback is not always available,
+	 * in that case we have rely on the pressure anyway.
+	 */
+	if (ts->get_pendown_state) {
+		if (unlikely(!ts->get_pendown_state())) {
+			tsc2004_send_up_event(ts);
+			ts->pendown = false;
+			goto out;
+		}
+
+		dev_dbg(&ts->client->dev, "pen is still down\n");
+	}
+
+	tsc2004_read_values(ts, &tc);
+
+	rt = tsc2004_calculate_pressure(ts, &tc);
+	if (rt > MAX_12BIT) {
+		/*
+		 * Sample found inconsistent by debouncing or pressure is
+		 * beyond the maximum. Don't report it to user space,
+		 * repeat at least once more the measurement.
+		 */
+		dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt);
+		goto out;
+
+	}
+
+	if (rt) {
+		struct input_dev *input = ts->input;
+
+		if (!ts->pendown) {
+			dev_dbg(&ts->client->dev, "DOWN\n");
+
+			input_report_key(input, BTN_TOUCH, 1);
+			ts->pendown = true;
+		}
+
+		input_report_abs(input, ABS_X, tc.x);
+		input_report_abs(input, ABS_Y, tc.y);
+		input_report_abs(input, ABS_PRESSURE, rt);
+
+		input_sync(input);
+
+		dev_dbg(&ts->client->dev, "point(%4d,%4d), pressure (%4u)\n",
+			tc.x, tc.y, rt);
+
+	} else if (!ts->get_pendown_state && ts->pendown) {
+		/*
+		 * We don't have callback to check pendown state, so we
+		 * have to assume that since pressure reported is 0 the
+		 * pen was lifted up.
+		 */
+		tsc2004_send_up_event(ts);
+		ts->pendown = false;
+	}
+
+ out:
+	if (ts->pendown)
+		schedule_delayed_work(&ts->work,
+				      msecs_to_jiffies(TS_POLL_PERIOD));
+	else
+		enable_irq(ts->irq);
+}
+
+static irqreturn_t tsc2004_irq(int irq, void *handle)
+{
+	struct tsc2004 *ts = handle;
+
+	if (!ts->get_pendown_state || likely(ts->get_pendown_state())) {
+		disable_irq_nosync(ts->irq);
+		schedule_delayed_work(&ts->work,
+				      msecs_to_jiffies(TS_POLL_DELAY));
+	}
+
+	if (ts->clear_penirq)
+		ts->clear_penirq();
+
+	return IRQ_HANDLED;
+}
+
+static void tsc2004_free_irq(struct tsc2004 *ts)
+{
+	free_irq(ts->irq, ts);
+	if (cancel_delayed_work_sync(&ts->work)) {
+		/*
+		 * Work was pending, therefore we need to enable
+		 * IRQ here to balance the disable_irq() done in the
+		 * interrupt handler.
+		 */
+		enable_irq(ts->irq);
+	}
+}
+
+static int __devinit tsc2004_probe(struct i2c_client *client,
+				   const struct i2c_device_id *id)
+{
+	struct tsc2004 *ts;
+	struct tsc2004_platform_data *pdata = pdata = client->dev.platform_data;
+	struct input_dev *input_dev;
+	int err;
+
+	if (!pdata) {
+		dev_err(&client->dev, "platform data is required!\n");
+		return -EINVAL;
+	}
+
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_SMBUS_READ_WORD_DATA))
+		return -EIO;
+
+	ts = kzalloc(sizeof(struct tsc2004), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!ts || !input_dev) {
+		err = -ENOMEM;
+		goto err_free_mem;
+	}
+
+	ts->client = client;
+	ts->irq = client->irq;
+	ts->input = input_dev;
+	INIT_DELAYED_WORK(&ts->work, tsc2004_work);
+
+	ts->model             = pdata->model;
+	ts->x_plate_ohms      = pdata->x_plate_ohms;
+	ts->get_pendown_state = pdata->get_pendown_state;
+	ts->clear_penirq      = pdata->clear_penirq;
+
+	snprintf(ts->phys, sizeof(ts->phys),
+		 "%s/input0", dev_name(&client->dev));
+
+	input_dev->name = "TSC2004 Touchscreen";
+	input_dev->phys = ts->phys;
+	input_dev->id.bustype = BUS_I2C;
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+	input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0);
+	input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, 0, 0);
+
+	if (pdata->init_platform_hw)
+		pdata->init_platform_hw();
+
+	err = request_irq(ts->irq, tsc2004_irq, IRQF_TRIGGER_FALLING,
+			client->dev.driver->name, ts);
+	if (err < 0) {
+		dev_err(&client->dev, "irq %d busy?\n", ts->irq);
+		goto err_free_mem;
+	}
+
+	/* Prepare for touch readings */
+	err = tsc2004_prepare_for_reading(ts);
+	if (err < 0)
+		goto err_free_irq;
+
+	err = input_register_device(input_dev);
+	if (err)
+		goto err_free_irq;
+
+	i2c_set_clientdata(client, ts);
+
+	return 0;
+
+ err_free_irq:
+	tsc2004_free_irq(ts);
+	if (pdata->exit_platform_hw)
+		pdata->exit_platform_hw();
+ err_free_mem:
+	input_free_device(input_dev);
+	kfree(ts);
+	return err;
+}
+
+static int __devexit tsc2004_remove(struct i2c_client *client)
+{
+	struct tsc2004	*ts = i2c_get_clientdata(client);
+	struct tsc2004_platform_data *pdata = client->dev.platform_data;
+
+	tsc2004_free_irq(ts);
+
+	if (pdata->exit_platform_hw)
+		pdata->exit_platform_hw();
+
+	input_unregister_device(ts->input);
+	kfree(ts);
+
+	return 0;
+}
+
+static struct i2c_device_id tsc2004_idtable[] = {
+	{ "tsc2004", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, tsc2004_idtable);
+
+static struct i2c_driver tsc2004_driver = {
+	.driver = {
+		.owner	= THIS_MODULE,
+		.name	= "tsc2004"
+	},
+	.id_table	= tsc2004_idtable,
+	.probe		= tsc2004_probe,
+	.remove		= __devexit_p(tsc2004_remove),
+};
+
+static int __init tsc2004_init(void)
+{
+	return i2c_add_driver(&tsc2004_driver);
+}
+
+static void __exit tsc2004_exit(void)
+{
+	i2c_del_driver(&tsc2004_driver);
+}
+
+module_init(tsc2004_init);
+module_exit(tsc2004_exit);
+
+MODULE_AUTHOR("Vaibhav Hiremath <hvaibhav@ti.com>");
+MODULE_DESCRIPTION("TSC2004 TouchScreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/i2c/tsc2004.h b/include/linux/i2c/tsc2004.h
new file mode 100644
index 0000000..044c36f
--- /dev/null
+++ b/include/linux/i2c/tsc2004.h
@@ -0,0 +1,17 @@ 
+#ifndef __LINUX_I2C_TSC2004_H
+#define __LINUX_I2C_TSC2004_H
+
+/* linux/i2c/tsc2004.h */
+
+struct tsc2004_platform_data {
+	u16	model;				/* 2004. */
+	u16	x_plate_ohms;
+
+	int	(*get_pendown_state)(void);
+	void	(*clear_penirq)(void);		/* If needed, clear 2nd level
+						   interrupt source */
+	int	(*init_platform_hw)(void);
+	void	(*exit_platform_hw)(void);
+};
+
+#endif