From: Vaibhav Hiremath <[email protected]>

Signed-off-by: Vaibhav Hiremath <[email protected]>
---
 drivers/input/touchscreen/Kconfig   |   11 +
 drivers/input/touchscreen/Makefile  |    1 +
 drivers/input/touchscreen/tsc2004.c |  525 +++++++++++++++++++++++++++++++++++
 include/linux/i2c/tsc2004.h         |   17 ++
 4 files changed, 554 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/tsc2004.c
 create mode 100644 include/linux/i2c/tsc2004.h

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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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
-- 
1.6.2.4

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to