]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ENGR00180621-1: Add ELAN capacity touch screen driver
authorRobby Cai <R63905@freescale.com>
Mon, 23 Apr 2012 08:12:20 +0000 (16:12 +0800)
committerLothar Waßmann <LW@KARO-electronics.de>
Fri, 24 May 2013 06:34:30 +0000 (08:34 +0200)
Add Elan ts driver.

Signed-off-by: Robby Cai <R63905@freescale.com>
Acked-by: Lily Zhang
drivers/input/touchscreen/Kconfig
drivers/input/touchscreen/Makefile
drivers/input/touchscreen/elan_ts.c [new file with mode: 0644]

index c698e138e86c12ee73ce39a5c6780b92cdad3501..79422488b1fc959f34b07b6a9d5cefa558bd2cd7 100755 (executable)
@@ -187,6 +187,18 @@ config TOUCHSCREEN_EGALAX
          To compile this driver as a module, choose M here: the
          module will be called egalax_ts.
 
+config TOUCHSCREEN_ELAN
+       tristate "ELAN touchscreen input driver"
+       depends on I2C
+       help
+         Say Y here if you have an I2C ELAN touchscreen
+         attached.
+
+         If unsure, say N.
+
+         To compile this driver as a module, choose M here: the
+         module will be called elan-touch.
+
 config TOUCHSCREEN_FUJITSU
        tristate "Fujitsu serial touchscreen"
        select SERIO
index 63c23d061916a1f3157c430167dc0a3e4f51a662..05155e0607cb185416dc3b267e127b19f744cd50 100755 (executable)
@@ -65,3 +65,4 @@ obj-$(CONFIG_TOUCHSCREEN_P1003)               += p1003_ts.o
 obj-$(CONFIG_TOUCHSCREEN_TPS6507X)     += tps6507x-ts.o
 obj-$(CONFIG_TOUCHSCREEN_MAX11801)     += max11801_ts.o
 obj-$(CONFIG_TOUCHSCREEN_EGALAX)       += egalax_ts.o
