lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <1385099816-2873-1-git-send-email-dusonlin@emc.com.tw>
Date:	Fri, 22 Nov 2013 13:56:55 +0800
From:	Duson Lin <dusonlin@....com.tw>
To:	linux-input@...r.kernel.org, linux-kernel@...r.kernel.org,
	dmitry.torokhov@...il.com, agnescheng@...gle.com,
	phoenix@....com.tw
Cc:	dusonlin@....com.tw
Subject: [PATCH] Input: add i2c/smbus driver for elan touchpad

This driver adds support for elan i2c/smbus touchpad found on some laptops PC
---
 drivers/input/mouse/Kconfig    |   10 +
 drivers/input/mouse/Makefile   |    1 +
 drivers/input/mouse/elan_i2c.c | 1846 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1857 insertions(+)
 create mode 100644 drivers/input/mouse/elan_i2c.c

diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index effa9c5..8ad4b38 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -215,6 +215,16 @@ config MOUSE_CYAPA
 	  To compile this driver as a module, choose M here: the module will be
 	  called cyapa.
 
+config MOUSE_ELAN_I2C
+	tristate "ELAN I2C Touchpad support"
+	depends on I2C
+	help
+	  This driver adds support for Elan I2C Trackpads.
+	  Say y here if you have a ELAN I2C Touchpad.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called elan_i2c.
+
 config MOUSE_INPORT
 	tristate "InPort/MS/ATIXL busmouse"
 	depends on ISA
diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
index c25efdb..24a12a6 100644
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_MOUSE_APPLETOUCH)		+= appletouch.o
 obj-$(CONFIG_MOUSE_ATARI)		+= atarimouse.o
 obj-$(CONFIG_MOUSE_BCM5974)		+= bcm5974.o
 obj-$(CONFIG_MOUSE_CYAPA)		+= cyapa.o
+obj-$(CONFIG_MOUSE_ELAN_I2C)		+= elan_i2c.o
 obj-$(CONFIG_MOUSE_GPIO)		+= gpio_mouse.o
 obj-$(CONFIG_MOUSE_INPORT)		+= inport.o
 obj-$(CONFIG_MOUSE_LOGIBM)		+= logibm.o
