diff mbox series

Input: ilitek_ts_i2c - support firmware update

Message ID 20210511140506.378070-1-joe_hung@ilitek.com
State New
Headers show
Series Input: ilitek_ts_i2c - support firmware update | expand

Commit Message

Joe Hung May 11, 2021, 2:05 p.m. UTC
Firmware upgrade via update_fw sysfs node for Lego series.
Lego series includes ILITEK 213X/23XX/25XX.

Tested/passed with evaluation board with ILI2520 IC.

Signed-off-by: Joe Hung <joe_hung@ilitek.com>
---
 drivers/input/touchscreen/ilitek_ts_i2c.c | 721 +++++++++++++++++++++-
 1 file changed, 719 insertions(+), 2 deletions(-)

Comments

Dmitry Torokhov July 23, 2021, 7:50 p.m. UTC | #1
Hi Joe,

On Tue, May 11, 2021 at 10:05:06PM +0800, Joe Hung wrote:
> +static int ilitek_parse_hex(struct ilitek_ts_data *ts, u32 *fw_size, u8 *fw_buf)

> +{

> +	int error;

> +	char *fw_file;

> +	const struct firmware *fw;

> +	struct device *dev = &ts->client->dev;

> +	u32 i, len, addr, type, exaddr = 0;

> +	u8 info[4], data[16];

> +

> +	fw_file = kasprintf(GFP_KERNEL, "ilitek_%04x.hex", ts->mcu_ver);

> +	if (!fw_file)

> +		return -ENOMEM;

> +

> +	error = request_firmware(&fw, fw_file, dev);

> +	kfree(fw_file);

> +	if (error) {

> +		dev_err(dev, "request firmware:%s failed, ret:%d\n",

> +			fw_file, error);

> +		return error;

> +	}

> +

> +	for (i = 0; i < fw->size; i++) {

> +		if (fw->data[i] == ':' ||

> +		    fw->data[i] == 0x0D ||

> +		    fw->data[i] == 0x0A)

> +			continue;

> +

> +		error = hex2bin(info, fw->data + i, sizeof(info));

> +		if (error)

> +			goto release_fw;

> +

> +		len = info[0];

> +		addr = get_unaligned_be16(info + 1);

> +		type = info[3];

> +

> +		error = hex2bin(data, fw->data + i + 8, len);

> +		if (error)

> +			goto release_fw;

> +

> +		switch (type) {

> +		case 0x01:

> +			goto release_fw;

> +		case 0x02:

> +			exaddr = get_unaligned_be16(data);

> +			exaddr <<= 4;

> +			break;

> +		case 0x04:

> +			exaddr = get_unaligned_be16(data);

> +			exaddr <<= 16;

> +			break;

> +		case 0xAC:

> +		case 0xAD:

> +			break;

> +		case 0x00:

> +			addr += exaddr;

> +			memcpy(fw_buf + addr, data, len);

> +			*fw_size = addr + len;

> +			break;

> +		default:

> +			dev_err(dev, "unexpected type:%x in hex\n", type);

> +			goto err_invalid;

> +		}


This looks like ihex. Is it? If so we have request_ihex_firmware() and
ihex2fw tool to convertihex firmware to compact binary format. Please
use it.

Thanks.

-- 
Dmitry
diff mbox series

Patch

diff --git a/drivers/input/touchscreen/ilitek_ts_i2c.c b/drivers/input/touchscreen/ilitek_ts_i2c.c
index c5d259c76adc..b5ada9b44ab1 100644
--- a/drivers/input/touchscreen/ilitek_ts_i2c.c
+++ b/drivers/input/touchscreen/ilitek_ts_i2c.c
@@ -21,6 +21,7 @@ 
 #include <linux/acpi.h>
 #include <linux/input/touchscreen.h>
 #include <asm/unaligned.h>
+#include <linux/firmware.h>
 
 
 #define ILITEK_TS_NAME					"ilitek_ts"
@@ -37,14 +38,48 @@ 
 #define ILITEK_TP_CMD_GET_MCU_VER			0x61
 #define ILITEK_TP_CMD_GET_IC_MODE			0xC0
 
+#define ILITEK_TP_CMD_SET_MOD_CTRL			0xF0
+#define ILITEK_TP_CMD_GET_SYS_BUSY			0x80
+#define ILITEK_TP_CMD_SET_W_FLASH			0xCC
+#define ILITEK_TP_CMD_SET_AP_MODE			0xC1
+#define ILITEK_TP_CMD_SET_BL_MODE			0xC2
+#define ILITEK_TP_CMD_GET_BLK_CRC			0xCD
+#define ILITEK_TP_CMD_SET_W_DATA			0xC3
+#define ILITEK_TP_CMD_SET_DATA_LEN			0xC9
+
 #define REPORT_COUNT_ADDRESS				61
 #define ILITEK_SUPPORT_MAX_POINT			40
 
+#define ILITEK_CRC_POLY					0x8408
+#define ILITEK_HEX_UPGRADE_SIZE				(256 * 1024)
+#define ILITEK_UPGRADE_LEN				2048
+#define MOD_BL						0x55
+#define MOD_AP						0x5A
+
+#define ENTER_NORMAL_MODE				0
+#define ENTER_SUSPEND_MODE				3
+
 struct ilitek_protocol_info {
 	u16 ver;
 	u8 ver_major;
 };
 
+struct ilitek_block_info {
+	u32 start;
+	u32 end;
+	u16 ic_crc;
+	u16 fw_crc;
+	bool chk_crc;
+};
+
+struct ilitek_upgrade_info {
+	u16 fw_mcu_ver;
+	u32 map_ver;
+	u32 blk_num;
+	u32 fw_size;
+	struct ilitek_block_info *blk;
+};
+
 struct ilitek_ts_data {
 	struct i2c_client		*client;
 	struct gpio_desc		*reset_gpio;
@@ -65,6 +100,9 @@  struct ilitek_ts_data {
 	s32				screen_min_x;
 	s32				screen_min_y;
 	s32				max_tp;
+
+	/* FW Upgrade */
+	struct ilitek_upgrade_info	upg;
 };
 
 struct ilitek_protocol_map {
@@ -84,6 +122,16 @@  enum ilitek_cmds {
 	SET_IC_SLEEP,
 	SET_IC_WAKE,
 
+	SET_MOD_CTRL,
+	GET_SYS_BUSY,
+	SET_FLASH_AP,
+	SET_BL_MODE,
+	SET_AP_MODE,
+	GET_BLK_CRC,
+	SET_DATA_LEN,
+	SET_FLASH_BL,
+	SET_W_DATA,
+
 	/* ALWAYS keep at the end */
 	MAX_CMD_CNT
 };
@@ -227,6 +275,133 @@  static int api_protocol_set_cmd(struct ilitek_ts_data *ts,
 	return 0;
 }
 
+static int ilitek_check_busy(struct ilitek_ts_data *ts, u32 timeout)
+{
+	int error;
+	u8 buf[2];
+	u32 t = 0;
+
+	do {
+		error = api_protocol_set_cmd(ts, GET_SYS_BUSY, NULL, buf);
+		if (error)
+			return error;
+
+		if ((buf[0] & 0x51) == 0x50)
+			return 0;
+
+		msleep(20);
+		t += 20;
+	} while (timeout > t);
+
+	return -EBUSY;
+}
+
+static int ilitek_set_flash_BL1_8(struct ilitek_ts_data *ts, u32 start, u32 end)
+{
+	u8 inbuf[64];
+
+	inbuf[3] = start & 0xFF;
+	inbuf[4] = (start >> 8) & 0xFF;
+	inbuf[5] = (start >> 16) & 0xFF;
+	inbuf[6] = end & 0xFF;
+	inbuf[7] = (end >> 8) & 0xFF;
+	inbuf[8] = (end >> 16) & 0xFF;
+	return api_protocol_set_cmd(ts, SET_FLASH_BL, inbuf, NULL);
+}
+
+static int ilitek_set_data_len(struct ilitek_ts_data *ts, u32 data_len)
+{
+	u8 inbuf[3];
+
+	inbuf[1] = data_len & 0xFF;
+	inbuf[2] = (data_len >> 8) & 0xFF;
+	return api_protocol_set_cmd(ts, SET_DATA_LEN, inbuf, NULL);
+}
+
+static int ilitek_get_blk_crc(struct ilitek_ts_data *ts, u32 start, u32 end,
+			      u8 type, u16 *crc)
+{
+	u8 inbuf[8], outbuf[2];
+	int error;
+
+	inbuf[1] = type;
+	inbuf[2] = start & 0xFF;
+	inbuf[3] = (start >> 8) & 0xFF;
+	inbuf[4] = (start >> 16) & 0xFF;
+	inbuf[5] = end & 0xFF;
+	inbuf[6] = (end >> 8) & 0xFF;
+	inbuf[7] = (end >> 16) & 0xFF;
+
+	error = api_protocol_set_cmd(ts, GET_BLK_CRC, inbuf, outbuf);
+	if (error < 0)
+		return error;
+
+	*crc = get_unaligned_le16(outbuf);
+	return 0;
+}
+
+static int ilitek_switch_BLmode(struct ilitek_ts_data *ts, bool to_BLmode)
+{
+	int error, i;
+	struct device *dev = &ts->client->dev;
+	u8 outbuf[64];
+
+	error = api_protocol_set_cmd(ts, GET_IC_MODE, NULL, outbuf);
+	if (error < 0)
+		return error;
+
+	dev_dbg(dev, "change mode:%x to %x\n", ts->ic_mode,
+		(to_BLmode) ? MOD_BL : MOD_AP);
+
+	if ((ts->ic_mode == MOD_AP && !to_BLmode) ||
+	    (ts->ic_mode == MOD_BL && to_BLmode))
+		return 0;
+
+	for (i = 0; i < 5; i++) {
+		error = api_protocol_set_cmd(ts, SET_FLASH_AP, NULL, NULL);
+		if (error < 0)
+			return error;
+		msleep(20);
+
+		error = api_protocol_set_cmd(ts, (to_BLmode) ? SET_BL_MODE :
+					     SET_AP_MODE, NULL, NULL);
+		if (error < 0)
+			return error;
+
+		msleep(500 + i * 100);
+
+		error = api_protocol_set_cmd(ts, GET_IC_MODE, NULL, outbuf);
+		if (error < 0)
+			return error;
+
+		if ((ts->ic_mode == MOD_AP && !to_BLmode) ||
+		    (ts->ic_mode == MOD_BL && to_BLmode))
+			return 0;
+	}
+
+	dev_err(dev, "switch mode failed, current mode:%X\n", ts->ic_mode);
+
+	return -EFAULT;
+}
+
+static int ilitek_set_testmode(struct ilitek_ts_data *ts, bool testmode)
+{
+	u8 inbuf[3];
+	int error;
+
+
+	if (testmode)
+		inbuf[1] = ENTER_SUSPEND_MODE;
+	else
+		inbuf[1] = ENTER_NORMAL_MODE;
+
+	error = api_protocol_set_cmd(ts, SET_MOD_CTRL, inbuf, NULL);
+	if (error < 0)
+		return error;
+
+	return 0;
+}
+
 static int api_protocol_get_ptl_ver(struct ilitek_ts_data *ts,
 				    u16 cmd, u8 *inbuf, u8 *outbuf)
 {
@@ -351,6 +526,101 @@  static int api_protocol_set_ic_wake(struct ilitek_ts_data *ts,
 	return ilitek_i2c_write_and_read(ts, buf, 1, 0, NULL, 0);
 }
 
+static int api_protocol_set_mode_ctrl(struct ilitek_ts_data *ts, u16 cmd,
+				      u8 *inbuf, u8 *outbuf)
+{
+	inbuf[0] = cmd;
+	inbuf[2] = 0;
+
+	return ilitek_i2c_write_and_read(ts, inbuf, 3, 100, NULL, 0);
+}
+
+static int api_protocol_get_sys_busy(struct ilitek_ts_data *ts,
+				     u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+	u8 buf[64];
+
+	buf[0] = cmd;
+	return ilitek_i2c_write_and_read(ts, buf, 1, 1, outbuf, 1);
+}
+
+static int api_protocol_set_write_flash_ap(struct ilitek_ts_data *ts,
+					   u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+	u8 buf[64];
+
+	buf[0] = cmd;
+	buf[1] = 0x5A;
+	buf[2] = 0xA5;
+	return ilitek_i2c_write_and_read(ts, buf, 3, 0, NULL, 0);
+}
+
+static int api_protocol_set_write_flash_bl(struct ilitek_ts_data *ts,
+					   u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+	inbuf[0] = cmd;
+	inbuf[1] = 0x5A;
+	inbuf[2] = 0xA5;
+	return ilitek_i2c_write_and_read(ts, inbuf, 9, 0, NULL, 0);
+}
+
+static int api_protocol_set_bl_mode(struct ilitek_ts_data *ts,
+				    u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+	u8 buf[64];
+
+	buf[0] = cmd;
+	return ilitek_i2c_write_and_read(ts, buf, 1, 0, NULL, 0);
+}
+
+static int api_protocol_set_ap_mode(struct ilitek_ts_data *ts,
+				    u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+	u8 buf[64];
+
+	buf[0] = cmd;
+	return ilitek_i2c_write_and_read(ts, buf, 1, 0, NULL, 0);
+}
+
+static int api_protocol_get_blk_crc(struct ilitek_ts_data *ts,
+				    u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+	int ret = 0;
+
+	inbuf[0] = cmd;
+
+	/* Ask FW to calculate CRC first */
+	if (inbuf[1] == 0) {
+		ret = ilitek_i2c_write_and_read(ts, inbuf, 8, 0, NULL, 0);
+		if (ret < 0)
+			return ret;
+		ret = ilitek_check_busy(ts, 2500);
+		if (ret < 0)
+			return ret;
+	}
+
+	inbuf[1] = 1;
+	return ilitek_i2c_write_and_read(ts, inbuf, 2, 1, outbuf, 2);
+}
+
+static int api_protocol_set_data_length(struct ilitek_ts_data *ts,
+					u16 cmd, u8 *inbuf, u8 *outbuf)
+{
+
+	inbuf[0] = cmd;
+	return ilitek_i2c_write_and_read(ts, inbuf, 3, 0, NULL, 0);
+}
+
+static int api_protocol_set_write_data(struct ilitek_ts_data *ts, u16 cmd,
+				       u8 *inbuf, u8 *outbuf)
+{
+
+	inbuf[0] = cmd;
+
+	return ilitek_i2c_write_and_read(ts, inbuf, ILITEK_UPGRADE_LEN + 1, 0,
+					 NULL, 0);
+}
+
 static const struct ilitek_protocol_map ptl_func_map[] = {
 	/* common cmds */
 	[GET_PTL_VER] = {
@@ -371,11 +641,11 @@  static const struct ilitek_protocol_map ptl_func_map[] = {
 	},
 	[GET_IC_MODE] = {
 		ILITEK_TP_CMD_GET_IC_MODE, "GET_IC_MODE",
-			   api_protocol_get_ic_mode
+		api_protocol_get_ic_mode
 	},
 	[GET_MCU_VER] = {
 		ILITEK_TP_CMD_GET_MCU_VER, "GET_MOD_VER",
-			   api_protocol_get_mcu_ver
+		api_protocol_get_mcu_ver
 	},
 	[SET_IC_SLEEP] = {
 		ILITEK_TP_CMD_SET_IC_SLEEP, "SET_IC_SLEEP",
@@ -385,6 +655,42 @@  static const struct ilitek_protocol_map ptl_func_map[] = {
 		ILITEK_TP_CMD_SET_IC_WAKE, "SET_IC_WAKE",
 		api_protocol_set_ic_wake
 	},
+	[SET_MOD_CTRL] = {
+		ILITEK_TP_CMD_SET_MOD_CTRL, "SET_MOD_CTRL",
+		api_protocol_set_mode_ctrl
+	},
+	[GET_SYS_BUSY] = {
+		ILITEK_TP_CMD_GET_SYS_BUSY, "GET_SYS_BUSY",
+		api_protocol_get_sys_busy
+	},
+	[SET_FLASH_AP] = {
+		ILITEK_TP_CMD_SET_W_FLASH, "SET_FLASH_AP",
+		api_protocol_set_write_flash_ap
+	},
+	[SET_BL_MODE] = {
+		ILITEK_TP_CMD_SET_BL_MODE, "SET_BL_MODE",
+		api_protocol_set_bl_mode
+	},
+	[SET_AP_MODE] = {
+		ILITEK_TP_CMD_SET_AP_MODE, "SET_AP_MODE",
+		api_protocol_set_ap_mode
+	},
+	[GET_BLK_CRC] = {
+		ILITEK_TP_CMD_GET_BLK_CRC, "GET_BLK_CRC",
+		api_protocol_get_blk_crc
+	},
+	[SET_DATA_LEN] = {
+		ILITEK_TP_CMD_SET_DATA_LEN, "SET_DATA_LEN",
+		api_protocol_set_data_length
+	},
+	[SET_FLASH_BL] = {
+		ILITEK_TP_CMD_SET_W_FLASH, "SET_FLASH_BL",
+		api_protocol_set_write_flash_bl
+	},
+	[SET_W_DATA] = {
+		ILITEK_TP_CMD_SET_W_DATA, "SET_W_DATA",
+		api_protocol_set_write_data
+	},
 };
 
 /* Probe APIs */
@@ -532,9 +838,419 @@  static ssize_t product_id_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(product_id);
 
+static u32 ilitek_find_endaddr(u32 start, u32 end, u8 *buf, bool ap)
+{
+	u32 addr;
+	char ap_tag[16] = "ILITek AP CRC   ";
+	char blk_tag[16] = "ILITek END TAG  ";
+	char tag[32];
+
+	memset(tag, 0xff, 32);
+	if (ap)
+		memcpy(tag + 16, ap_tag, 16);
+	else
+		memcpy(tag + 16, blk_tag, 16);
+
+	for (addr = start; addr <= end - 32 &&
+	     addr < ILITEK_HEX_UPGRADE_SIZE - 32; addr++) {
+		if (!strncmp(buf + addr, tag, 32))
+			return addr + 33;
+	}
+
+	return end;
+}
+
+static int ilitek_info_mapping(struct ilitek_ts_data *ts, u32 addr, u8 *buf)
+{
+	u32 idx, i, start, end;
+	struct device *dev = &ts->client->dev;
+
+	idx = addr + 0x06;
+	ts->upg.fw_mcu_ver = get_unaligned_le16(buf + idx);
+
+	idx = addr + 0x00;
+	ts->upg.map_ver = buf[idx + 2] << 16 | buf[idx + 1] << 8 | buf[idx];
+
+	if (ts->upg.map_ver < 0x10000)
+		return -EINVAL;
+
+	ts->upg.blk_num = buf[addr + 0x50];
+
+	ts->upg.blk = kcalloc(ts->upg.blk_num, sizeof(struct ilitek_block_info),
+			      GFP_KERNEL);
+	if (!ts->upg.blk)
+		return -ENOMEM;
+
+	for (i = 0; i < ts->upg.blk_num; i++) {
+		idx = addr + 0x54 + 3 * i;
+		start = buf[idx + 2] << 16 | buf[idx + 1] << 8 | buf[idx];
+
+		if (i == ts->upg.blk_num - 1)
+			idx = addr + 123;
+		else
+			idx += 3;
+
+		end = buf[idx + 2] << 16 | buf[idx + 1] << 8 | buf[idx];
+
+		if (i == 0)
+			end = ilitek_find_endaddr(start, end, buf, true);
+		else
+			end = ilitek_find_endaddr(start, end, buf, false);
+
+		ts->upg.blk[i].start = start;
+		ts->upg.blk[i].end = end;
+
+		dev_dbg(dev, "block[%u] start: %#x, end: %#x\n", i, start, end);
+	}
+
+	return 0;
+}
+
+static u16 ilitek_update_crc(u16 crc, u8 newbyte)
+{
+	u8 i;
+
+	crc = crc ^ newbyte;
+
+	for (i = 0; i < 8; i++) {
+		if (crc & 0x01) {
+			crc = crc >> 1;
+			crc ^= ILITEK_CRC_POLY;
+		} else {
+			crc = crc >> 1;
+		}
+	}
+	return crc;
+}
+
+static u16 ilitek_get_fw_crc(u32 start, u32 end, u8 *buf)
+{
+	u16 CRC = 0;
+	u32 i = 0;
+
+	for (i = start; i <= end - 2; i++)
+		CRC = ilitek_update_crc(CRC, buf[i]);
+
+	return CRC;
+}
+
+static int ilitek_check_blk(struct ilitek_ts_data *ts, u8 *buf, bool *update)
+{
+	u32 i;
+	int error;
+	struct device *dev = &ts->client->dev;
+	struct ilitek_block_info *blk = ts->upg.blk;
+
+	for (i = 0; i < ts->upg.blk_num; i++) {
+		error = ilitek_get_blk_crc(ts, blk[i].start, blk[i].end, 0,
+					   &blk[i].ic_crc);
+		if (error < 0) {
+			dev_err(dev, "get blk crc failed, ret:%d\n", error);
+			return error;
+		}
+
+		blk[i].fw_crc = ilitek_get_fw_crc(blk[i].start,
+						   blk[i].end, buf);
+
+		if (blk[i].ic_crc != blk[i].fw_crc) {
+			blk[i].chk_crc = false;
+			*update = true;
+		} else
+			blk[i].chk_crc = true;
+
+		dev_dbg(dev, "block[%u]: start:%#x, end:%#x, ic crc:%#x, dri crc:%#x\n",
+			i, blk[i].start, blk[i].end,
+			blk[i].ic_crc, blk[i].fw_crc);
+	}
+
+	return 0;
+}
+
+static int ilitek_program_blk(struct ilitek_ts_data *ts, u8 *buf,
+			      u32 cnt, const u32 len)
+{
+	int error;
+	u32 i;
+	u8 *inbuf;
+	struct device *dev = &ts->client->dev;
+	struct ilitek_block_info *blk = ts->upg.blk;
+
+	inbuf = kmalloc(len + 1, GFP_KERNEL);
+	if (!inbuf)
+		return -ENOMEM;
+	memset(inbuf, 0xff, len + 1);
+
+	error = ilitek_set_flash_BL1_8(ts, blk[cnt].start, blk[cnt].end);
+	if (error < 0)
+		goto err_free;
+
+	for (i = blk[cnt].start; i < blk[cnt].end; i += len) {
+		memcpy(inbuf + 1, buf + i, len);
+
+		error = api_protocol_set_cmd(ts, SET_W_DATA, inbuf, NULL);
+		if (error < 0)
+			goto err_free;
+
+		error = ilitek_check_busy(ts, 2000);
+		if (error < 0) {
+			dev_err(dev, "check busy failed, ret:%d\n", error);
+			goto err_free;
+		}
+	}
+
+	error = ilitek_get_blk_crc(ts, blk[cnt].start, blk[cnt].end, 1,
+				   &blk[cnt].ic_crc);
+	if (error < 0) {
+		dev_err(dev, "get blk crc failed, ret:%d\n", error);
+		goto err_free;
+	}
+
+	if (blk[cnt].ic_crc != blk[cnt].fw_crc) {
+		dev_err(dev, "ic_crc:%x dri_crc:%x not matched\n",
+			blk[cnt].ic_crc, blk[cnt].fw_crc);
+		error = -EFAULT;
+	}
+
+err_free:
+	kfree(inbuf);
+	return error;
+}
+
+static int ilitek_upgrade_BL1_8(struct ilitek_ts_data *ts, u8 *buf)
+{
+	int error;
+	u32 cnt;
+	struct device *dev = &ts->client->dev;
+	struct ilitek_block_info *blk = ts->upg.blk;
+
+	error = ilitek_set_data_len(ts, ILITEK_UPGRADE_LEN);
+	if (error < 0)
+		return error;
+
+	for (cnt = 0; cnt < ts->upg.blk_num; cnt++) {
+		if (blk[cnt].chk_crc == true)
+			continue;
+
+		error = ilitek_program_blk(ts, buf, cnt, ILITEK_UPGRADE_LEN);
+		if (error < 0) {
+			dev_err(dev, "upgrade failed, ret:%d\n", error);
+			return error;
+		}
+	}
+
+	return 0;
+}
+
+static int ilitek_upgrade_firmware(struct ilitek_ts_data *ts, u8 *buf)
+{
+	int error, retry = 2;
+	struct device *dev = &ts->client->dev;
+	u8 outbuf[64];
+	bool need_update = false;
+
+	/* Check MCU version between device and firmware file */
+	if (ts->upg.fw_mcu_ver != ts->mcu_ver) {
+		dev_err(dev, "MCU version (MCU:%x and FW:%x) not match\n",
+			 ts->mcu_ver, ts->upg.fw_mcu_ver);
+		return -EINVAL;
+	}
+
+Retry:
+	if (--retry < 0) {
+		dev_err(dev, "retry 2 times upgrade fail, ret:%d\n", error);
+		return error;
+	}
+
+	ilitek_reset(ts, ts->reset_time);
+
+	error = ilitek_set_testmode(ts, true);
+	if (error < 0)
+		goto Retry;
+
+	error = ilitek_check_busy(ts, 1000);
+	if (error < 0)
+		goto Retry;
+
+	error = ilitek_check_blk(ts, buf, &need_update);
+	if (error < 0)
+		goto Retry;
+
+	if (need_update) {
+		error = ilitek_switch_BLmode(ts, true);
+		if (error < 0)
+			goto Retry;
+
+		/* get bootloader version */
+		error = api_protocol_set_cmd(ts, GET_PTL_VER, NULL, outbuf);
+		if (error < 0)
+			goto Retry;
+
+		error = ilitek_upgrade_BL1_8(ts, buf);
+		if (error < 0)
+			goto Retry;
+
+		error = ilitek_switch_BLmode(ts, false);
+		if (error < 0)
+			goto Retry;
+	}
+
+	/* switch back to normal mode after reset */
+	ilitek_reset(ts, ts->reset_time);
+
+	error = ilitek_protocol_init(ts);
+	if (error < 0)
+		goto Retry;
+
+	error = ilitek_read_tp_info(ts, true);
+	if (error < 0)
+		goto Retry;
+
+	return 0;
+}
+
+static int ilitek_parse_hex(struct ilitek_ts_data *ts, u32 *fw_size, u8 *fw_buf)
+{
+	int error;
+	char *fw_file;
+	const struct firmware *fw;
+	struct device *dev = &ts->client->dev;
+	u32 i, len, addr, type, exaddr = 0;
+	u8 info[4], data[16];
+
+	fw_file = kasprintf(GFP_KERNEL, "ilitek_%04x.hex", ts->mcu_ver);
+	if (!fw_file)
+		return -ENOMEM;
+
+	error = request_firmware(&fw, fw_file, dev);
+	kfree(fw_file);
+	if (error) {
+		dev_err(dev, "request firmware:%s failed, ret:%d\n",
+			fw_file, error);
+		return error;
+	}
+
+	for (i = 0; i < fw->size; i++) {
+		if (fw->data[i] == ':' ||
+		    fw->data[i] == 0x0D ||
+		    fw->data[i] == 0x0A)
+			continue;
+
+		error = hex2bin(info, fw->data + i, sizeof(info));
+		if (error)
+			goto release_fw;
+
+		len = info[0];
+		addr = get_unaligned_be16(info + 1);
+		type = info[3];
+
+		error = hex2bin(data, fw->data + i + 8, len);
+		if (error)
+			goto release_fw;
+
+		switch (type) {
+		case 0x01:
+			goto release_fw;
+		case 0x02:
+			exaddr = get_unaligned_be16(data);
+			exaddr <<= 4;
+			break;
+		case 0x04:
+			exaddr = get_unaligned_be16(data);
+			exaddr <<= 16;
+			break;
+		case 0xAC:
+		case 0xAD:
+			break;
+		case 0x00:
+			addr += exaddr;
+			memcpy(fw_buf + addr, data, len);
+			*fw_size = addr + len;
+			break;
+		default:
+			dev_err(dev, "unexpected type:%x in hex\n", type);
+			goto err_invalid;
+		}
+
+		i += 10 + (len * 2);
+	}
+
+err_invalid:
+	error = -EINVAL;
+
+release_fw:
+	release_firmware(fw);
+
+	return error;
+}
+
+
+static int ilitek_update_fw_v6(struct ilitek_ts_data *ts)
+{
+	u8 *fw_buf;
+	int error;
+	struct device *dev = &ts->client->dev;
+
+	fw_buf = vmalloc(ILITEK_HEX_UPGRADE_SIZE);
+	if (!fw_buf)
+		return -ENOMEM;
+	memset(fw_buf, 0xFF, ILITEK_HEX_UPGRADE_SIZE);
+
+	error = ilitek_parse_hex(ts, &ts->upg.fw_size, fw_buf);
+	if (error < 0) {
+		dev_err(dev, "parse fw file failed, err:%d\n", error);
+		goto err_free_buf;
+	}
+
+	error = ilitek_info_mapping(ts, 0x3020, fw_buf);
+	if (error < 0) {
+		dev_err(dev, "check hex mapping fail, ret:%d\n", error);
+		goto err_free_buf;
+	}
+
+	error = ilitek_upgrade_firmware(ts, fw_buf);
+	if (error < 0) {
+		dev_err(dev, "upgrade fail, ret:%d\n", error);
+		goto err_free_buf;
+	}
+
+	dev_dbg(dev, "update fw success\n");
+
+err_free_buf:
+	vfree(fw_buf);
+	kfree(ts->upg.blk);
+
+	return error;
+}
+
+static ssize_t update_fw_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	int error;
+	struct i2c_client *client = to_i2c_client(dev);
+	struct ilitek_ts_data *ts = i2c_get_clientdata(client);
+
+	disable_irq(client->irq);
+
+	error = ilitek_update_fw_v6(ts);
+
+	enable_irq(client->irq);
+
+	if (error < 0) {
+		dev_err(dev, "ILITEK FW upgrade failed, ret:%d\n", error);
+		return error;
+	}
+
+	dev_dbg(dev, "firmware upgrade success !\n");
+
+	return count;
+}
+static DEVICE_ATTR_WO(update_fw);
+
 static struct attribute *ilitek_sysfs_attrs[] = {
 	&dev_attr_firmware_version.attr,
 	&dev_attr_product_id.attr,
+	&dev_attr_update_fw.attr,
 	NULL
 };
 
@@ -597,6 +1313,7 @@  static int ilitek_ts_i2c_probe(struct i2c_client *client,
 	}
 
 	error = devm_device_add_group(dev, &ilitek_attrs_group);
+
 	if (error) {
 		dev_err(dev, "sysfs create group failed: %d\n", error);
 		return error;