+obj-$(CONFIG_TOUCHSCREEN_ELAN)         += elan_ts.o
diff --git a/drivers/input/touchscreen/elan_ts.c b/drivers/input/touchscreen/elan_ts.c
new file mode 100644 (file)
index 0000000..7edd87d
--- /dev/null
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2007-2008 HTC Corporation.
+ *
+ * This driver is adapted from elan8232_i2c.c written by Shan-Fu Chiou
+ * <sfchiou@gmail.com> and Jay Tu <jay_tu@htc.com>.
+ * This driver is also adapted from the ELAN Touch Screen driver
+ * written by Stanley Zeng <stanley.zeng@emc.com.tw>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/input.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/hrtimer.h>
+#include <linux/gpio.h>
+
+static const char ELAN_TS_NAME[] = "elan-touch";
+
+#define ELAN_TS_X_MAX          1088
+#define ELAN_TS_Y_MAX          768
+#define ELAN_USER_X_MAX                800
+#define ELAN_USER_Y_MAX                600
+#define IDX_PACKET_SIZE                8
+
+enum {
+       hello_packet = 0x55,
+       idx_coordinate_packet = 0x5a,
+};
+
+enum {
+       idx_finger_state = 7,
+};
+
+static struct workqueue_struct *elan_wq;
+
+static struct elan_data {
+       int intr_gpio;
+       int use_irq;
+       struct hrtimer timer;
+       struct work_struct work;
+       struct i2c_client *client;
+       struct input_dev *input;
+       wait_queue_head_t wait;
+} elan_touch_data;
+
+/*--------------------------------------------------------------*/
+static int elan_touch_detect_int_level(void)
+{
+       unsigned v;
+       v = gpio_get_value(elan_touch_data.intr_gpio);
+
+       return v;
+}
+
+static int __elan_touch_poll(struct i2c_client *client)
+{
+       int status = 0, retry = 20;
+
+       do {
+               status = elan_touch_detect_int_level();
+               retry--;
+               mdelay(20);
+       } while (status == 1 && retry > 0);
+
+       return (status == 0 ? 0 : -ETIMEDOUT);
+}
+
+static int elan_touch_poll(struct i2c_client *client)
+{
+       return __elan_touch_poll(client);
+}
+
+static int __hello_packet_handler(struct i2c_client *client)
+{
+       int rc;
+       uint8_t buf_recv[4] = { 0 };
+
+       rc = elan_touch_poll(client);
+
+       if (rc < 0) {
+               return -EINVAL;
+       }
+
+       rc = i2c_master_recv(client, buf_recv, 4);
+
+       if (rc != 4) {
+               return rc;
+       } else {
+               int i;
+               printk("hello packet: [0x%02x 0x%02x 0x%02x 0x%02x]\n",
+                      buf_recv[0], buf_recv[1], buf_recv[2], buf_recv[3]);
+
+               for (i = 0; i < 4; i++)
+                       if (buf_recv[i] != hello_packet)
+                               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static inline int elan_touch_parse_xy(uint8_t *data, uint16_t *x,
+                                     uint16_t *y)
+{
+       *x = (data[0] & 0xf0);
+       *x <<= 4;
+       *x |= data[1];
+       if (*x >= ELAN_TS_X_MAX)
+               *x = ELAN_TS_X_MAX;
+       *x = ((((ELAN_TS_X_MAX -
+                *x) * 1000) / ELAN_TS_X_MAX) * ELAN_USER_X_MAX) / 1000;
+
+       *y = (data[0] & 0x0f);
+       *y <<= 8;
+       *y |= data[2];
+       if (*y >= ELAN_TS_Y_MAX)
+               *y = ELAN_TS_Y_MAX;
+       *y = ((((ELAN_TS_Y_MAX -
+                *y) * 1000) / ELAN_TS_Y_MAX) * ELAN_USER_Y_MAX) / 1000;
+
+       return 0;
+}
+
+/*     __elan_touch_init -- hand shaking with touch panel
+ *
+ *     1.recv hello packet
+ */
+static int __elan_touch_init(struct i2c_client *client)
+{
+       int rc;
+       rc = __hello_packet_handler(client);
+       if (rc < 0)
+               goto hand_shake_failed;
+
+hand_shake_failed:
+       return rc;
+}
+
+static int elan_touch_recv_data(struct i2c_client *client, uint8_t * buf)
+{
+       int rc, bytes_to_recv = IDX_PACKET_SIZE;
+
+       if (buf == NULL)
+               return -EINVAL;
+
+       memset(buf, 0, bytes_to_recv);
+       rc = i2c_master_recv(client, buf, bytes_to_recv);
+       if (rc != bytes_to_recv) {
+               return -EINVAL;
+       }
+
+       return rc;
+}
+
+static void elan_touch_report_data(struct i2c_client *client, uint8_t * buf)
+{
+       switch (buf[0]) {
+       case idx_coordinate_packet:
+       {
+               uint16_t x1, x2, y1, y2;
+               uint8_t finger_stat;
+
+               finger_stat = (buf[idx_finger_state] & 0x06) >> 1;
+
+               if (finger_stat == 0) {
+                       input_report_key(elan_touch_data.input, BTN_TOUCH, 0);
+                       input_report_key(elan_touch_data.input, BTN_2, 0);
+               } else if (finger_stat == 1) {
+                       elan_touch_parse_xy(&buf[1], &x1, &y1);
+                       input_report_abs(elan_touch_data.input, ABS_X, x1);
+                       input_report_abs(elan_touch_data.input, ABS_Y, y1);
+                       input_report_key(elan_touch_data.input, BTN_TOUCH, 1);
+                       input_report_key(elan_touch_data.input, BTN_2, 0);
+               } else if (finger_stat == 2) {
+                       elan_touch_parse_xy(&buf[1], &x1, &y1);
+                       input_report_abs(elan_touch_data.input, ABS_X, x1);
+                       input_report_abs(elan_touch_data.input, ABS_Y, y1);
+                       input_report_key(elan_touch_data.input, BTN_TOUCH, 1);
+                       elan_touch_parse_xy(&buf[4], &x2, &y2);
+                       input_report_abs(elan_touch_data.input, ABS_HAT0X, x2);
+                       input_report_abs(elan_touch_data.input, ABS_HAT0Y, y2);
+                       input_report_key(elan_touch_data.input, BTN_2, 1);
+               }
+               input_sync(elan_touch_data.input);
+               break;
+       }
+
+       default:
+               break;
+       }
+}
+
+static void elan_touch_work_func(struct work_struct *work)
+{
+       int rc;
+       uint8_t buf[IDX_PACKET_SIZE] = { 0 };
+       struct i2c_client *client = elan_touch_data.client;
+
+       if (elan_touch_detect_int_level())
+               return;
+
+       rc = elan_touch_recv_data(client, buf);
+       if (rc < 0)
+               return;
+
+       elan_touch_report_data(client, buf);
+}
+
+static irqreturn_t elan_touch_ts_interrupt(int irq, void *dev_id)
+{
+       queue_work(elan_wq, &elan_touch_data.work);
+
+       return IRQ_HANDLED;
+}
+
+static enum hrtimer_restart elan_touch_timer_func(struct hrtimer *timer)
+{
+       queue_work(elan_wq, &elan_touch_data.work);
+       hrtimer_start(&elan_touch_data.timer, ktime_set(0, 12500000),
+                     HRTIMER_MODE_REL);
+
+       return HRTIMER_NORESTART;
+}
+
+static int elan_touch_register_interrupt(struct i2c_client *client)
+{
+       int err = 0;
+
+       if (client->irq) {
+               elan_touch_data.use_irq = 1;
+               err =
+                   request_irq(client->irq, elan_touch_ts_interrupt,
+                               IRQF_TRIGGER_FALLING, ELAN_TS_NAME,
+                               &elan_touch_data);
+
+               if (err < 0) {
+                       printk("%s(%s): Can't allocate irq %d\n", __FILE__,
+                              __func__, client->irq);
+                       elan_touch_data.use_irq = 0;
+               }
+       }
+
+       if (!elan_touch_data.use_irq) {
+               hrtimer_init(&elan_touch_data.timer, CLOCK_MONOTONIC,
+                            HRTIMER_MODE_REL);
+               elan_touch_data.timer.function = elan_touch_timer_func;
+               hrtimer_start(&elan_touch_data.timer, ktime_set(1, 0),
+                             HRTIMER_MODE_REL);
+       }
+
+       printk("elan ts starts in %s mode.\n",
+              elan_touch_data.use_irq == 1 ? "interrupt" : "polling");
+
+       return 0;
+}
+
+static int elan_touch_probe(struct i2c_client *client,
+                           const struct i2c_device_id *id)
+{
+       int err = 0;
+
+       elan_wq = create_singlethread_workqueue("elan_wq");
+       if (!elan_wq) {
+               err = -ENOMEM;
+               goto fail;
+       }
+
+       elan_touch_data.client = client;
+       strlcpy(client->name, ELAN_TS_NAME, I2C_NAME_SIZE);
+
+       INIT_WORK(&elan_touch_data.work, elan_touch_work_func);
+
+       elan_touch_data.intr_gpio = irq_to_gpio(client->irq);
+       pr_debug("irq_to_gpio irq %d, gpio %d\n", client->irq,
+                elan_touch_data.intr_gpio);
+
+       elan_touch_data.input = input_allocate_device();
+       if (elan_touch_data.input == NULL) {
+               err = -ENOMEM;
+               goto fail;
+       }
+
+       err = __elan_touch_init(client);
+       if (err < 0) {
+               printk("elan - Read Hello Packet Failed\n");
+               goto fail;
+       }
+
+       elan_touch_data.input->name = ELAN_TS_NAME;
+       elan_touch_data.input->id.bustype = BUS_I2C;
+
+       set_bit(EV_SYN, elan_touch_data.input->evbit);
+
+       set_bit(EV_KEY, elan_touch_data.input->evbit);
+       set_bit(BTN_TOUCH, elan_touch_data.input->keybit);
+       set_bit(BTN_2, elan_touch_data.input->keybit);
+
+       set_bit(EV_ABS, elan_touch_data.input->evbit);
+       set_bit(ABS_X, elan_touch_data.input->absbit);
+       set_bit(ABS_Y, elan_touch_data.input->absbit);
+       set_bit(ABS_HAT0X, elan_touch_data.input->absbit);
+       set_bit(ABS_HAT0Y, elan_touch_data.input->absbit);
+
+       input_set_abs_params(elan_touch_data.input, ABS_X, 0, ELAN_USER_X_MAX,
+                            0, 0);
+       input_set_abs_params(elan_touch_data.input, ABS_Y, 0, ELAN_USER_Y_MAX,
+                            0, 0);
+       input_set_abs_params(elan_touch_data.input, ABS_HAT0X, 0,
+                            ELAN_USER_X_MAX, 0, 0);
+       input_set_abs_params(elan_touch_data.input, ABS_HAT0Y, 0,
+                            ELAN_USER_Y_MAX, 0, 0);
+
+       err = input_register_device(elan_touch_data.input);
+       if (err < 0) {
+               goto fail;
+       }
+
+       elan_touch_register_interrupt(elan_touch_data.client);
+
+       return 0;
+
+fail:
+       input_free_device(elan_touch_data.input);
+       if (elan_wq)
+               destroy_workqueue(elan_wq);
+       return err;
+}
+
+static int elan_touch_remove(struct i2c_client *client)
+{
+       if (elan_wq)
+               destroy_workqueue(elan_wq);
+
+       input_unregister_device(elan_touch_data.input);
+
+       if (elan_touch_data.use_irq)
+               free_irq(client->irq, client);
+       else
+               hrtimer_cancel(&elan_touch_data.timer);
+       return 0;
+}
+
+/* -------------------------------------------------------------------- */
+static const struct i2c_device_id elan_touch_id[] = {
+       {"elan-touch", 0},
+       {}
+};
+
+static struct i2c_driver elan_touch_driver = {
+       .probe = elan_touch_probe,
+       .remove = elan_touch_remove,
+       .id_table = elan_touch_id,
+       .driver = {
+                  .name = "elan-touch",
+                  .owner = THIS_MODULE,
+                  },
+};
+
+static int __init elan_touch_init(void)
+{
+       return i2c_add_driver(&elan_touch_driver);
+}
+
+static void __exit elan_touch_exit(void)
+{
+       i2c_del_driver(&elan_touch_driver);
+}
+
+module_init(elan_touch_init);
+module_exit(elan_touch_exit);
+
+MODULE_AUTHOR("Stanley Zeng <stanley.zeng@emc.com.tw>");
+MODULE_DESCRIPTION("ELAN Touch Screen driver");
+MODULE_LICENSE("GPL");