diff --git a/drivers/input/mouse/elan_i2c.c b/drivers/input/mouse/elan_i2c.c
new file mode 100644
index 0000000..9892ee1
--- /dev/null
+++ b/drivers/input/mouse/elan_i2c.c
@@ -0,0 +1,1846 @@
+/*
+ * Elan I2C/SMBus Touchpad driver
+ *
+ * Copyright (c) 2013 ELAN Microelectronics Corp.
+ *
+ * Author: 林政維 (Duson Lin) <dusonlin@....com.tw>
+ * Version: 1.4.6
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/cdev.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/input.h>
+#include <linux/uaccess.h>
+#include <linux/jiffies.h>
+
+#define DRIVER_NAME		"elan_i2c"
+#define ELAN_DRIVER_VERSION	"1.4.6"
+#define ETP_PRESSURE_OFFSET	25
+#define ETP_MAX_PRESSURE	255
+#define ETP_FWIDTH_REDUCE	90
+#define ETP_FINGER_WIDTH	15
+
+#define ELAN_ADAPTER_FUNC_NONE   0
+#define ELAN_ADAPTER_FUNC_I2C    1
+#define ELAN_ADAPTER_FUNC_SMBUS  2
+#define ELAN_ADAPTER_FUNC_BOTH   3
+
+/* Length of Elan touchpad information */
+#define ETP_INF_LENGTH		2
+#define ETP_MAX_FINGERS		5
+#define ETP_FINGER_DATA_LEN	5
+#define ETP_REPORT_ID		0x5D
+#define ETP_MAX_REPORT_LEN	34
+#define ETP_ENABLE_ABS		0x0001
+#define ETP_ENABLE_CALIBRATE	0x0002
+#define ETP_DISABLE_CALIBRATE	0x0000
+
+/* Elan smbus command */
+#define ETP_SMBUS_IAP_CMD		0x00
+#define ETP_SMBUS_ENABLE_TP		0x20
+#define ETP_SMBUS_DISABLE_TP		0x21
+#define ETP_SMBUS_IAP_PASSWORD_WRITE	0x29
+#define ETP_SMBUS_IAP_PASSWORD_READ	0x80
+#define ETP_SMBUS_WRITE_FW_BLOCK	0x2A
+#define ETP_SMBUS_IAP_RESET_CMD		0x2B
+#define ETP_SMBUS_RANGE_CMD		0xA0
+#define ETP_SMBUS_FW_VERSION_CMD	0xA1
+#define ETP_SMBUS_XY_TRACENUM_CMD	0xA2
+#define ETP_SMBUS_SM_VERSION_CMD	0xA3
+#define ETP_SMBUS_UNIQUEID_CMD		0xA3
+#define ETP_SMBUS_RESOLUTION_CMD	0xA4
+#define ETP_SMBUS_HELLOPACKET_CMD	0xA7
+#define ETP_SMBUS_PACKET_QUERY		0xA8
+#define ETP_SMBUS_IAP_VERSION_CMD	0xAC
+#define ETP_SMBUS_IAP_CTRL_CMD		0xAD
+#define ETP_SMBUS_IAP_CHECKSUM_CMD	0xAE
+#define ETP_SMBUS_FW_CHECKSUM_CMD	0xAF
+#define ETP_SMBUS_MAX_BASELINE_CMD	0xC3
+#define ETP_SMBUS_MIN_BASELINE_CMD	0xC4
+#define ETP_SMBUS_CALIBRATE_QUERY	0xC5
+#define ETP_SMBUS_REPORT_LEN		32
+#define ETP_SMBUS_FINGER_DATA_OFFSET	2
+#define ETP_SMBUS_HELLOPACKET_LEN	5
+#define ETP_SMBUS_IAP_PASSWORD		0x1234
+#define ETP_SMBUS_IAP_MODE_ON		(1<<6)
+
+/* Elan i2c command */
+#define ETP_I2C_RESET			0x0100
+#define ETP_I2C_WAKE_UP			0x0800
+#define ETP_I2C_SLEEP			0x0801
+#define ETP_I2C_DESC_CMD		0x0001
+#define ETP_I2C_REPORT_DESC_CMD		0x0002
+#define ETP_I2C_STAND_CMD		0x0005
+#define ETP_I2C_UNIQUEID_CMD		0x0101
+#define ETP_I2C_FW_VERSION_CMD		0x0102
+#define ETP_I2C_SM_VERSION_CMD		0x0103
+#define ETP_I2C_XY_TRACENUM_CMD		0x0105
+#define ETP_I2C_MAX_X_AXIS_CMD		0x0106
+#define ETP_I2C_MAX_Y_AXIS_CMD		0x0107
+#define ETP_I2C_RESOLUTION_CMD		0x0108
+#define ETP_I2C_IAP_VERSION_CMD		0x0110
+#define ETP_I2C_SET_CMD			0x0300
+#define ETP_I2C_MAX_BASELINE_CMD	0x0306
+#define ETP_I2C_MIN_BASELINE_CMD	0x0307
+#define ETP_I2C_FW_CHECKSUM_CMD		0x030F
+#define ETP_I2C_IAP_CTRL_CMD		0x0310
+#define ETP_I2C_IAP_CMD			0x0311
+#define ETP_I2C_IAP_RESET_CMD		0x0314
+#define ETP_I2C_IAP_CHECKSUM_CMD	0x0315
+#define ETP_I2C_CALIBRATE_CMD		0x0316
+#define ETP_I2C_REPORT_LEN		34
+#define ETP_I2C_FINGER_DATA_OFFSET	4
+#define ETP_I2C_REPORT_ID_OFFSET	2
+#define ETP_I2C_DESC_LENGTH		30
+#define ETP_I2C_REPORT_DESC_LENGTH	158
+#define ETP_I2C_IAP_PASSWORD		0x1EA5
+#define ETP_I2C_IAP_RESET		0xF0F0
+#define ETP_I2C_MAIN_MODE_ON		(1<<9)
+#define ETP_I2C_IAP_REG_L		0x01
+#define ETP_I2C_IAP_REG_H		0x06
+
+/* IAP F/W updater */
+#define ETP_FW_NAME		"elan_i2c.bin"
+#define ETP_IAP_VERSION_ADDR	0x0082
+#define ETP_IAP_START_ADDR	0x0083
+#define ETP_FW_IAP_PAGE_ERR	(1<<5)
+#define ETP_FW_IAP_INTERFACE_ERR (1<<4)
+#define ETP_FW_PAGE_SIZE	64
+#define ETP_FW_PAGE_COUNT	768
+#define ETP_FW_SIZE		(ETP_FW_PAGE_SIZE * ETP_FW_PAGE_COUNT)
+enum {UNKNOWN_MODE, IAP_MODE, MAIN_MODE};
+
+struct dbfs_data {
+	bool	bfetch;
+	u8	buffer[ETP_MAX_REPORT_LEN];
+};
+
+/* The main device structure */
+struct elan_tp_data {
+	struct i2c_client	*client;
+	struct input_dev	*input;
+	unsigned int		max_x;
+	unsigned int		max_y;
+	unsigned int		width_x;
+	unsigned int		width_y;
+	unsigned int		irq;
+
+	/* fields required for IAP firmware updater */
+	u16			unique_id;
+	u16			fw_version;
+	u16			sm_version;
+	u16			iap_version;
+	bool			updated_fw;
+	u16			iap_start_addr;
+
+	/* irq wake is enabled */
+	bool			irq_wake;
+	bool			smbus;
+	bool			enable_detail_info;
+
+	/* fields required for debug fs */
+	struct mutex		dbfs_mutex;
+	struct dentry		*dbfs_root;
+	struct dbfs_data	dbfs_buffer;
+};
+
+u8 val[256];
+static int elan_i2c_read_cmd(struct i2c_client *client, u16 reg, u8 *val);
+static int elan_i2c_write_cmd(struct i2c_client *client, u16 reg, u16 cmd);
+static int elan_initialize(struct elan_tp_data *data);
+
+/*
+ **************************************************************
+ * debugfs interface
+ **************************************************************
+*/
+static int elan_dbfs_open(struct inode *inode, struct file *file)
+{
+	int retval;
+	struct elan_tp_data *data = inode->i_private;
+
+	if (!data)
+		return -ENODEV;
+
+	retval = mutex_lock_interruptible(&data->dbfs_mutex);
+	if (retval)
+		return retval;
+
+	if (!kobject_get(&data->client->dev.kobj)) {
+		retval = -ENODEV;
+		goto dbfs_out;
+	}
+
+	file->private_data = data;
+dbfs_out:
+	mutex_unlock(&data->dbfs_mutex);
+	return 0;
+}
+
+static int elan_dbfs_release(struct inode *inode, struct file *file)
+{
+	struct elan_tp_data *data = file->private_data;
+	int retval;
+	if (!data)
+		return -ENODEV;
+
+	retval = mutex_lock_interruptible(&data->dbfs_mutex);
+	if (retval)
+		return retval;
+	file->private_data = NULL;
+	kobject_put(&data->client->dev.kobj);
+	mutex_unlock(&data->dbfs_mutex);
+	return 0;
+}
+
+
+static ssize_t elan_dbfs_read(struct file *file,
+		char __user *buffer, size_t count, loff_t *ppos)
+{
+	struct elan_tp_data *data = file->private_data;
+	int retval;
+	if (!data)
+		return -ENODEV;
+
+	retval = mutex_lock_interruptible(&data->dbfs_mutex);
+	if (retval)
+		return -EFAULT;
+	if (data->dbfs_buffer.bfetch == false) {
+		if (!copy_to_user(buffer, data->dbfs_buffer.buffer, count)) {
+			data->dbfs_buffer.bfetch = true;
+			retval = count;
+		} else {
+			retval = -2;
+		}
+	} else {
+		retval = -4;
+	}
+	mutex_unlock(&data->dbfs_mutex);
+	return retval;
+}
+
+static ssize_t elan_dbfs_write(struct file *file,
+		const char __user *buffer, size_t count, loff_t *ppos)
+{
+	struct elan_tp_data *data = file->private_data;
+	int retval;
+	if (!data)
+		return -ENODEV;
+
+	retval = mutex_lock_interruptible(&data->dbfs_mutex);
+	if (retval)
+		return -EFAULT;
+	retval = count;
+	mutex_unlock(&data->dbfs_mutex);
+	return retval;
+}
+
+static long elan_dbfs_ioctrl(struct file *file,
+		unsigned int cmd, unsigned long arg)
+{
+	int retval = 0;
+	struct elan_tp_data *data = file->private_data;
+
+	retval = mutex_lock_interruptible(&data->dbfs_mutex);
+	if (retval)
+		return retval;
+	mutex_unlock(&data->dbfs_mutex);
+	return retval;
+}
+
+static const struct file_operations elan_debug_fops = {
+	.open = elan_dbfs_open,
+	.release = elan_dbfs_release,
+	.read = elan_dbfs_read,
+	.write = elan_dbfs_write,
+	.unlocked_ioctl = elan_dbfs_ioctrl
+};
+
+static int elan_dbfs_init(struct elan_tp_data *data)
+{
+	/* Create a global debugfs root for all elan devices */
+	/* sys/kernel/debug/elan */
+	data->dbfs_root = debugfs_create_dir("elan", NULL);
+	if (!data->dbfs_root) {
+		dev_err(&data->client->dev, "cannot create dbfs_root.\n");
+		return -ENODEV;
+	}
+	mutex_init(&data->dbfs_mutex);
+
+	debugfs_create_file(DRIVER_NAME, 0777,
+				data->dbfs_root, data, &elan_debug_fops);
+	data->dbfs_buffer.bfetch = false;
+	return 0;
+}
+
+/**********************************************************
+ * IAP firmware updater related routines                  *
+ **********************************************************
+*/
+
+static int elan_iap_getmode(struct elan_tp_data *data)
+{
+	u16 constant;
+	int retval;
+	struct i2c_client *client = data->client;
+
+	if (data->smbus) {
+		retval = i2c_smbus_read_block_data(client,
+					ETP_SMBUS_IAP_CTRL_CMD, val);
+		if (retval < 0) {
+			dev_err(&client->dev, "read iap ctrol fail.\n");
+			return UNKNOWN_MODE;
+		}
+		constant = be16_to_cpup((__be16 *)val);
+		dev_dbg(&client->dev, "smbus iap control reg: 0x%04x.\n",
+								constant);
+		if ((constant & ETP_SMBUS_IAP_MODE_ON) == 0x00)
+			return MAIN_MODE;
+	} else {
+		retval = elan_i2c_read_cmd(client, ETP_I2C_IAP_CTRL_CMD, val);
+		if (retval < 0) {
+			dev_err(&client->dev, "read iap ctrol fail.\n");
+			return UNKNOWN_MODE;
+		}
+		constant = le16_to_cpup((__le16 *)val);
+		dev_dbg(&client->dev, "i2c iap control reg: 0x%04x.\n",
+								constant);
+		if (constant & ETP_I2C_MAIN_MODE_ON)
+			return MAIN_MODE;
+	}
+
+	return IAP_MODE;
+}
+
+static int elan_iap_checksum(struct elan_tp_data *data)
+{
+	int retval = 0;
+	u16 checksum = -1;
+	struct i2c_client *client = data->client;
+
+	if (data->smbus) {
+		retval = i2c_smbus_read_block_data(client,
+					ETP_SMBUS_IAP_CHECKSUM_CMD, val);
+		if (retval < 0) {
+			dev_err(&client->dev, "Read checksum fail, %d\n",
+								retval);
+			return -1;
+		}
+		checksum = be16_to_cpup((__be16 *)val);
+	} else {
+		retval = elan_i2c_read_cmd(client,
+					ETP_I2C_IAP_CHECKSUM_CMD, val);
+		if (retval < 0) {
+			dev_err(&client->dev, "Read checksum fail, %d\n",
+								retval);
+			return -1;
+		}
+		checksum = le16_to_cpup((__le16 *)val);
+	}
+	return checksum;
+}
+
+static bool elan_iap_reset(struct elan_tp_data *data)
+{
+	int retval = 0;
+	struct i2c_client *client = data->client;
+
+	if (data->smbus)
+		retval = i2c_smbus_write_byte(client,
+						ETP_SMBUS_IAP_RESET_CMD);
+	else
+		retval = elan_i2c_write_cmd(client, ETP_I2C_IAP_RESET_CMD,
+						ETP_I2C_IAP_RESET);
+	if (retval < 0) {
+		dev_err(&client->dev, "cannot reset IC, %d\n", retval);
+		return false;
+	}
+	return true;
+}
+
+static bool elan_iap_setflashkey(struct elan_tp_data *data)
+{
+	int retval = 0;
+	struct i2c_client *client = data->client;
+	u8 smbus_cmd[4] = {0x00, 0x0B, 0x00, 0x5A};
+
+	if (data->smbus)
+		retval = i2c_smbus_write_block_data(client,
+				ETP_SMBUS_IAP_CMD, 4, smbus_cmd);
+	else
+		retval = elan_i2c_write_cmd(client, ETP_I2C_IAP_CMD,
+					ETP_I2C_IAP_PASSWORD);
+	if (retval < 0) {
+		dev_err(&client->dev, "cannot set flash key, %d\n", retval);
+		return false;
+	}
+
+	return true;
+}
+
+static int elan_check_fw(struct elan_tp_data *data,
+				const struct firmware *fw)
+{
+	struct device *dev = &data->client->dev;
+
+	/* Firmware must match exact PAGE_NUM * PAGE_SIZE bytes */
+	if (fw->size != ETP_FW_SIZE) {
+		dev_err(dev, "invalid firmware size = %zu, expected %d.\n",
+					fw->size, ETP_FW_SIZE);
+		return -EBADF;
+	}
+
+	/* Get IAP Start Address*/
+	memcpy(val, &fw->data[ETP_IAP_START_ADDR * 2], 2);
+	data->iap_start_addr = le16_to_cpup((__le16 *)val);
+	return 0;
+}
+
+
+static int elan_smbus_prepare_fw_update(struct elan_tp_data *data)
+{
+	struct i2c_client *client = data->client;
+	struct device *dev = &data->client->dev;
+	u16 password;
+	u8 cmd[4] = {0x0F, 0x78, 0x00, 0x06};
+
+	/* Get FW in which mode	(IAP_MODE/MAIN_MODE)  */
+	int mode = elan_iap_getmode(data);
+	if (mode == UNKNOWN_MODE)
+		return -1;
+
+	if (mode == MAIN_MODE) {
+
+		/* set flash key*/
+		if (elan_iap_setflashkey(data) == false) {
+			dev_err(dev, "cannot set flash key\n");
+			return -1;
+		}
+
+		/* write iap password */
+		if (i2c_smbus_write_byte(client,
+				ETP_SMBUS_IAP_PASSWORD_WRITE) < 0) {
+			dev_err(dev, "cannot write iap password\n");
+			return -1;
+		}
+
+		if (i2c_smbus_write_block_data(client,
+				ETP_SMBUS_IAP_CMD, 4, cmd) < 0) {
+			dev_err(dev, "cannot write cmd\n");
+			return -1;
+		}
+
+		/* read password to check we enabled successfully. */
+		if (i2c_smbus_read_block_data(client,
+				ETP_SMBUS_IAP_PASSWORD_READ, val) < 0) {
+			dev_err(dev, "cannot get iap password\n");
+			return -1;
+		}
+		password = be16_to_cpup((__be16 *)val);
+
+		if (password != ETP_SMBUS_IAP_PASSWORD) {
+			dev_err(dev, "wrong iap password = 0x%X\n", password);
+			return -1;
+		}
+		/* wait 30ms, from MAIN_MODE change to IAP_MODE*/
+		msleep(30);
+	}
+
+	/* set flash key*/
+	if (elan_iap_setflashkey(data) == false) {
+		dev_err(dev, "cannot set flash key\n");
+		return -1;
+	}
+
+	/* Reset IC */
+	if (elan_iap_reset(data) == false) {
+		dev_err(dev, "iap reset fail.\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int elan_i2c_prepare_fw_update(struct elan_tp_data *data)
+{
+	struct i2c_client *client = data->client;
+	struct device *dev = &data->client->dev;
+	u16 password;
+
+	/* Get FW in which mode	(IAP_MODE/MAIN_MODE)  */
+	int mode = elan_iap_getmode(data);
+	if (mode == UNKNOWN_MODE)
+		return -1;
+
+	if (mode == IAP_MODE) {
+		/* Reset IC */
+		if (elan_iap_reset(data) == false)
+			return -1;
+		msleep(30);
+	}
+
+	/* set flash key*/
+	if (elan_iap_setflashkey(data) == false) {
+		dev_err(dev, "cannot set flash key\n");
+		return -1;
+	}
+
+	/* Wait for F/W IAP initialization */
+	if (mode == MAIN_MODE)
+		msleep(100);
+	else
+		msleep(30);
+
+	/* check is in iap mode or not*/
+	if (elan_iap_getmode(data) == MAIN_MODE) {
+		dev_err(dev, "status wrong.\n");
+		return -1;
+	}
+
+	/* set flash key again */
+	if (elan_iap_setflashkey(data) == false) {
+		dev_err(dev, "cannot set flash key\n");
+		return -1;
+	}
+
+	/* Wait for F/W IAP initialization */
+	if (mode == MAIN_MODE)
+		msleep(100);
+	else
+		msleep(30);
+
+	/* read back to check we actually enabled successfully. */
+	if (elan_i2c_read_cmd(client, ETP_I2C_IAP_CMD, val) < 0) {
+		dev_err(dev, "cannot get iap register\n");
+		return -1;
+	}
+	password = le16_to_cpup((__le16 *)val);
+
+	if (password != ETP_I2C_IAP_PASSWORD) {
+		dev_err(dev, "wrong iap password = 0x%X\n", password);
+		return -1;
+	}
+	return 0;
+}
+
+static bool elan_iap_page_write_ok(struct elan_tp_data *data)
+{
+	u16 constant;
+	int retval = 0;
+	struct i2c_client *client = data->client;
+
+
+	if (data->smbus) {
+		retval = i2c_smbus_read_block_data(client,
+					ETP_SMBUS_IAP_CTRL_CMD, val);
+		if (retval < 0)
+			return false;
+		constant = be16_to_cpup((__be16 *)val);
+	} else {
+		retval = elan_i2c_read_cmd(client,
+					ETP_I2C_IAP_CTRL_CMD, val);
+		if (retval < 0)
+			return false;
+		constant = le16_to_cpup((__le16 *)val);
+	}
+
+	if (constant & ETP_FW_IAP_PAGE_ERR)
+		return false;
+
+	if (constant & ETP_FW_IAP_INTERFACE_ERR)
+		return false;
+	return true;
+}
+
+static int elan_smbus_write_fw_block(struct elan_tp_data *data,
+				const u8 *page, u16 checksum, int idx)
+{
+	struct device *dev = &data->client->dev;
+	int half_page_size = ETP_FW_PAGE_SIZE / 2;
+	int repeat = 3;
+
+	do {
+		/* due to smbus can write 32 bytes one time,
+		so, we must write data 2 times.
+		*/
+		i2c_smbus_write_block_data(data->client,
+					ETP_SMBUS_WRITE_FW_BLOCK,
+					half_page_size,
+					page);
+		i2c_smbus_write_block_data(data->client,
+					ETP_SMBUS_WRITE_FW_BLOCK,
+					half_page_size,
+					(page + half_page_size));
+		/* Wait for F/W to update one page ROM data. */
+		usleep_range(8000, 10000);
+		if (elan_iap_page_write_ok(data))
+			break;
+		dev_info(dev, "IAP retry this page! [%d]\n", idx);
+		repeat--;
+	} while (repeat == 0);
+
+	if (repeat > 0)
+		return 0;
+	return -1;
+
+}
+
+static int elan_i2c_write_fw_block(struct elan_tp_data *data,
+				const u8 *page, u16 checksum, int idx)
+{
+	struct device *dev = &data->client->dev;
+	int ret;
+	int repeat = 3;
+	u8 page_store[ETP_FW_PAGE_SIZE + 4];
+
+	page_store[0] = ETP_I2C_IAP_REG_L;
+	page_store[1] = ETP_I2C_IAP_REG_H;
+	memcpy(&page_store[2], page, ETP_FW_PAGE_SIZE);
+
+	/* recode checksum at last two bytes */
+	page_store[ETP_FW_PAGE_SIZE+2] = (u8)(checksum & 0xFF);
+	page_store[ETP_FW_PAGE_SIZE+3] = (u8)((checksum >> 8)&0xFF);
+
+	do {
+		ret = i2c_master_send(data->client, page_store,
+						ETP_FW_PAGE_SIZE + 4);
+
+		/* Wait for F/W to update one page ROM data. */
+		msleep(20);
+
+		if (ret == (ETP_FW_PAGE_SIZE + 4)) {
+			if (elan_iap_page_write_ok(data))
+				break;
+		}
+		dev_dbg(dev, "IAP retry this page! [%d]\n", idx);
+		repeat--;
+	} while (repeat == 0);
+
+	if (repeat > 0)
+		return 0;
+	return -1;
+}
+
+static int elan_write_fw_block(struct elan_tp_data *data,
+				const u8 *page, u16 checksum, int idx)
+{
+	int ret;
+	if (data->smbus)
+		ret = elan_smbus_write_fw_block(data, page, checksum, idx);
+	else
+		ret = elan_i2c_write_fw_block(data, page, checksum, idx);
+	return ret;
+}
+
+static int elan_prepare_fw_update(struct elan_tp_data *data)
+{
+	int ret = 0;
+	if (data->smbus)
+		ret = elan_smbus_prepare_fw_update(data);
+	else
+		ret = elan_i2c_prepare_fw_update(data);
+	return ret;
+}
+
+static int elan_firmware(struct elan_tp_data *data)
+{
+	struct device *dev = &data->client->dev;
+	const struct firmware *fw;
+	const char *fw_name = ETP_FW_NAME;
+	int i, j, ret;
+	u16 boot_page_count;
+	u16 sw_checksum, fw_checksum;
+	data->updated_fw = true;
+
+	dev_info(dev, "Start firmware update....\n");
+
+	ret = request_firmware(&fw, ETP_FW_NAME, dev);
+	if (ret) {
+		dev_err(dev, "cannot load firmware from %s, %d\n",
+							fw_name, ret);
+		goto done;
+	}
+	/* check fw data match current iap version */
+	ret = elan_check_fw(data, fw);
+	if (ret) {
+		dev_err(dev, "Invalid Elan firmware from %s, %d\n",
+							fw_name, ret);
+		goto done;
+	}
+	/* setup IAP status */
+	ret = elan_prepare_fw_update(data);
+	if (ret)
+		goto done;
+	sw_checksum = 0;
+	fw_checksum = 0;
+	boot_page_count = (data->iap_start_addr * 2) / ETP_FW_PAGE_SIZE;
+	for (i = boot_page_count; i < ETP_FW_PAGE_COUNT; i++) {
+		u16 checksum = 0;
+		const u8 *page = &fw->data[i * ETP_FW_PAGE_SIZE];
+
+		for (j = 0; j < ETP_FW_PAGE_SIZE; j += 2)
+			checksum += ((page[j + 1] << 8) | page[j]);
+
+		ret = elan_write_fw_block(data, page, checksum, i);
+		if (ret) {
+			dev_err(dev, "write page %d fail\n", i);
+			goto done;
+		}
+		sw_checksum += checksum;
+	}
+
+	/* Wait WDT reset and power on reset */
+	msleep(600);
+
+	/* check checksum */
+	fw_checksum = elan_iap_checksum(data);
+	if (sw_checksum != fw_checksum) {
+		dev_err(dev, "checksum diff sw=[%04X], fw=[%04X]\n",
+					sw_checksum, fw_checksum);
+		ret = -1;
+		goto done;
+	}
+	ret = 0;
+done:
+	if (ret != 0) {
+		elan_iap_reset(data);
+		data->updated_fw = false;
+	} else {
+		if (data->smbus) {
+			data->updated_fw = false;
+			elan_initialize(data);
+		}
+	}
+	release_firmware(fw);
+	return ret;
+}
+
+/******************************************************************
+* Elan smbus interface
+*******************************************************************
+*/
+static int elan_smbus_initialize(struct i2c_client *client)
+{
+	u8 check[ETP_SMBUS_HELLOPACKET_LEN] = {0x55, 0x55, 0x55, 0x55, 0x55};
+	u8 values[ETP_SMBUS_HELLOPACKET_LEN] = {0, 0, 0, 0, 0};
+	int ret;
+
+	/* Get hello packet */
+	ret = i2c_smbus_read_block_data(client,
+				ETP_SMBUS_HELLOPACKET_CMD, values);
+	if (ret != ETP_SMBUS_HELLOPACKET_LEN) {
+		dev_err(&client->dev, "hello packet length fail\n");
+		return -1;
+	}
+
+	/* compare hello packet */
+	if (memcmp(values, check, ETP_SMBUS_HELLOPACKET_LEN)) {
+		dev_err(&client->dev, "hello packet fail [%x %x %x %x %x]\n",
+		values[0], values[1], values[2], values[3], values[4]);
+		return -1;
+	}
+
+	/* enable tp */
+	ret = i2c_smbus_write_byte(client, ETP_SMBUS_ENABLE_TP);
+	return ret;
+}
+
+static int elan_smbus_enable_calibrate(struct i2c_client *client)
+{
+	u8 cmd[4] = {0x00, 0x07, 0x00, ETP_ENABLE_ABS|ETP_ENABLE_CALIBRATE};
+
+	return i2c_smbus_write_block_data(client,
+				ETP_SMBUS_IAP_CMD, 4, cmd);
+}
+
+static int elan_smbus_disable_calibrate(struct i2c_client *client)
+{
+	u8 cmd[4] = {0x00, 0x07, 0x00, ETP_ENABLE_ABS|ETP_DISABLE_CALIBRATE};
+
+	return i2c_smbus_write_block_data(client,
+				ETP_SMBUS_IAP_CMD, 4, cmd);
+}
+
+static int elan_smbus_enable_absolute_mode(struct i2c_client *client)
+{
+	u8 cmd[4] = {0x00, 0x07, 0x00, ETP_ENABLE_ABS};
+
+	return i2c_smbus_write_block_data(client, ETP_SMBUS_IAP_CMD, 4, cmd);
+}
+
+/*****************************************************************
+* Elan i2c interface
+******************************************************************
+*/
+static int elan_i2c_read_block(struct i2c_client *client,
+				 u16 reg, u8 *val, u16 len)
+{
+	struct i2c_msg msgs[2];
+	u8 buf[2];
+	int ret;
+
+	buf[0] = reg & 0xff;
+	buf[1] = (reg >> 8) & 0xff;
+
+	msgs[0].addr = client->addr;
+	msgs[0].flags = client->flags & I2C_M_TEN;
+	msgs[0].len = 2;
+	msgs[0].buf = buf;
+
+	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 ? -EIO : 0;
+}
+
+static int elan_i2c_read_cmd(struct i2c_client *client, u16 reg, u8 *val)
+{
+	int retval;
+
+	retval = elan_i2c_read_block(client, reg, val, ETP_INF_LENGTH);
+	if (retval < 0) {
+		dev_err(&client->dev, "reading cmd (0x%04x) fail.\n", reg);
+		return retval;
+	}
+	return 0;
+}
+
+static int elan_i2c_write_cmd(struct i2c_client *client, u16 reg, u16 cmd)
+{
+	struct i2c_msg msg;
+	u8 buf[4];
+	int ret;
+
+	buf[0] = reg & 0xff;
+	buf[1] = (reg >> 8) & 0xff;
+	buf[2] = cmd & 0xff;
+	buf[3] = (cmd >> 8) & 0xff;
+
+	msg.addr = client->addr;
+	msg.flags = client->flags & I2C_M_TEN;
+	msg.len = 4;
+	msg.buf = buf;
+
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	return ret != 1 ? -EIO : 0;
+}
+
+static int elan_i2c_reset(struct i2c_client *client)
+{
+	return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
+						ETP_I2C_RESET);
+}
+
+static int elan_i2c_wake_up(struct i2c_client *client)
+{
+	return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
+						ETP_I2C_WAKE_UP);
+}
+
+static int elan_i2c_sleep(struct i2c_client *client)
+{
+	return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
+						ETP_I2C_SLEEP);
+}
+
+static int elan_i2c_enable_absolute_mode(struct i2c_client *client)
+{
+	return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD,
+						ETP_ENABLE_ABS);
+}
+
+static int elan_i2c_enable_calibrate(struct i2c_client *client)
+{
+	return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD,
+			ETP_ENABLE_ABS|ETP_ENABLE_CALIBRATE);
+}
+
+static int elan_i2c_disable_calibrate(struct i2c_client *client)
+{
+	return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD,
+			ETP_ENABLE_ABS|ETP_DISABLE_CALIBRATE);
+}
+
+static int elan_i2c_get_desc(struct i2c_client *client, u8 *val)
+{
+	return elan_i2c_read_block(client, ETP_I2C_DESC_CMD, val,
+					ETP_I2C_DESC_LENGTH);
+}
+
+static int elan_i2c_get_report_desc(struct i2c_client *client, u8 *val)
+{
+	return elan_i2c_read_block(client, ETP_I2C_REPORT_DESC_CMD,
+				 val, ETP_I2C_REPORT_DESC_LENGTH);
+}
+
+static int elan_i2c_initialize(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	int rc;
+
+	rc = elan_i2c_reset(client);
+	if (rc < 0) {
+		dev_err(dev, "device reset failed.\n");
+		return -1;
+	}
+
+	/* wait for get reset return flag */
+	msleep(100);
+	/* get reset return flag 0000 */
+	rc = i2c_master_recv(client, val, ETP_INF_LENGTH);
+	if (rc < 0) {
+		dev_err(dev, "get device reset return value failed.\n");
+		return -1;
+	}
+
+	rc = elan_i2c_get_desc(client, val);
+	if (rc < 0) {
+		dev_err(dev, "cannot get device descriptor.\n");
+		return -1;
+	}
+
+	rc = elan_i2c_get_report_desc(client, val);
+	if (rc < 0) {
+		dev_err(dev, "fetching report descriptor failed.\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/**************************************************************************
+* Genernal functions
+***************************************************************************
+*/
+
+/*
+ * (value from firmware) * 10 + 790 = dpi
+ * we also have to convert dpi to dots/mm (*10/254 to avoid floating point)
+ */
+static unsigned int elan_convert_res(char val)
+{
+	int res;
+	if (val & 0x80) {
+		val = ~val + 1;
+		res = (790 - val * 10) * 10 / 254;
+	} else
+		res = (val * 10 + 790) * 10 / 254;
+	return res;
+}
+
+static int elan_get_iap_version(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+					ETP_SMBUS_IAP_VERSION_CMD, val);
+		ret = val[2];
+	} else {
+		elan_i2c_read_cmd(data->client,
+				ETP_I2C_IAP_VERSION_CMD, val);
+		ret = val[0];
+	}
+	return ret;
+}
+
+static int elan_get_x_max(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+					ETP_SMBUS_RANGE_CMD, val);
+		ret = (0x0f & val[0]) << 8 | val[1];
+	} else {
+		elan_i2c_read_cmd(data->client,
+					ETP_I2C_MAX_X_AXIS_CMD, val);
+		ret = (0x0f & val[1]) << 8 | val[0];
+	}
+	return ret;
+}
+
+static int elan_get_y_max(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+					ETP_SMBUS_RANGE_CMD, val);
+		ret = (0xf0 & val[0]) << 4 | val[2];
+	} else {
+		elan_i2c_read_cmd(data->client,
+					ETP_I2C_MAX_Y_AXIS_CMD, val);
+		ret = (0x0f & val[1]) << 8 | val[0];
+	}
+	return ret;
+}
+
+static int elan_get_x_tracenum(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+				ETP_SMBUS_XY_TRACENUM_CMD, val);
+		ret = (val[1] - 1);
+	} else {
+		elan_i2c_read_cmd(data->client,
+				ETP_I2C_XY_TRACENUM_CMD, val);
+		ret = (val[0] - 1);
+	}
+	return ret;
+}
+
+static int elan_get_y_tracenum(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+				ETP_SMBUS_XY_TRACENUM_CMD, val);
+		ret = (val[2] - 1);
+	} else {
+		ret = elan_i2c_read_cmd(data->client,
+				ETP_I2C_XY_TRACENUM_CMD, val);
+		ret = (val[1] - 1);
+	}
+	return ret;
+}
+
+static int elan_get_fw_version(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+				ETP_SMBUS_FW_VERSION_CMD, val);
+		ret = val[2];
+	} else {
+		elan_i2c_read_cmd(data->client,
+				ETP_I2C_FW_VERSION_CMD, val);
+		ret = val[0];
+	}
+	return ret;
+}
+
+static int elan_get_sm_version(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus)
+		i2c_smbus_read_block_data(data->client,
+				ETP_SMBUS_SM_VERSION_CMD, val);
+	else
+		elan_i2c_read_block(data->client,
+				ETP_I2C_SM_VERSION_CMD, val, 1);
+	ret = val[0];
+	return ret;
+}
+
+static int elan_get_unique_id(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+				ETP_SMBUS_UNIQUEID_CMD, val);
+		ret = val[1];
+	} else {
+		elan_i2c_read_cmd(data->client,
+				ETP_I2C_UNIQUEID_CMD, val);
+		ret = val[0];
+	}
+	return ret;
+}
+
+static int elan_get_x_resolution(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+				ETP_SMBUS_RESOLUTION_CMD, val);
+		ret = elan_convert_res(val[1] & 0x0F);
+	} else {
+		elan_i2c_read_cmd(data->client,
+				ETP_I2C_RESOLUTION_CMD, val);
+		ret = elan_convert_res(val[0]);
+	}
+	return ret;
+}
+
+static int elan_get_y_resolution(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+				ETP_SMBUS_RESOLUTION_CMD, val);
+		ret = elan_convert_res((val[1] & 0xF0) >> 4);
+	} else {
+		elan_i2c_read_cmd(data->client,
+				ETP_I2C_RESOLUTION_CMD, val);
+		ret = elan_convert_res(val[1]);
+	}
+	return ret;
+}
+
+static int elan_get_fw_checksum(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+				ETP_SMBUS_FW_CHECKSUM_CMD, val);
+		ret = be16_to_cpup((__be16 *)val);
+	} else {
+		elan_i2c_read_cmd(data->client,
+				ETP_I2C_FW_CHECKSUM_CMD, val);
+		ret = le16_to_cpup((__le16 *)val);
+	}
+	return ret;
+}
+
+static int elan_get_max_baseline(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+				ETP_SMBUS_MAX_BASELINE_CMD, val);
+		ret = be16_to_cpup((__be16 *)val);
+	} else {
+		elan_i2c_read_cmd(data->client,
+				ETP_I2C_MAX_BASELINE_CMD, val);
+		ret = le16_to_cpup((__le16 *)val);
+	}
+	return ret;
+}
+
+static int elan_get_min_baseline(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		i2c_smbus_read_block_data(data->client,
+				ETP_SMBUS_MIN_BASELINE_CMD, val);
+		ret = be16_to_cpup((__be16 *)val);
+	} else {
+		elan_i2c_read_cmd(data->client,
+			ETP_I2C_MIN_BASELINE_CMD, val);
+		ret = le16_to_cpup((__le16 *)val);
+	}
+	return ret;
+}
+
+static int elan_enable_calibrate(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus)
+		ret = elan_smbus_enable_calibrate(data->client);
+	else
+		ret = elan_i2c_enable_calibrate(data->client);
+	return ret;
+}
+
+static int elan_disable_calibrate(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus)
+		ret = elan_smbus_disable_calibrate(data->client);
+	else
+		ret = elan_i2c_disable_calibrate(data->client);
+	return ret;
+}
+
+static int elan_initialize(struct elan_tp_data *data)
+{
+	int ret;
+	if (data->smbus) {
+		ret = elan_smbus_initialize(data->client);
+		if (ret < 0) {
+			dev_err(&data->client->dev,
+				"device initialize failed.\n");
+			goto err_initialize;
+		}
+
+		ret = elan_smbus_enable_absolute_mode(data->client);
+		if (ret < 0)
+			dev_err(&data->client->dev,
+				"cannot switch to absolute mode.\n");
+	} else {
+		ret = elan_i2c_initialize(data->client);
+		if (ret < 0) {
+			dev_err(&data->client->dev,
+				"device initialize failed.\n");
+			goto err_initialize;
+		}
+
+		ret = elan_i2c_enable_absolute_mode(data->client);
+		if (ret < 0) {
+			dev_err(&data->client->dev,
+				"cannot switch to absolute mode.\n");
+			goto err_initialize;
+		}
+
+		ret = elan_i2c_wake_up(data->client);
+		if (ret < 0)
+			dev_err(&data->client->dev,
+					"device wake up failed.\n");
+	}
+err_initialize:
+	return ret;
+}
+
+/********************************************************************
+ * below routines export interfaces to sysfs file system.
+ * so user can get firmware/driver/hardware information using cat command.
+ * e.g.: use below command to get firmware version
+ *      cat /sys/bus/i2c/drivers/elan_i2c/1-0015/firmware_version
+ *******************************************************************
+ */
+static ssize_t elan_sysfs_enable_detailinfo(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+	data->enable_detail_info = true;
+	return sprintf(buf, "enable\n");
+}
+
+static ssize_t elan_sysfs_read_fw_checksum(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	unsigned int checksum = 0;
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+	if (data->enable_detail_info == true) {
+		checksum = elan_get_fw_checksum(data);
+		data->enable_detail_info = false;
+	}
+	return sprintf(buf, "0x%04x\n", checksum);
+}
+
+static ssize_t elan_sysfs_read_unique_id(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+	data->unique_id = elan_get_unique_id(data);
+	return sprintf(buf, "0x%04x\n", data->unique_id);
+}
+
+static ssize_t elan_sysfs_read_driver_ver(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	return sprintf(buf, "%s\n", ELAN_DRIVER_VERSION);
+}
+
+static ssize_t elan_sysfs_read_fw_ver(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+	data->fw_version = elan_get_fw_version(data);
+	return sprintf(buf, "0x%04x\n", data->fw_version);
+}
+
+static ssize_t elan_sysfs_read_sm_ver(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+	data->sm_version = elan_get_sm_version(data);
+	return sprintf(buf, "0x%04x\n", data->sm_version);
+}
+
+static ssize_t elan_sysfs_read_iap_ver(struct device *dev,
+				     struct device_attribute *attr,
+				     char *buf)
+{
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+	data->iap_version = elan_get_iap_version(data);
+	return sprintf(buf, "0x%04x\n", data->iap_version);
+}
+
+
+static ssize_t elan_sysfs_update_fw(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+	int ret;
+	ret = elan_firmware(data);
+	if (ret)
+		dev_err(dev, "firmware update failed.\n");
+	else
+		dev_info(dev, "firmware update succeeded.\n");
+	return ret ? ret : count;
+}
+
+static ssize_t elan_sysfs_calibrate(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+	/* start calibarate cmd */
+	u8 smbus_cmd[4] = {0x00, 0x08, 0x00, 0x01};
+	int tries = 20;
+	int ret = 0;
+	val[0] = 0;
+
+	disable_irq(data->irq);
+	elan_enable_calibrate(data);
+	if (data->smbus)
+		i2c_smbus_write_block_data(data->client,
+				ETP_SMBUS_IAP_CMD, 4, smbus_cmd);
+	else
+		elan_i2c_write_cmd(data->client,
+					ETP_I2C_CALIBRATE_CMD, 1);
+
+	do {
+		/* wait 250ms and check finish or not */
+		msleep(250);
+
+		if (data->smbus)
+			i2c_smbus_read_block_data(data->client,
+					ETP_SMBUS_CALIBRATE_QUERY, val);
+		else
+			elan_i2c_read_block(data->client,
+					ETP_I2C_CALIBRATE_CMD, val, 1);
+
+		/* calibrate finish */
+		if (val[0] == 0)
+			break;
+	} while (--tries);
+
+	elan_disable_calibrate(data);
+	enable_irq(data->irq);
+
+	if (tries == 0) {
+		dev_err(dev, "Failed to calibrate. Timeout.\n");
+		ret = -ETIMEDOUT;
+	}
+	return sprintf(buf, "calibration finish\n");
+}
+
+
+static ssize_t elan_sysfs_read_baseline(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+	int max_baseline, min_baseline;
+
+	disable_irq(data->irq);
+	elan_enable_calibrate(data);
+	msleep(250);
+	max_baseline = elan_get_max_baseline(data);
+	min_baseline = elan_get_min_baseline(data);
+	elan_disable_calibrate(data);
+	enable_irq(data->irq);
+	return sprintf(buf, "max:%d min:%d\n", max_baseline, min_baseline);
+}
+
+static ssize_t elan_sysfs_reinitialize(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+	int ret;
+
+	disable_irq(data->irq);
+	ret = elan_initialize(data);
+	enable_irq(data->irq);
+
+	if (ret < 0)
+		return sprintf(buf, "reinitialize fail\n");
+
+	return sprintf(buf, "reinitialize success\n");
+}
+
+static DEVICE_ATTR(unique_id, S_IRUGO, elan_sysfs_read_unique_id, NULL);
+static DEVICE_ATTR(firmware_version, S_IRUGO, elan_sysfs_read_fw_ver, NULL);
+static DEVICE_ATTR(sample_version, S_IRUGO, elan_sysfs_read_sm_ver, NULL);
+static DEVICE_ATTR(driver_version, S_IRUGO, elan_sysfs_read_driver_ver, NULL);
+static DEVICE_ATTR(iap_version, S_IRUGO, elan_sysfs_read_iap_ver, NULL);
+static DEVICE_ATTR(fw_checksum, S_IRUGO, elan_sysfs_read_fw_checksum, NULL);
+static DEVICE_ATTR(open_info, S_IRUGO, elan_sysfs_enable_detailinfo, NULL);
+static DEVICE_ATTR(baseline, S_IRUGO, elan_sysfs_read_baseline, NULL);
+static DEVICE_ATTR(reinitialize, S_IRUGO, elan_sysfs_reinitialize, NULL);
+static DEVICE_ATTR(calibrate, S_IRUGO, elan_sysfs_calibrate, NULL);
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, elan_sysfs_update_fw);
+
+static struct attribute *elan_sysfs_entries[] = {
+	&dev_attr_unique_id.attr,
+	&dev_attr_firmware_version.attr,
+	&dev_attr_sample_version.attr,
+	&dev_attr_driver_version.attr,
+	&dev_attr_iap_version.attr,
+	&dev_attr_fw_checksum.attr,
+	&dev_attr_open_info.attr,
+	&dev_attr_baseline.attr,
+	&dev_attr_reinitialize.attr,
+	&dev_attr_calibrate.attr,
+	&dev_attr_update_fw.attr,
+	NULL,
+};
+
+static const struct attribute_group elan_sysfs_group = {
+	.attrs = elan_sysfs_entries,
+};
+
+/*****************************************************************
+* Elan isr functions
+******************************************************************
+*/
+
+static int elan_check_packet(struct elan_tp_data *data, u8 *packet)
+{
+	u8 rid;
+
+	if (data->smbus)
+		rid = packet[0];
+	else
+		rid = packet[ETP_I2C_REPORT_ID_OFFSET];
+
+	/* check report id */
+	if (rid != ETP_REPORT_ID) {
+		dev_err(&data->client->dev, "report id [%x] fail.\n", rid);
+		return -1;
+	}
+	return 0;
+}
+
+static void elan_report_absolute(struct elan_tp_data *data, u8 *packet)
+{
+	struct input_dev *input = data->input;
+	u8 *finger_data;
+	bool finger_on;
+	int pos_x, pos_y;
+	int pressure, mk_x, mk_y;
+	int i, area_x, area_y, major, minor, new_pressure;
+	int finger_count = 0;
+	int btn_click;
+	u8  tp_info;
+
+	if (data->smbus) {
+		finger_data = &packet[ETP_SMBUS_FINGER_DATA_OFFSET];
+		tp_info = packet[1];
+	} else {
+		finger_data = &packet[ETP_I2C_FINGER_DATA_OFFSET];
+		tp_info = packet[3];
+	}
+
+	btn_click = (tp_info & 0x01);
+	for (i = 0; i < ETP_MAX_FINGERS; i++) {
+		finger_on = (tp_info >> (3 + i)) & 0x01;
+
+		/* analyze touched finger raw data*/
+		if (finger_on) {
+			pos_x = ((finger_data[0] & 0xf0) << 4) |
+				finger_data[1];
+			pos_y = ((finger_data[0] & 0x0f) << 8) |
+				finger_data[2];
+			pos_y =  data->max_y - pos_y;
+			mk_x = (finger_data[3] & 0x0f);
+			mk_y = (finger_data[3] >> 4);
+			pressure = finger_data[4];
+
+			/*
+			to avoid fat finger be as palm, so reduce the
+			width x and y per trace
+			*/
+			area_x = mk_x * (data->width_x - ETP_FWIDTH_REDUCE);
+			area_y = mk_y * (data->width_y - ETP_FWIDTH_REDUCE);
+
+			major = max(area_x, area_y);
+			minor = min(area_x, area_y);
+
+			new_pressure = pressure + ETP_PRESSURE_OFFSET;
+			if (new_pressure > ETP_MAX_PRESSURE)
+				new_pressure = ETP_MAX_PRESSURE;
+
+			input_mt_slot(input, i);
+			input_mt_report_slot_state(input, MT_TOOL_FINGER,
+									true);
+			input_report_abs(input, ABS_MT_POSITION_X, pos_x);
+			input_report_abs(input, ABS_MT_POSITION_Y, pos_y);
+			input_report_abs(input, ABS_MT_PRESSURE, new_pressure);
+			input_report_abs(input, ABS_TOOL_WIDTH, mk_x);
+			input_report_abs(input, ABS_MT_TOUCH_MAJOR, major);
+			input_report_abs(input, ABS_MT_TOUCH_MINOR, minor);
+			finger_data += ETP_FINGER_DATA_LEN;
+			finger_count++;
+		} else {
+			input_mt_slot(input, i);
+			input_mt_report_slot_state(input,
+						MT_TOOL_FINGER, false);
+		}
+	}
+
+	input_report_key(input, BTN_LEFT, (btn_click == 1));
+	input_mt_report_pointer_emulation(input, true);
+	input_sync(input);
+}
+
+static irqreturn_t elan_isr(int irq, void *dev_id)
+{
+	struct elan_tp_data *data = dev_id;
+	u8 raw[ETP_MAX_REPORT_LEN];
+	int retval;
+	int report_len;
+
+	retval = mutex_lock_interruptible(&data->dbfs_mutex);
+	if (retval)
+		return IRQ_HANDLED;
+
+	/*
+	Only in I2C protocol, when IAP all page wrote finish, driver will
+	get one INT signal from high to low, and driver must get 0000
+	to confirm IAP is finished.
+	*/
+	if (data->updated_fw) {
+		retval = i2c_master_recv(data->client, raw,
+						ETP_INF_LENGTH);
+		if (retval == 2 && !le16_to_cpup((__le16 *)raw)) {
+			dev_info(&data->client->dev,
+				"reinitializing after F/W update...");
+			elan_initialize(data);
+		}
+		data->updated_fw = false;
+		goto elan_isr_end;
+	}
+
+	if (data->smbus) {
+		report_len = ETP_SMBUS_REPORT_LEN;
+		retval = i2c_smbus_read_block_data(data->client,
+					ETP_SMBUS_PACKET_QUERY, raw);
+	} else {
+		report_len = ETP_I2C_REPORT_LEN;
+		retval = i2c_master_recv(data->client, raw, report_len);
+	}
+
+	if (retval != report_len) {
+		dev_err(&data->client->dev, "wrong packet len(%d)", retval);
+		goto elan_isr_end;
+	}
+
+	if (elan_check_packet(data, raw) < 0) {
+		dev_err(&data->client->dev, "wrong packet format.");
+		goto elan_isr_end;
+	}
+	elan_report_absolute(data, raw);
+	data->dbfs_buffer.bfetch = false;
+	memcpy(data->dbfs_buffer.buffer, raw, report_len);
+
+elan_isr_end:
+	mutex_unlock(&data->dbfs_mutex);
+	return IRQ_HANDLED;
+}
+
+
+static int elan_input_dev_create(struct elan_tp_data *data)
+{
+	struct i2c_client *client = data->client;
+	struct input_dev *input;
+	unsigned int x_res, y_res;
+	int ret;
+
+	data->input = input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+	input->name = "Elan Touchpad";
+	input->id.bustype = BUS_I2C;
+	input->dev.parent = &data->client->dev;
+
+	__set_bit(INPUT_PROP_POINTER, input->propbit);
+	__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+	__set_bit(EV_KEY, input->evbit);
+	__set_bit(EV_ABS, input->evbit);
+
+	__set_bit(BTN_LEFT, input->keybit);
+	__set_bit(BTN_TOUCH, input->keybit);
+	__set_bit(BTN_TOOL_FINGER, input->keybit);
+	__set_bit(BTN_TOOL_DOUBLETAP, input->keybit);
+	__set_bit(BTN_TOOL_TRIPLETAP, input->keybit);
+	__set_bit(BTN_TOOL_QUADTAP, input->keybit);
+	__set_bit(BTN_TOOL_QUINTTAP, input->keybit);
+
+	__set_bit(ABS_MT_TOUCH_MAJOR, input->absbit);
+	__set_bit(ABS_MT_TOUCH_MINOR, input->absbit);
+	__set_bit(ABS_MT_POSITION_X, input->absbit);
+	__set_bit(ABS_MT_POSITION_Y, input->absbit);
+
+	data->unique_id = elan_get_unique_id(data);
+	data->fw_version = elan_get_fw_version(data);
+	data->sm_version = elan_get_sm_version(data);
+	data->iap_version = elan_get_iap_version(data);
+	data->max_x = elan_get_x_max(data);
+	data->max_y = elan_get_y_max(data);
+	data->width_x = data->max_x / elan_get_x_tracenum(data);
+	data->width_y = data->max_y / elan_get_y_tracenum(data);
+	x_res = elan_get_x_resolution(data);
+	y_res = elan_get_y_resolution(data);
+
+	dev_info(&client->dev,
+		"Elan Touchpad Information:\n"
+		"    Module unique ID:  0x%04x\n"
+		"    Firmware Version:  0x%04x\n"
+		"    Sample Version:  0x%04x\n"
+		"    IAP Version:  0x%04x\n"
+		"    Max ABS X,Y:   %d,%d\n"
+		"    Width X,Y:   %d,%d\n"
+		"    Resolution X,Y:   %d,%d (dots/mm)\n",
+		data->unique_id,
+		data->fw_version,
+		data->sm_version,
+		data->iap_version,
+		data->max_x, data->max_y,
+		data->width_x, data->width_y,
+		(char)x_res, (char)y_res);
+
+	input_set_abs_params(input, ABS_X, 0, data->max_x, 0, 0);
+	input_set_abs_params(input, ABS_Y, 0, data->max_y, 0, 0);
+	input_abs_set_res(input, ABS_X, x_res);
+	input_abs_set_res(input, ABS_Y, y_res);
+	input_set_abs_params(input, ABS_PRESSURE, 0, ETP_MAX_PRESSURE, 0, 0);
+	input_set_abs_params(input, ABS_TOOL_WIDTH, 0, ETP_FINGER_WIDTH, 0, 0);
+
+	/* handle pointer emulation and unused slots in core */
+	ret = input_mt_init_slots(input, ETP_MAX_FINGERS,
+				  INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED);
+	if (ret) {
+		dev_info(&client->dev, "allocate MT slots failed, %d\n", ret);
+		goto err_free_device;
+	}
+	input_set_abs_params(input, ABS_MT_POSITION_X, 0, data->max_x, 0, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, data->max_y, 0, 0);
+	input_abs_set_res(input, ABS_MT_POSITION_X, x_res);
+	input_abs_set_res(input, ABS_MT_POSITION_Y, y_res);
+	input_set_abs_params(input, ABS_MT_PRESSURE, 0,
+				ETP_MAX_PRESSURE, 0, 0);
+	input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0,
+		ETP_FINGER_WIDTH * max(data->width_x, data->width_y), 0, 0);
+	input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0,
+		ETP_FINGER_WIDTH * min(data->width_x, data->width_y), 0, 0);
+
+	/* Register the device in input subsystem */
+	ret = input_register_device(input);
+	if (ret) {
+		dev_err(&client->dev, "input device register failed, %d\n",
+			ret);
+		goto err_free_device;
+	}
+
+	return 0;
+
+err_free_device:
+	input_free_device(input);
+	return ret;
+}
+
+static u8 elan_check_adapter_functionality(struct i2c_client *client)
+{
+	u8 ret = ELAN_ADAPTER_FUNC_NONE;
+
+	if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+		ret |= ELAN_ADAPTER_FUNC_I2C;
+	if (i2c_check_functionality(client->adapter,
+					I2C_FUNC_SMBUS_BYTE_DATA |
+					I2C_FUNC_SMBUS_BLOCK_DATA |
+					I2C_FUNC_SMBUS_I2C_BLOCK))
+		ret |= ELAN_ADAPTER_FUNC_SMBUS;
+	return ret;
+}
+
+static int elan_probe(struct i2c_client *client,
+				    const struct i2c_device_id *dev_id)
+{
+	struct elan_tp_data *data;
+	int ret;
+	u8 adapter_func;
+	union i2c_smbus_data dummy;
+	struct device *dev = &client->dev;
+
+	adapter_func = elan_check_adapter_functionality(client);
+	if (adapter_func == ELAN_ADAPTER_FUNC_NONE) {
+		dev_err(dev, "not a supported I2C/SMBus adapter\n");
+		return -EIO;
+	}
+
+	/* Make sure there is something at this address */
+	if (dev->of_node && i2c_smbus_xfer(client->adapter, client->addr, 0,
+			 I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0)
+		return -ENODEV;
+
+	data = kzalloc(sizeof(struct elan_tp_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	/* check protocol type */
+	if (adapter_func == ELAN_ADAPTER_FUNC_SMBUS)
+		data->smbus = true;
+	else
+		data->smbus = false;
+	data->client = client;
+	data->updated_fw = false;
+	data->enable_detail_info = false;
+	data->irq = client->irq;
+
+	ret = elan_dbfs_init(data);
+	if (ret < 0) {
+		dev_err(&client->dev, "error create elan debugfs.\n");
+		goto err_dbfs_init;
+	}
+	ret = request_threaded_irq(client->irq, NULL, elan_isr,
+				  IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+				  client->name, data);
+	if (ret < 0) {
+		dev_err(&client->dev, "cannot register irq=%d\n",
+							 client->irq);
+		goto err_irq;
+	}
+
+	/* initial elan touch pad */
+	ret = elan_initialize(data);
+	if (ret < 0)
+		goto err_init;
+
+	/* create input device */
+	ret = elan_input_dev_create(data);
+	if (ret < 0)
+		goto err_input_dev;
+
+	device_init_wakeup(&client->dev, 1);
+	ret = sysfs_create_group(&client->dev.kobj, &elan_sysfs_group);
+	if (ret < 0) {
+		dev_err(&client->dev, "cannot register dev attribute %d", ret);
+		goto err_create_group;
+	}
+	i2c_set_clientdata(client, data);
+	return 0;
+
+err_create_group:
+	input_free_device(data->input);
+err_input_dev:
+err_init:
+	free_irq(data->irq, data);
+err_irq:
+	debugfs_remove_recursive(data->dbfs_root);
+	mutex_destroy(&data->dbfs_mutex);
+err_dbfs_init:
+	kfree(data);
+	dev_err(&client->dev, "Elan Trackpad probe fail!\n");
+	return ret;
+}
+
+static int elan_remove(struct i2c_client *client)
+{
+	struct elan_tp_data *data = i2c_get_clientdata(client);
+
+	free_irq(data->irq, data);
+	debugfs_remove_recursive(data->dbfs_root);
+	mutex_destroy(&data->dbfs_mutex);
+
+	input_free_device(data->input);
+	input_unregister_device(data->input);
+	kfree(data);
+	sysfs_remove_group(&client->dev.kobj, &elan_sysfs_group);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int elan_suspend(struct device *dev)
+{
+	int ret;
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+
+	disable_irq(data->irq);
+	if (data->smbus)
+		ret = i2c_smbus_write_byte(data->client,
+					ETP_SMBUS_DISABLE_TP);
+	else
+		ret = elan_i2c_sleep(data->client);
+
+	if (ret < 0) {
+		dev_err(dev, "suspend mode failed, %d\n", ret);
+	} else {
+		if (device_may_wakeup(dev))
+			data->irq_wake = (enable_irq_wake(data->irq) == 0);
+	}
+	return 0;
+}
+
+static int elan_resume(struct device *dev)
+{
+	int ret = 0;
+	struct elan_tp_data *data = dev_get_drvdata(dev);
+
+	if (device_may_wakeup(dev) && data->irq_wake)
+		disable_irq_wake(data->irq);
+
+	ret = elan_initialize(data);
+	if (ret < 0)
+		dev_err(dev, "resume active power failed, %d\n", ret);
+
+	enable_irq(data->irq);
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(elan_pm_ops, elan_suspend, elan_resume);
+
+static const struct i2c_device_id elan_id[] = {
+	{ DRIVER_NAME, 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, elan_id);
+
+static struct i2c_driver elan_driver = {
+	.driver = {
+		.name	= DRIVER_NAME,
+		.owner	= THIS_MODULE,
+		.pm	= &elan_pm_ops,
+	},
+	.probe		= elan_probe,
+	.remove		= elan_remove,
+	.id_table	= elan_id,
+};
+
+
+static int __init elan_init(void)
+{
+	int ret;
+	ret = i2c_add_driver(&elan_driver);
+	if (ret) {
+		pr_err("elan driver register FAILED.\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static void __exit elan_exit(void)
+{
+	i2c_del_driver(&elan_driver);
+}
+
+module_init(elan_init);
+module_exit(elan_exit);
+
+MODULE_AUTHOR("Duson Lin <dusonlin@....com.tw>");
+MODULE_DESCRIPTION("Elan I2C/SMBus Touchpad driver");
+MODULE_LICENSE("GPL");
-- 
1.7.10.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