All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
@ 2013-12-27 12:15 scott
  2014-11-20  8:11 ` Dmitry Torokhov
  0 siblings, 1 reply; 14+ messages in thread
From: scott @ 2013-12-27 12:15 UTC (permalink / raw
  To: Scott Liu, Dmitry Torokhov, linux-kernel, linux-input
  Cc: Vincent Wang, Jeff Chuang, Agnes Cheng

From: Scott Liu <scott.liu@emc.com.tw>

This patch is for Elan eKTH Touchscreen product, I2C adpater module.

Signed-off-by: Scott Liu <scott.liu@emc.com.tw>
---
 drivers/input/touchscreen/Kconfig      |   12 +
 drivers/input/touchscreen/Makefile     |    1 +
 drivers/input/touchscreen/elants_i2c.c | 1789 ++++++++++++++++++++++++++++++++
 3 files changed, 1802 insertions(+)
 create mode 100644 drivers/input/touchscreen/elants_i2c.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 961d58d..b400faa 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -317,6 +317,18 @@ config TOUCHSCREEN_GUNZE
 	  To compile this driver as a module, choose M here: the
 	  module will be called gunze.
 
+config TOUCHSCREEN_ELAN
+	tristate "Elan touchscreens"
+	depends on I2C
+	help
+	  Say Y here if you have an Elan touchscreen connected to
+	  your system.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called elan.
+
 config TOUCHSCREEN_ELO
 	tristate "Elo serial touchscreens"
 	select SERIO
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 62801f2..687d5a7 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06)	+= edt-ft5x06.o
 obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE)	+= hampshire.o
 obj-$(CONFIG_TOUCHSCREEN_GUNZE)		+= gunze.o
 obj-$(CONFIG_TOUCHSCREEN_EETI)		+= eeti_ts.o
+obj-$(CONFIG_TOUCHSCREEN_ELAN)		+= elants_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_ELO)		+= elo.o
 obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
 obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c
new file mode 100644
index 0000000..e46675a
--- /dev/null
+++ b/drivers/input/touchscreen/elants_i2c.c
@@ -0,0 +1,1789 @@
+/*
+ * Elan Microelectronics touchpanels with I2C interface
+ *
+ * Copyright (C) 2012 Elan Microelectronics Corporation.
+ * Scott Liu <scott.liu@emc.com.tw>
+ *
+ * This code is partly based on hid-multitouch.c:
+ *
+ *  Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr>
+ *  Copyright (c) 2010-2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ *  Copyright (c) 2010-2012 Ecole Nationale de l'Aviation Civile, France
+ *
+ */
+
+/*
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/async.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/buffer_head.h>
+#include <linux/version.h>
+#include <linux/kfifo.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/firmware.h>
+#include <linux/version.h>
+#include <linux/input/mt.h>
+
+/* debug option */
+static bool debug = false;
+module_param(debug, bool, 0444);
+MODULE_PARM_DESC(debug, "print a lot of debug information");
+
+#define elan_dbg(client, fmt, arg...)   \
+	do {	\
+		if (debug)      \
+			dev_printk(KERN_DEBUG, \
+				&client->dev, fmt, ##arg);	\
+	} while (0)
+
+/*
+ * Device, Driver information
+ */
+#define DEVICE_NAME	"elants_i2c"
+
+#define DRV_MA_VER 1
+#define DRV_MI_VER 0
+#define DRV_SUB_MI_VER 4
+
+#define _str(s) (#s)
+#define str(s)  _str(s)
+#define DRIVER_VERSION  str(DRV_MA_VER.DRV_MI_VER.DRV_SUB_MI_VER)
+
+/* For CMT (must match XRANGE/YRANGE as defined in board config */
+#define X_PIXELS_PER_MM	13
+#define Y_PIXELS_PER_MM	14
+
+/*
+ * Finger report description
+ */
+
+#define MAX_CONTACT_NUM	10
+
+/*
+ * kfifo buffer size, used for Read command handshake
+ */
+
+#define FIFO_SIZE       (64)
+
+/*
+ *Convert from rows or columns into resolution
+ */
+
+#define ELAN_TS_RESOLUTION(n)   ((n - 1) * 64)
+
+static const char hello_packet[4] = { 0x55, 0x55, 0x55, 0x55 };
+static const char iniap_packet[4] = { 0x55, 0xaa, 0x33, 0xcc };
+static const char recov_packet[4] = { 0x55, 0x55, 0x80, 0x80 };
+
+/*
+ * Elan I2C Address definition
+ */
+
+#define	DEV_MASTER	0x10
+#define	DEV_SLAVE1	0x20
+#define	DEV_SLAVE2	0x21
+#define	MAX_DEVICE	3
+
+/*
+ * Finger report header byte definition
+ */
+
+#define	REPORT_HEADER_10_FINGER	0x62
+
+/*
+ * Buffer mode Queue Header information
+ */
+
+#define	QUEUE_HEADER_SINGLE	0x62
+#define	QUEUE_HEADER_NORMAL	0X63
+#define QUEUE_HEADER_WAIT	0x64
+#define	QUEUE_HEADER_SIZE	4
+
+#define PACKET_SIZE	55
+#define	MAX_PACKET_LEN	169
+
+/*
+ * Command header definition
+ */
+
+#define	CMD_HEADER_WRITE	0x54
+#define	CMD_HEADER_READ	0x53
+#define CMD_HEADER_6B_READ	0x5B
+#define	CMD_HEADER_RESP	0x52
+#define	CMD_HEADER_6B_RESP	0x9B
+#define	CMD_HEADER_HELLO	0x55
+#define	CMD_HEADER_REK	0x66
+#define	CMD_RESP_LEN	4
+
+/*
+ * FW information position
+ */
+
+#define	FW_POS_HEADER	0
+#define	FW_POS_STATE	1
+#define	FW_POS_TOTAL	2
+#define	FW_POS_CHECKSUM	34
+#define	FW_POS_WIDTH	35
+#define FW_POS_PRESSURE 45
+
+/*
+ * test_bit definition
+ */
+
+#define	LOCK_FILE_OPERATE	0
+#define	LOCK_CMD_HANDSHAKE	1
+#define	LOCK_FINGER_REPORT	2
+#define	LOCK_FW_UPDATE	3
+
+/*
+ * kfifo return value definition
+ */
+
+#define RET_OK	0
+#define	RET_CMDRSP	1
+#define	RET_FAIL	-1
+
+/*
+ * define boot condition definition
+ */
+
+#define	E_BOOT_NORM	0
+#define	E_BOOT_IAP	1
+
+/* FW read command, 0x53 0x?? 0x0, 0x01 */
+#define	E_ELAN_INFO_FW_VER	0x00
+#define	E_ELAN_INFO_BC_VER	0x10
+#define E_ELAN_INFO_TEST_VER	0xe0
+#define	E_ELAN_INFO_FW_ID	0xf0
+
+/**
+ * struct multi_queue_header - used by buffer queue header
+ *
+ * @packet_id: packet_id represented status of buffer.
+ * @report_count: number of finger report in buffer.
+ * @report_length: total length exclusive queue length.
+ */
+struct multi_queue_header {
+	u8 packet_id;
+	u8 report_count;
+	u8 report_length;
+	u8 reserved;
+};
+
+struct mt_slot {
+	__s32 x, y, p, w, h;
+	__s32 contactid;	/* the device ContactID assigned to this slot */
+	bool touch_state;	/* is the touch valid? */
+	bool seen_in_this_frame;	/* has this slot been updated */
+};
+
+struct mt_device {
+	struct mt_slot curdata;	/* placeholder of incoming data */
+	__u8 num_received;	/* how many contacts we received */
+	__u8 num_expected;	/* expected last contact index */
+	__u8 maxcontacts;
+	bool curvalid;		/* is the current contact valid? */
+	unsigned mt_flags;	/* flags to pass to input-mt */
+	struct mt_slot *slots;
+};
+
+/**
+ * struct elants_data - represents a global define of elants device
+ */
+struct elants_data {
+
+	bool irq_wake;
+
+	/* [0] : Solution version
+	   [1] : minor version */
+	union {
+		u8 _fw_version[2];
+		u16 fw_version;
+	};
+
+	u8 bc_version;
+	u8 iap_version;
+
+	union {
+		u8 _test_version[2];
+		u16 test_version;
+	};
+
+	bool fw_enabled;
+	int rows;
+	int cols;
+	int x_max;
+	int y_max;
+#define IAP_MODE_ENABLE	1
+	unsigned int iap_mode;
+	unsigned int rx_size;
+
+	u8 packet_size;
+
+	struct multi_queue_header mq_header;
+
+	struct i2c_client *client;
+	struct input_dev *input;
+	struct task_struct *thread;
+
+	struct mutex mutex;	/* Protects I2C accesses to device */
+	struct mutex tr_mutex;	/* Protects I2C tx/rx */
+
+	unsigned long flags;	/* device flags */
+
+	unsigned short i2caddr;
+
+	struct kfifo fifo;
+	struct mutex fifo_mutex;
+	wait_queue_head_t wait;
+	spinlock_t rx_kfifo_lock;
+
+	struct mt_device td;	/* mt device */
+
+	/* fields required for debug fs */
+	struct mutex dbfs_mutex;
+	struct dentry *dbfs_root;
+
+	/* Add for TS driver debug */
+	long int irq_received;
+	long int packet_received;
+	long int touched_sync;
+	long int no_touched_sync;
+	long int checksum_correct;
+	long int wdt_reset;
+	u16 checksum_fail;
+	u16 header_fail;
+	u16 mq_header_fail;
+};
+
+/*
+ *  Function implement
+ */
+static inline void elan_msleep(u32 t)
+{
+	/*
+	 * If the sleeping time is 10us - 20ms, usleep_range() is recommended.
+	 * Read Documentation/timers/timers-howto.txt
+	 */
+	usleep_range(t * 1000, t * 1000 + 500);
+}
+
+static inline bool elan_chip_number(u8 addr)
+{
+	return addr == DEV_MASTER ? 1 : 0;
+}
+
+/**
+ *	elan_set_data - set command to TP.
+ *
+ *	@client: our i2c device
+ *	@data: command or data which will send to TP.
+ *	@len: command length usually.
+ *
+ *	set command to our TP.
+ */
+static int elan_set_data(struct i2c_client *client, const u8 *data, size_t len)
+{
+	struct elants_data *ts = i2c_get_clientdata(client);
+	struct i2c_adapter *adap = client->adapter;
+	struct i2c_msg msg;
+	int rc = 0;
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+	elan_dbg(client,
+		 "%s cmd: %*phC, addr=%x\n",
+		 __func__, (int)len, data, ts->i2caddr);
+
+	mutex_lock(&ts->tr_mutex);
+
+	msg.addr = ts->i2caddr;
+	msg.flags = ts->client->flags & I2C_M_TEN;
+	msg.len = len;
+	msg.buf = (char *)data;
+
+	rc = i2c_transfer(adap, &msg, 1);
+	if (rc != 1)
+		dev_err(&ts->client->dev,
+			"i2c_transfer write fail, rc=%d\n", rc);
+
+	mutex_unlock(&ts->tr_mutex);
+
+	/* If everything went ok (i.e. 1 msg transmitted), return #0
+	   transmitted, else error code. */
+	return (rc < 0) ? -EIO : 0;
+
+}
+
+/**
+ *	elan_get_data - get command to TP.
+ *
+ *	@client: our i2c device
+ *	@data: data to be received from TP.
+ *	@len: command length usually.
+ *
+ *	get data from our TP.
+ */
+static int elan_get_data(struct i2c_client *client, const u8 *buf, size_t len)
+{
+	struct elants_data *ts = i2c_get_clientdata(client);
+	struct i2c_adapter *adap = client->adapter;
+	struct i2c_msg msg;
+	int rc = 0;
+
+	dev_dbg(&client->dev, "Enter: %s id:0x%x\n", __func__, ts->i2caddr);
+
+	mutex_lock(&ts->tr_mutex);
+
+	msg.addr = ts->i2caddr;
+	msg.flags = client->flags & I2C_M_TEN;
+	msg.flags |= I2C_M_RD;
+	msg.len = len;
+	msg.buf = (char *)buf;
+
+	rc = i2c_transfer(adap, &msg, 1);
+	if (rc != 1)
+		dev_err(&client->dev, "i2c_transfer read fail, rc=%d\n", rc);
+
+	elan_dbg(client, "%s len=%zu data:%*phC\n",
+		 __func__, len, (int)len, buf);
+
+	mutex_unlock(&ts->tr_mutex);
+
+	/* If everything went ok (i.e. 1 msg transmitted), return #0
+	   transmitted, else error code. */
+	return rc != 1 ? -EIO : 0;
+}
+
+/**
+ *	elan_i2c_read_block
+ *
+ *	@client: our i2c device
+ *	@cmd: command
+ *	@val: data to be received from TP.
+ *	@len: command length usually.
+ *
+ *	get data from our TP by continueous read.
+ */
+static int elan_i2c_read_block(struct i2c_client *client,
+			       u8 *cmd, u8 *val, u16 len)
+{
+	struct i2c_msg msgs[2];
+	int ret;
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	msgs[0].addr = client->addr;
+	msgs[0].flags = client->flags & I2C_M_TEN;
+	msgs[0].len = 4;
+	msgs[0].buf = cmd;
+
+	msgs[1].addr = client->addr;
+	msgs[1].flags = client->flags & I2C_M_TEN;
+	msgs[1].flags |= I2C_M_RD;
+	msgs[1].len = len;
+	msgs[1].buf = val;
+
+	ret = i2c_transfer(client->adapter, msgs, 2);
+	return (ret == 2) ? len : ret;
+}
+
+static int elan_dbfs_open(struct inode *inode, struct file *file)
+{
+	int retval = 0;
+	struct elants_data *ts = inode->i_private;
+
+	dev_dbg(&ts->client->dev, "Enter %s\n", __func__);
+
+	if (!ts)
+		return -ENODEV;
+
+	retval = mutex_lock_interruptible(&ts->dbfs_mutex);
+	if (retval)
+		return retval;
+
+	if (!kobject_get(&ts->client->dev.kobj)) {
+		retval = -ENODEV;
+		goto dbfs_out;
+	}
+
+	file->private_data = ts;
+dbfs_out:
+	mutex_unlock(&ts->dbfs_mutex);
+	return retval;
+}
+
+static ssize_t elan_dbfs_read(struct file *file,
+			      char __user *buffer, size_t count, loff_t *ppos)
+{
+	u8 rxbuf[256];
+	int ret;
+	struct elants_data *ts = file->private_data;
+	struct i2c_adapter *adap = ts->client->adapter;
+	struct i2c_msg msg;
+
+	if (count > 256)
+		return -EMSGSIZE;
+
+	msg.addr = ts->i2caddr;
+	msg.flags = ts->client->flags & I2C_M_TEN;
+	msg.flags |= I2C_M_RD;
+	msg.len = count;
+	msg.buf = rxbuf;
+
+	ret = i2c_transfer(adap, &msg, 1);
+	if (ret == 1)
+		if (copy_to_user(buffer, rxbuf, count))
+			return -EFAULT;
+
+	/* If everything went ok (i.e. 1 msg transmitted), return #bytes
+	   transmitted, else error code. */
+	return (ret == 1) ? count : ret;
+}
+
+static ssize_t elan_dbfs_write(struct file *file,
+			       const char __user *buffer, size_t count,
+			       loff_t *ppos)
+{
+	int ret;
+	u8 txbuf[256];
+	struct elants_data *ts = file->private_data;
+	struct i2c_adapter *adap = ts->client->adapter;
+	struct i2c_msg msg;
+
+	if (count > 256)
+		return -EMSGSIZE;
+
+	if (copy_from_user(txbuf, buffer, count))
+		return -EFAULT;
+
+	msg.addr = ts->i2caddr;
+	msg.flags = ts->client->flags & I2C_M_TEN;
+	msg.len = count;
+	msg.buf = (char *)txbuf;
+
+	ret = i2c_transfer(adap, &msg, 1);
+	if (ret != 1)
+		dev_err(&ts->client->dev,
+			"i2c_master_send fail, ret=%d\n", ret);
+
+	/* If everything went ok (i.e. 1 msg transmitted), return #bytes
+	   transmitted, else error code. */
+	return (ret == 1) ? count : ret;
+}
+
+static int elan_dbfs_release(struct inode *inode, struct file *file)
+{
+	struct elants_data *ts = file->private_data;
+
+	if (!ts)
+		return -ENODEV;
+
+	if (ts->dbfs_root) {
+		debugfs_remove_recursive(ts->dbfs_root);
+		mutex_destroy(&ts->dbfs_mutex);
+	}
+
+	return 0;
+}
+
+static const struct file_operations elan_debug_fops = {
+	.owner = THIS_MODULE,
+	.open = elan_dbfs_open,
+	.release = elan_dbfs_release,
+	.read = elan_dbfs_read,
+	.write = elan_dbfs_write,
+};
+
+static int elan_dbfs_init(struct elants_data *ts)
+{
+	/* Create a global debugfs root for all elan ts devices */
+	ts->dbfs_root = debugfs_create_dir(DEVICE_NAME, NULL);
+	if (ts->dbfs_root == ERR_PTR(-ENODEV))
+		ts->dbfs_root = NULL;
+
+	mutex_init(&ts->dbfs_mutex);
+
+	debugfs_create_file("elan-iap", 0777,
+			    ts->dbfs_root, ts, &elan_debug_fops);
+
+	return 0;
+}
+
+/**
+ * elan_sw_reset - software reset.
+ *
+ * @client: our i2c device
+ *	retval >0 means reset success,
+ *	otherwise is fail.
+ *
+ */
+static int elan_sw_reset(struct i2c_client *client)
+{
+	int ret;
+	const u8 srst[4] = { 0x77, 0x77, 0x77, 0x77 };
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	ret = i2c_master_send(client, srst, sizeof(srst));
+
+	if (ret != sizeof(srst)) {
+		dev_err(&client->dev, "%s: i2c_master_send failed\n", __func__);
+		return -ENODEV;
+	}
+
+	/* Wait to send fastboot command */
+	elan_msleep(10);
+
+	return ret;
+}
+
+static int elan_boot(struct i2c_client *client, u16 addr, u8 type)
+{
+	int rc;
+	struct elants_data *ts = i2c_get_clientdata(client);
+	uint8_t command[2][4] = {
+		{0x4D, 0x61, 0x69, 0x6E},
+		{0x45, 0x49, 0x41, 0x50},
+	};
+
+	ts->i2caddr = addr;
+	rc = elan_set_data(client, command[(int32_t) type], 4);
+	if (rc < 0) {
+		if (type == E_BOOT_IAP)
+			dev_err(&client->dev, "Boot IAP fail, error=%d\n", rc);
+		else
+			dev_dbg(&client->dev,
+				"Boot normal fail, error=%d\n", rc);
+		ts->i2caddr = DEV_MASTER;
+		return -EINVAL;
+	}
+	dev_info(&client->dev, "Boot success -- 0x%x\n", addr);
+	ts->i2caddr = DEV_MASTER;
+	return 0;
+}
+
+/**
+ *	__hello_packet_handler	- hadle hello packet.
+ *
+ *	@client: our i2c device
+ *	@return:
+ *		>0 means success,
+ *		otherwise fail.
+ *
+ *	Normal hello packet is {0x55, 0x55, 0x55, 0x55}
+ *	recovery mode hello packet is {0x55, 0x55, 0x80, 0x80}
+ */
+static int __hello_packet_handler(struct i2c_client *client)
+{
+	int rc = 0;
+	uint8_t buf_recv[4] = { 0 };
+	struct elants_data *ts = i2c_get_clientdata(client);
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	rc = elan_get_data(client, buf_recv, 4);
+
+	/* Print buf_recv anyway */
+	dev_info(&client->dev, "rc=%d HelloPacket:%*phC\n",
+			rc, 4, buf_recv);
+
+	if (rc < 0) {
+		dev_err(&client->dev,
+			"%s: Try recovery because of no hello\n", __func__);
+	}
+
+	if (memcmp(buf_recv, hello_packet, 4)) {
+		if (!memcmp(buf_recv, recov_packet, 4)) {
+			dev_info(&client->dev,
+				 "got mainflow recovery message\n");
+
+			ts->iap_mode = IAP_MODE_ENABLE;
+			set_bit(LOCK_FW_UPDATE, &ts->flags);
+		}
+
+		return -ENODEV;
+	}
+
+	ts->i2caddr = DEV_MASTER;
+
+	return rc;
+}
+
+/**
+ * Firmware version is for something big movement.
+ * ex: CodeBase update.
+ */
+static int __fw_packet_handler(struct i2c_client *client)
+{
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int rc, tries = 3;
+	const uint8_t cmd[] = { CMD_HEADER_READ,
+		E_ELAN_INFO_FW_VER, 0x00, 0x01
+	};
+	uint8_t buf_recv[4] = { 0x0 };
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	/* Command not support in IAP recovery mode */
+	if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+		return 0;
+retry:
+	rc = elan_i2c_read_block(client, (u8 *) cmd, buf_recv, 4);
+	if (rc < 0)
+		elan_dbg(client,
+			 "read fw version rc=%d, buf=%*phC\n", rc, 4, buf_recv);
+
+	if (buf_recv[0] == CMD_HEADER_RESP) {
+		ts->_fw_version[0] = ((buf_recv[1] & 0x0f) << 4) |
+		    ((buf_recv[2] & 0xf0) >> 4);
+		ts->_fw_version[1] = ((buf_recv[2] & 0x0f) << 4) |
+		    ((buf_recv[3] & 0xf0) >> 4);
+
+		if (ts->fw_version == 0x00 || ts->fw_version == 0xFF) {
+			dev_err(&client->dev,
+				"\n\nFW version is empty, "
+				"suggest IAP ELAN chip\n\n");
+			return -EINVAL;
+		}
+	} else {
+		elan_dbg(client, "read fw retry tries=%d\n", tries);
+		if (tries > 0) {
+			tries--;
+			goto retry;
+		}
+
+		ts->fw_version = 0xffff;
+		dev_err(&client->dev,
+			"\n\nFW version is empty, "
+			"suggest IAP ELAN chip\n\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * Test version is for something minor change.
+ * ex: parameters, coordination
+ */
+static int __test_packet_handler(struct i2c_client *client)
+{
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int rc, tries = 3;
+	const uint8_t cmd[] = { CMD_HEADER_READ,
+		E_ELAN_INFO_TEST_VER, 0x00, 0x01
+	};
+	uint8_t buf_recv[4] = { 0x0 };
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	/* Command not support in IAP recovery mode */
+	if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+		return 0;
+retry:
+	rc = elan_i2c_read_block(client, (u8 *) cmd, buf_recv, 4);
+	if (rc < 0) {
+		elan_dbg(client, "read test version error rc=%d, buf=%*phC\n",
+			 rc, 4, buf_recv);
+		return rc;
+	}
+
+	if (buf_recv[0] == CMD_HEADER_RESP) {
+		ts->_test_version[0] = ((buf_recv[1] & 0x0f) << 4) |
+		    ((buf_recv[2] & 0xf0) >> 4);
+		ts->_test_version[1] = ((buf_recv[2] & 0x0f) << 4) |
+		    ((buf_recv[3] & 0xf0) >> 4);
+	} else {
+		elan_dbg(client, "read fw retry tries=%d\n", tries);
+		if (tries > 0) {
+			tries--;
+			goto retry;
+		}
+		ts->test_version = 0xffff;
+		dev_err(&client->dev, "Get test version fail\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int __tp_info_handler(struct i2c_client *client)
+{
+	struct i2c_msg msgs[2];
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int rc;
+	uint8_t buf_recv[17] = { 0x0 };
+	const uint8_t get_resolution_cmd[] = {
+		CMD_HEADER_6B_READ, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	/* Command not support in IAP recovery mode */
+	if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+		return 0;
+
+	msgs[0].addr = client->addr;
+	msgs[0].flags = client->flags & I2C_M_TEN;
+	msgs[0].len = 6;
+	msgs[0].buf = (u8 *) get_resolution_cmd;
+
+	msgs[1].addr = client->addr;
+	msgs[1].flags = client->flags & I2C_M_TEN;
+	msgs[1].flags |= I2C_M_RD;
+	msgs[1].len = sizeof(buf_recv);
+	msgs[1].buf = buf_recv;
+
+	rc = i2c_transfer(client->adapter, msgs, 2);
+	if (rc < 0) {
+		elan_dbg(client, "tp_info error rc=%d\n", rc);
+		return rc;
+	}
+
+	if (buf_recv[0] != CMD_HEADER_6B_RESP)
+		return -EINVAL;
+
+	ts->rows = (buf_recv[2] + buf_recv[6] + buf_recv[10]);
+	ts->cols = (buf_recv[3] + buf_recv[7] + buf_recv[11]);
+
+	if (ts->rows < 2 || ts->cols < 2) {
+		dev_warn(&client->dev,
+			 "Invalid resolution (%d, %d)\n", ts->rows, ts->cols);
+
+		ts->rows = -1;
+		ts->cols = -1;
+
+		return 0;
+	}
+
+	return 0;
+}
+
+/**
+*	elan_touch_get_bc_ver	- obtain bootcode data from device
+*
+*	@client: the interface we are querying
+*
+*	Send a bootcode version command and fill the results into the
+*	elan device structures. Caller must hold the mutex
+*
+*	Returns 0 or an error code
+*/
+static int __bc_packet_handler(struct i2c_client *client)
+{
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 get_bc_ver_cmd[] = { CMD_HEADER_READ,
+		E_ELAN_INFO_BC_VER, 0x00, 0x01
+	};
+	u8 buf_recv[4];
+	int rc;
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	/* Command not support in IAP recovery mode */
+	if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+		return 0;
+
+	rc = elan_i2c_read_block(client, (u8 *) get_bc_ver_cmd,
+				 buf_recv, sizeof(buf_recv));
+	if (rc < 0) {
+		dev_err(&client->dev,
+			"Read FW version error rc=%d, buf=%*phC\n", rc, 4,
+			buf_recv);
+		return rc;
+	}
+
+	ts->bc_version = (((buf_recv[1] & 0x0f) << 4) |
+			  ((buf_recv[2] & 0xf0) >> 4));
+	ts->iap_version = (((buf_recv[2] & 0x0f) << 4) |
+			   ((buf_recv[3] & 0xf0) >> 4));
+	return 0;
+}
+
+static int __elan_fastboot(struct i2c_client *client)
+{
+	int rc = 0;
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	rc = elan_boot(client, DEV_MASTER, E_BOOT_NORM);
+	if (rc < 0)
+		return -1;
+
+	/* Wait for Hello packets */
+	msleep(50);
+
+	return rc;
+}
+
+static int elan_open(struct input_dev *input)
+{
+	struct elants_data *ts = input_get_drvdata(input);
+
+	dev_dbg(&ts->client->dev, "Enter: %s\n", __func__);
+
+	return 0;
+}
+
+static void elan_close(struct input_dev *input)
+{
+	struct elants_data *ts = input_get_drvdata(input);
+
+	dev_dbg(&ts->client->dev, "Enter %s\n", __func__);
+
+	return;
+}
+
+/**
+*	elan_touch_pull_frame - pull a frame from the fifo
+*
+*	@ts: our elan touch device
+*	@buf: data buffer
+*
+*	Pulls a frame from the FIFO into the provided ehr and data buffer.
+*	The data buffer must be at least CMD_RESP_LEN bytes long.
+*/
+static int elan_touch_pull_frame(struct elants_data *ts, u8 *buf)
+{
+	dev_dbg(&ts->client->dev, "Enter: %s\n", __func__);
+
+	WARN_ON(kfifo_out_locked(&ts->fifo, buf, CMD_RESP_LEN,
+				 &ts->rx_kfifo_lock)
+		!= CMD_RESP_LEN);
+	return CMD_RESP_LEN;
+}
+
+/**
+ *	elan_touch_fifo_clean_old - Make room for new frames
+ *
+ *	@ed: our elan touch device
+ *	@room: space needed
+ *
+ *	Empty old frames out of the FIFO until we can fit the new one into
+ *	the other end.
+ */
+static void elan_touch_fifo_clean_old(struct elants_data *ts, int room)
+{
+	u8 buffer[CMD_RESP_LEN];
+
+	dev_dbg(&ts->client->dev, "Enter: %s\n", __func__);
+
+	while (kfifo_len(&ts->fifo) + room >= FIFO_SIZE)
+		elan_touch_pull_frame(ts, buffer);
+}
+
+/**
+ *	elan_getrepoinfo - parse Multi-queue report header
+ *
+ *	@client: our i2c device
+ *	@buf: buffer data
+ *
+ *	parsing report header and get data length.
+ *
+ */
+static int elan_getrepoinfo(struct i2c_client *client, uint8_t *buf)
+{
+	struct elants_data *ts = i2c_get_clientdata(client);
+	struct multi_queue_header *buff = (struct multi_queue_header *)buf;
+	struct multi_queue_header *mq = &ts->mq_header;
+	const u8 wait_packet[4] = { 0x64, 0x64, 0x64, 0x64 };
+	int times = 10, rc = 0;
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	switch (buf[FW_POS_HEADER]) {
+	case CMD_HEADER_HELLO:
+		if (!memcmp(buf, hello_packet, 4))
+			ts->wdt_reset++;
+		return RET_CMDRSP;
+	case CMD_HEADER_RESP:
+	case CMD_HEADER_REK:
+		/* Queue the data, using the fifo lock to serialize
+		 * the multiple accesses to the FIFO
+		 */
+		elan_dbg(client, "recv CMD_HEADER_RESP\n");
+
+		mutex_lock(&ts->fifo_mutex);
+		if (kfifo_len(&ts->fifo) + CMD_RESP_LEN >= FIFO_SIZE)
+			elan_touch_fifo_clean_old(ts, CMD_RESP_LEN);
+		/* Push the data */
+		kfifo_in_locked(&ts->fifo, buf,
+				CMD_RESP_LEN, &ts->rx_kfifo_lock);
+		mutex_unlock(&ts->fifo_mutex);
+
+		elan_dbg(client, "wake_up [%*phC]\n", 4, buf);
+		wake_up_interruptible(&ts->wait);
+		return RET_CMDRSP;
+		/* Buffer mode header */
+	case QUEUE_HEADER_NORMAL:
+		elan_dbg(client,
+			 "report_count=%d report_len=%d\n",
+			 buff->report_count, buff->report_length);
+
+		if (buff->report_count <= 3) {
+			mq->report_count = buff->report_count;
+			mq->report_length = buff->report_length;
+			ts->rx_size = mq->report_length;
+			ts->packet_size = mq->report_length / mq->report_count;
+		} else
+			return RET_FAIL;
+
+		break;
+	case QUEUE_HEADER_WAIT:
+		dev_info(&client->dev,
+			 "QUEUE_HEADER_WAIT %x:%x:%x:%x\n",
+			 buf[0], buf[1], buf[2], buf[3]);
+		/* BUGFIX: buff[0] might be wrong (0x63),
+		 * check buff[1 ~ 3] is enough
+		 */
+		if (!memcmp(buf + 1, &wait_packet[1], 3)) {
+			do {
+				udelay(30);
+				elan_get_data(client, (u8 *) buff, ts->rx_size);
+			} while ((buff->packet_id != QUEUE_HEADER_NORMAL) &&
+				 (--times > 0));
+			if (times > 0)
+				rc = elan_getrepoinfo(client, (uint8_t *) buff);
+			else
+				return RET_FAIL;
+			elan_dbg(client,
+				 "Detect Wait_Header:rx_size=%d, "
+				 "report_count=%d report_len=%d\n",
+				 ts->rx_size,
+				 mq->report_count, mq->report_length);
+		} else
+			dev_err(&client->dev,
+				"ERROR!! wait header:%x:%x:%x:%x\n",
+				buf[0], buf[1], buf[2], buf[3]);
+		break;
+		/* Not buffer mode, it's single word mode */
+	case QUEUE_HEADER_SINGLE:
+		mq->report_count = 1;
+		mq->report_length = PACKET_SIZE;
+		ts->rx_size = mq->report_length;
+		return ts->rx_size;
+	default:
+		dev_err(&client->dev,
+			"unknown multi-queue command!! --%x %x:%x:%x--\n",
+			buf[0], buf[1], buf[2], buf[3]);
+		ts->mq_header_fail++;
+
+		/* If glitch causes frame error, drop all finger report */
+		ts->rx_size = MAX_PACKET_LEN;
+		elan_get_data(client, (u8 *) buff, ts->rx_size);
+		return RET_FAIL;
+	}
+
+	return ts->rx_size;
+}
+
+/**
+ *	elan_touch_checksum -  Add for checksum mechanism
+ *
+ *	@client : our i2c device
+ *	@buf : buffer data
+ *
+ *	caculating checksum for make sure all data validity.
+ */
+static int elan_touch_checksum(struct i2c_client *client, u8 *buf)
+{
+	u8 i = 0, checksum = 0;
+	struct elants_data *ts = i2c_get_clientdata(client);
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	for (i = 0; i < FW_POS_CHECKSUM; i++)
+		checksum = checksum + buf[i];
+
+	ts->checksum_correct = checksum;
+
+	if (checksum != buf[FW_POS_CHECKSUM]) {
+		ts->checksum_fail++;
+		dev_err(&client->dev,
+			"elan touch checksum fail: %02x:%02x\n",
+			checksum, buf[FW_POS_CHECKSUM]);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+/**
+ *	elan_parse_fid - parse the 10 fid bits
+ *
+ *	@data: the input bit stream
+ *	@fid: an array of fid values
+ *
+ *	Unpack the 10 bits into an array.
+ *	FIXME: Review whether we can't just use << operators after making
+ *	sure the bits are native endian ?
+ */
+static inline void elan_parse_fid(u8 *data, u8 *fid)
+{
+	fid[0] = (data[0] & 0x01);
+	fid[1] = (data[0] & 0x02);
+	fid[2] = (data[0] & 0x04);
+	fid[3] = (data[0] & 0x08);
+	fid[4] = (data[0] & 0x10);
+	fid[5] = (data[0] & 0x20);
+	fid[6] = (data[0] & 0x40);
+	fid[7] = (data[0] & 0x80);
+	fid[8] = (data[1] & 0x10);
+	fid[9] = (data[1] & 0x20);
+}
+
+/**
+ *      elan_parse_wid - parse width data with length of 5-bit
+ *
+ *      @data: the input bit stream
+ *      @wid: an array of width level
+ *
+ *      Unpack the 10 bits into an array.
+ */
+static inline void elan_parse_wid(u8 *data, u8 *wid)
+{
+	wid[0] = (data[0] & 0x1f);
+	wid[1] = (data[1] & 0x1f);
+	wid[2] = (data[2] & 0x1f);
+	wid[3] = (data[3] & 0x1f);
+	wid[4] = (data[4] & 0x1f);
+	wid[5] = (data[5] & 0x1f);
+	wid[6] = (data[6] & 0x1f);
+	wid[7] = (data[7] & 0x1f);
+	wid[8] = (data[8] & 0x1f);
+	wid[9] = (data[9] & 0x1f);
+
+	return;
+}
+
+/**
+ *      elan_parse_pid - parse pressure data with length of 8-bit
+ *
+ *      @data: the input bit stream
+ *      @wid: an array of width level
+ *
+ *      Unpack the 10 bits into an array.
+ */
+static inline void elan_parse_pid(u8 *data, u8 *pressure)
+{
+	pressure[0] = data[0];
+	pressure[1] = data[1];
+	pressure[2] = data[2];
+	pressure[3] = data[3];
+	pressure[4] = data[4];
+	pressure[5] = data[5];
+	pressure[6] = data[6];
+	pressure[7] = data[7];
+	pressure[8] = data[8];
+	pressure[9] = data[9];
+
+	return;
+}
+
+static inline int elan_parse_xy(uint8_t *data, uint16_t *x, uint16_t *y)
+{
+	*x = *y = 0;
+
+	*x = (data[0] & 0xf0);
+	*x <<= 4;
+	*x |= data[1];
+
+	*y = (data[0] & 0x0f);
+	*y <<= 8;
+	*y |= data[2];
+
+	return 0;
+}
+
+static inline int elan_lookup_wid(u8 data, u16 *w, u16 *h)
+{
+	static u16 pre_w = 0, pre_h = 0, cur_w, cur_h;
+	const u8 width_lookup_table[15][2] = {
+		{3, 2}, {3, 2}, {6, 3},
+		{6, 3}, {10, 3}, {15, 4},
+		{15, 5}, {15, 5}, {15, 5},
+		{15, 5}, {15, 5}, {15, 5},
+		{15, 5}, {15, 5}, {15, 5},
+	};
+
+	cur_w = width_lookup_table[data][0] * 17;
+	cur_h = width_lookup_table[data][1] * 17;
+
+	*w = (cur_w + pre_w) >> 1;
+	*h = (cur_h + pre_h) >> 1;
+
+	pre_w = cur_w;
+	pre_h = cur_h;
+
+	return 0;
+}
+
+static int elan_mt_compute_slot(struct mt_device *td)
+{
+	int i;
+	for (i = 0; i < td->maxcontacts; ++i) {
+		if (td->slots[i].contactid == td->curdata.contactid &&
+		    td->slots[i].touch_state)
+			return i;
+	}
+	for (i = 0; i < td->maxcontacts; ++i) {
+		if (!td->slots[i].seen_in_this_frame &&
+		    !td->slots[i].touch_state)
+			return i;
+	}
+	/* should not occurs. If this happens that means
+	 * that the device sent more touches that it says
+	 * in the report descriptor. It is ignored then. */
+	return -1;
+}
+
+/*
+* this function is called when a whole contact has been processed,
+* so that it can assign it to a slot and store the data there
+*/
+static void elan_mt_complete_slot(struct mt_device *td)
+{
+	td->curdata.seen_in_this_frame = true;
+	if (td->curvalid) {
+		int slotnum = elan_mt_compute_slot(td);
+
+		if (slotnum >= 0 && slotnum < td->maxcontacts)
+			td->slots[slotnum] = td->curdata;
+	}
+	td->num_received++;
+}
+
+/*
+* this function is called when a whole packet has been received and processed,
+* so that it can decide what to send to the input layer.
+*/
+static void elan_mt_emit_event(struct mt_device *td, struct input_dev *input)
+{
+	struct elants_data *ts = container_of(td, struct elants_data, td);
+	int i;
+
+	for (i = 0; i < td->maxcontacts; ++i) {
+		struct mt_slot *s = &(td->slots[i]);
+		if (!s->seen_in_this_frame)
+			s->touch_state = false;
+
+		input_mt_slot(input, i);
+		input_mt_report_slot_state(input, MT_TOOL_FINGER,
+					   s->touch_state);
+		if (s->touch_state) {
+			/* this finger is on the screen */
+			int major = max(s->w, s->h), minor = min(s->w, s->h);
+
+			elan_dbg(ts->client, "i=%d x=%d y=%d p=%d w=%d h=%d.\n",
+				 i, s->x, s->y, s->p, major, minor);
+
+			input_event(input, EV_ABS, ABS_MT_POSITION_X, s->x);
+			input_event(input, EV_ABS, ABS_MT_POSITION_Y, s->y);
+			input_event(input, EV_ABS, ABS_MT_PRESSURE, s->p);
+			input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
+		}
+		s->seen_in_this_frame = false;
+	}
+
+	input_mt_report_pointer_emulation(input, true);
+	input_sync(input);
+	td->num_received = 0;
+}
+
+/**
+ *	elan_mt_event - process finger reports
+ *	@ts: our touchscreen
+ *	@finger_stat: number of fingers in packet
+ *	@buf: received buffer
+ *
+ *	Walk the received report and process the finger data, extracting
+ *	and reporting co-ordinates. No locking is needed here as the workqueue
+ *	does our threading for us.
+ */
+static int elan_mt_event(struct elants_data *ts, int finger_stat, u8 *buf)
+{
+	int i;
+	u8 fid[MAX_CONTACT_NUM] = { 0 };
+	u8 wid[MAX_CONTACT_NUM] = { 0 };
+	u8 pid[MAX_CONTACT_NUM] = { 0 };
+	u16 x = 0, y = 0, w = 0, h = 0;
+	struct i2c_client *client = ts->client;
+	struct mt_device *td = &ts->td;
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	/* Parsing Finger, Width, Pressure field */
+	elan_parse_fid(&buf[FW_POS_STATE], &fid[0]);
+	elan_parse_wid(&buf[FW_POS_WIDTH], &wid[0]);
+	elan_parse_pid(&buf[FW_POS_PRESSURE], &pid[0]);
+
+	/* number of fingers expects in this frame */
+	td->num_expected = finger_stat;
+	for (i = 0; i < td->maxcontacts; i++) {
+		if (finger_stat == 0)
+			break;
+
+		/* tracking id */
+		td->curdata.contactid = (fid[i] > 0) ? i + 1 : 0;
+
+		if (td->curdata.contactid == 0)
+			continue;
+
+		td->curdata.touch_state = true;
+
+		elan_parse_xy(&buf[3 + i * 3], &x, &y);
+		td->curdata.x = x;
+		td->curdata.y = y;
+		td->curdata.p = pid[i];
+
+		h = w = wid[i];
+		td->curdata.w = w;
+		td->curdata.h = h;
+
+		finger_stat--;
+
+		elan_mt_complete_slot(td);
+	}
+
+	if (td->num_received >= td->num_expected)
+		elan_mt_emit_event(td, ts->input);
+
+	return 1;
+}
+
+/**
+ *	elan_report_data - report finger report to user space.
+ *
+ *	@client : our i2c device
+ *	@buf : raw data from TP device.
+ *
+ *	- reporting finger data to user space.
+ *
+ */
+static void elan_report_data(struct i2c_client *client, uint8_t *buf)
+{
+	struct elants_data *ts = i2c_get_clientdata(client);
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	switch (buf[FW_POS_HEADER]) {
+	case REPORT_HEADER_10_FINGER:{
+			u8 finger_stat = buf[FW_POS_TOTAL] & 0x0f;
+			elan_dbg(client, "finger_stat == %d\n", finger_stat);
+			elan_dbg(client, "finger:%*phC\n", 10, buf);
+
+			/* Enter right process, reset int_status */
+			ts->packet_received++;
+
+			if (likely(finger_stat != 0)) {
+				ts->td.curvalid = true;
+				ts->touched_sync++;
+			} else {
+				ts->no_touched_sync++;
+			}
+			elan_mt_event(ts, finger_stat, buf);
+		}
+		break;
+	default:
+		ts->header_fail++;
+		dev_warn(&client->dev,
+			 "%s: unknown packet type: %*phC\n", __func__, 10, buf);
+		break;
+	}
+
+	return;
+}
+
+static irqreturn_t elan_work_func(int irq, void *work)
+{
+	struct elants_data *ts = work;
+	struct i2c_client *client = ts->client;
+
+	uint8_t buf[MAX_PACKET_LEN] = { 0x0 };
+	u8 pos = 0, rc = 0;
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	ts->irq_received++;
+
+	if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+		return IRQ_HANDLED;
+
+	mutex_lock(&ts->mutex);
+
+	set_bit(LOCK_FINGER_REPORT, &ts->flags);
+
+	/* - Read multi_queue header */
+	rc = elan_get_data(client, (u8 *) buf, ts->rx_size);
+	if (rc < 0)
+		goto fail;
+
+	/*  - Get multi_queue header info */
+	rc = elan_getrepoinfo(ts->client, buf);
+	if (rc < 0 || rc == RET_CMDRSP)
+		goto fail;
+
+	/*  - check if packet size is valid */
+	if ((ts->packet_size != PACKET_SIZE)) {
+		dev_err(&ts->client->dev, "%s: uncorrect packet size = %d\n",
+			__func__, ts->packet_size);
+		goto fail;
+	}
+
+	/* - Get finger report data */
+	rc = elan_get_data(client, (u8 *) buf, ts->rx_size);
+	if (rc < 0)
+		goto fail;
+
+	clear_bit(LOCK_FINGER_REPORT, &ts->flags);
+
+	mutex_unlock(&ts->mutex);
+
+	/* - parsing report and send out */
+	while (ts->mq_header.report_count--) {
+		if (elan_touch_checksum(ts->client, buf + pos) == 0)
+			elan_report_data(ts->client, buf + pos);
+		pos = pos + ts->packet_size;
+		udelay(10);
+	}
+
+	ts->rx_size = QUEUE_HEADER_SIZE;
+
+	return IRQ_HANDLED;
+
+fail:
+	clear_bit(LOCK_FINGER_REPORT, &ts->flags);
+	mutex_unlock(&ts->mutex);
+	ts->rx_size = QUEUE_HEADER_SIZE;
+
+	return IRQ_HANDLED;
+}
+
+static int remove_elants(struct i2c_client *client)
+{
+	int ret = 0;
+	struct elants_data *ts = i2c_get_clientdata(client);
+
+	if ((client->irq))
+		free_irq(client->irq, ts);
+
+	if (ts->input)
+		input_unregister_device(ts->input);
+
+	if (&ts->mutex)
+		mutex_destroy(&ts->mutex);
+	if (&ts->tr_mutex)
+		mutex_destroy(&ts->tr_mutex);
+	if (&ts->fifo_mutex)
+		mutex_destroy(&ts->fifo_mutex);
+
+	kfree(ts->td.slots);
+	kfifo_free(&ts->fifo);
+	kfree(ts);
+
+	return ret;
+}
+
+static int elan_input_dev_create(struct elants_data *ts)
+{
+	int err = 0;
+	struct i2c_client *client = ts->client;
+
+	if (ts->rows > 0 && ts->cols > 0) {
+		/* translate trace number to TSP resolution */
+		ts->x_max = ELAN_TS_RESOLUTION(ts->rows);
+		ts->y_max = ELAN_TS_RESOLUTION(ts->cols);
+	} else
+		dev_warn(&client->dev, "trace number error, %d,%d\n",
+			 ts->rows, ts->cols);
+
+	/* Clear the existing one if it exists */
+	if (ts->input) {
+		input_unregister_device(ts->input);
+		ts->input = NULL;
+	}
+
+	ts->input = input_allocate_device();
+	if (ts->input == NULL) {
+		dev_err(&client->dev, "Failed to allocate input device\n");
+		return -ENOMEM;
+	}
+
+	ts->input->name = "Elan-Touchscreen";
+	ts->input->id.bustype = BUS_I2C;
+	ts->input->dev.parent = &ts->client->dev;
+	ts->input->open = elan_open;
+	ts->input->close = elan_close;
+
+	__set_bit(BTN_TOUCH, ts->input->keybit);
+	__set_bit(EV_ABS, ts->input->evbit);
+	__set_bit(EV_KEY, ts->input->evbit);
+
+	/*! - Single touch input params setup */
+	input_set_abs_params(ts->input, ABS_X, 0, ts->x_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_Y, 0, ts->y_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_X, X_PIXELS_PER_MM);
+	input_abs_set_res(ts->input, ABS_Y, Y_PIXELS_PER_MM);
+
+	ts->td.maxcontacts = MAX_CONTACT_NUM;
+	ts->td.mt_flags |= INPUT_MT_DIRECT;
+
+	/* Multitouch input params setup */
+	err =
+	    input_mt_init_slots(ts->input, ts->td.maxcontacts, ts->td.mt_flags);
+	if (err) {
+		dev_err(&client->dev,
+			"allocate memory for MT slots failed, %d\n", err);
+		goto err_free_device;
+	}
+
+	input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->x_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->y_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_X, X_PIXELS_PER_MM);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_Y, Y_PIXELS_PER_MM);
+
+	input_set_drvdata(ts->input, ts);
+
+	ts->td.slots = kzalloc(MAX_CONTACT_NUM * sizeof(struct mt_slot),
+			       GFP_KERNEL);
+	if (!ts->td.slots) {
+		dev_err(&client->dev, "cannot allocate multitouch slots\n");
+		goto err_free_device;
+	}
+
+	err = input_register_device(ts->input);
+	if (err) {
+		dev_err(&client->dev, "unable to register input device\n");
+		goto err_free_slot;
+	}
+
+	return 0;
+
+err_free_slot:
+	kfree(ts->td.slots);
+err_free_device:
+	input_free_device(ts->input);
+	ts->input = NULL;
+	return err;
+}
+
+/**
+ *	elan_initialize - initialization process.
+ *
+ *	@client: our i2c client
+ *
+ *	set our TP up
+ *	-# reset
+ *	-# hello packet
+ *	-# fw version
+ *	-# test version
+ *	-# TP info (resolution)
+ *
+ */
+static int elan_initialize(struct i2c_client *client)
+{
+	struct elants_data *ts = i2c_get_clientdata(client);
+	u8 buf[4] = { 0 };
+	int rc;
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	/* handle recovery packets */
+	rc = elan_get_data(client, buf, 3);
+	if (rc == 0) {
+		if (!memcmp(buf, recov_packet + 2, 2)) {
+			ts->iap_mode = IAP_MODE_ENABLE;
+			set_bit(LOCK_FW_UPDATE, &ts->flags);
+			dev_err(&client->dev, "Get recovery packet == %*phC\n",
+				3, buf);
+
+			return -ENODEV;
+		}
+	}
+
+	rc = elan_sw_reset(client);
+	if (rc < 0) {
+		dev_err(&client->dev, "Software reset failed\n");
+		/* continue */
+	}
+
+	ts->rx_size = QUEUE_HEADER_SIZE;
+
+	rc = __elan_fastboot(client);
+	if (rc < 0)
+		dev_err(&client->dev, "fastboot failed, rc=%d\n", rc);
+
+	/*! - elan hello packet init */
+	rc = __hello_packet_handler(client);
+	if (rc < 0) {
+		dev_err(&client->dev, "hello packet error.\n");
+
+		return -ENODEV;
+	}
+
+	/* elan fw version */
+	rc = __fw_packet_handler(client);
+	if (rc < 0) {
+		dev_err(&client->dev, "firmware checking error rc=%d\n", rc);
+
+		if (rc == -EINVAL) {
+			set_bit(LOCK_FW_UPDATE, &ts->flags);
+			ts->iap_mode = IAP_MODE_ENABLE;
+		}
+	}
+
+	/* elan TP information */
+	rc = __tp_info_handler(client);
+	if (rc < 0) {
+		dev_err(&client->dev, "TP information checking error.\n");
+		/* Go through down */
+	}
+
+	/* elan test version */
+	rc = __test_packet_handler(client);
+	if (rc < 0) {
+		dev_err(&client->dev, "test version error\n");
+		/* Go through down */
+	}
+
+	/* Get TS BootCode version */
+	rc = __bc_packet_handler(client);
+	if (rc < 0) {
+		dev_err(&client->dev, "TP get BC version error.\n");
+		/* Go through down */
+	}
+
+	return 0;
+}
+
+/**
+ *	elan_initialize_async	- init touch device.
+ *
+ *	@work: /INT work queue
+ *
+ *	Perform real probe for our I2C device and if successful configure
+ *	it up as an input device. If not then clean up and return an error
+ *	code.
+ */
+
+static void elan_initialize_async(void *data, async_cookie_t cookie)
+{
+	struct elants_data *ts = data;
+	struct i2c_client *client = ts->client;
+	int err = 0;
+
+	mutex_lock(&ts->mutex);
+
+	err = elan_initialize(client);
+	if (err < 0)
+		dev_err(&client->dev, "probe failed! unbind device.\n");
+
+	err = elan_input_dev_create(ts);
+	if (err) {
+		dev_err(&client->dev, "%s crated failed, %d\n", __func__, err);
+		goto fail_un;
+	}
+
+	dev_info(&client->dev, "Elan Touchscreen Information:\n\r");
+	dev_info(&client->dev,
+		 "    Firmware Version:  0x%04x\n\r", ts->fw_version);
+	dev_info(&client->dev,
+		 "    Test Version:  0x%04x\n\r", ts->test_version);
+	dev_info(&client->dev, "    BC Version:  0x%04x\n\r", ts->bc_version);
+	dev_info(&client->dev, "    IAP Version:  0x%04x\n\r", ts->iap_version);
+	dev_info(&client->dev, "    Trace Num:   %d, %d\n", ts->rows, ts->cols);
+	dev_info(&client->dev, "    Resolution X,Y:  %d,%d\n",
+			ts->x_max, ts->y_max);
+
+	err = request_threaded_irq(client->irq, NULL,
+				   elan_work_func,
+				   IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+				   client->name, ts);
+	if (err) {
+		dev_err(&client->dev, "Failed to register interrupt\n");
+		goto fail_un;
+	}
+
+	mutex_unlock(&ts->mutex);
+
+	return;
+
+fail_un:
+	mutex_unlock(&ts->mutex);
+	remove_elants(client);
+	return;
+}
+
+/**
+ *	elan_probe - probe for touchpad
+ *
+ *	Perform setup and probe for our I2C device and if successful configure
+ *	it up as an input device. If not then clean up and return an error
+ *	code.
+ */
+static int elan_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	long err = -1;
+	struct elan_i2c_platform_data *pdata = NULL;
+	struct elants_data *ts =
+	    kzalloc(sizeof(struct elants_data), GFP_KERNEL);
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		dev_err(&client->dev,
+			"%s: i2c check functionality error\n", DEVICE_NAME);
+		return -ENODEV;
+	}
+
+	ts = kzalloc(sizeof(struct elants_data), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	mutex_init(&ts->mutex);
+	mutex_init(&ts->tr_mutex);
+	mutex_init(&ts->fifo_mutex);
+	init_waitqueue_head(&ts->wait);
+	spin_lock_init(&ts->rx_kfifo_lock);
+
+	err = elan_dbfs_init(ts);
+	if (err < 0) {
+		dev_err(&client->dev, "error create elan debugfs.\n");
+		goto fail_un;
+	} else
+		ts->fw_enabled = 1;
+
+	pdata = client->dev.platform_data;
+	if (!pdata) {
+		dev_err(&client->dev,
+			"%s No platform data provided\n", DEVICE_NAME);
+	}
+
+	/* set initial i2c address */
+	client->addr = DEV_MASTER;
+	ts->i2caddr = client->addr;
+
+	ts->client = client;
+	i2c_set_clientdata(client, ts);
+
+	/* initial kfifo */
+	err = kfifo_alloc(&ts->fifo, FIFO_SIZE, GFP_KERNEL);
+	if (!kfifo_initialized(&ts->fifo)) {
+		dev_err(&client->dev, "%s error kfifo_alloc\n", __func__);
+		goto fail_un;
+	}
+
+	/* Says HELLO to touch device */
+	async_schedule(elan_initialize_async, ts);
+
+	device_init_wakeup(&client->dev, true);
+
+	return 0;
+
+fail_un:
+	remove_elants(client);
+	return err;
+}
+
+static int elan_remove(struct i2c_client *client)
+{
+	return remove_elants(client);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int elan_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_sleep_cmd[] = { 0x54, 0x50, 0x00, 0x01 };
+	int rc = 0;
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	/* Command not support in IAP recovery mode */
+	if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+		return 0;
+
+	mutex_lock(&ts->mutex);
+	rc = elan_set_data(client, set_sleep_cmd, sizeof(set_sleep_cmd));
+	if (rc < 0)
+		dev_err(&client->dev, "suspend command failed!\n");
+
+	if (device_may_wakeup(dev))
+		ts->irq_wake = (enable_irq_wake(client->irq) == 0);
+
+	disable_irq(client->irq);
+
+	mutex_unlock(&ts->mutex);
+
+	return 0;
+}
+
+static int elan_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_active_cmd[] = { 0x54, 0x58, 0x00, 0x01 };
+	int rc = 0;
+
+	dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+	/* Command not support in IAP recovery mode */
+	if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+		return 0;
+
+	if (device_may_wakeup(dev) && ts->irq_wake)
+		disable_irq_wake(client->irq);
+
+	mutex_lock(&ts->mutex);
+
+	rc = elan_set_data(client, set_active_cmd, sizeof(set_active_cmd));
+	if (rc < 0)
+		dev_err(&client->dev, "resume command failed!\n");
+
+	enable_irq(client->irq);
+
+	mutex_unlock(&ts->mutex);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(elan_pm_ops, elan_suspend, elan_resume);
+
+static const struct i2c_device_id elan_ts_id[] = {
+	{DEVICE_NAME, 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, elan_ts_id);
+
+static struct i2c_driver elan_ts_driver = {
+	.probe = elan_probe,
+	.remove = elan_remove,
+	.id_table = elan_ts_id,
+	.driver = {
+		   .name = DEVICE_NAME,
+		   .owner = THIS_MODULE,
+		   .pm = &elan_pm_ops,
+		   },
+};
+
+module_i2c_driver(elan_ts_driver);
+
+MODULE_VERSION(DEVICE_NAME);
+MODULE_DESCRIPTION("Elan I2c Touchscreen driver");
+MODULE_LICENSE("GPL");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
  2013-12-27 12:15 [PATCH v1] Input: elants_i2c: Add Elan touchscreen support scott
@ 2014-11-20  8:11 ` Dmitry Torokhov
  2014-11-20  8:55     ` ELAN 劉嘉駿
  2014-11-20 10:31   ` Oliver Neukum
  0 siblings, 2 replies; 14+ messages in thread
