summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorEric Nelson <eric.nelson@boundarydevices.com>2012-06-05 20:31:16 -0700
committerEric Nelson <eric.nelson@boundarydevices.com>2013-02-12 10:45:33 -0700
commit46e0492e028632e22501b095bc0c21c5b52dab1a (patch)
tree4abcf078e318cf9abaf4e58800408f3254194af3 /drivers
parent3592972292651b0fddeb929864f03a9d80816b8c (diff)
add tsc2004 resistive touch driver
Diffstat (limited to 'drivers')
-rw-r--r--drivers/input/touchscreen/Kconfig11
-rw-r--r--drivers/input/touchscreen/Makefile1
-rw-r--r--drivers/input/touchscreen/tsc2004.c561
3 files changed, 573 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index eabf4098ca1c..3e14e8b3fc6b 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -708,6 +708,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 94a71c1fa93d..aa918bbe26b5 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o
obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o
obj-$(CONFIG_TOUCHSCREEN_TSC2005) += tsc2005.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_WM831X) += wm831x-ts.o
diff --git a/drivers/input/touchscreen/tsc2004.c b/drivers/input/touchscreen/tsc2004.c
new file mode 100644
index 000000000000..37af845c2ac9
--- /dev/null
+++ b/drivers/input/touchscreen/tsc2004.c
@@ -0,0 +1,561 @@
+/*
+ * 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/tsc2007.h>
+
+static int calibration[7];
+module_param_array(calibration, int, NULL, S_IRUGO | S_IWUSR);
+
+static void translate(u16 *px, u16 *py)
+{
+ int x, y, x1, y1;
+ if (calibration[6]) {
+ x1 = *px;
+ y1 = *py;
+
+ x = calibration[0] * x1 +
+ calibration[1] * y1 +
+ calibration[2];
+ x /= calibration[6];
+ if (x < 0)
+ x = 0;
+ y = calibration[3] * x1 +
+ calibration[4] * y1 +
+ calibration[5];
+ y /= calibration[6];
+ if (y < 0)
+ y = 0;
+ *px = x ;
+ *py = y ;
+ }
+}
+
+#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_xyz_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;
+ }
+
+ /*
+ * We need to swap byte order for little-endian cpus.
+ * 12 bit precision, high 4 bits should be zero
+ */
+ val = be16_to_cpu(data) & 0xfff;
+
+ 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 = cpu_to_be16(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;
+ int retries ;
+
+ /* Reset the TSC, configure for 12 bit */
+ retries = 0 ;
+ do {
+ /* 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)
+ printk (KERN_ERR "%s: write_cmd %d\n", __func__, err );
+ } while ( (err < 0) && (3 < retries++) );
+
+ 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_xyz_data(tsc, cmd);
+
+ /* Read Y Measurement */
+ cmd = TSC2004_CMD0(Y_REG, PND0_FALSE, READ_REG);
+ tc->y = tsc2004_read_xyz_data(tsc, cmd);
+
+ /* Read Z1 Measurement */
+ cmd = TSC2004_CMD0(Z1_REG, PND0_FALSE, READ_REG);
+ tc->z1 = tsc2004_read_xyz_data(tsc, cmd);
+
+ /* Read Z2 Measurement */
+ cmd = TSC2004_CMD0(Z2_REG, PND0_FALSE, READ_REG);
+ tc->z2 = tsc2004_read_xyz_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;
+
+ translate(&tc.x, &tc.y);
+
+ 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 tsc2007_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";
+ 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 tsc2007_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");