From: Dmitry Torokhov @ 2014-11-20  8:11 UTC (permalink / raw
  To: scott
  Cc: linux-kernel, linux-input, Vincent Wang, Jeff Chuang,
	Benson Leung, Benjamin Tissoires, Henrik Rydberg, David Herrmann

Hi Scott,

On Fri, Dec 27, 2013 at 08:15:15PM +0800, scott wrote:
> From: Scott Liu <scott.liu@emc.com.tw>
> 
> This patch is for Elan eKTH Touchscreen product, I2C adpater module.
> 

As you know I have been making some changes to the driver in Chromium
tree, here is the latest version of the driver. I'd like to get it in
3.19 (pending community review of course).

Thanks! 

-- 
Dmitry

Input: add support for Elan eKTH I2C touchscreens

From: Scott Liu <scott.liu@emc.com.tw>

This driver supports Elan eKTH I2C touchscreen controllers. Note that these
are using custom protocol, as opposed to other Elan parts that use
HID-over-I2C and are supported by the standard HID-multitouch driver.

Signed-off-by: Scott Liu <scott.liu@emc.com.tw>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 .../devicetree/bindings/input/elants_i2c.txt       |   33 +
 drivers/input/touchscreen/Kconfig                  |   12 
 drivers/input/touchscreen/Makefile                 |    1 
 drivers/input/touchscreen/elants_i2c.c             | 1271 ++++++++++++++++++++
 4 files changed, 1317 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/elants_i2c.txt
 create mode 100644 drivers/input/touchscreen/elants_i2c.c

diff --git a/Documentation/devicetree/bindings/input/elants_i2c.txt b/Documentation/devicetree/bindings/input/elants_i2c.txt
new file mode 100644
index 0000000..a765232
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/elants_i2c.txt
@@ -0,0 +1,33 @@
+Elantech I2C Touchscreen
+
+Required properties:
+- compatible: must be "elan,ekth3500".
+- reg: I2C address of the chip.
+- interrupt-parent: a phandle for the interrupt controller (see interrupt
+  binding[0]).
+- interrupts: interrupt to which the chip is connected (see interrupt
+  binding[0]).
+
+Optional properties:
+- wakeup-source: touchscreen can be used as a wakeup source.
+- pinctrl-names: should be "default" (see pinctrl binding [1]).
+- pinctrl-0: a phandle pointing to the pin settings for the device (see
+  pinctrl binding [1]).
+
+[0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+[1]: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
+
+Example:
+	&i2c1 {
+		/* ... */
+
+		touchscreen@10 {
+			compatible = "elan,ekth3500";
+			reg = <0x10>;
+			interrupt-parent = <&gpio4>;
+			interrupts = <0x0 IRQ_TYPE_EDGE_FALLING>;
+			wakeup-source;
+		};
+
+		/* ... */
+	};
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 568a020..5891752 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -347,6 +347,18 @@ config TOUCHSCREEN_GUNZE
 	  To compile this driver as a module, choose M here: the
 	  module will be called gunze.
 
+config TOUCHSCREEN_ELAN
+	tristate "Elan eKTH I2C touchscreen"
+	depends on I2C
+	help
+	  Say Y here if you have an Elan eKTH I2C touchscreen
+	  connected to your system.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called elants_i2c.
+
 config TOUCHSCREEN_ELO
 	tristate "Elo serial touchscreens"
 	select SERIO
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index dab4a56..0242fea 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06)	+= edt-ft5x06.o
 obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE)	+= hampshire.o
 obj-$(CONFIG_TOUCHSCREEN_GUNZE)		+= gunze.o
 obj-$(CONFIG_TOUCHSCREEN_EETI)		+= eeti_ts.o
+obj-$(CONFIG_TOUCHSCREEN_ELAN)		+= elants_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_ELO)		+= elo.o
 obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
 obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c
new file mode 100644
index 0000000..a510f7e
--- /dev/null
+++ b/drivers/input/touchscreen/elants_i2c.c
@@ -0,0 +1,1271 @@
+/*
+ * Elan Microelectronics touch panels with I2C interface
+ *
+ * Copyright (C) 2014 Elan Microelectronics Corporation.
+ * Scott Liu <scott.liu@emc.com.tw>
+ *
+ * This code is partly based on hid-multitouch.c:
+ *
+ *  Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr>
+ *  Copyright (c) 2010-2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ *  Copyright (c) 2010-2012 Ecole Nationale de l'Aviation Civile, France
+ *
+ *
+ * This code is partly based on i2c-hid.c:
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ */
+
+/*
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/async.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/buffer_head.h>
+#include <linux/version.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/version.h>
+#include <linux/input/mt.h>
+#include <linux/acpi.h>
+#include <linux/of.h>
+#include <asm/unaligned.h>
+
+/* Device, Driver information */
+#define DEVICE_NAME	"elants_i2c"
+#define DRV_VERSION	"1.0.9"
+
+/* Convert from rows or columns into resolution */
+#define ELAN_TS_RESOLUTION(n, m)   (((n) - 1) * (m))
+
+/* FW header data */
+#define HEADER_SIZE		4
+#define FW_HDR_TYPE		0
+#define FW_HDR_COUNT		1
+#define FW_HDR_LENGTH		2
+
+/* Buffer mode Queue Header information */
+#define QUEUE_HEADER_SINGLE	0x62
+#define QUEUE_HEADER_NORMAL	0X63
+#define QUEUE_HEADER_WAIT	0x64
+
+/* Command header definition */
+#define CMD_HEADER_WRITE	0x54
+#define CMD_HEADER_READ		0x53
+#define CMD_HEADER_6B_READ	0x5B
+#define CMD_HEADER_RESP		0x52
+#define CMD_HEADER_6B_RESP	0x9B
+#define CMD_HEADER_HELLO	0x55
+#define CMD_HEADER_REK		0x66
+
+/* FW position data */
+#define PACKET_SIZE		55
+#define MAX_CONTACT_NUM		10
+#define FW_POS_HEADER		0
+#define FW_POS_STATE		1
+#define FW_POS_TOTAL		2
+#define FW_POS_XY		3
+#define FW_POS_CHECKSUM		34
+#define FW_POS_WIDTH		35
+#define FW_POS_PRESSURE		45
+
+#define HEADER_REPORT_10_FINGER	0x62
+
+/* Header (4 bytes) plus 3 fill 10-finger packets */
+#define MAX_PACKET_SIZE		169
+
+#define BOOT_TIME_DELAY_MS	50
+
+/* FW read command, 0x53 0x?? 0x0, 0x01 */
+#define E_ELAN_INFO_FW_VER	0x00
+#define E_ELAN_INFO_BC_VER	0x10
+#define E_ELAN_INFO_TEST_VER	0xE0
+#define E_ELAN_INFO_FW_ID	0xF0
+#define E_INFO_OSR		0xD6
+#define E_INFO_PHY_SCAN		0xD7
+#define E_INFO_PHY_DRIVER	0xD8
+
+#define MAX_RETRIES		3
+#define MAX_FW_UPDATE_RETRIES	30
+
+#define ELAN_FW_PAGESIZE	132
+#define ELAN_FW_FILENAME	"elants_i2c.bin"
+
+/* calibration timeout definition */
+#define ELAN_CALI_TIMEOUT_MSEC	10000
+
+enum elants_state {
+	ELAN_STATE_NORMAL,
+	ELAN_WAIT_QUEUE_HEADER,
+	ELAN_WAIT_RECALIBRATION,
+};
+
+enum elants_iap_mode {
+	ELAN_IAP_OPERATIONAL,
+	ELAN_IAP_RECOVERY,
+};
+
+/* struct elants_data - represents state of Elan touchscreen device */
+struct elants_data {
+	struct i2c_client *client;
+	struct input_dev *input;
+
+	u16 fw_version;
+	u8 test_version;
+	u8 solution_version;
+	u8 bc_version;
+	u8 iap_version;
+	u16 hw_version;
+	unsigned int x_res;	/* resolution in units/mm */
+	unsigned int y_res;
+	unsigned int x_max;
+	unsigned int y_max;
+
+	enum elants_state state;
+	enum elants_iap_mode iap_mode;
+
+	/* Guards against concurrent access to the device via sysfs */
+	struct mutex sysfs_mutex;
+
+	u8 cmd_resp[HEADER_SIZE];
+	struct completion cmd_done;
+
+	u8 buf[MAX_PACKET_SIZE];
+
+	bool wake_irq_enabled;
+};
+
+static int elants_i2c_send(struct i2c_client *client,
+			   const void *data, size_t size)
+{
+	int ret;
+
+	ret = i2c_master_send(client, data, size);
+	if (ret == size)
+		return 0;
+
+	if (ret >= 0)
+		ret = -EIO;
+
+	dev_err(&client->dev, "%s failed (%*ph): %d\n",
+		__func__, (int)size, data, ret);
+
+	return ret;
+}
+
+static int elants_i2c_read(struct i2c_client *client, void *data, size_t size)
+{
+	int ret;
+
+	ret = i2c_master_recv(client, data, size);
+	if (ret == size)
+		return 0;
+
+	if (ret >= 0)
+		ret = -EIO;
+
+	dev_err(&client->dev, "%s failed: %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int elants_i2c_execute_command(struct i2c_client *client,
+				      const u8 *cmd, size_t cmd_size,
+				      u8 *resp, size_t resp_size)
+{
+	struct i2c_msg msgs[2];
+	int ret;
+	u8 expected_response;
+
+	switch (cmd[0]) {
+	case CMD_HEADER_READ:
+		expected_response = CMD_HEADER_RESP;
+		break;
+
+	case CMD_HEADER_6B_READ:
+		expected_response = CMD_HEADER_6B_RESP;
+		break;
+
+	default:
+		dev_err(&client->dev, "%s: invalid command %*ph\n",
+			__func__, (int)cmd_size, cmd);
+		return -EINVAL;
+	}
+
+	msgs[0].addr = client->addr;
+	msgs[0].flags = client->flags & I2C_M_TEN;
+	msgs[0].len = cmd_size;
+	msgs[0].buf = (u8 *)cmd;
+
+	msgs[1].addr = client->addr;
+	msgs[1].flags = client->flags & I2C_M_TEN;
+	msgs[1].flags |= I2C_M_RD;
+	msgs[1].len = resp_size;
+	msgs[1].buf = resp;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0)
+		return ret;
+
+	if (ret != ARRAY_SIZE(msgs) || resp[FW_HDR_TYPE] != expected_response)
+		return -EIO;
+
+	return 0;
+}
+
+static int elants_i2c_calibrate(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int ret, error;
+	static const u8 w_flashkey[] = { 0x54, 0xC0, 0xE1, 0x5A };
+	static const u8 rek[] = { 0x54, 0x29, 0x00, 0x01 };
+	static const u8 rek_resp[] = { CMD_HEADER_REK, 0x66, 0x66, 0x66 };
+
+	disable_irq(client->irq);
+
+	ts->state = ELAN_WAIT_RECALIBRATION;
+	reinit_completion(&ts->cmd_done);
+
+	elants_i2c_send(client, w_flashkey, sizeof(w_flashkey));
+	elants_i2c_send(client, rek, sizeof(rek));
+
+	enable_irq(client->irq);
+
+	ret = wait_for_completion_interruptible_timeout(&ts->cmd_done,
+				msecs_to_jiffies(ELAN_CALI_TIMEOUT_MSEC));
+
+	ts->state = ELAN_STATE_NORMAL;
+
+	if (ret <= 0) {
+		error = ret < 0 ? ret : -ETIMEDOUT;
+		dev_err(&client->dev,
+			"error while waiting for calibration to complete: %d\n",
+			error);
+		return error;
+	}
+
+	if (memcmp(rek_resp, ts->cmd_resp, sizeof(rek_resp))) {
+		dev_err(&client->dev,
+			"unexpected calibration response: %*ph\n",
+			(int)sizeof(ts->cmd_resp), ts->cmd_resp);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int elants_i2c_sw_reset(struct i2c_client *client)
+{
+	const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
+	int error;
+
+	error = elants_i2c_send(client, soft_rst_cmd,
+				sizeof(soft_rst_cmd));
+	if (error) {
+		dev_err(&client->dev, "software reset failed: %d\n", error);
+		return error;
+	}
+
+	/*
+	 * We should wait at least 10 msec (but no more than 40) before
+	 * sending fastboot or IAP command to the device.
+	 */
+	msleep(30);
+
+	return 0;
+}
+
+static u16 elants_i2c_parse_version(u8 *buf)
+{
+	return get_unaligned_be32(buf) >> 4;
+}
+
+static int elants_i2c_query_fw_id(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_ID, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			ts->hw_version = elants_i2c_parse_version(resp);
+			if (ts->hw_version != 0xffff)
+				return 0;
+		}
+
+		dev_dbg(&client->dev, "read fw id error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev,
+		"Failed to read fw id or fw id is invalid\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_fw_version(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_VER, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			ts->fw_version = elants_i2c_parse_version(resp);
+			if (ts->fw_version != 0x0000 &&
+			    ts->fw_version != 0xffff)
+				return 0;
+		}
+
+		dev_dbg(&client->dev, "read fw version error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev,
+		"Failed to read fw version or fw version is invalid\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_test_version(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	u16 version;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_TEST_VER, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			version = elants_i2c_parse_version(resp);
+			ts->test_version = version >> 8;
+			ts->solution_version = version & 0xff;
+
+			return 0;
+		}
+
+		dev_dbg(&client->dev,
+			"read test version error rc=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev, "Failed to read test version\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_bc_version(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_BC_VER, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+	u16 version;
+	int error;
+
+	error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev,
+			"read BC version error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+		return error;
+	}
+
+	version = elants_i2c_parse_version(resp);
+	ts->bc_version = version >> 8;
+	ts->iap_version = version & 0xff;
+
+	return 0;
+}
+
+static int elants_i2c_query_ts_info(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int error;
+	u8 resp[17];
+	u16 phy_x, phy_y, rows, cols, osr;
+	const u8 get_resolution_cmd[] = {
+		CMD_HEADER_6B_READ, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+	const u8 get_osr_cmd[] = {
+		CMD_HEADER_READ, E_INFO_OSR, 0x00, 0x01
+	};
+	const u8 get_physical_scan_cmd[] = {
+		CMD_HEADER_READ, E_INFO_PHY_SCAN, 0x00, 0x01
+	};
+	const u8 get_physical_drive_cmd[] = {
+		CMD_HEADER_READ, E_INFO_PHY_DRIVER, 0x00, 0x01
+	};
+
+	/* Get trace number */
+	error = elants_i2c_execute_command(client,
+					   get_resolution_cmd,
+					   sizeof(get_resolution_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get resolution command failed: %d\n",
+			error);
+		return error;
+	}
+
+	rows = resp[2] + resp[6] + resp[10];
+	cols = resp[3] + resp[7] + resp[11];
+
+	/* Process mm_to_pixel information */
+	error = elants_i2c_execute_command(client,
+					   get_osr_cmd, sizeof(get_osr_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get osr command failed: %d\n",
+			error);
+		return error;
+	}
+
+	osr = resp[3];
+
+	error = elants_i2c_execute_command(client,
+					   get_physical_scan_cmd,
+					   sizeof(get_physical_scan_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get physical scan command failed: %d\n",
+			error);
+		return error;
+	}
+
+	phy_x = get_unaligned_be16(&resp[2]);
+
+	error = elants_i2c_execute_command(client,
+					   get_physical_drive_cmd,
+					   sizeof(get_physical_drive_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get physical drive command failed: %d\n",
+			error);
+		return error;
+	}
+
+	phy_y = get_unaligned_be16(&resp[2]);
+
+	dev_dbg(&client->dev, "phy_x=%d, phy_y=%d\n", phy_x, phy_y);
+
+	if (rows == 0 || cols == 0 || osr == 0) {
+		dev_warn(&client->dev,
+			 "invalid trace number data: %d, %d, %d\n",
+			 rows, cols, osr);
+	} else {
+		/* translate trace number to TS resolution */
+		ts->x_max = ELAN_TS_RESOLUTION(rows, osr);
+		ts->x_res = DIV_ROUND_CLOSEST(ts->x_max, phy_x);
+		ts->y_max = ELAN_TS_RESOLUTION(cols, osr);
+		ts->y_res = DIV_ROUND_CLOSEST(ts->y_max, phy_y);
+	}
+
+	return 0;
+}
+
+static int elants_i2c_fastboot(struct i2c_client *client)
+{
+	const u8 boot_cmd[] = { 0x4D, 0x61, 0x69, 0x6E };
+	int error;
+
+	error = elants_i2c_send(client, boot_cmd, sizeof(boot_cmd));
+	if (error) {
+		dev_err(&client->dev, "boot failed: %d\n", error);
+		return error;
+	}
+
+	dev_dbg(&client->dev, "boot success -- 0x%x\n", client->addr);
+	return 0;
+}
+
+static int elants_i2c_initialize(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
+	const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
+	u8 buf[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_sw_reset(client);
+		if (error) {
+			/* Continue initializing if it's the last try */
+			if (retry_cnt < MAX_RETRIES - 1)
+				continue;
+		}
+
+		error = elants_i2c_fastboot(client);
+		if (error) {
+			/* Continue initializing if it's the last try */
+			if (retry_cnt < MAX_RETRIES - 1)
+				continue;
+		}
+
+		/* Wait for Hello packet */
+		msleep(BOOT_TIME_DELAY_MS);
+
+		error = elants_i2c_read(client, buf, sizeof(buf));
+		if (error) {
+			dev_err(&client->dev,
+				"failed to read 'hello' packet: %d\n", error);
+		} else if (!memcmp(buf, hello_packet, sizeof(hello_packet))) {
+			ts->iap_mode = ELAN_IAP_OPERATIONAL;
+			break;
+		} else if (!memcmp(buf, recov_packet, sizeof(recov_packet))) {
+			/*
+			 * Setting error code will mark device
+			 * in recovery mode below.
+			 */
+			error = -EIO;
+			break;
+		} else {
+			error = -EINVAL;
+			dev_err(&client->dev,
+				"invalid 'hello' packet: %*ph\n",
+				(int)sizeof(buf), buf);
+		}
+	}
+
+	if (!error)
+		error = elants_i2c_query_fw_id(ts);
+	if (!error)
+		error = elants_i2c_query_fw_version(ts);
+
+	if (error) {
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+	} else {
+		elants_i2c_query_test_version(ts);
+		elants_i2c_query_bc_version(ts);
+		elants_i2c_query_ts_info(ts);
+	}
+
+	return 0;
+}
+
+/*
+ * Firmware update interface.
+ */
+
+static int elants_i2c_fw_write_page(struct i2c_client *client,
+				    const void *page)
+{
+	const u8 ack_ok[] = { 0xaa, 0xaa };
+	u8 buf[2];
+	int retry;
+	int error;
+
+	for (retry = 0; retry < MAX_FW_UPDATE_RETRIES; retry++) {
+		error = elants_i2c_send(client, page, ELAN_FW_PAGESIZE);
+		if (error) {
+			dev_err(&client->dev,
+				"IAP Write Page failed: %d\n", error);
+			continue;
+		}
+
+		error = elants_i2c_read(client, buf, 2);
+		if (error) {
+			dev_err(&client->dev,
+				"IAP Ack read failed: %d\n", error);
+			return error;
+		}
+
+		if (!memcmp(buf, ack_ok, sizeof(ack_ok)))
+			return 0;
+
+		error = -EIO;
+		dev_err(&client->dev,
+			"IAP Get Ack Error [%02x:%02x]\n",
+			buf[0], buf[1]);
+	}
+
+	return error;
+}
+
+static int elants_i2c_do_update_firmware(struct i2c_client *client,
+					 const struct firmware *fw,
+					 bool force)
+{
+	const u8 enter_iap[] = { 0x45, 0x49, 0x41, 0x50 };
+	const u8 enter_iap2[] = { 0x54, 0x00, 0x12, 0x34 };
+	const u8 iap_ack[] = { 0x55, 0xaa, 0x33, 0xcc };
+	u8 buf[HEADER_SIZE];
+	u16 send_id;
+	int page, n_fw_pages;
+	int error;
+
+	/* Recovery mode detection! */
+	if (force) {
+		dev_dbg(&client->dev, "Recovery mode procedure\n");
+		error = elants_i2c_send(client, enter_iap2, sizeof(enter_iap2));
+	} else {
+		/* Start IAP Procedure */
+		dev_dbg(&client->dev, "Normal IAP procedure\n");
+		elants_i2c_sw_reset(client);
+
+		error = elants_i2c_send(client, enter_iap, sizeof(enter_iap));
+	}
+
+	if (error) {
+		dev_err(&client->dev, "failed to enter IAP mode: %d\n", error);
+		return error;
+	}
+
+	msleep(20);
+
+	/* check IAP state */
+	error = elants_i2c_read(client, buf, 4);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to read IAP acknowledgement: %d\n",
+			error);
+		return error;
+	}
+
+	if (memcmp(buf, iap_ack, sizeof(iap_ack))) {
+		dev_err(&client->dev,
+			"failed to enter IAP: %*ph (expected %*ph)\n",
+			(int)sizeof(buf), buf, (int)sizeof(iap_ack), iap_ack);
+		return -EIO;
+	}
+
+	dev_info(&client->dev, "successfully entered IAP mode");
+
+	send_id = client->addr;
+	error = elants_i2c_send(client, &send_id, 1);
+	if (error) {
+		dev_err(&client->dev, "sending dummy byte failed: %d\n",
+			error);
+		return error;
+	}
+
+	/* Clear the last page of Master */
+	error = elants_i2c_send(client, fw->data, ELAN_FW_PAGESIZE);
+	if (error) {
+		dev_err(&client->dev, "clearing of the last page failed: %d\n",
+			error);
+		return error;
+	}
+
+	error = elants_i2c_read(client, buf, 2);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to read ACK for clearing the last page: %d\n",
+			error);
+		return error;
+	}
+
+	n_fw_pages = fw->size / ELAN_FW_PAGESIZE;
+	dev_dbg(&client->dev, "IAP Pages = %d\n", n_fw_pages);
+
+	for (page = 0; page < n_fw_pages; page++) {
+		error = elants_i2c_fw_write_page(client,
+					fw->data + page * ELAN_FW_PAGESIZE);
+		if (error) {
+			dev_err(&client->dev,
+				"failed to write FW page %d: %d\n",
+				page, error);
+			return error;
+		}
+	}
+
+	/* Old iap needs to wait 200ms for WDT and rest is for hello packets */
+	msleep(300);
+
+	dev_info(&client->dev, "firmware update completed\n");
+	return 0;
+}
+
+static int elants_i2c_fw_update(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	const struct firmware *fw;
+	int error;
+
+	error = request_firmware(&fw, ELAN_FW_FILENAME, &client->dev);
+	if (error) {
+		dev_err(&client->dev, "failed to request firmware %s: %d\n",
+			ELAN_FW_FILENAME, error);
+		return error;
+	}
+
+	if (fw->size % ELAN_FW_PAGESIZE) {
+		dev_err(&client->dev, "invalid firmware length: %zu\n",
+			fw->size);
+		error = -EINVAL;
+		goto out;
+	}
+
+	disable_irq(client->irq);
+
+	error = elants_i2c_do_update_firmware(client, fw,
+					ts->iap_mode == ELAN_IAP_RECOVERY);
+	if (error) {
+		dev_err(&client->dev, "firmware update failed: %d\n", error);
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+		goto out_enable_irq;
+	}
+
+	error = elants_i2c_initialize(ts);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to initialize device after firmware update: %d\n",
+			error);
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+		goto out_enable_irq;
+	}
+
+	ts->iap_mode = ELAN_IAP_OPERATIONAL;
+
+out_enable_irq:
+	ts->state = ELAN_STATE_NORMAL;
+	enable_irq(client->irq);
+	msleep(100);
+
+	if (!error)
+		elants_i2c_calibrate(ts);
+out:
+	release_firmware(fw);
+	return error;
+}
+
+/*
+ * Event reporting.
+ */
+
+static void elants_i2c_mt_event(struct elants_data *ts, u8 *buf)
+{
+	struct input_dev *input = ts->input;
+	unsigned int n_fingers;
+	u16 finger_state;
+	int i;
+
+	n_fingers = buf[FW_POS_STATE + 1] & 0x0f;
+	finger_state = ((buf[FW_POS_STATE + 1] & 0x30) << 4) |
+			buf[FW_POS_STATE];
+
+	dev_dbg(&ts->client->dev,
+		"n_fingers: %u, state: %04x\n",  n_fingers, finger_state);
+
+	for (i = 0; i < MAX_CONTACT_NUM && n_fingers; i++) {
+		if (finger_state & 1) {
+			unsigned int x, y, p, w;
+			u8 *pos;
+
+			pos = &buf[FW_POS_XY + i * 3];
+			x = (((u16)pos[0] & 0xf0) << 4) | pos[1];
+			y = (((u16)pos[0] & 0x0f) << 8) | pos[2];
+			p = buf[FW_POS_PRESSURE + i];
+			w = buf[FW_POS_WIDTH + i];
+
+			dev_dbg(&ts->client->dev, "i=%d x=%d y=%d p=%d w=%d\n",
+				i, x, y, p, w);
+
+			input_mt_slot(input, i);
+			input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+			input_event(input, EV_ABS, ABS_MT_POSITION_X, x);
+			input_event(input, EV_ABS, ABS_MT_POSITION_Y, y);
+			input_event(input, EV_ABS, ABS_MT_PRESSURE, p);
+			input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, w);
+
+			n_fingers--;
+		}
+
+		finger_state >>= 1;
+	}
+
+	input_mt_sync_frame(input);
+	input_sync(input);
+}
+
+static u8 elants_i2c_calculate_checksum(u8 *buf)
+{
+	u8 checksum = 0;
+	u8 i;
+
+	for (i = 0; i < FW_POS_CHECKSUM; i++)
+		checksum += buf[i];
+
+	return checksum;
+}
+
+static void elants_i2c_event(struct elants_data *ts, u8 *buf)
+{
+	u8 checksum = elants_i2c_calculate_checksum(buf);
+
+	if (unlikely(buf[FW_POS_CHECKSUM] != checksum))
+		dev_warn(&ts->client->dev,
+			 "%s: invalid checksum for packet %02x: %02x vs. %02x\n",
+			 __func__, buf[FW_POS_HEADER],
+			 checksum, buf[FW_POS_CHECKSUM]);
+	else if (unlikely(buf[FW_POS_HEADER] != HEADER_REPORT_10_FINGER))
+		dev_warn(&ts->client->dev,
+			 "%s: unknown packet type: %02x\n",
+			 __func__, buf[FW_POS_HEADER]);
+	else
+		elants_i2c_mt_event(ts, buf);
+}
+
+static irqreturn_t elants_i2c_irq(int irq, void *_dev)
+{
+	const u8 wait_packet[] = { 0x64, 0x64, 0x64, 0x64 };
+	struct elants_data *ts = _dev;
+	struct i2c_client *client = ts->client;
+	int report_count, report_len;
+	int i;
+	int len;
+
+	len = i2c_master_recv(client, ts->buf, sizeof(ts->buf));
+	if (len < 0) {
+		dev_err(&client->dev, "%s: failed to read data: %d\n",
+			__func__, len);
+		goto out;
+	}
+
+	dev_dbg(&client->dev, "%s: packet %*ph\n",
+		__func__, HEADER_SIZE, ts->buf);
+
+	switch (ts->state) {
+	case ELAN_WAIT_RECALIBRATION:
+		if (ts->buf[FW_HDR_TYPE] == CMD_HEADER_REK) {
+			memcpy(ts->cmd_resp, ts->buf, sizeof(ts->cmd_resp));
+			complete(&ts->cmd_done);
+			ts->state = ELAN_STATE_NORMAL;
+		}
+		break;
+
+	case ELAN_WAIT_QUEUE_HEADER:
+		if (ts->buf[FW_HDR_TYPE] != QUEUE_HEADER_NORMAL)
+			break;
+
+		ts->state = ELAN_STATE_NORMAL;
+		/* fall through */
+
+	case ELAN_STATE_NORMAL:
+
+		switch (ts->buf[FW_HDR_TYPE]) {
+		case CMD_HEADER_HELLO:
+		case CMD_HEADER_RESP:
+		case CMD_HEADER_REK:
+			break;
+
+		case QUEUE_HEADER_WAIT:
+			if (memcmp(ts->buf, wait_packet, sizeof(wait_packet))) {
+				dev_err(&client->dev,
+					"invalid wait packet %*ph\n",
+					HEADER_SIZE, ts->buf);
+			} else {
+				ts->state = ELAN_WAIT_QUEUE_HEADER;
+				udelay(30);
+			}
+			break;
+
+		case QUEUE_HEADER_SINGLE:
+			elants_i2c_event(ts, &ts->buf[HEADER_SIZE]);
+			break;
+
+		case QUEUE_HEADER_NORMAL:
+			report_count = ts->buf[FW_HDR_COUNT];
+			if (report_count > 3) {
+				dev_err(&client->dev,
+					"too large report count: %*ph\n",
+					HEADER_SIZE, ts->buf);
+				break;
+			}
+
+			report_len = ts->buf[FW_HDR_LENGTH] / report_count;
+			if (report_len != PACKET_SIZE) {
+				dev_err(&client->dev,
+					"mismatching report length: %*ph\n",
+					HEADER_SIZE, ts->buf);
+				break;
+			}
+
+			for (i = 0; i < report_count; i++) {
+				u8 *buf = ts->buf + HEADER_SIZE +
+							i * PACKET_SIZE;
+				elants_i2c_event(ts, buf);
+			}
+			break;
+
+		default:
+			dev_err(&client->dev, "unknown packet %*ph\n",
+				HEADER_SIZE, ts->buf);
+			break;
+		}
+		break;
+	}
+
+out:
+	return IRQ_HANDLED;
+}
+
+/*
+ * sysfs interface
+ */
+static ssize_t calibrate_store(struct device *dev,
+			       struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int error;
+
+	error = mutex_lock_interruptible(&ts->sysfs_mutex);
+	if (error)
+		return error;
+
+	error = elants_i2c_calibrate(ts);
+
+	mutex_unlock(&ts->sysfs_mutex);
+	return error ?: count;
+}
+
+static ssize_t write_update_fw(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int error;
+
+	error = mutex_lock_interruptible(&ts->sysfs_mutex);
+	if (error)
+		return error;
+
+	error = elants_i2c_fw_update(ts);
+	dev_dbg(dev, "firmware update result: %d\n", error);
+
+	mutex_unlock(&ts->sysfs_mutex);
+	return error ?: count;
+}
+
+static ssize_t show_iap_mode(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+
+	return sprintf(buf, "%s\n",
+		       ts->iap_mode == ELAN_IAP_OPERATIONAL ?
+				"Normal" : "Recovery");
+}
+
+static DEVICE_ATTR(calibrate, S_IWUSR, NULL, calibrate_store);
+static DEVICE_ATTR(iap_mode, S_IRUGO, show_iap_mode, NULL);
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, write_update_fw);
+
+struct elants_version_attribute {
+	struct device_attribute dattr;
+	size_t field_offset;
+	size_t field_size;
+};
+
+#define __ELANTS_FIELD_SIZE(_field)					\
+	sizeof(((struct elants_data *)NULL)->_field)
+#define __ELANTS_VERIFY_SIZE(_field)					\
+	(BUILD_BUG_ON_ZERO(__ELANTS_FIELD_SIZE(_field) > 2) +		\
+	 __ELANTS_FIELD_SIZE(_field))
+#define ELANTS_VERSION_ATTR(_field)					\
+	struct elants_version_attribute elants_ver_attr_##_field = {	\
+		.dattr = __ATTR(_field, S_IRUGO,			\
+				elants_version_attribute_show, NULL),	\
+		.field_offset = offsetof(struct elants_data, _field),	\
+		.field_size = __ELANTS_VERIFY_SIZE(_field),		\
+	}
+
+static ssize_t elants_version_attribute_show(struct device *dev,
+					     struct device_attribute *dattr,
+					     char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	struct elants_version_attribute *attr =
+		container_of(dattr, struct elants_version_attribute, dattr);
+	u8 *field = (u8 *)((char *)ts + attr->field_offset);
+	unsigned int fmt_size;
+	unsigned int val;
+
+	if (attr->field_size == 1) {
+		val = *field;
+		fmt_size = 2; /* 2 HEX digits */
+	} else {
+		val = *(u16 *)field;
+		fmt_size = 4; /* 4 HEX digits */
+	}
+
+	return sprintf(buf, "%0*x\n", fmt_size, val);
+}
+
+static ELANTS_VERSION_ATTR(fw_version);
+static ELANTS_VERSION_ATTR(hw_version);
+static ELANTS_VERSION_ATTR(test_version);
+static ELANTS_VERSION_ATTR(solution_version);
+static ELANTS_VERSION_ATTR(bc_version);
+static ELANTS_VERSION_ATTR(iap_version);
+
+static struct attribute *elants_attributes[] = {
+	&dev_attr_calibrate.attr,
+	&dev_attr_update_fw.attr,
+	&dev_attr_iap_mode.attr,
+
+	&elants_ver_attr_fw_version.dattr.attr,
+	&elants_ver_attr_hw_version.dattr.attr,
+	&elants_ver_attr_test_version.dattr.attr,
+	&elants_ver_attr_solution_version.dattr.attr,
+	&elants_ver_attr_bc_version.dattr.attr,
+	&elants_ver_attr_iap_version.dattr.attr,
+	NULL
+};
+
+static struct attribute_group elants_attribute_group = {
+	.attrs = elants_attributes,
+};
+
+static void elants_i2c_remove_sysfs_group(void *_data)
+{
+	struct elants_data *ts = _data;
+
+	sysfs_remove_group(&ts->client->dev.kobj, &elants_attribute_group);
+}
+
+static int elants_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	union i2c_smbus_data dummy;
+	struct elants_data *ts;
+	unsigned long irqflags;
+	int error;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		dev_err(&client->dev,
+			"%s: i2c check functionality error\n", DEVICE_NAME);
+		return -ENXIO;
+	}
+
+	/* Make sure there is something at this address */
+	if (i2c_smbus_xfer(client->adapter, client->addr, 0,
+			I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) {
+		dev_err(&client->dev, "nothing at this address\n");
+		return -ENXIO;
+	}
+
+	ts = devm_kzalloc(&client->dev, sizeof(struct elants_data), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	mutex_init(&ts->sysfs_mutex);
+	init_completion(&ts->cmd_done);
+
+	ts->client = client;
+	i2c_set_clientdata(client, ts);
+
+	error = elants_i2c_initialize(ts);
+	if (error) {
+		dev_err(&client->dev, "failed to initialize: %d\n", error);
+		return error;
+	}
+
+	ts->input = devm_input_allocate_device(&client->dev);
+	if (!ts->input) {
+		dev_err(&client->dev, "Failed to allocate input device\n");
+		return -ENOMEM;
+	}
+
+	ts->input->name = "Elan Touchscreen";
+	ts->input->id.bustype = BUS_I2C;
+
+	__set_bit(BTN_TOUCH, ts->input->keybit);
+	__set_bit(EV_ABS, ts->input->evbit);
+	__set_bit(EV_KEY, ts->input->evbit);
+
+	/* Single touch input params setup */
+	input_set_abs_params(ts->input, ABS_X, 0, ts->x_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_Y, 0, ts->y_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_X, ts->x_res);
+	input_abs_set_res(ts->input, ABS_Y, ts->y_res);
+
+	/* Multitouch input params setup */
+	error = input_mt_init_slots(ts->input, MAX_CONTACT_NUM,
+				    INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to initialize MT slots: %d\n", error);
+		return error;
+	}
+
+	input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->x_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->y_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->x_res);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->y_res);
+
+	input_set_drvdata(ts->input, ts);
+
+	error = input_register_device(ts->input);
+	if (error) {
+		dev_err(&client->dev,
+			"unable to register input device: %d\n", error);
+		return error;
+	}
+
+	/*
+	 * Systems using device tree should set up interrupt via DTS,
+	 * the rest will use the default falling edge interrupts.
+	 */
+	irqflags = client->dev.of_node ? 0 : IRQF_TRIGGER_FALLING;
+
+	error = devm_request_threaded_irq(&client->dev, client->irq,
+					  NULL, elants_i2c_irq,
+					  irqflags | IRQF_ONESHOT,
+					  client->name, ts);
+	if (error) {
+		dev_err(&client->dev, "Failed to register interrupt\n");
+		return error;
+	}
+
+	/*
+	 * Systems using device tree should set up wakeup via DTS,
+	 * the rest will configure device as wakeup source by default.
+	 */
+	if (!client->dev.of_node)
+		device_init_wakeup(&client->dev, true);
+
+	error = sysfs_create_group(&client->dev.kobj, &elants_attribute_group);
+	if (error) {
+		dev_err(&client->dev, "failed to create sysfs attributes: %d\n",
+			error);
+		return error;
+	}
+
+	error = devm_add_action(&client->dev,
+				elants_i2c_remove_sysfs_group, ts);
+	if (error) {
+		elants_i2c_remove_sysfs_group(ts);
+		dev_err(&client->dev,
+			"Failed to add sysfs cleanup action: %d\n",
+			error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int __maybe_unused elants_i2c_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_sleep_cmd[] = { 0x54, 0x50, 0x00, 0x01 };
+	int retry_cnt;
+	int error;
+
+	/* Command not support in IAP recovery mode */
+	if (ts->iap_mode != ELAN_IAP_OPERATIONAL)
+		return -EBUSY;
+
+	disable_irq(client->irq);
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_send(client, set_sleep_cmd,
+					sizeof(set_sleep_cmd));
+		if (!error)
+			break;
+
+		dev_err(&client->dev, "suspend command failed: %d\n", error);
+	}
+
+	if (device_may_wakeup(dev))
+		ts->wake_irq_enabled = (enable_irq_wake(client->irq) == 0);
+
+	return 0;
+}
+
+static int __maybe_unused elants_i2c_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_active_cmd[] = { 0x54, 0x58, 0x00, 0x01 };
+	int retry_cnt;
+	int error;
+
+	if (device_may_wakeup(dev) && ts->wake_irq_enabled)
+		disable_irq_wake(client->irq);
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_send(client, set_active_cmd,
+					sizeof(set_active_cmd));
+		if (!error)
+			break;
+
+		dev_err(&client->dev, "resume command failed: %d\n", error);
+	}
+
+	ts->state = ELAN_STATE_NORMAL;
+	enable_irq(client->irq);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(elants_i2c_pm_ops,
+			 elants_i2c_suspend, elants_i2c_resume);
+
+static const struct i2c_device_id elants_i2c_id[] = {
+	{ DEVICE_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, elants_i2c_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id elants_acpi_id[] = {
+	{ "ELAN0001", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, elants_acpi_id);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id elants_of_match[] = {
+	{ .compatible = "elan,ekth3500" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, elants_of_match);
+#endif
+
+static struct i2c_driver elants_i2c_driver = {
+	.probe = elants_i2c_probe,
+	.id_table = elants_i2c_id,
+	.driver = {
+		.name = DEVICE_NAME,
+		.owner = THIS_MODULE,
+		.pm = &elants_i2c_pm_ops,
+		.acpi_match_table = ACPI_PTR(elants_acpi_id),
+		.of_match_table = of_match_ptr(elants_of_match),
+	},
+};
+module_i2c_driver(elants_i2c_driver);
+
+MODULE_AUTHOR("Scott Liu <scott.liu@emc.com.tw>");
+MODULE_DESCRIPTION("Elan I2c Touchscreen driver");
+MODULE_VERSION(DRV_VERSION);
+MODULE_LICENSE("GPL");

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* RE: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
  2014-11-20  8:11 ` Dmitry Torokhov
@ 2014-11-20  8:55     ` ELAN 劉嘉駿
  2014-11-20 10:31   ` Oliver Neukum
  1 sibling, 0 replies; 14+ messages in thread
From: ELAN 劉嘉駿 @ 2014-11-20  8:55 UTC (permalink / raw
  To: 'Dmitry Torokhov'
  Cc: linux-kernel, linux-input, 'Vincent Wang',
	'Jeff Chuang', 'Benson Leung',
	'Benjamin Tissoires', 'Henrik Rydberg',
	'David Herrmann'

Hi Dmitry:
	Good to hear this, we will add more feature based on this version.

Thanks.
--
Scott


-----Original Message-----
From: Dmitry Torokhov [mailto:dmitry.torokhov@gmail.com] 
Sent: Thursday, November 20, 2014 4:11 PM
To: scott
Cc: linux-kernel@vger.kernel.org; linux-input@vger.kernel.org; Vincent Wang;
Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik Rydberg; David
Herrmann
Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support

Hi Scott,

On Fri, Dec 27, 2013 at 08:15:15PM +0800, scott wrote:
> From: Scott Liu <scott.liu@emc.com.tw>
> 
> This patch is for Elan eKTH Touchscreen product, I2C adpater module.
> 

As you know I have been making some changes to the driver in Chromium tree,
here is the latest version of the driver. I'd like to get it in
3.19 (pending community review of course).

Thanks! 

--
Dmitry

Input: add support for Elan eKTH I2C touchscreens

From: Scott Liu <scott.liu@emc.com.tw>

This driver supports Elan eKTH I2C touchscreen controllers. Note that these
are using custom protocol, as opposed to other Elan parts that use
HID-over-I2C and are supported by the standard HID-multitouch driver.

Signed-off-by: Scott Liu <scott.liu@emc.com.tw>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 .../devicetree/bindings/input/elants_i2c.txt       |   33 +
 drivers/input/touchscreen/Kconfig                  |   12 
 drivers/input/touchscreen/Makefile                 |    1 
 drivers/input/touchscreen/elants_i2c.c             | 1271
++++++++++++++++++++
 4 files changed, 1317 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/elants_i2c.txt
 create mode 100644 drivers/input/touchscreen/elants_i2c.c

diff --git a/Documentation/devicetree/bindings/input/elants_i2c.txt
b/Documentation/devicetree/bindings/input/elants_i2c.txt
new file mode 100644
index 0000000..a765232
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/elants_i2c.txt
@@ -0,0 +1,33 @@
+Elantech I2C Touchscreen
+
+Required properties:
+- compatible: must be "elan,ekth3500".
+- reg: I2C address of the chip.
+- interrupt-parent: a phandle for the interrupt controller (see 
+interrupt
+  binding[0]).
+- interrupts: interrupt to which the chip is connected (see interrupt
+  binding[0]).
+
+Optional properties:
+- wakeup-source: touchscreen can be used as a wakeup source.
+- pinctrl-names: should be "default" (see pinctrl binding [1]).
+- pinctrl-0: a phandle pointing to the pin settings for the device (see
+  pinctrl binding [1]).
+
+[0]: 
+Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+[1]: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
+
+Example:
+	&i2c1 {
+		/* ... */
+
+		touchscreen@10 {
+			compatible = "elan,ekth3500";
+			reg = <0x10>;
+			interrupt-parent = <&gpio4>;
+			interrupts = <0x0 IRQ_TYPE_EDGE_FALLING>;
+			wakeup-source;
+		};
+
+		/* ... */
+	};
diff --git a/drivers/input/touchscreen/Kconfig
b/drivers/input/touchscreen/Kconfig
index 568a020..5891752 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -347,6 +347,18 @@ config TOUCHSCREEN_GUNZE
 	  To compile this driver as a module, choose M here: the
 	  module will be called gunze.
 
+config TOUCHSCREEN_ELAN
+	tristate "Elan eKTH I2C touchscreen"
+	depends on I2C
+	help
+	  Say Y here if you have an Elan eKTH I2C touchscreen
+	  connected to your system.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called elants_i2c.
+
 config TOUCHSCREEN_ELO
 	tristate "Elo serial touchscreens"
 	select SERIO
diff --git a/drivers/input/touchscreen/Makefile
b/drivers/input/touchscreen/Makefile
index dab4a56..0242fea 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06)	+= edt-ft5x06.o
 obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE)	+= hampshire.o
 obj-$(CONFIG_TOUCHSCREEN_GUNZE)		+= gunze.o
 obj-$(CONFIG_TOUCHSCREEN_EETI)		+= eeti_ts.o
+obj-$(CONFIG_TOUCHSCREEN_ELAN)		+= elants_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_ELO)		+= elo.o
 obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
 obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
diff --git a/drivers/input/touchscreen/elants_i2c.c
b/drivers/input/touchscreen/elants_i2c.c
new file mode 100644
index 0000000..a510f7e
--- /dev/null
+++ b/drivers/input/touchscreen/elants_i2c.c
@@ -0,0 +1,1271 @@
+/*
+ * Elan Microelectronics touch panels with I2C interface
+ *
+ * Copyright (C) 2014 Elan Microelectronics Corporation.
+ * Scott Liu <scott.liu@emc.com.tw>
+ *
+ * This code is partly based on hid-multitouch.c:
+ *
+ *  Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr>
+ *  Copyright (c) 2010-2012 Benjamin Tissoires 
+<benjamin.tissoires@gmail.com>
+ *  Copyright (c) 2010-2012 Ecole Nationale de l'Aviation Civile, 
+France
+ *
+ *
+ * This code is partly based on i2c-hid.c:
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ */
+
+/*
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/async.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/buffer_head.h>
+#include <linux/version.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/version.h>
+#include <linux/input/mt.h>
+#include <linux/acpi.h>
+#include <linux/of.h>
+#include <asm/unaligned.h>
+
+/* Device, Driver information */
+#define DEVICE_NAME	"elants_i2c"
+#define DRV_VERSION	"1.0.9"
+
+/* Convert from rows or columns into resolution */
+#define ELAN_TS_RESOLUTION(n, m)   (((n) - 1) * (m))
+
+/* FW header data */
+#define HEADER_SIZE		4
+#define FW_HDR_TYPE		0
+#define FW_HDR_COUNT		1
+#define FW_HDR_LENGTH		2
+
+/* Buffer mode Queue Header information */
+#define QUEUE_HEADER_SINGLE	0x62
+#define QUEUE_HEADER_NORMAL	0X63
+#define QUEUE_HEADER_WAIT	0x64
+
+/* Command header definition */
+#define CMD_HEADER_WRITE	0x54
+#define CMD_HEADER_READ		0x53
+#define CMD_HEADER_6B_READ	0x5B
+#define CMD_HEADER_RESP		0x52
+#define CMD_HEADER_6B_RESP	0x9B
+#define CMD_HEADER_HELLO	0x55
+#define CMD_HEADER_REK		0x66
+
+/* FW position data */
+#define PACKET_SIZE		55
+#define MAX_CONTACT_NUM		10
+#define FW_POS_HEADER		0
+#define FW_POS_STATE		1
+#define FW_POS_TOTAL		2
+#define FW_POS_XY		3
+#define FW_POS_CHECKSUM		34
+#define FW_POS_WIDTH		35
+#define FW_POS_PRESSURE		45
+
+#define HEADER_REPORT_10_FINGER	0x62
+
+/* Header (4 bytes) plus 3 fill 10-finger packets */
+#define MAX_PACKET_SIZE		169
+
+#define BOOT_TIME_DELAY_MS	50
+
+/* FW read command, 0x53 0x?? 0x0, 0x01 */
+#define E_ELAN_INFO_FW_VER	0x00
+#define E_ELAN_INFO_BC_VER	0x10
+#define E_ELAN_INFO_TEST_VER	0xE0
+#define E_ELAN_INFO_FW_ID	0xF0
+#define E_INFO_OSR		0xD6
+#define E_INFO_PHY_SCAN		0xD7
+#define E_INFO_PHY_DRIVER	0xD8
+
+#define MAX_RETRIES		3
+#define MAX_FW_UPDATE_RETRIES	30
+
+#define ELAN_FW_PAGESIZE	132
+#define ELAN_FW_FILENAME	"elants_i2c.bin"
+
+/* calibration timeout definition */
+#define ELAN_CALI_TIMEOUT_MSEC	10000
+
+enum elants_state {
+	ELAN_STATE_NORMAL,
+	ELAN_WAIT_QUEUE_HEADER,
+	ELAN_WAIT_RECALIBRATION,
+};
+
+enum elants_iap_mode {
+	ELAN_IAP_OPERATIONAL,
+	ELAN_IAP_RECOVERY,
+};
+
+/* struct elants_data - represents state of Elan touchscreen device */ 
+struct elants_data {
+	struct i2c_client *client;
+	struct input_dev *input;
+
+	u16 fw_version;
+	u8 test_version;
+	u8 solution_version;
+	u8 bc_version;
+	u8 iap_version;
+	u16 hw_version;
+	unsigned int x_res;	/* resolution in units/mm */
+	unsigned int y_res;
+	unsigned int x_max;
+	unsigned int y_max;
+
+	enum elants_state state;
+	enum elants_iap_mode iap_mode;
+
+	/* Guards against concurrent access to the device via sysfs */
+	struct mutex sysfs_mutex;
+
+	u8 cmd_resp[HEADER_SIZE];
+	struct completion cmd_done;
+
+	u8 buf[MAX_PACKET_SIZE];
+
+	bool wake_irq_enabled;
+};
+
+static int elants_i2c_send(struct i2c_client *client,
+			   const void *data, size_t size)
+{
+	int ret;
+
+	ret = i2c_master_send(client, data, size);
+	if (ret == size)
+		return 0;
+
+	if (ret >= 0)
+		ret = -EIO;
+
+	dev_err(&client->dev, "%s failed (%*ph): %d\n",
+		__func__, (int)size, data, ret);
+
+	return ret;
+}
+
+static int elants_i2c_read(struct i2c_client *client, void *data, 
+size_t size) {
+	int ret;
+
+	ret = i2c_master_recv(client, data, size);
+	if (ret == size)
+		return 0;
+
+	if (ret >= 0)
+		ret = -EIO;
+
+	dev_err(&client->dev, "%s failed: %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int elants_i2c_execute_command(struct i2c_client *client,
+				      const u8 *cmd, size_t cmd_size,
+				      u8 *resp, size_t resp_size)
+{
+	struct i2c_msg msgs[2];
+	int ret;
+	u8 expected_response;
+
+	switch (cmd[0]) {
+	case CMD_HEADER_READ:
+		expected_response = CMD_HEADER_RESP;
+		break;
+
+	case CMD_HEADER_6B_READ:
+		expected_response = CMD_HEADER_6B_RESP;
+		break;
+
+	default:
+		dev_err(&client->dev, "%s: invalid command %*ph\n",
+			__func__, (int)cmd_size, cmd);
+		return -EINVAL;
+	}
+
+	msgs[0].addr = client->addr;
+	msgs[0].flags = client->flags & I2C_M_TEN;
+	msgs[0].len = cmd_size;
+	msgs[0].buf = (u8 *)cmd;
+
+	msgs[1].addr = client->addr;
+	msgs[1].flags = client->flags & I2C_M_TEN;
+	msgs[1].flags |= I2C_M_RD;
+	msgs[1].len = resp_size;
+	msgs[1].buf = resp;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0)
+		return ret;
+
+	if (ret != ARRAY_SIZE(msgs) || resp[FW_HDR_TYPE] !=
expected_response)
+		return -EIO;
+
+	return 0;
+}
+
+static int elants_i2c_calibrate(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int ret, error;
+	static const u8 w_flashkey[] = { 0x54, 0xC0, 0xE1, 0x5A };
+	static const u8 rek[] = { 0x54, 0x29, 0x00, 0x01 };
+	static const u8 rek_resp[] = { CMD_HEADER_REK, 0x66, 0x66, 0x66 };
+
+	disable_irq(client->irq);
+
+	ts->state = ELAN_WAIT_RECALIBRATION;
+	reinit_completion(&ts->cmd_done);
+
+	elants_i2c_send(client, w_flashkey, sizeof(w_flashkey));
+	elants_i2c_send(client, rek, sizeof(rek));
+
+	enable_irq(client->irq);
+
+	ret = wait_for_completion_interruptible_timeout(&ts->cmd_done,
+				msecs_to_jiffies(ELAN_CALI_TIMEOUT_MSEC));
+
+	ts->state = ELAN_STATE_NORMAL;
+
+	if (ret <= 0) {
+		error = ret < 0 ? ret : -ETIMEDOUT;
+		dev_err(&client->dev,
+			"error while waiting for calibration to complete:
%d\n",
+			error);
+		return error;
+	}
+
+	if (memcmp(rek_resp, ts->cmd_resp, sizeof(rek_resp))) {
+		dev_err(&client->dev,
+			"unexpected calibration response: %*ph\n",
+			(int)sizeof(ts->cmd_resp), ts->cmd_resp);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int elants_i2c_sw_reset(struct i2c_client *client) {
+	const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
+	int error;
+
+	error = elants_i2c_send(client, soft_rst_cmd,
+				sizeof(soft_rst_cmd));
+	if (error) {
+		dev_err(&client->dev, "software reset failed: %d\n", error);
+		return error;
+	}
+
+	/*
+	 * We should wait at least 10 msec (but no more than 40) before
+	 * sending fastboot or IAP command to the device.
+	 */
+	msleep(30);
+
+	return 0;
+}
+
+static u16 elants_i2c_parse_version(u8 *buf) {
+	return get_unaligned_be32(buf) >> 4;
+}
+
+static int elants_i2c_query_fw_id(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_ID, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			ts->hw_version = elants_i2c_parse_version(resp);
+			if (ts->hw_version != 0xffff)
+				return 0;
+		}
+
+		dev_dbg(&client->dev, "read fw id error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev,
+		"Failed to read fw id or fw id is invalid\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_fw_version(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_VER, 0x00, 0x01
};
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			ts->fw_version = elants_i2c_parse_version(resp);
+			if (ts->fw_version != 0x0000 &&
+			    ts->fw_version != 0xffff)
+				return 0;
+		}
+
+		dev_dbg(&client->dev, "read fw version error=%d,
buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev,
+		"Failed to read fw version or fw version is invalid\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_test_version(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	u16 version;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_TEST_VER, 0x00, 0x01
};
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			version = elants_i2c_parse_version(resp);
+			ts->test_version = version >> 8;
+			ts->solution_version = version & 0xff;
+
+			return 0;
+		}
+
+		dev_dbg(&client->dev,
+			"read test version error rc=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev, "Failed to read test version\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_bc_version(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_BC_VER, 0x00, 0x01
};
+	u8 resp[HEADER_SIZE];
+	u16 version;
+	int error;
+
+	error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev,
+			"read BC version error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+		return error;
+	}
+
+	version = elants_i2c_parse_version(resp);
+	ts->bc_version = version >> 8;
+	ts->iap_version = version & 0xff;
+
+	return 0;
+}
+
+static int elants_i2c_query_ts_info(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error;
+	u8 resp[17];
+	u16 phy_x, phy_y, rows, cols, osr;
+	const u8 get_resolution_cmd[] = {
+		CMD_HEADER_6B_READ, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+	const u8 get_osr_cmd[] = {
+		CMD_HEADER_READ, E_INFO_OSR, 0x00, 0x01
+	};
+	const u8 get_physical_scan_cmd[] = {
+		CMD_HEADER_READ, E_INFO_PHY_SCAN, 0x00, 0x01
+	};
+	const u8 get_physical_drive_cmd[] = {
+		CMD_HEADER_READ, E_INFO_PHY_DRIVER, 0x00, 0x01
+	};
+
+	/* Get trace number */
+	error = elants_i2c_execute_command(client,
+					   get_resolution_cmd,
+					   sizeof(get_resolution_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get resolution command failed: %d\n",
+			error);
+		return error;
+	}
+
+	rows = resp[2] + resp[6] + resp[10];
+	cols = resp[3] + resp[7] + resp[11];
+
+	/* Process mm_to_pixel information */
+	error = elants_i2c_execute_command(client,
+					   get_osr_cmd, sizeof(get_osr_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get osr command failed: %d\n",
+			error);
+		return error;
+	}
+
+	osr = resp[3];
+
+	error = elants_i2c_execute_command(client,
+					   get_physical_scan_cmd,
+					   sizeof(get_physical_scan_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get physical scan command failed:
%d\n",
+			error);
+		return error;
+	}
+
+	phy_x = get_unaligned_be16(&resp[2]);
+
+	error = elants_i2c_execute_command(client,
+					   get_physical_drive_cmd,
+					   sizeof(get_physical_drive_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get physical drive command failed:
%d\n",
+			error);
+		return error;
+	}
+
+	phy_y = get_unaligned_be16(&resp[2]);
+
+	dev_dbg(&client->dev, "phy_x=%d, phy_y=%d\n", phy_x, phy_y);
+
+	if (rows == 0 || cols == 0 || osr == 0) {
+		dev_warn(&client->dev,
+			 "invalid trace number data: %d, %d, %d\n",
+			 rows, cols, osr);
+	} else {
+		/* translate trace number to TS resolution */
+		ts->x_max = ELAN_TS_RESOLUTION(rows, osr);
+		ts->x_res = DIV_ROUND_CLOSEST(ts->x_max, phy_x);
+		ts->y_max = ELAN_TS_RESOLUTION(cols, osr);
+		ts->y_res = DIV_ROUND_CLOSEST(ts->y_max, phy_y);
+	}
+
+	return 0;
+}
+
+static int elants_i2c_fastboot(struct i2c_client *client) {
+	const u8 boot_cmd[] = { 0x4D, 0x61, 0x69, 0x6E };
+	int error;
+
+	error = elants_i2c_send(client, boot_cmd, sizeof(boot_cmd));
+	if (error) {
+		dev_err(&client->dev, "boot failed: %d\n", error);
+		return error;
+	}
+
+	dev_dbg(&client->dev, "boot success -- 0x%x\n", client->addr);
+	return 0;
+}
+
+static int elants_i2c_initialize(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
+	const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
+	u8 buf[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_sw_reset(client);
+		if (error) {
+			/* Continue initializing if it's the last try */
+			if (retry_cnt < MAX_RETRIES - 1)
+				continue;
+		}
+
+		error = elants_i2c_fastboot(client);
+		if (error) {
+			/* Continue initializing if it's the last try */
+			if (retry_cnt < MAX_RETRIES - 1)
+				continue;
+		}
+
+		/* Wait for Hello packet */
+		msleep(BOOT_TIME_DELAY_MS);
+
+		error = elants_i2c_read(client, buf, sizeof(buf));
+		if (error) {
+			dev_err(&client->dev,
+				"failed to read 'hello' packet: %d\n",
error);
+		} else if (!memcmp(buf, hello_packet, sizeof(hello_packet)))
{
+			ts->iap_mode = ELAN_IAP_OPERATIONAL;
+			break;
+		} else if (!memcmp(buf, recov_packet, sizeof(recov_packet)))
{
+			/*
+			 * Setting error code will mark device
+			 * in recovery mode below.
+			 */
+			error = -EIO;
+			break;
+		} else {
+			error = -EINVAL;
+			dev_err(&client->dev,
+				"invalid 'hello' packet: %*ph\n",
+				(int)sizeof(buf), buf);
+		}
+	}
+
+	if (!error)
+		error = elants_i2c_query_fw_id(ts);
+	if (!error)
+		error = elants_i2c_query_fw_version(ts);
+
+	if (error) {
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+	} else {
+		elants_i2c_query_test_version(ts);
+		elants_i2c_query_bc_version(ts);
+		elants_i2c_query_ts_info(ts);
+	}
+
+	return 0;
+}
+
+/*
+ * Firmware update interface.
+ */
+
+static int elants_i2c_fw_write_page(struct i2c_client *client,
+				    const void *page)
+{
+	const u8 ack_ok[] = { 0xaa, 0xaa };
+	u8 buf[2];
+	int retry;
+	int error;
+
+	for (retry = 0; retry < MAX_FW_UPDATE_RETRIES; retry++) {
+		error = elants_i2c_send(client, page, ELAN_FW_PAGESIZE);
+		if (error) {
+			dev_err(&client->dev,
+				"IAP Write Page failed: %d\n", error);
+			continue;
+		}
+
+		error = elants_i2c_read(client, buf, 2);
+		if (error) {
+			dev_err(&client->dev,
+				"IAP Ack read failed: %d\n", error);
+			return error;
+		}
+
+		if (!memcmp(buf, ack_ok, sizeof(ack_ok)))
+			return 0;
+
+		error = -EIO;
+		dev_err(&client->dev,
+			"IAP Get Ack Error [%02x:%02x]\n",
+			buf[0], buf[1]);
+	}
+
+	return error;
+}
+
+static int elants_i2c_do_update_firmware(struct i2c_client *client,
+					 const struct firmware *fw,
+					 bool force)
+{
+	const u8 enter_iap[] = { 0x45, 0x49, 0x41, 0x50 };
+	const u8 enter_iap2[] = { 0x54, 0x00, 0x12, 0x34 };
+	const u8 iap_ack[] = { 0x55, 0xaa, 0x33, 0xcc };
+	u8 buf[HEADER_SIZE];
+	u16 send_id;
+	int page, n_fw_pages;
+	int error;
+
+	/* Recovery mode detection! */
+	if (force) {
+		dev_dbg(&client->dev, "Recovery mode procedure\n");
+		error = elants_i2c_send(client, enter_iap2,
sizeof(enter_iap2));
+	} else {
+		/* Start IAP Procedure */
+		dev_dbg(&client->dev, "Normal IAP procedure\n");
+		elants_i2c_sw_reset(client);
+
+		error = elants_i2c_send(client, enter_iap,
sizeof(enter_iap));
+	}
+
+	if (error) {
+		dev_err(&client->dev, "failed to enter IAP mode: %d\n",
error);
+		return error;
+	}
+
+	msleep(20);
+
+	/* check IAP state */
+	error = elants_i2c_read(client, buf, 4);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to read IAP acknowledgement: %d\n",
+			error);
+		return error;
+	}
+
+	if (memcmp(buf, iap_ack, sizeof(iap_ack))) {
+		dev_err(&client->dev,
+			"failed to enter IAP: %*ph (expected %*ph)\n",
+			(int)sizeof(buf), buf, (int)sizeof(iap_ack),
iap_ack);
+		return -EIO;
+	}
+
+	dev_info(&client->dev, "successfully entered IAP mode");
+
+	send_id = client->addr;
+	error = elants_i2c_send(client, &send_id, 1);
+	if (error) {
+		dev_err(&client->dev, "sending dummy byte failed: %d\n",
+			error);
+		return error;
+	}
+
+	/* Clear the last page of Master */
+	error = elants_i2c_send(client, fw->data, ELAN_FW_PAGESIZE);
+	if (error) {
+		dev_err(&client->dev, "clearing of the last page failed:
%d\n",
+			error);
+		return error;
+	}
+
+	error = elants_i2c_read(client, buf, 2);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to read ACK for clearing the last page:
%d\n",
+			error);
+		return error;
+	}
+
+	n_fw_pages = fw->size / ELAN_FW_PAGESIZE;
+	dev_dbg(&client->dev, "IAP Pages = %d\n", n_fw_pages);
+
+	for (page = 0; page < n_fw_pages; page++) {
+		error = elants_i2c_fw_write_page(client,
+					fw->data + page * ELAN_FW_PAGESIZE);
+		if (error) {
+			dev_err(&client->dev,
+				"failed to write FW page %d: %d\n",
+				page, error);
+			return error;
+		}
+	}
+
+	/* Old iap needs to wait 200ms for WDT and rest is for hello packets
*/
+	msleep(300);
+
+	dev_info(&client->dev, "firmware update completed\n");
+	return 0;
+}
+
+static int elants_i2c_fw_update(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	const struct firmware *fw;
+	int error;
+
+	error = request_firmware(&fw, ELAN_FW_FILENAME, &client->dev);
+	if (error) {
+		dev_err(&client->dev, "failed to request firmware %s: %d\n",
+			ELAN_FW_FILENAME, error);
+		return error;
+	}
+
+	if (fw->size % ELAN_FW_PAGESIZE) {
+		dev_err(&client->dev, "invalid firmware length: %zu\n",
+			fw->size);
+		error = -EINVAL;
+		goto out;
+	}
+
+	disable_irq(client->irq);
+
+	error = elants_i2c_do_update_firmware(client, fw,
+					ts->iap_mode == ELAN_IAP_RECOVERY);
+	if (error) {
+		dev_err(&client->dev, "firmware update failed: %d\n",
error);
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+		goto out_enable_irq;
+	}
+
+	error = elants_i2c_initialize(ts);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to initialize device after firmware update:
%d\n",
+			error);
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+		goto out_enable_irq;
+	}
+
+	ts->iap_mode = ELAN_IAP_OPERATIONAL;
+
+out_enable_irq:
+	ts->state = ELAN_STATE_NORMAL;
+	enable_irq(client->irq);
+	msleep(100);
+
+	if (!error)
+		elants_i2c_calibrate(ts);
+out:
+	release_firmware(fw);
+	return error;
+}
+
+/*
+ * Event reporting.
+ */
+
+static void elants_i2c_mt_event(struct elants_data *ts, u8 *buf) {
+	struct input_dev *input = ts->input;
+	unsigned int n_fingers;
+	u16 finger_state;
+	int i;
+
+	n_fingers = buf[FW_POS_STATE + 1] & 0x0f;
+	finger_state = ((buf[FW_POS_STATE + 1] & 0x30) << 4) |
+			buf[FW_POS_STATE];
+
+	dev_dbg(&ts->client->dev,
+		"n_fingers: %u, state: %04x\n",  n_fingers, finger_state);
+
+	for (i = 0; i < MAX_CONTACT_NUM && n_fingers; i++) {
+		if (finger_state & 1) {
+			unsigned int x, y, p, w;
+			u8 *pos;
+
+			pos = &buf[FW_POS_XY + i * 3];
+			x = (((u16)pos[0] & 0xf0) << 4) | pos[1];
+			y = (((u16)pos[0] & 0x0f) << 8) | pos[2];
+			p = buf[FW_POS_PRESSURE + i];
+			w = buf[FW_POS_WIDTH + i];
+
+			dev_dbg(&ts->client->dev, "i=%d x=%d y=%d p=%d
w=%d\n",
+				i, x, y, p, w);
+
+			input_mt_slot(input, i);
+			input_mt_report_slot_state(input, MT_TOOL_FINGER,
true);
+			input_event(input, EV_ABS, ABS_MT_POSITION_X, x);
+			input_event(input, EV_ABS, ABS_MT_POSITION_Y, y);
+			input_event(input, EV_ABS, ABS_MT_PRESSURE, p);
+			input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, w);
+
+			n_fingers--;
+		}
+
+		finger_state >>= 1;
+	}
+
+	input_mt_sync_frame(input);
+	input_sync(input);
+}
+
+static u8 elants_i2c_calculate_checksum(u8 *buf) {
+	u8 checksum = 0;
+	u8 i;
+
+	for (i = 0; i < FW_POS_CHECKSUM; i++)
+		checksum += buf[i];
+
+	return checksum;
+}
+
+static void elants_i2c_event(struct elants_data *ts, u8 *buf) {
+	u8 checksum = elants_i2c_calculate_checksum(buf);
+
+	if (unlikely(buf[FW_POS_CHECKSUM] != checksum))
+		dev_warn(&ts->client->dev,
+			 "%s: invalid checksum for packet %02x: %02x vs.
%02x\n",
+			 __func__, buf[FW_POS_HEADER],
+			 checksum, buf[FW_POS_CHECKSUM]);
+	else if (unlikely(buf[FW_POS_HEADER] != HEADER_REPORT_10_FINGER))
+		dev_warn(&ts->client->dev,
+			 "%s: unknown packet type: %02x\n",
+			 __func__, buf[FW_POS_HEADER]);
+	else
+		elants_i2c_mt_event(ts, buf);
+}
+
+static irqreturn_t elants_i2c_irq(int irq, void *_dev) {
+	const u8 wait_packet[] = { 0x64, 0x64, 0x64, 0x64 };
+	struct elants_data *ts = _dev;
+	struct i2c_client *client = ts->client;
+	int report_count, report_len;
+	int i;
+	int len;
+
+	len = i2c_master_recv(client, ts->buf, sizeof(ts->buf));
+	if (len < 0) {
+		dev_err(&client->dev, "%s: failed to read data: %d\n",
+			__func__, len);
+		goto out;
+	}
+
+	dev_dbg(&client->dev, "%s: packet %*ph\n",
+		__func__, HEADER_SIZE, ts->buf);
+
+	switch (ts->state) {
+	case ELAN_WAIT_RECALIBRATION:
+		if (ts->buf[FW_HDR_TYPE] == CMD_HEADER_REK) {
+			memcpy(ts->cmd_resp, ts->buf, sizeof(ts->cmd_resp));
+			complete(&ts->cmd_done);
+			ts->state = ELAN_STATE_NORMAL;
+		}
+		break;
+
+	case ELAN_WAIT_QUEUE_HEADER:
+		if (ts->buf[FW_HDR_TYPE] != QUEUE_HEADER_NORMAL)
+			break;
+
+		ts->state = ELAN_STATE_NORMAL;
+		/* fall through */
+
+	case ELAN_STATE_NORMAL:
+
+		switch (ts->buf[FW_HDR_TYPE]) {
+		case CMD_HEADER_HELLO:
+		case CMD_HEADER_RESP:
+		case CMD_HEADER_REK:
+			break;
+
+		case QUEUE_HEADER_WAIT:
+			if (memcmp(ts->buf, wait_packet,
sizeof(wait_packet))) {
+				dev_err(&client->dev,
+					"invalid wait packet %*ph\n",
+					HEADER_SIZE, ts->buf);
+			} else {
+				ts->state = ELAN_WAIT_QUEUE_HEADER;
+				udelay(30);
+			}
+			break;
+
+		case QUEUE_HEADER_SINGLE:
+			elants_i2c_event(ts, &ts->buf[HEADER_SIZE]);
+			break;
+
+		case QUEUE_HEADER_NORMAL:
+			report_count = ts->buf[FW_HDR_COUNT];
+			if (report_count > 3) {
+				dev_err(&client->dev,
+					"too large report count: %*ph\n",
+					HEADER_SIZE, ts->buf);
+				break;
+			}
+
+			report_len = ts->buf[FW_HDR_LENGTH] / report_count;
+			if (report_len != PACKET_SIZE) {
+				dev_err(&client->dev,
+					"mismatching report length: %*ph\n",
+					HEADER_SIZE, ts->buf);
+				break;
+			}
+
+			for (i = 0; i < report_count; i++) {
+				u8 *buf = ts->buf + HEADER_SIZE +
+							i * PACKET_SIZE;
+				elants_i2c_event(ts, buf);
+			}
+			break;
+
+		default:
+			dev_err(&client->dev, "unknown packet %*ph\n",
+				HEADER_SIZE, ts->buf);
+			break;
+		}
+		break;
+	}
+
+out:
+	return IRQ_HANDLED;
+}
+
+/*
+ * sysfs interface
+ */
+static ssize_t calibrate_store(struct device *dev,
+			       struct device_attribute *attr,
+			      const char *buf, size_t count) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int error;
+
+	error = mutex_lock_interruptible(&ts->sysfs_mutex);
+	if (error)
+		return error;
+
+	error = elants_i2c_calibrate(ts);
+
+	mutex_unlock(&ts->sysfs_mutex);
+	return error ?: count;
+}
+
+static ssize_t write_update_fw(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int error;
+
+	error = mutex_lock_interruptible(&ts->sysfs_mutex);
+	if (error)
+		return error;
+
+	error = elants_i2c_fw_update(ts);
+	dev_dbg(dev, "firmware update result: %d\n", error);
+
+	mutex_unlock(&ts->sysfs_mutex);
+	return error ?: count;
+}
+
+static ssize_t show_iap_mode(struct device *dev,
+			     struct device_attribute *attr, char *buf) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+
+	return sprintf(buf, "%s\n",
+		       ts->iap_mode == ELAN_IAP_OPERATIONAL ?
+				"Normal" : "Recovery");
+}
+
+static DEVICE_ATTR(calibrate, S_IWUSR, NULL, calibrate_store); static 
+DEVICE_ATTR(iap_mode, S_IRUGO, show_iap_mode, NULL); static 
+DEVICE_ATTR(update_fw, S_IWUSR, NULL, write_update_fw);
+
+struct elants_version_attribute {
+	struct device_attribute dattr;
+	size_t field_offset;
+	size_t field_size;
+};
+
+#define __ELANTS_FIELD_SIZE(_field)					\
+	sizeof(((struct elants_data *)NULL)->_field)
+#define __ELANTS_VERIFY_SIZE(_field)					\
+	(BUILD_BUG_ON_ZERO(__ELANTS_FIELD_SIZE(_field) > 2) +		\
+	 __ELANTS_FIELD_SIZE(_field))
+#define ELANTS_VERSION_ATTR(_field)					\
+	struct elants_version_attribute elants_ver_attr_##_field = {	\
+		.dattr = __ATTR(_field, S_IRUGO,			\
+				elants_version_attribute_show, NULL),	\
+		.field_offset = offsetof(struct elants_data, _field),	\
+		.field_size = __ELANTS_VERIFY_SIZE(_field),		\
+	}
+
+static ssize_t elants_version_attribute_show(struct device *dev,
+					     struct device_attribute *dattr,
+					     char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	struct elants_version_attribute *attr =
+		container_of(dattr, struct elants_version_attribute, dattr);
+	u8 *field = (u8 *)((char *)ts + attr->field_offset);
+	unsigned int fmt_size;
+	unsigned int val;
+
+	if (attr->field_size == 1) {
+		val = *field;
+		fmt_size = 2; /* 2 HEX digits */
+	} else {
+		val = *(u16 *)field;
+		fmt_size = 4; /* 4 HEX digits */
+	}
+
+	return sprintf(buf, "%0*x\n", fmt_size, val); }
+
+static ELANTS_VERSION_ATTR(fw_version); static 
+ELANTS_VERSION_ATTR(hw_version); static 
+ELANTS_VERSION_ATTR(test_version);
+static ELANTS_VERSION_ATTR(solution_version);
+static ELANTS_VERSION_ATTR(bc_version); static 
+ELANTS_VERSION_ATTR(iap_version);
+
+static struct attribute *elants_attributes[] = {
+	&dev_attr_calibrate.attr,
+	&dev_attr_update_fw.attr,
+	&dev_attr_iap_mode.attr,
+
+	&elants_ver_attr_fw_version.dattr.attr,
+	&elants_ver_attr_hw_version.dattr.attr,
+	&elants_ver_attr_test_version.dattr.attr,
+	&elants_ver_attr_solution_version.dattr.attr,
+	&elants_ver_attr_bc_version.dattr.attr,
+	&elants_ver_attr_iap_version.dattr.attr,
+	NULL
+};
+
+static struct attribute_group elants_attribute_group = {
+	.attrs = elants_attributes,
+};
+
+static void elants_i2c_remove_sysfs_group(void *_data) {
+	struct elants_data *ts = _data;
+
+	sysfs_remove_group(&ts->client->dev.kobj, &elants_attribute_group);
}
+
+static int elants_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	union i2c_smbus_data dummy;
+	struct elants_data *ts;
+	unsigned long irqflags;
+	int error;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		dev_err(&client->dev,
+			"%s: i2c check functionality error\n", DEVICE_NAME);
+		return -ENXIO;
+	}
+
+	/* Make sure there is something at this address */
+	if (i2c_smbus_xfer(client->adapter, client->addr, 0,
+			I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) {
+		dev_err(&client->dev, "nothing at this address\n");
+		return -ENXIO;
+	}
+
+	ts = devm_kzalloc(&client->dev, sizeof(struct elants_data),
GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	mutex_init(&ts->sysfs_mutex);
+	init_completion(&ts->cmd_done);
+
+	ts->client = client;
+	i2c_set_clientdata(client, ts);
+
+	error = elants_i2c_initialize(ts);
+	if (error) {
+		dev_err(&client->dev, "failed to initialize: %d\n", error);
+		return error;
+	}
+
+	ts->input = devm_input_allocate_device(&client->dev);
+	if (!ts->input) {
+		dev_err(&client->dev, "Failed to allocate input device\n");
+		return -ENOMEM;
+	}
+
+	ts->input->name = "Elan Touchscreen";
+	ts->input->id.bustype = BUS_I2C;
+
+	__set_bit(BTN_TOUCH, ts->input->keybit);
+	__set_bit(EV_ABS, ts->input->evbit);
+	__set_bit(EV_KEY, ts->input->evbit);
+
+	/* Single touch input params setup */
+	input_set_abs_params(ts->input, ABS_X, 0, ts->x_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_Y, 0, ts->y_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_X, ts->x_res);
+	input_abs_set_res(ts->input, ABS_Y, ts->y_res);
+
+	/* Multitouch input params setup */
+	error = input_mt_init_slots(ts->input, MAX_CONTACT_NUM,
+				    INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to initialize MT slots: %d\n", error);
+		return error;
+	}
+
+	input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->x_max, 0,
0);
+	input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->y_max, 0,
0);
+	input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->x_res);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->y_res);
+
+	input_set_drvdata(ts->input, ts);
+
+	error = input_register_device(ts->input);
+	if (error) {
+		dev_err(&client->dev,
+			"unable to register input device: %d\n", error);
+		return error;
+	}
+
+	/*
+	 * Systems using device tree should set up interrupt via DTS,
+	 * the rest will use the default falling edge interrupts.
+	 */
+	irqflags = client->dev.of_node ? 0 : IRQF_TRIGGER_FALLING;
+
+	error = devm_request_threaded_irq(&client->dev, client->irq,
+					  NULL, elants_i2c_irq,
+					  irqflags | IRQF_ONESHOT,
+					  client->name, ts);
+	if (error) {
+		dev_err(&client->dev, "Failed to register interrupt\n");
+		return error;
+	}
+
+	/*
+	 * Systems using device tree should set up wakeup via DTS,
+	 * the rest will configure device as wakeup source by default.
+	 */
+	if (!client->dev.of_node)
+		device_init_wakeup(&client->dev, true);
+
+	error = sysfs_create_group(&client->dev.kobj,
&elants_attribute_group);
+	if (error) {
+		dev_err(&client->dev, "failed to create sysfs attributes:
%d\n",
+			error);
+		return error;
+	}
+
+	error = devm_add_action(&client->dev,
+				elants_i2c_remove_sysfs_group, ts);
+	if (error) {
+		elants_i2c_remove_sysfs_group(ts);
+		dev_err(&client->dev,
+			"Failed to add sysfs cleanup action: %d\n",
+			error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int __maybe_unused elants_i2c_suspend(struct device *dev) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_sleep_cmd[] = { 0x54, 0x50, 0x00, 0x01 };
+	int retry_cnt;
+	int error;
+
+	/* Command not support in IAP recovery mode */
+	if (ts->iap_mode != ELAN_IAP_OPERATIONAL)
+		return -EBUSY;
+
+	disable_irq(client->irq);
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_send(client, set_sleep_cmd,
+					sizeof(set_sleep_cmd));
+		if (!error)
+			break;
+
+		dev_err(&client->dev, "suspend command failed: %d\n",
error);
+	}
+
+	if (device_may_wakeup(dev))
+		ts->wake_irq_enabled = (enable_irq_wake(client->irq) == 0);
+
+	return 0;
+}
+
+static int __maybe_unused elants_i2c_resume(struct device *dev) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_active_cmd[] = { 0x54, 0x58, 0x00, 0x01 };
+	int retry_cnt;
+	int error;
+
+	if (device_may_wakeup(dev) && ts->wake_irq_enabled)
+		disable_irq_wake(client->irq);
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_send(client, set_active_cmd,
+					sizeof(set_active_cmd));
+		if (!error)
+			break;
+
+		dev_err(&client->dev, "resume command failed: %d\n", error);
+	}
+
+	ts->state = ELAN_STATE_NORMAL;
+	enable_irq(client->irq);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(elants_i2c_pm_ops,
+			 elants_i2c_suspend, elants_i2c_resume);
+
+static const struct i2c_device_id elants_i2c_id[] = {
+	{ DEVICE_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, elants_i2c_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id elants_acpi_id[] = {
+	{ "ELAN0001", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, elants_acpi_id); #endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id elants_of_match[] = {
+	{ .compatible = "elan,ekth3500" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, elants_of_match); #endif
+
+static struct i2c_driver elants_i2c_driver = {
+	.probe = elants_i2c_probe,
+	.id_table = elants_i2c_id,
+	.driver = {
+		.name = DEVICE_NAME,
+		.owner = THIS_MODULE,
+		.pm = &elants_i2c_pm_ops,
+		.acpi_match_table = ACPI_PTR(elants_acpi_id),
+		.of_match_table = of_match_ptr(elants_of_match),
+	},
+};
+module_i2c_driver(elants_i2c_driver);
+
+MODULE_AUTHOR("Scott Liu <scott.liu@emc.com.tw>"); 
+MODULE_DESCRIPTION("Elan I2c Touchscreen driver"); 
+MODULE_VERSION(DRV_VERSION); MODULE_LICENSE("GPL");


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* RE: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
@ 2014-11-20  8:55     ` ELAN 劉嘉駿
  0 siblings, 0 replies; 14+ messages in thread
From: ELAN 劉嘉駿 @ 2014-11-20  8:55 UTC (permalink / raw
  To: 'Dmitry Torokhov'
  Cc: linux-kernel, linux-input, 'Vincent Wang',
	'Jeff Chuang', 'Benson Leung',
	'Benjamin Tissoires', 'Henrik Rydberg',
	'David Herrmann'

Hi Dmitry:
	Good to hear this, we will add more feature based on this version.

Thanks.
--
Scott


-----Original Message-----
From: Dmitry Torokhov [mailto:dmitry.torokhov@gmail.com] 
Sent: Thursday, November 20, 2014 4:11 PM
To: scott
Cc: linux-kernel@vger.kernel.org; linux-input@vger.kernel.org; Vincent Wang;
Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik Rydberg; David
Herrmann
Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support

Hi Scott,

On Fri, Dec 27, 2013 at 08:15:15PM +0800, scott wrote:
> From: Scott Liu <scott.liu@emc.com.tw>
> 
> This patch is for Elan eKTH Touchscreen product, I2C adpater module.
> 

As you know I have been making some changes to the driver in Chromium tree,
here is the latest version of the driver. I'd like to get it in
3.19 (pending community review of course).

Thanks! 

--
Dmitry

Input: add support for Elan eKTH I2C touchscreens

From: Scott Liu <scott.liu@emc.com.tw>

This driver supports Elan eKTH I2C touchscreen controllers. Note that these
are using custom protocol, as opposed to other Elan parts that use
HID-over-I2C and are supported by the standard HID-multitouch driver.

Signed-off-by: Scott Liu <scott.liu@emc.com.tw>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 .../devicetree/bindings/input/elants_i2c.txt       |   33 +
 drivers/input/touchscreen/Kconfig                  |   12 
 drivers/input/touchscreen/Makefile                 |    1 
 drivers/input/touchscreen/elants_i2c.c             | 1271
++++++++++++++++++++
 4 files changed, 1317 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/elants_i2c.txt
 create mode 100644 drivers/input/touchscreen/elants_i2c.c

diff --git a/Documentation/devicetree/bindings/input/elants_i2c.txt
b/Documentation/devicetree/bindings/input/elants_i2c.txt
new file mode 100644
index 0000000..a765232
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/elants_i2c.txt
@@ -0,0 +1,33 @@
+Elantech I2C Touchscreen
+
+Required properties:
+- compatible: must be "elan,ekth3500".
+- reg: I2C address of the chip.
+- interrupt-parent: a phandle for the interrupt controller (see 
+interrupt
+  binding[0]).
+- interrupts: interrupt to which the chip is connected (see interrupt
+  binding[0]).
+
+Optional properties:
+- wakeup-source: touchscreen can be used as a wakeup source.
+- pinctrl-names: should be "default" (see pinctrl binding [1]).
+- pinctrl-0: a phandle pointing to the pin settings for the device (see
+  pinctrl binding [1]).
+
+[0]: 
+Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+[1]: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
+
+Example:
+	&i2c1 {
+		/* ... */
+
+		touchscreen@10 {
+			compatible = "elan,ekth3500";
+			reg = <0x10>;
+			interrupt-parent = <&gpio4>;
+			interrupts = <0x0 IRQ_TYPE_EDGE_FALLING>;
+			wakeup-source;
+		};
+
+		/* ... */
+	};
diff --git a/drivers/input/touchscreen/Kconfig
b/drivers/input/touchscreen/Kconfig
index 568a020..5891752 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -347,6 +347,18 @@ config TOUCHSCREEN_GUNZE
 	  To compile this driver as a module, choose M here: the
 	  module will be called gunze.
 
+config TOUCHSCREEN_ELAN
+	tristate "Elan eKTH I2C touchscreen"
+	depends on I2C
+	help
+	  Say Y here if you have an Elan eKTH I2C touchscreen
+	  connected to your system.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called elants_i2c.
+
 config TOUCHSCREEN_ELO
 	tristate "Elo serial touchscreens"
 	select SERIO
diff --git a/drivers/input/touchscreen/Makefile
b/drivers/input/touchscreen/Makefile
index dab4a56..0242fea 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06)	+= edt-ft5x06.o
 obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE)	+= hampshire.o
 obj-$(CONFIG_TOUCHSCREEN_GUNZE)		+= gunze.o
 obj-$(CONFIG_TOUCHSCREEN_EETI)		+= eeti_ts.o
+obj-$(CONFIG_TOUCHSCREEN_ELAN)		+= elants_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_ELO)		+= elo.o
 obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
 obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
diff --git a/drivers/input/touchscreen/elants_i2c.c
b/drivers/input/touchscreen/elants_i2c.c
new file mode 100644
index 0000000..a510f7e
--- /dev/null
+++ b/drivers/input/touchscreen/elants_i2c.c
@@ -0,0 +1,1271 @@
+/*
+ * Elan Microelectronics touch panels with I2C interface
+ *
+ * Copyright (C) 2014 Elan Microelectronics Corporation.
+ * Scott Liu <scott.liu@emc.com.tw>
+ *
+ * This code is partly based on hid-multitouch.c:
+ *
+ *  Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr>
+ *  Copyright (c) 2010-2012 Benjamin Tissoires 
+<benjamin.tissoires@gmail.com>
+ *  Copyright (c) 2010-2012 Ecole Nationale de l'Aviation Civile, 
+France
+ *
+ *
+ * This code is partly based on i2c-hid.c:
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ */
+
+/*
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/async.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/buffer_head.h>
+#include <linux/version.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/version.h>
+#include <linux/input/mt.h>
+#include <linux/acpi.h>
+#include <linux/of.h>
+#include <asm/unaligned.h>
+
+/* Device, Driver information */
+#define DEVICE_NAME	"elants_i2c"
+#define DRV_VERSION	"1.0.9"
+
+/* Convert from rows or columns into resolution */
+#define ELAN_TS_RESOLUTION(n, m)   (((n) - 1) * (m))
+
+/* FW header data */
+#define HEADER_SIZE		4
+#define FW_HDR_TYPE		0
+#define FW_HDR_COUNT		1
+#define FW_HDR_LENGTH		2
+
+/* Buffer mode Queue Header information */
+#define QUEUE_HEADER_SINGLE	0x62
+#define QUEUE_HEADER_NORMAL	0X63
+#define QUEUE_HEADER_WAIT	0x64
+
+/* Command header definition */
+#define CMD_HEADER_WRITE	0x54
+#define CMD_HEADER_READ		0x53
+#define CMD_HEADER_6B_READ	0x5B
+#define CMD_HEADER_RESP		0x52
+#define CMD_HEADER_6B_RESP	0x9B
+#define CMD_HEADER_HELLO	0x55
+#define CMD_HEADER_REK		0x66
+
+/* FW position data */
+#define PACKET_SIZE		55
+#define MAX_CONTACT_NUM		10
+#define FW_POS_HEADER		0
+#define FW_POS_STATE		1
+#define FW_POS_TOTAL		2
+#define FW_POS_XY		3
+#define FW_POS_CHECKSUM		34
+#define FW_POS_WIDTH		35
+#define FW_POS_PRESSURE		45
+
+#define HEADER_REPORT_10_FINGER	0x62
+
+/* Header (4 bytes) plus 3 fill 10-finger packets */
+#define MAX_PACKET_SIZE		169
+
+#define BOOT_TIME_DELAY_MS	50
+
+/* FW read command, 0x53 0x?? 0x0, 0x01 */
+#define E_ELAN_INFO_FW_VER	0x00
+#define E_ELAN_INFO_BC_VER	0x10
+#define E_ELAN_INFO_TEST_VER	0xE0
+#define E_ELAN_INFO_FW_ID	0xF0
+#define E_INFO_OSR		0xD6
+#define E_INFO_PHY_SCAN		0xD7
+#define E_INFO_PHY_DRIVER	0xD8
+
+#define MAX_RETRIES		3
+#define MAX_FW_UPDATE_RETRIES	30
+
+#define ELAN_FW_PAGESIZE	132
+#define ELAN_FW_FILENAME	"elants_i2c.bin"
+
+/* calibration timeout definition */
+#define ELAN_CALI_TIMEOUT_MSEC	10000
+
+enum elants_state {
+	ELAN_STATE_NORMAL,
+	ELAN_WAIT_QUEUE_HEADER,
+	ELAN_WAIT_RECALIBRATION,
+};
+
+enum elants_iap_mode {
+	ELAN_IAP_OPERATIONAL,
+	ELAN_IAP_RECOVERY,
+};
+
+/* struct elants_data - represents state of Elan touchscreen device */ 
+struct elants_data {
+	struct i2c_client *client;
+	struct input_dev *input;
+
+	u16 fw_version;
+	u8 test_version;
+	u8 solution_version;
+	u8 bc_version;
+	u8 iap_version;
+	u16 hw_version;
+	unsigned int x_res;	/* resolution in units/mm */
+	unsigned int y_res;
+	unsigned int x_max;
+	unsigned int y_max;
+
+	enum elants_state state;
+	enum elants_iap_mode iap_mode;
+
+	/* Guards against concurrent access to the device via sysfs */
+	struct mutex sysfs_mutex;
+
+	u8 cmd_resp[HEADER_SIZE];
+	struct completion cmd_done;
+
+	u8 buf[MAX_PACKET_SIZE];
+
+	bool wake_irq_enabled;
+};
+
+static int elants_i2c_send(struct i2c_client *client,
+			   const void *data, size_t size)
+{
+	int ret;
+
+	ret = i2c_master_send(client, data, size);
+	if (ret == size)
+		return 0;
+
+	if (ret >= 0)
+		ret = -EIO;
+
+	dev_err(&client->dev, "%s failed (%*ph): %d\n",
+		__func__, (int)size, data, ret);
+
+	return ret;
+}
+
+static int elants_i2c_read(struct i2c_client *client, void *data, 
+size_t size) {
+	int ret;
+
+	ret = i2c_master_recv(client, data, size);
+	if (ret == size)
+		return 0;
+
+	if (ret >= 0)
+		ret = -EIO;
+
+	dev_err(&client->dev, "%s failed: %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int elants_i2c_execute_command(struct i2c_client *client,
+				      const u8 *cmd, size_t cmd_size,
+				      u8 *resp, size_t resp_size)
+{
+	struct i2c_msg msgs[2];
+	int ret;
+	u8 expected_response;
+
+	switch (cmd[0]) {
+	case CMD_HEADER_READ:
+		expected_response = CMD_HEADER_RESP;
+		break;
+
+	case CMD_HEADER_6B_READ:
+		expected_response = CMD_HEADER_6B_RESP;
+		break;
+
+	default:
+		dev_err(&client->dev, "%s: invalid command %*ph\n",
+			__func__, (int)cmd_size, cmd);
+		return -EINVAL;
+	}
+
+	msgs[0].addr = client->addr;
+	msgs[0].flags = client->flags & I2C_M_TEN;
+	msgs[0].len = cmd_size;
+	msgs[0].buf = (u8 *)cmd;
+
+	msgs[1].addr = client->addr;
+	msgs[1].flags = client->flags & I2C_M_TEN;
+	msgs[1].flags |= I2C_M_RD;
+	msgs[1].len = resp_size;
+	msgs[1].buf = resp;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0)
+		return ret;
+
+	if (ret != ARRAY_SIZE(msgs) || resp[FW_HDR_TYPE] !=
expected_response)
+		return -EIO;
+
+	return 0;
+}
+
+static int elants_i2c_calibrate(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int ret, error;
+	static const u8 w_flashkey[] = { 0x54, 0xC0, 0xE1, 0x5A };
+	static const u8 rek[] = { 0x54, 0x29, 0x00, 0x01 };
+	static const u8 rek_resp[] = { CMD_HEADER_REK, 0x66, 0x66, 0x66 };
+
+	disable_irq(client->irq);
+
+	ts->state = ELAN_WAIT_RECALIBRATION;
+	reinit_completion(&ts->cmd_done);
+
+	elants_i2c_send(client, w_flashkey, sizeof(w_flashkey));
+	elants_i2c_send(client, rek, sizeof(rek));
+
+	enable_irq(client->irq);
+
+	ret = wait_for_completion_interruptible_timeout(&ts->cmd_done,
+				msecs_to_jiffies(ELAN_CALI_TIMEOUT_MSEC));
+
+	ts->state = ELAN_STATE_NORMAL;
+
+	if (ret <= 0) {
+		error = ret < 0 ? ret : -ETIMEDOUT;
+		dev_err(&client->dev,
+			"error while waiting for calibration to complete:
%d\n",
+			error);
+		return error;
+	}
+
+	if (memcmp(rek_resp, ts->cmd_resp, sizeof(rek_resp))) {
+		dev_err(&client->dev,
+			"unexpected calibration response: %*ph\n",
+			(int)sizeof(ts->cmd_resp), ts->cmd_resp);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int elants_i2c_sw_reset(struct i2c_client *client) {
+	const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
+	int error;
+
+	error = elants_i2c_send(client, soft_rst_cmd,
+				sizeof(soft_rst_cmd));
+	if (error) {
+		dev_err(&client->dev, "software reset failed: %d\n", error);
+		return error;
+	}
+
+	/*
+	 * We should wait at least 10 msec (but no more than 40) before
+	 * sending fastboot or IAP command to the device.
+	 */
+	msleep(30);
+
+	return 0;
+}
+
+static u16 elants_i2c_parse_version(u8 *buf) {
+	return get_unaligned_be32(buf) >> 4;
+}
+
+static int elants_i2c_query_fw_id(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_ID, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			ts->hw_version = elants_i2c_parse_version(resp);
+			if (ts->hw_version != 0xffff)
+				return 0;
+		}
+
+		dev_dbg(&client->dev, "read fw id error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev,
+		"Failed to read fw id or fw id is invalid\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_fw_version(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_VER, 0x00, 0x01
};
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			ts->fw_version = elants_i2c_parse_version(resp);
+			if (ts->fw_version != 0x0000 &&
+			    ts->fw_version != 0xffff)
+				return 0;
+		}
+
+		dev_dbg(&client->dev, "read fw version error=%d,
buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev,
+		"Failed to read fw version or fw version is invalid\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_test_version(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	u16 version;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_TEST_VER, 0x00, 0x01
};
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			version = elants_i2c_parse_version(resp);
+			ts->test_version = version >> 8;
+			ts->solution_version = version & 0xff;
+
+			return 0;
+		}
+
+		dev_dbg(&client->dev,
+			"read test version error rc=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev, "Failed to read test version\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_bc_version(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_BC_VER, 0x00, 0x01
};
+	u8 resp[HEADER_SIZE];
+	u16 version;
+	int error;
+
+	error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev,
+			"read BC version error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+		return error;
+	}
+
+	version = elants_i2c_parse_version(resp);
+	ts->bc_version = version >> 8;
+	ts->iap_version = version & 0xff;
+
+	return 0;
+}
+
+static int elants_i2c_query_ts_info(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error;
+	u8 resp[17];
+	u16 phy_x, phy_y, rows, cols, osr;
+	const u8 get_resolution_cmd[] = {
+		CMD_HEADER_6B_READ, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+	const u8 get_osr_cmd[] = {
+		CMD_HEADER_READ, E_INFO_OSR, 0x00, 0x01
+	};
+	const u8 get_physical_scan_cmd[] = {
+		CMD_HEADER_READ, E_INFO_PHY_SCAN, 0x00, 0x01
+	};
+	const u8 get_physical_drive_cmd[] = {
+		CMD_HEADER_READ, E_INFO_PHY_DRIVER, 0x00, 0x01
+	};
+
+	/* Get trace number */
+	error = elants_i2c_execute_command(client,
+					   get_resolution_cmd,
+					   sizeof(get_resolution_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get resolution command failed: %d\n",
+			error);
+		return error;
+	}
+
+	rows = resp[2] + resp[6] + resp[10];
+	cols = resp[3] + resp[7] + resp[11];
+
+	/* Process mm_to_pixel information */
+	error = elants_i2c_execute_command(client,
+					   get_osr_cmd, sizeof(get_osr_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get osr command failed: %d\n",
+			error);
+		return error;
+	}
+
+	osr = resp[3];
+
+	error = elants_i2c_execute_command(client,
+					   get_physical_scan_cmd,
+					   sizeof(get_physical_scan_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get physical scan command failed:
%d\n",
+			error);
+		return error;
+	}
+
+	phy_x = get_unaligned_be16(&resp[2]);
+
+	error = elants_i2c_execute_command(client,
+					   get_physical_drive_cmd,
+					   sizeof(get_physical_drive_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get physical drive command failed:
%d\n",
+			error);
+		return error;
+	}
+
+	phy_y = get_unaligned_be16(&resp[2]);
+
+	dev_dbg(&client->dev, "phy_x=%d, phy_y=%d\n", phy_x, phy_y);
+
+	if (rows == 0 || cols == 0 || osr == 0) {
+		dev_warn(&client->dev,
+			 "invalid trace number data: %d, %d, %d\n",
+			 rows, cols, osr);
+	} else {
+		/* translate trace number to TS resolution */
+		ts->x_max = ELAN_TS_RESOLUTION(rows, osr);
+		ts->x_res = DIV_ROUND_CLOSEST(ts->x_max, phy_x);
+		ts->y_max = ELAN_TS_RESOLUTION(cols, osr);
+		ts->y_res = DIV_ROUND_CLOSEST(ts->y_max, phy_y);
+	}
+
+	return 0;
+}
+
+static int elants_i2c_fastboot(struct i2c_client *client) {
+	const u8 boot_cmd[] = { 0x4D, 0x61, 0x69, 0x6E };
+	int error;
+
+	error = elants_i2c_send(client, boot_cmd, sizeof(boot_cmd));
+	if (error) {
+		dev_err(&client->dev, "boot failed: %d\n", error);
+		return error;
+	}
+
+	dev_dbg(&client->dev, "boot success -- 0x%x\n", client->addr);
+	return 0;
+}
+
+static int elants_i2c_initialize(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
+	const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
+	u8 buf[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_sw_reset(client);
+		if (error) {
+			/* Continue initializing if it's the last try */
+			if (retry_cnt < MAX_RETRIES - 1)
+				continue;
+		}
+
+		error = elants_i2c_fastboot(client);
+		if (error) {
+			/* Continue initializing if it's the last try */
+			if (retry_cnt < MAX_RETRIES - 1)
+				continue;
+		}
+
+		/* Wait for Hello packet */
+		msleep(BOOT_TIME_DELAY_MS);
+
+		error = elants_i2c_read(client, buf, sizeof(buf));
+		if (error) {
+			dev_err(&client->dev,
+				"failed to read 'hello' packet: %d\n",
error);
+		} else if (!memcmp(buf, hello_packet, sizeof(hello_packet)))
{
+			ts->iap_mode = ELAN_IAP_OPERATIONAL;
+			break;
+		} else if (!memcmp(buf, recov_packet, sizeof(recov_packet)))
{
+			/*
+			 * Setting error code will mark device
+			 * in recovery mode below.
+			 */
+			error = -EIO;
+			break;
+		} else {
+			error = -EINVAL;
+			dev_err(&client->dev,
+				"invalid 'hello' packet: %*ph\n",
+				(int)sizeof(buf), buf);
+		}
+	}
+
+	if (!error)
+		error = elants_i2c_query_fw_id(ts);
+	if (!error)
+		error = elants_i2c_query_fw_version(ts);
+
+	if (error) {
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+	} else {
+		elants_i2c_query_test_version(ts);
+		elants_i2c_query_bc_version(ts);
+		elants_i2c_query_ts_info(ts);
+	}
+
+	return 0;
+}
+
+/*
+ * Firmware update interface.
+ */
+
+static int elants_i2c_fw_write_page(struct i2c_client *client,
+				    const void *page)
+{
+	const u8 ack_ok[] = { 0xaa, 0xaa };
+	u8 buf[2];
+	int retry;
+	int error;
+
+	for (retry = 0; retry < MAX_FW_UPDATE_RETRIES; retry++) {
+		error = elants_i2c_send(client, page, ELAN_FW_PAGESIZE);
+		if (error) {
+			dev_err(&client->dev,
+				"IAP Write Page failed: %d\n", error);
+			continue;
+		}
+
+		error = elants_i2c_read(client, buf, 2);
+		if (error) {
+			dev_err(&client->dev,
+				"IAP Ack read failed: %d\n", error);
+			return error;
+		}
+
+		if (!memcmp(buf, ack_ok, sizeof(ack_ok)))
+			return 0;
+
+		error = -EIO;
+		dev_err(&client->dev,
+			"IAP Get Ack Error [%02x:%02x]\n",
+			buf[0], buf[1]);
+	}
+
+	return error;
+}
+
+static int elants_i2c_do_update_firmware(struct i2c_client *client,
+					 const struct firmware *fw,
+					 bool force)
+{
+	const u8 enter_iap[] = { 0x45, 0x49, 0x41, 0x50 };
+	const u8 enter_iap2[] = { 0x54, 0x00, 0x12, 0x34 };
+	const u8 iap_ack[] = { 0x55, 0xaa, 0x33, 0xcc };
+	u8 buf[HEADER_SIZE];
+	u16 send_id;
+	int page, n_fw_pages;
+	int error;
+
+	/* Recovery mode detection! */
+	if (force) {
+		dev_dbg(&client->dev, "Recovery mode procedure\n");
+		error = elants_i2c_send(client, enter_iap2,
sizeof(enter_iap2));
+	} else {
+		/* Start IAP Procedure */
+		dev_dbg(&client->dev, "Normal IAP procedure\n");
+		elants_i2c_sw_reset(client);
+
+		error = elants_i2c_send(client, enter_iap,
sizeof(enter_iap));
+	}
+
+	if (error) {
+		dev_err(&client->dev, "failed to enter IAP mode: %d\n",
error);
+		return error;
+	}
+
+	msleep(20);
+
+	/* check IAP state */
+	error = elants_i2c_read(client, buf, 4);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to read IAP acknowledgement: %d\n",
+			error);
+		return error;
+	}
+
+	if (memcmp(buf, iap_ack, sizeof(iap_ack))) {
+		dev_err(&client->dev,
+			"failed to enter IAP: %*ph (expected %*ph)\n",
+			(int)sizeof(buf), buf, (int)sizeof(iap_ack),
iap_ack);
+		return -EIO;
+	}
+
+	dev_info(&client->dev, "successfully entered IAP mode");
+
+	send_id = client->addr;
+	error = elants_i2c_send(client, &send_id, 1);
+	if (error) {
+		dev_err(&client->dev, "sending dummy byte failed: %d\n",
+			error);
+		return error;
+	}
+
+	/* Clear the last page of Master */
+	error = elants_i2c_send(client, fw->data, ELAN_FW_PAGESIZE);
+	if (error) {
+		dev_err(&client->dev, "clearing of the last page failed:
%d\n",
+			error);
+		return error;
+	}
+
+	error = elants_i2c_read(client, buf, 2);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to read ACK for clearing the last page:
%d\n",
+			error);
+		return error;
+	}
+
+	n_fw_pages = fw->size / ELAN_FW_PAGESIZE;
+	dev_dbg(&client->dev, "IAP Pages = %d\n", n_fw_pages);
+
+	for (page = 0; page < n_fw_pages; page++) {
+		error = elants_i2c_fw_write_page(client,
+					fw->data + page * ELAN_FW_PAGESIZE);
+		if (error) {
+			dev_err(&client->dev,
+				"failed to write FW page %d: %d\n",
+				page, error);
+			return error;
+		}
+	}
+
+	/* Old iap needs to wait 200ms for WDT and rest is for hello packets
*/
+	msleep(300);
+
+	dev_info(&client->dev, "firmware update completed\n");
+	return 0;
+}
+
+static int elants_i2c_fw_update(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	const struct firmware *fw;
+	int error;
+
+	error = request_firmware(&fw, ELAN_FW_FILENAME, &client->dev);
+	if (error) {
+		dev_err(&client->dev, "failed to request firmware %s: %d\n",
+			ELAN_FW_FILENAME, error);
+		return error;
+	}
+
+	if (fw->size % ELAN_FW_PAGESIZE) {
+		dev_err(&client->dev, "invalid firmware length: %zu\n",
+			fw->size);
+		error = -EINVAL;
+		goto out;
+	}
+
+	disable_irq(client->irq);
+
+	error = elants_i2c_do_update_firmware(client, fw,
+					ts->iap_mode == ELAN_IAP_RECOVERY);
+	if (error) {
+		dev_err(&client->dev, "firmware update failed: %d\n",
error);
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+		goto out_enable_irq;
+	}
+
+	error = elants_i2c_initialize(ts);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to initialize device after firmware update:
%d\n",
+			error);
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+		goto out_enable_irq;
+	}
+
+	ts->iap_mode = ELAN_IAP_OPERATIONAL;
+
+out_enable_irq:
+	ts->state = ELAN_STATE_NORMAL;
+	enable_irq(client->irq);
+	msleep(100);
+
+	if (!error)
+		elants_i2c_calibrate(ts);
+out:
+	release_firmware(fw);
+	return error;
+}
+
+/*
+ * Event reporting.
+ */
+
+static void elants_i2c_mt_event(struct elants_data *ts, u8 *buf) {
+	struct input_dev *input = ts->input;
+	unsigned int n_fingers;
+	u16 finger_state;
+	int i;
+
+	n_fingers = buf[FW_POS_STATE + 1] & 0x0f;
+	finger_state = ((buf[FW_POS_STATE + 1] & 0x30) << 4) |
+			buf[FW_POS_STATE];
+
+	dev_dbg(&ts->client->dev,
+		"n_fingers: %u, state: %04x\n",  n_fingers, finger_state);
+
+	for (i = 0; i < MAX_CONTACT_NUM && n_fingers; i++) {
+		if (finger_state & 1) {
+			unsigned int x, y, p, w;
+			u8 *pos;
+
+			pos = &buf[FW_POS_XY + i * 3];
+			x = (((u16)pos[0] & 0xf0) << 4) | pos[1];
+			y = (((u16)pos[0] & 0x0f) << 8) | pos[2];
+			p = buf[FW_POS_PRESSURE + i];
+			w = buf[FW_POS_WIDTH + i];
+
+			dev_dbg(&ts->client->dev, "i=%d x=%d y=%d p=%d
w=%d\n",
+				i, x, y, p, w);
+
+			input_mt_slot(input, i);
+			input_mt_report_slot_state(input, MT_TOOL_FINGER,
true);
+			input_event(input, EV_ABS, ABS_MT_POSITION_X, x);
+			input_event(input, EV_ABS, ABS_MT_POSITION_Y, y);
+			input_event(input, EV_ABS, ABS_MT_PRESSURE, p);
+			input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, w);
+
+			n_fingers--;
+		}
+
+		finger_state >>= 1;
+	}
+
+	input_mt_sync_frame(input);
+	input_sync(input);
+}
+
+static u8 elants_i2c_calculate_checksum(u8 *buf) {
+	u8 checksum = 0;
+	u8 i;
+
+	for (i = 0; i < FW_POS_CHECKSUM; i++)
+		checksum += buf[i];
+
+	return checksum;
+}
+
+static void elants_i2c_event(struct elants_data *ts, u8 *buf) {
+	u8 checksum = elants_i2c_calculate_checksum(buf);
+
+	if (unlikely(buf[FW_POS_CHECKSUM] != checksum))
+		dev_warn(&ts->client->dev,
+			 "%s: invalid checksum for packet %02x: %02x vs.
%02x\n",
+			 __func__, buf[FW_POS_HEADER],
+			 checksum, buf[FW_POS_CHECKSUM]);
+	else if (unlikely(buf[FW_POS_HEADER] != HEADER_REPORT_10_FINGER))
+		dev_warn(&ts->client->dev,
+			 "%s: unknown packet type: %02x\n",
+			 __func__, buf[FW_POS_HEADER]);
+	else
+		elants_i2c_mt_event(ts, buf);
+}
+
+static irqreturn_t elants_i2c_irq(int irq, void *_dev) {
+	const u8 wait_packet[] = { 0x64, 0x64, 0x64, 0x64 };
+	struct elants_data *ts = _dev;
+	struct i2c_client *client = ts->client;
+	int report_count, report_len;
+	int i;
+	int len;
+
+	len = i2c_master_recv(client, ts->buf, sizeof(ts->buf));
+	if (len < 0) {
+		dev_err(&client->dev, "%s: failed to read data: %d\n",
+			__func__, len);
+		goto out;
+	}
+
+	dev_dbg(&client->dev, "%s: packet %*ph\n",
+		__func__, HEADER_SIZE, ts->buf);
+
+	switch (ts->state) {
+	case ELAN_WAIT_RECALIBRATION:
+		if (ts->buf[FW_HDR_TYPE] == CMD_HEADER_REK) {
+			memcpy(ts->cmd_resp, ts->buf, sizeof(ts->cmd_resp));
+			complete(&ts->cmd_done);
+			ts->state = ELAN_STATE_NORMAL;
+		}
+		break;
+
+	case ELAN_WAIT_QUEUE_HEADER:
+		if (ts->buf[FW_HDR_TYPE] != QUEUE_HEADER_NORMAL)
+			break;
+
+		ts->state = ELAN_STATE_NORMAL;
+		/* fall through */
+
+	case ELAN_STATE_NORMAL:
+
+		switch (ts->buf[FW_HDR_TYPE]) {
+		case CMD_HEADER_HELLO:
+		case CMD_HEADER_RESP:
+		case CMD_HEADER_REK:
+			break;
+
+		case QUEUE_HEADER_WAIT:
+			if (memcmp(ts->buf, wait_packet,
sizeof(wait_packet))) {
+				dev_err(&client->dev,
+					"invalid wait packet %*ph\n",
+					HEADER_SIZE, ts->buf);
+			} else {
+				ts->state = ELAN_WAIT_QUEUE_HEADER;
+				udelay(30);
+			}
+			break;
+
+		case QUEUE_HEADER_SINGLE:
+			elants_i2c_event(ts, &ts->buf[HEADER_SIZE]);
+			break;
+
+		case QUEUE_HEADER_NORMAL:
+			report_count = ts->buf[FW_HDR_COUNT];
+			if (report_count > 3) {
+				dev_err(&client->dev,
+					"too large report count: %*ph\n",
+					HEADER_SIZE, ts->buf);
+				break;
+			}
+
+			report_len = ts->buf[FW_HDR_LENGTH] / report_count;
+			if (report_len != PACKET_SIZE) {
+				dev_err(&client->dev,
+					"mismatching report length: %*ph\n",
+					HEADER_SIZE, ts->buf);
+				break;
+			}
+
+			for (i = 0; i < report_count; i++) {
+				u8 *buf = ts->buf + HEADER_SIZE +
+							i * PACKET_SIZE;
+				elants_i2c_event(ts, buf);
+			}
+			break;
+
+		default:
+			dev_err(&client->dev, "unknown packet %*ph\n",
+				HEADER_SIZE, ts->buf);
+			break;
+		}
+		break;
+	}
+
+out:
+	return IRQ_HANDLED;
+}
+
+/*
+ * sysfs interface
+ */
+static ssize_t calibrate_store(struct device *dev,
+			       struct device_attribute *attr,
+			      const char *buf, size_t count) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int error;
+
+	error = mutex_lock_interruptible(&ts->sysfs_mutex);
+	if (error)
+		return error;
+
+	error = elants_i2c_calibrate(ts);
+
+	mutex_unlock(&ts->sysfs_mutex);
+	return error ?: count;
+}
+
+static ssize_t write_update_fw(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int error;
+
+	error = mutex_lock_interruptible(&ts->sysfs_mutex);
+	if (error)
+		return error;
+
+	error = elants_i2c_fw_update(ts);
+	dev_dbg(dev, "firmware update result: %d\n", error);
+
+	mutex_unlock(&ts->sysfs_mutex);
+	return error ?: count;
+}
+
+static ssize_t show_iap_mode(struct device *dev,
+			     struct device_attribute *attr, char *buf) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+
+	return sprintf(buf, "%s\n",
+		       ts->iap_mode == ELAN_IAP_OPERATIONAL ?
+				"Normal" : "Recovery");
+}
+
+static DEVICE_ATTR(calibrate, S_IWUSR, NULL, calibrate_store); static 
+DEVICE_ATTR(iap_mode, S_IRUGO, show_iap_mode, NULL); static 
+DEVICE_ATTR(update_fw, S_IWUSR, NULL, write_update_fw);
+
+struct elants_version_attribute {
+	struct device_attribute dattr;
+	size_t field_offset;
+	size_t field_size;
+};
+
+#define __ELANTS_FIELD_SIZE(_field)					\
+	sizeof(((struct elants_data *)NULL)->_field)
+#define __ELANTS_VERIFY_SIZE(_field)					\
+	(BUILD_BUG_ON_ZERO(__ELANTS_FIELD_SIZE(_field) > 2) +		\
+	 __ELANTS_FIELD_SIZE(_field))
+#define ELANTS_VERSION_ATTR(_field)					\
+	struct elants_version_attribute elants_ver_attr_##_field = {	\
+		.dattr = __ATTR(_field, S_IRUGO,			\
+				elants_version_attribute_show, NULL),	\
+		.field_offset = offsetof(struct elants_data, _field),	\
+		.field_size = __ELANTS_VERIFY_SIZE(_field),		\
+	}
+
+static ssize_t elants_version_attribute_show(struct device *dev,
+					     struct device_attribute *dattr,
+					     char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	struct elants_version_attribute *attr =
+		container_of(dattr, struct elants_version_attribute, dattr);
+	u8 *field = (u8 *)((char *)ts + attr->field_offset);
+	unsigned int fmt_size;
+	unsigned int val;
+
+	if (attr->field_size == 1) {
+		val = *field;
+		fmt_size = 2; /* 2 HEX digits */
+	} else {
+		val = *(u16 *)field;
+		fmt_size = 4; /* 4 HEX digits */
+	}
+
+	return sprintf(buf, "%0*x\n", fmt_size, val); }
+
+static ELANTS_VERSION_ATTR(fw_version); static 
+ELANTS_VERSION_ATTR(hw_version); static 
+ELANTS_VERSION_ATTR(test_version);
+static ELANTS_VERSION_ATTR(solution_version);
+static ELANTS_VERSION_ATTR(bc_version); static 
+ELANTS_VERSION_ATTR(iap_version);
+
+static struct attribute *elants_attributes[] = {
+	&dev_attr_calibrate.attr,
+	&dev_attr_update_fw.attr,
+	&dev_attr_iap_mode.attr,
+
+	&elants_ver_attr_fw_version.dattr.attr,
+	&elants_ver_attr_hw_version.dattr.attr,
+	&elants_ver_attr_test_version.dattr.attr,
+	&elants_ver_attr_solution_version.dattr.attr,
+	&elants_ver_attr_bc_version.dattr.attr,
+	&elants_ver_attr_iap_version.dattr.attr,
+	NULL
+};
+
+static struct attribute_group elants_attribute_group = {
+	.attrs = elants_attributes,
+};
+
+static void elants_i2c_remove_sysfs_group(void *_data) {
+	struct elants_data *ts = _data;
+
+	sysfs_remove_group(&ts->client->dev.kobj, &elants_attribute_group);
}
+
+static int elants_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	union i2c_smbus_data dummy;
+	struct elants_data *ts;
+	unsigned long irqflags;
+	int error;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		dev_err(&client->dev,
+			"%s: i2c check functionality error\n", DEVICE_NAME);
+		return -ENXIO;
+	}
+
+	/* Make sure there is something at this address */
+	if (i2c_smbus_xfer(client->adapter, client->addr, 0,
+			I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) {
+		dev_err(&client->dev, "nothing at this address\n");
+		return -ENXIO;
+	}
+
+	ts = devm_kzalloc(&client->dev, sizeof(struct elants_data),
GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	mutex_init(&ts->sysfs_mutex);
+	init_completion(&ts->cmd_done);
+
+	ts->client = client;
+	i2c_set_clientdata(client, ts);
+
+	error = elants_i2c_initialize(ts);
+	if (error) {
+		dev_err(&client->dev, "failed to initialize: %d\n", error);
+		return error;
+	}
+
+	ts->input = devm_input_allocate_device(&client->dev);
+	if (!ts->input) {
+		dev_err(&client->dev, "Failed to allocate input device\n");
+		return -ENOMEM;
+	}
+
+	ts->input->name = "Elan Touchscreen";
+	ts->input->id.bustype = BUS_I2C;
+
+	__set_bit(BTN_TOUCH, ts->input->keybit);
+	__set_bit(EV_ABS, ts->input->evbit);
+	__set_bit(EV_KEY, ts->input->evbit);
+
+	/* Single touch input params setup */
+	input_set_abs_params(ts->input, ABS_X, 0, ts->x_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_Y, 0, ts->y_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_X, ts->x_res);
+	input_abs_set_res(ts->input, ABS_Y, ts->y_res);
+
+	/* Multitouch input params setup */
+	error = input_mt_init_slots(ts->input, MAX_CONTACT_NUM,
+				    INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to initialize MT slots: %d\n", error);
+		return error;
+	}
+
+	input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->x_max, 0,
0);
+	input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->y_max, 0,
0);
+	input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->x_res);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->y_res);
+
+	input_set_drvdata(ts->input, ts);
+
+	error = input_register_device(ts->input);
+	if (error) {
+		dev_err(&client->dev,
+			"unable to register input device: %d\n", error);
+		return error;
+	}
+
+	/*
+	 * Systems using device tree should set up interrupt via DTS,
+	 * the rest will use the default falling edge interrupts.
+	 */
+	irqflags = client->dev.of_node ? 0 : IRQF_TRIGGER_FALLING;
+
+	error = devm_request_threaded_irq(&client->dev, client->irq,
+					  NULL, elants_i2c_irq,
+					  irqflags | IRQF_ONESHOT,
+					  client->name, ts);
+	if (error) {
+		dev_err(&client->dev, "Failed to register interrupt\n");
+		return error;
+	}
+
+	/*
+	 * Systems using device tree should set up wakeup via DTS,
+	 * the rest will configure device as wakeup source by default.
+	 */
+	if (!client->dev.of_node)
+		device_init_wakeup(&client->dev, true);
+
+	error = sysfs_create_group(&client->dev.kobj,
&elants_attribute_group);
+	if (error) {
+		dev_err(&client->dev, "failed to create sysfs attributes:
%d\n",
+			error);
+		return error;
+	}
+
+	error = devm_add_action(&client->dev,
+				elants_i2c_remove_sysfs_group, ts);
+	if (error) {
+		elants_i2c_remove_sysfs_group(ts);
+		dev_err(&client->dev,
+			"Failed to add sysfs cleanup action: %d\n",
+			error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int __maybe_unused elants_i2c_suspend(struct device *dev) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_sleep_cmd[] = { 0x54, 0x50, 0x00, 0x01 };
+	int retry_cnt;
+	int error;
+
+	/* Command not support in IAP recovery mode */
+	if (ts->iap_mode != ELAN_IAP_OPERATIONAL)
+		return -EBUSY;
+
+	disable_irq(client->irq);
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_send(client, set_sleep_cmd,
+					sizeof(set_sleep_cmd));
+		if (!error)
+			break;
+
+		dev_err(&client->dev, "suspend command failed: %d\n",
error);
+	}
+
+	if (device_may_wakeup(dev))
+		ts->wake_irq_enabled = (enable_irq_wake(client->irq) == 0);
+
+	return 0;
+}
+
+static int __maybe_unused elants_i2c_resume(struct device *dev) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_active_cmd[] = { 0x54, 0x58, 0x00, 0x01 };
+	int retry_cnt;
+	int error;
+
+	if (device_may_wakeup(dev) && ts->wake_irq_enabled)
+		disable_irq_wake(client->irq);
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_send(client, set_active_cmd,
+					sizeof(set_active_cmd));
+		if (!error)
+			break;
+
+		dev_err(&client->dev, "resume command failed: %d\n", error);
+	}
+
+	ts->state = ELAN_STATE_NORMAL;
+	enable_irq(client->irq);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(elants_i2c_pm_ops,
+			 elants_i2c_suspend, elants_i2c_resume);
+
+static const struct i2c_device_id elants_i2c_id[] = {
+	{ DEVICE_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, elants_i2c_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id elants_acpi_id[] = {
+	{ "ELAN0001", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, elants_acpi_id); #endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id elants_of_match[] = {
+	{ .compatible = "elan,ekth3500" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, elants_of_match); #endif
+
+static struct i2c_driver elants_i2c_driver = {
+	.probe = elants_i2c_probe,
+	.id_table = elants_i2c_id,
+	.driver = {
+		.name = DEVICE_NAME,
+		.owner = THIS_MODULE,
+		.pm = &elants_i2c_pm_ops,
+		.acpi_match_table = ACPI_PTR(elants_acpi_id),
+		.of_match_table = of_match_ptr(elants_of_match),
+	},
+};
+module_i2c_driver(elants_i2c_driver);
+
+MODULE_AUTHOR("Scott Liu <scott.liu@emc.com.tw>"); 
+MODULE_DESCRIPTION("Elan I2c Touchscreen driver"); 
+MODULE_VERSION(DRV_VERSION); MODULE_LICENSE("GPL");


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
  2014-11-20  8:11 ` Dmitry Torokhov
  2014-11-20  8:55     ` ELAN 劉嘉駿
@ 2014-11-20 10:31   ` Oliver Neukum
  2014-11-20 12:25       ` ELAN 劉嘉駿
  2014-11-20 17:47     ` Dmitry Torokhov
  1 sibling, 2 replies; 14+ messages in thread
From: Oliver Neukum @ 2014-11-20 10:31 UTC (permalink / raw
  To: Dmitry Torokhov
  Cc: scott, linux-kernel, linux-input, Vincent Wang, Jeff Chuang,
	Benson Leung, Benjamin Tissoires, Henrik Rydberg, David Herrmann



> +static int elants_i2c_sw_reset(struct i2c_client *client)
> +{
> +       const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
> +       int error;
> +
> +       error = elants_i2c_send(client, soft_rst_cmd,
> +                               sizeof(soft_rst_cmd));
> +       if (error) {
> +               dev_err(&client->dev, "software reset failed: %d\n",
> error);
> +               return error;
> +       }
> +
> +       /*
> +        * We should wait at least 10 msec (but no more than 40)
> before
> +        * sending fastboot or IAP command to the device.
> +        */
> +       msleep(30);

If timing is critical in both ways, you should take our guarantee
if sufficient sleep. I suggest you sleep 11msecs.


> +static int elants_i2c_initialize(struct elants_data *ts)
> +{
> +       struct i2c_client *client = ts->client;
> +       int error, retry_cnt;
> +       const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
> +       const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
> +       u8 buf[HEADER_SIZE];
> +

Strictly speaking you should disable preemption here.

> +       for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
> +               error = elants_i2c_sw_reset(client);
> +               if (error) {
> +                       /* Continue initializing if it's the last try
> */
> +                       if (retry_cnt < MAX_RETRIES - 1)
> +                               continue;
> +               }

	Regards
		Oliver

-- 
Oliver Neukum <oneukum@suse.de>


^ permalink raw reply	[flat|nested] 14+ messages in thread

* RE: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
  2014-11-20 10:31   ` Oliver Neukum
@ 2014-11-20 12:25       ` ELAN 劉嘉駿
  2014-11-20 17:47     ` Dmitry Torokhov
  1 sibling, 0 replies; 14+ messages in thread
From: ELAN 劉嘉駿 @ 2014-11-20 12:25 UTC (permalink / raw
  To: 'Oliver Neukum', 'Dmitry Torokhov'
  Cc: linux-kernel, linux-input, 'Vincent Wang',
	'Jeff Chuang', 'Benson Leung',
	'Benjamin Tissoires', 'Henrik Rydberg',
	'David Herrmann'


Hi Oliver:

-----Original Message-----
From: Oliver Neukum [mailto:oneukum@suse.de] 
Sent: Thursday, November 20, 2014 6:32 PM
To: Dmitry Torokhov
Cc: scott; linux-kernel@vger.kernel.org; linux-input@vger.kernel.org; Vincent Wang; Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik Rydberg; David Herrmann
Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support



> +static int elants_i2c_sw_reset(struct i2c_client *client) {
> +       const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
> +       int error;
> +
> +       error = elants_i2c_send(client, soft_rst_cmd,
> +                               sizeof(soft_rst_cmd));
> +       if (error) {
> +               dev_err(&client->dev, "software reset failed: %d\n",
> error);
> +               return error;
> +       }
> +
> +       /*
> +        * We should wait at least 10 msec (but no more than 40)
> before
> +        * sending fastboot or IAP command to the device.
> +        */
> +       msleep(30);

If timing is critical in both ways, you should take our guarantee if sufficient sleep. I suggest you sleep 11msecs.

Sorry, this is our firmware tricky but it should be sleep longer than 30ms. Actually 70ms.
(Dmitry, I need to modify this delay time to 70ms. Sorry for the changed over and over again; 
	because we consider big sensor solution which need to use more ICs and need more longer delay)

thanks,
Scott

> +static int elants_i2c_initialize(struct elants_data *ts) {
> +       struct i2c_client *client = ts->client;
> +       int error, retry_cnt;
> +       const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
> +       const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
> +       u8 buf[HEADER_SIZE];
> +

Strictly speaking you should disable preemption here.


> +       for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
> +               error = elants_i2c_sw_reset(client);
> +               if (error) {
> +                       /* Continue initializing if it's the last try
> */
> +                       if (retry_cnt < MAX_RETRIES - 1)
> +                               continue;
> +               }

	Regards
		Oliver

--
Oliver Neukum <oneukum@suse.de>



^ permalink raw reply	[flat|nested] 14+ messages in thread

* RE: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
@ 2014-11-20 12:25       ` ELAN 劉嘉駿
  0 siblings, 0 replies; 14+ messages in thread
From: ELAN 劉嘉駿 @ 2014-11-20 12:25 UTC (permalink / raw
  To: 'Oliver Neukum', 'Dmitry Torokhov'
  Cc: linux-kernel, linux-input, 'Vincent Wang',
	'Jeff Chuang', 'Benson Leung',
	'Benjamin Tissoires', 'Henrik Rydberg',
	'David Herrmann'


Hi Oliver:

-----Original Message-----
From: Oliver Neukum [mailto:oneukum@suse.de] 
Sent: Thursday, November 20, 2014 6:32 PM
To: Dmitry Torokhov
Cc: scott; linux-kernel@vger.kernel.org; linux-input@vger.kernel.org; Vincent Wang; Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik Rydberg; David Herrmann
Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support



> +static int elants_i2c_sw_reset(struct i2c_client *client) {
> +       const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
> +       int error;
> +
> +       error = elants_i2c_send(client, soft_rst_cmd,
> +                               sizeof(soft_rst_cmd));
> +       if (error) {
> +               dev_err(&client->dev, "software reset failed: %d\n",
> error);
> +               return error;
> +       }
> +
> +       /*
> +        * We should wait at least 10 msec (but no more than 40)
> before
> +        * sending fastboot or IAP command to the device.
> +        */
> +       msleep(30);

If timing is critical in both ways, you should take our guarantee if sufficient sleep. I suggest you sleep 11msecs.

Sorry, this is our firmware tricky but it should be sleep longer than 30ms. Actually 70ms.
(Dmitry, I need to modify this delay time to 70ms. Sorry for the changed over and over again; 
	because we consider big sensor solution which need to use more ICs and need more longer delay)

thanks,
Scott

> +static int elants_i2c_initialize(struct elants_data *ts) {
> +       struct i2c_client *client = ts->client;
> +       int error, retry_cnt;
> +       const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
> +       const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
> +       u8 buf[HEADER_SIZE];
> +

Strictly speaking you should disable preemption here.


> +       for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
> +               error = elants_i2c_sw_reset(client);
> +               if (error) {
> +                       /* Continue initializing if it's the last try
> */
> +                       if (retry_cnt < MAX_RETRIES - 1)
> +                               continue;
> +               }

	Regards
		Oliver

--
Oliver Neukum <oneukum@suse.de>



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
  2014-11-20 10:31   ` Oliver Neukum
  2014-11-20 12:25       ` ELAN 劉嘉駿
@ 2014-11-20 17:47     ` Dmitry Torokhov
  2014-11-20 20:27       ` Oliver Neukum
  1 sibling, 1 reply; 14+ messages in thread
From: Dmitry Torokhov @ 2014-11-20 17:47 UTC (permalink / raw
  To: Oliver Neukum
  Cc: scott, linux-kernel, linux-input, Vincent Wang, Jeff Chuang,
	Benson Leung, Benjamin Tissoires, Henrik Rydberg, David Herrmann

Hi Oliver,

On Thu, Nov 20, 2014 at 11:31:30AM +0100, Oliver Neukum wrote:
> > +static int elants_i2c_initialize(struct elants_data *ts)
> > +{
> > +       struct i2c_client *client = ts->client;
> > +       int error, retry_cnt;
> > +       const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
> > +       const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
> > +       u8 buf[HEADER_SIZE];
> > +
> 
> Strictly speaking you should disable preemption here.

Umm, why?

> 
> > +       for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
> > +               error = elants_i2c_sw_reset(client);
> > +               if (error) {
> > +                       /* Continue initializing if it's the last try
> > */
> > +                       if (retry_cnt < MAX_RETRIES - 1)
> > +                               continue;
> > +               }

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
  2014-11-20 12:25       ` ELAN 劉嘉駿
@ 2014-11-20 17:51         ` 'Dmitry Torokhov'
  -1 siblings, 0 replies; 14+ messages in thread
From: 'Dmitry Torokhov' @ 2014-11-20 17:51 UTC (permalink / raw
  To: ELAN 劉嘉駿
  Cc: 'Oliver Neukum', linux-kernel, linux-input,
	'Vincent Wang', 'Jeff Chuang',
	'Benson Leung', 'Benjamin Tissoires',
	'Henrik Rydberg', 'David Herrmann'

On Thu, Nov 20, 2014 at 08:25:15PM +0800, ELAN 劉嘉駿 wrote:
> 
> Hi Oliver:
> 
> -----Original Message-----
> From: Oliver Neukum [mailto:oneukum@suse.de] 
> Sent: Thursday, November 20, 2014 6:32 PM
> To: Dmitry Torokhov
> Cc: scott; linux-kernel@vger.kernel.org; linux-input@vger.kernel.org; Vincent Wang; Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik Rydberg; David Herrmann
> Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
> 
> 
> 
> > +static int elants_i2c_sw_reset(struct i2c_client *client) {
> > +       const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
> > +       int error;
> > +
> > +       error = elants_i2c_send(client, soft_rst_cmd,
> > +                               sizeof(soft_rst_cmd));
> > +       if (error) {
> > +               dev_err(&client->dev, "software reset failed: %d\n",
> > error);
> > +               return error;
> > +       }
> > +
> > +       /*
> > +        * We should wait at least 10 msec (but no more than 40)
> > before
> > +        * sending fastboot or IAP command to the device.
> > +        */
> > +       msleep(30);
> 
> If timing is critical in both ways, you should take our guarantee if sufficient sleep. I suggest you sleep 11msecs.
> 
> Sorry, this is our firmware tricky but it should be sleep longer than 30ms. Actually 70ms.
> (Dmitry, I need to modify this delay time to 70ms. Sorry for the changed over and over again; 
> 	because we consider big sensor solution which need to use more ICs and need more longer delay)
> 

Wait, before you said that upper bound is critical, otherwise the device will
ignore the next command and will not enter IAP mode. So what will happen if we
increase the wait to 70 msecs and use the driver with smaller sensors?

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
@ 2014-11-20 17:51         ` 'Dmitry Torokhov'
  0 siblings, 0 replies; 14+ messages in thread
From: 'Dmitry Torokhov' @ 2014-11-20 17:51 UTC (permalink / raw
  To: ELAN 劉嘉駿
  Cc: 'Oliver Neukum', linux-kernel, linux-input,
	'Vincent Wang', 'Jeff Chuang',
	'Benson Leung', 'Benjamin Tissoires',
	'Henrik Rydberg', 'David Herrmann'

On Thu, Nov 20, 2014 at 08:25:15PM +0800, ELAN 劉嘉駿 wrote:
> 
> Hi Oliver:
> 
> -----Original Message-----
> From: Oliver Neukum [mailto:oneukum@suse.de] 
> Sent: Thursday, November 20, 2014 6:32 PM
> To: Dmitry Torokhov
> Cc: scott; linux-kernel@vger.kernel.org; linux-input@vger.kernel.org; Vincent Wang; Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik Rydberg; David Herrmann
> Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
> 
> 
> 
> > +static int elants_i2c_sw_reset(struct i2c_client *client) {
> > +       const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
> > +       int error;
> > +
> > +       error = elants_i2c_send(client, soft_rst_cmd,
> > +                               sizeof(soft_rst_cmd));
> > +       if (error) {
> > +               dev_err(&client->dev, "software reset failed: %d\n",
> > error);
> > +               return error;
> > +       }
> > +
> > +       /*
> > +        * We should wait at least 10 msec (but no more than 40)
> > before
> > +        * sending fastboot or IAP command to the device.
> > +        */
> > +       msleep(30);
> 
> If timing is critical in both ways, you should take our guarantee if sufficient sleep. I suggest you sleep 11msecs.
> 
> Sorry, this is our firmware tricky but it should be sleep longer than 30ms. Actually 70ms.
> (Dmitry, I need to modify this delay time to 70ms. Sorry for the changed over and over again; 
> 	because we consider big sensor solution which need to use more ICs and need more longer delay)
> 

Wait, before you said that upper bound is critical, otherwise the device will
ignore the next command and will not enter IAP mode. So what will happen if we
increase the wait to 70 msecs and use the driver with smaller sensors?

Thanks.

-- 
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
  2014-11-20 17:47     ` Dmitry Torokhov
@ 2014-11-20 20:27       ` Oliver Neukum
  2014-11-20 20:35         ` Dmitry Torokhov
  0 siblings, 1 reply; 14+ messages in thread
From: Oliver Neukum @ 2014-11-20 20:27 UTC (permalink / raw
  To: Dmitry Torokhov
  Cc: scott, linux-kernel, linux-input, Vincent Wang, Jeff Chuang,
	Benson Leung, Benjamin Tissoires, Henrik Rydberg, David Herrmann

On Thu, 2014-11-20 at 09:47 -0800, Dmitry Torokhov wrote:
> Hi Oliver,
> 
> On Thu, Nov 20, 2014 at 11:31:30AM +0100, Oliver Neukum wrote:
> > > +static int elants_i2c_initialize(struct elants_data *ts)
> > > +{
> > > +       struct i2c_client *client = ts->client;
> > > +       int error, retry_cnt;
> > > +       const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
> > > +       const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
> > > +       u8 buf[HEADER_SIZE];
> > > +
> > 
> > Strictly speaking you should disable preemption here.
> 
> Umm, why?

You said the upper bound matters. So you need to protect yourself
against losing too much time by preemption.

	Regards
		Oliver



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
  2014-11-20 20:27       ` Oliver Neukum
@ 2014-11-20 20:35         ` Dmitry Torokhov
  0 siblings, 0 replies; 14+ messages in thread
From: Dmitry Torokhov @ 2014-11-20 20:35 UTC (permalink / raw
  To: Oliver Neukum
  Cc: scott, linux-kernel, linux-input, Vincent Wang, Jeff Chuang,
	Benson Leung, Benjamin Tissoires, Henrik Rydberg, David Herrmann

On Thu, Nov 20, 2014 at 09:27:35PM +0100, Oliver Neukum wrote:
> On Thu, 2014-11-20 at 09:47 -0800, Dmitry Torokhov wrote:
> > Hi Oliver,
> > 
> > On Thu, Nov 20, 2014 at 11:31:30AM +0100, Oliver Neukum wrote:
> > > > +static int elants_i2c_initialize(struct elants_data *ts)
> > > > +{
> > > > +       struct i2c_client *client = ts->client;
> > > > +       int error, retry_cnt;
> > > > +       const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
> > > > +       const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
> > > > +       u8 buf[HEADER_SIZE];
> > > > +
> > > 
> > > Strictly speaking you should disable preemption here.
> > 
> > Umm, why?
> 
> You said the upper bound matters. So you need to protect yourself
> against losing too much time by preemption.

We are using i2c so we can't really disable preemption here... I think
we  have enough slack, although I would indeed lower the sleep time.
I'll let Scott comment on that.

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 14+ messages in thread

* RE: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
  2014-11-20 17:51         ` 'Dmitry Torokhov'
@ 2014-11-21  1:16           ` ELAN 劉嘉駿
  -1 siblings, 0 replies; 14+ messages in thread
From: ELAN 劉嘉駿 @ 2014-11-21  1:16 UTC (permalink / raw
  To: 'Dmitry Torokhov'
  Cc: 'Oliver Neukum', linux-kernel, linux-input,
	'Vincent Wang', 'Jeff Chuang',
	'Benson Leung', 'Benjamin Tissoires',
	'Henrik Rydberg', 'David Herrmann', james.chen,
	'蔡仁豪'

Hi Dmitry:


> -----Original Message-----
> From: 'Dmitry Torokhov' [mailto:dmitry.torokhov@gmail.com]
> Sent: Friday, November 21, 2014 1:52 AM
> To: ELAN 劉嘉駿
> Cc: 'Oliver Neukum'; linux-kernel@vger.kernel.org; linux-input@vger.kernel.org;
> 'Vincent Wang'; 'Jeff Chuang'; 'Benson Leung'; 'Benjamin Tissoires'; 'Henrik
> Rydberg'; 'David Herrmann'
> Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
> 
> On Thu, Nov 20, 2014 at 08:25:15PM +0800, ELAN 劉嘉駿 wrote:
> >
> > Hi Oliver:
> >
> > -----Original Message-----
> > From: Oliver Neukum [mailto:oneukum@suse.de]
> > Sent: Thursday, November 20, 2014 6:32 PM
> > To: Dmitry Torokhov
> > Cc: scott; linux-kernel@vger.kernel.org; linux-input@vger.kernel.org;
> > Vincent Wang; Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik
> > Rydberg; David Herrmann
> > Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen
> > support
> >
> >
> >
> > > +static int elants_i2c_sw_reset(struct i2c_client *client) {
> > > +       const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
> > > +       int error;
> > > +
> > > +       error = elants_i2c_send(client, soft_rst_cmd,
> > > +                               sizeof(soft_rst_cmd));
> > > +       if (error) {
> > > +               dev_err(&client->dev, "software reset failed: %d\n",
> > > error);
> > > +               return error;
> > > +       }
> > > +
> > > +       /*
> > > +        * We should wait at least 10 msec (but no more than 40)
> > > before
> > > +        * sending fastboot or IAP command to the device.
> > > +        */
> > > +       msleep(30);
> >
> > If timing is critical in both ways, you should take our guarantee if sufficient
> sleep. I suggest you sleep 11msecs.
> >
> > Sorry, this is our firmware tricky but it should be sleep longer than 30ms.
> Actually 70ms.
> > (Dmitry, I need to modify this delay time to 70ms. Sorry for the changed over
> and over again;
> > 	because we consider big sensor solution which need to use more ICs
> > and need more longer delay)
> >
> 
> Wait, before you said that upper bound is critical, otherwise the device will
> ignore the next command and will not enter IAP mode. So what will happen if
> we increase the wait to 70 msecs and use the driver with smaller sensors?
> 

I think the timeout(40ms) was wrong information. (We do have that, but we will no longer use it on Chrome projects)
As we encountered some problem on Chrome project of big sensor solution, and we already have resolved by modify bootcode timeout value.

Precisely speaking, the 40ms bootcode is special solution for specific project, and general solution is the 100ms bootcode.
So we have replaced 40ms bootcode with 100ms bootcode. That’s what our firmware engineer told me.


Thanks.
--
Scott

> Thanks.
> 
> --
> Dmitry


^ permalink raw reply	[flat|nested] 14+ messages in thread

* RE: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
@ 2014-11-21  1:16           ` ELAN 劉嘉駿
  0 siblings, 0 replies; 14+ messages in thread
From: ELAN 劉嘉駿 @ 2014-11-21  1:16 UTC (permalink / raw
  To: 'Dmitry Torokhov'
  Cc: 'Oliver Neukum', linux-kernel, linux-input,
	'Vincent Wang', 'Jeff Chuang',
	'Benson Leung', 'Benjamin Tissoires',
	'Henrik Rydberg', 'David Herrmann', james.chen,
	'蔡仁豪'

Hi Dmitry:


> -----Original Message-----
> From: 'Dmitry Torokhov' [mailto:dmitry.torokhov@gmail.com]
> Sent: Friday, November 21, 2014 1:52 AM
> To: ELAN 劉嘉駿
> Cc: 'Oliver Neukum'; linux-kernel@vger.kernel.org; linux-input@vger.kernel.org;
> 'Vincent Wang'; 'Jeff Chuang'; 'Benson Leung'; 'Benjamin Tissoires'; 'Henrik
> Rydberg'; 'David Herrmann'
> Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
> 
> On Thu, Nov 20, 2014 at 08:25:15PM +0800, ELAN 劉嘉駿 wrote:
> >
> > Hi Oliver:
> >
> > -----Original Message-----
> > From: Oliver Neukum [mailto:oneukum@suse.de]
> > Sent: Thursday, November 20, 2014 6:32 PM
> > To: Dmitry Torokhov
> > Cc: scott; linux-kernel@vger.kernel.org; linux-input@vger.kernel.org;
> > Vincent Wang; Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik
> > Rydberg; David Herrmann
> > Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen
> > support
> >
> >
> >
> > > +static int elants_i2c_sw_reset(struct i2c_client *client) {
> > > +       const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
> > > +       int error;
> > > +
> > > +       error = elants_i2c_send(client, soft_rst_cmd,
> > > +                               sizeof(soft_rst_cmd));
> > > +       if (error) {
> > > +               dev_err(&client->dev, "software reset failed: %d\n",
> > > error);
> > > +               return error;
> > > +       }
> > > +
> > > +       /*
> > > +        * We should wait at least 10 msec (but no more than 40)
> > > before
> > > +        * sending fastboot or IAP command to the device.
> > > +        */
> > > +       msleep(30);
> >
> > If timing is critical in both ways, you should take our guarantee if sufficient
> sleep. I suggest you sleep 11msecs.
> >
> > Sorry, this is our firmware tricky but it should be sleep longer than 30ms.
> Actually 70ms.
> > (Dmitry, I need to modify this delay time to 70ms. Sorry for the changed over
> and over again;
> > 	because we consider big sensor solution which need to use more ICs
> > and need more longer delay)
> >
> 
> Wait, before you said that upper bound is critical, otherwise the device will
> ignore the next command and will not enter IAP mode. So what will happen if
> we increase the wait to 70 msecs and use the driver with smaller sensors?
> 

I think the timeout(40ms) was wrong information. (We do have that, but we will no longer use it on Chrome projects)
As we encountered some problem on Chrome project of big sensor solution, and we already have resolved by modify bootcode timeout value.

Precisely speaking, the 40ms bootcode is special solution for specific project, and general solution is the 100ms bootcode.
So we have replaced 40ms bootcode with 100ms bootcode. That’s what our firmware engineer told me.


Thanks.
--
Scott

> Thanks.
> 
> --
> Dmitry

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

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2014-11-21  1:16 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-12-27 12:15 [PATCH v1] Input: elants_i2c: Add Elan touchscreen support scott
2014-11-20  8:11 ` Dmitry Torokhov
2014-11-20  8:55   ` ELAN 劉嘉駿
2014-11-20  8:55     ` ELAN 劉嘉駿
2014-11-20 10:31   ` Oliver Neukum
2014-11-20 12:25     ` ELAN 劉嘉駿
2014-11-20 12:25       ` ELAN 劉嘉駿
2014-11-20 17:51       ` 'Dmitry Torokhov'
2014-11-20 17:51         ` 'Dmitry Torokhov'
2014-11-21  1:16         ` ELAN 劉嘉駿
2014-11-21  1:16           ` ELAN 劉嘉駿
2014-11-20 17:47     ` Dmitry Torokhov
2014-11-20 20:27       ` Oliver Neukum
2014-11-20 20:35         ` Dmitry Torokhov

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.