Ver código fonte

touch: goodix: v1.2.3

V1.2.3 from vendor.

Change-Id: Idf75e5ebf2947a53a5d243ddb0a9bc71362bbdd6
Signed-off-by: xulinkun <[email protected]>
Git-commit: 58cc7874cb5e13490a6c8720b308fa56847a1f51
Git-repo: https://github.com/goodix/goodix_ts_berlin
Signed-off-by: Fei Mao <[email protected]>
xulinkun 3 anos atrás
pai
commit
37268c4170
14 arquivos alterados com 10826 adições e 15 exclusões
  1. 20 0
      Kconfig
  2. 12 15
      Makefile
  3. 192 0
      docs/Porting_Guide.md
  4. 1398 0
      goodix_brl_fwupdate.c
  5. 1434 0
      goodix_brl_hw.c
  6. 264 0
      goodix_brl_i2c.c
  7. 299 0
      goodix_brl_spi.c
  8. 329 0
      goodix_cfg_bin.c
  9. 2263 0
      goodix_ts_core.c
  10. 630 0
      goodix_ts_core.h
  11. 384 0
      goodix_ts_gesture.c
  12. 2919 0
      goodix_ts_inspect.c
  13. 503 0
      goodix_ts_tools.c
  14. 179 0
      goodix_ts_utils.c

+ 20 - 0
Kconfig

@@ -0,0 +1,20 @@
+#
+# Goodix touchscreen driver configuration
+#
+menuconfig TOUCHSCREEN_GOODIX_BRL
+	tristate "Goodix berlin touchscreen"
+	help
+	  Say Y here if you have a Goodix berlin series touch controller
+	  to your system.
+	  
+	  If build module, say M.
+	  If unsure, say N.
+
+if TOUCHSCREEN_GOODIX_BRL
+
+config TOUCHSCREEN_GOODIX_BRL_SPI
+	bool "support SPI bus connection" 
+	help
+		Say Y here if the touchscreen is connected via SPI bus.
+
+endif

+ 12 - 15
Makefile

@@ -1,15 +1,12 @@
-
-KBUILD_OPTIONS+= TOUCH_ROOT=$(KERNEL_SRC)/$(M)
-
-all:
-	$(MAKE) -C $(KERNEL_SRC) M=$(M) modules $(KBUILD_OPTIONS)
-
-modules_install:
-	$(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(M) modules_install
-
-%:
-	$(MAKE) -C $(KERNEL_SRC) M=$(M) $@ $(KBUILD_OPTIONS)
-
-clean:
-	rm -f *.o *.ko *.mod.c *.mod.o *~ .*.cmd Module.symvers
-	rm -rf .tmp_versions
+obj-$(CONFIG_TOUCHSCREEN_GOODIX_BRL) += goodix_core.o
+goodix_core-y := \
+				goodix_brl_i2c.o \
+				goodix_brl_spi.o \
+				goodix_ts_core.o \
+				goodix_brl_hw.o \
+				goodix_cfg_bin.o \
+				goodix_ts_utils.o \
+				goodix_brl_fwupdate.o \
+				goodix_ts_gesture.o \
+				goodix_ts_inspect.o \
+				goodix_ts_tools.o

+ 192 - 0
docs/Porting_Guide.md

@@ -0,0 +1,192 @@
+# **Berlin Driver Porting Guide**
+
+## **Introduce**
+Berlin series driver is currently support BerlinA(GT9897), BerlinB(GT9966, GT7986). And support I2C or SPI connection.
+
+## **Driver source file prepare**
+1. Move driver source code to $KERNEL_SRC/drivers/input/touchscree/
+2. Change $KERNEL_SRC/drivers/input/touchscree/Makefile  
+	Add this line to the Makefile
+	```
+	obj-y += goodix_berlin_driver/
+	```
+3. Change $KERNEL_SRC/drivers/input/touchscree/Kconfg  
+	Add this line to the Kconfig
+	```
+	source "drivers/input/touchscreen/goodix_berlin_driver/Kconfig"
+	```
+
+## **Add device declaration in the board devicetree**
+Please add Goodix touch device declaration info in the board devicetree, you can refer the Appendix goodix-ts-i2c-dtsi or goodix-ts-spi-dtsi  to see how to set deivce properties.
+
+## **Build driver**
+When build kernel you will see the following promt to let you confirm how to build the driver. This driver support built-in kernel or build as modules.
+
+**Setting up in the menu:**
+1. In the $KERNEL_SRC directory, exec `make menuconfig`, then select  
+`Device Drivers ---> Input device support ---> Touchscreens --->`  
+2. Find `Goodix berlin touchscreen` menu, you can select `<*>`(build in kernel) or `<M>`(build a module).
+3. Enter `Goodix berlin touchscreen`, you can see `support SPI bus connection` item. If 
+you are on SPI connection, select `<*>`, or on I2C connection.
+
+**Setting up in the defconfig file:**
+1. Add the following in you defconfig file.
+	```
+	CONFIG_TOUCHSCREEN_GOODIX_BRL=y
+	```
+	or
+	```
+	CONFIG_TOUCHSCREEN_GOODIX_BRL=m
+	```
+2. If you are on SPI connection, add the following.
+	```
+	CONFIG_TOUCHSCREEN_GOODIX_BRL_SPI=y
+	```
+
+## **Appendix**
+
+**goodix-ts-i2c-dtsi**
+
+```dts
+devicetree binding for Goodix i2c touchdriver
+Required properties:
+- compatible: device & driver matching.
+	* for berlin series touch device, souch as "goodix,gt9897", "goodix,gt9966"
+
+- reg: i2c client address, value can be 0x14 or 0x5d. please refer to datasheet.
+- goodix,reset-gpio: reset gpio.
+- goodix,irq-gpio: interrupt gpio. 
+- goodix,irq-flags: irq trigger type config, value should be:
+	       1 - rising edge,
+	       2 - falling edge,
+	       4 - high level,
+	       5 - low level.
+- goodix,panel-max-x: max resolution of x direction.
+- goodix,panel-max-y: max resolution of y direction.
+- goodix,panel-max-w: panel max width value.
+- goodix,panel-max-p: pen device max pressure value.
+
+Optional properties:
+- goodix,avdd-name: set name of regulator.
+- avdd-supply: power supply for the touch device.
+  example of regulator:
+	goodix,avdd-name = "avdd";
+	avdd-supply = <&pm8916_l15>;
+- iovdd-supply: power supply for digital io circuit
+  example of regulator:
+	goodix,iovdd-name = "iovdd";
+	iovdd-supply = <&pm8916_l16>;
+- goodix,pen-enable: set this property if you want support stylus.
+	goodix,pen-enable;
+- goodix,firmware-name: set firmware file name, if not configured, use the default name.
+- goodix,config-name: set config file name, if not configured, use the default name.
+Example 1:
+goodix-berlin@5d {
+	compatible = "goodix,gt9897";
+	reg = <0x5d>;
+	goodix,reset-gpio = <&msm_gpio 12 0x0>;
+	goodix,irq-gpio = <&msm_gpio 13 0x2800>;
+	goodix,irq-flags = <2>; /* 1:trigger rising, 2:trigger falling;*/
+	goodix,panel-max-x = <720>;
+	goodix,panel-max-y = <1280>;
+	goodix,panel-max-w = <255>;
+};
+
+Example 2:
+goodix-berlin@5d {
+	compatible = "goodix,gt9966";
+	goodix,avdd-name = "avdd";
+	avdd-supply = <&pm8916_l15>;
+	goodix,iovdd-name = "iovdd";
+	iovdd-supply = <&pm8916_l16>;
+
+	reg = <0x5d>;
+	goodix,reset-gpio = <&msm_gpio 12 0x0>;
+	goodix,irq-gpio = <&msm_gpio 13 0x2800>;
+	goodix,irq-flags = <2>; /* 1:trigger rising, 2:trigger falling;*/
+	goodix,panel-max-x = <720>;
+	goodix,panel-max-y = <1280>;
+	goodix,panel-max-w = <255>;
+	goodix,panel-max-p = <4096>; /* max pressure that pen device supported */
+	goodix,pen-enable; /* support active stylus device*/
+
+	goodix,firmware-name = "goodix_firmware.bin";
+	goodix,config-name = "goodix_cfg_group.bin";
+};
+```
+
+**goodix-ts-spi-dtsi**
+
+```dts
+devicetree binding for Goodix spi touchdriver
+
+Required properties:
+- compatible: device & driver matching.
+	* for berlin series touch device, souch as "goodix,gt9897T"
+
+- spi-max-frequency: set spi transfer speed.
+- reg: depend on CS gpio.
+- goodix,reset-gpio: reset gpio.
+- goodix,irq-gpio: interrupt gpio.
+- goodix,irq-flags: irq trigger type config, value should be:
+	       1 - rising edge,
+	       2 - falling edge,
+	       4 - high level,
+	       5 - low level.
+- goodix,panel-max-x: max resolution of x direction.
+- goodix,panel-max-y: max resolution of y direction.
+- goodix,panel-max-w: panel max width value.
+- goodix,panel-max-p: pen device max pressure value.
+
+Optional properties:
+- goodix,avdd-name: set name of regulator.
+- avdd-supply: power supply for the touch device.
+  example of regulator:
+	goodix,avdd-name = "avdd";
+	avdd-supply = <&pm8916_l15>;
+- iovdd-supply: power supply for digital io circuit
+  example of regulator:
+	goodix,iovdd-name = "iovdd";
+	iovdd-supply = <&pm8916_l16>;
+- goodix,pen-enable: set this property if you want support stylus.
+	goodix,pen-enable;
+- goodix,firmware-name: set firmware file name, if not configured, use the default name.
+- goodix,config-name: set config file name, if not configured, use the default name.	
+Example 1:
+goodix-berlin@0 {
+	compatible = "goodix,gt9897";
+	reg = <0>;
+	spi-max-frequency = <1000000>;
+	goodix,reset-gpio = <&msm_gpio 12 0x0>;
+	goodix,irq-gpio = <&msm_gpio 13 0x2800>;
+	goodix,irq-flags = <2>; /* 1:trigger rising, 2:trigger falling;*/
+	goodix,panel-max-x = <720>;
+	goodix,panel-max-y = <1280>;
+	goodix,panel-max-w = <255>;
+};
+
+Example 2:
+goodix-berlin@0 {
+	compatible = "goodix,gt9966S";
+	reg = <0>;
+	spi-max-frequency = <1000000>;
+
+	goodix,avdd-name = "avdd";
+	avdd-supply = <&pm8916_l15>;
+	goodix,iovdd-name = "iovdd";
+	iovdd-supply = <&pm8916_l16>;
+
+	goodix,reset-gpio = <&msm_gpio 12 0x0>;
+	goodix,irq-gpio = <&msm_gpio 13 0x2800>;
+	goodix,irq-flags = <2>; /* 1:trigger rising, 2:trigger falling;*/
+	goodix,panel-max-x = <720>;
+	goodix,panel-max-y = <1280>;
+	goodix,panel-max-w = <255>;
+	goodix,panel-max-p = <4096>; /* max pressure that pen device supported */
+	goodix,pen-enable; /* support active stylus device*/
+
+	goodix,firmware-name = "goodix_firmware.bin";
+	goodix,config-name = "goodix_cfg_group.bin";	
+};
+```
+

+ 1398 - 0
goodix_brl_fwupdate.c

@@ -0,0 +1,1398 @@
+ /*
+  * Goodix Touchscreen Driver
+  * Copyright (C) 2020 - 2021 Goodix, Inc.
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be a reference
+  * to you, when you are integrating the GOODiX's CTP IC into your system,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  */
+#include "goodix_ts_core.h"
+
+#define BUS_TYPE_SPI					1
+#define BUS_TYPE_I2C					0
+
+#define GOODIX_BUS_RETRY_TIMES			3
+
+#define FW_HEADER_SIZE_BRA				256
+#define FW_HEADER_SIZE					512
+#define FW_SUBSYS_INFO_SIZE				10
+#define FW_SUBSYS_INFO_OFFSET_BRA		36
+#define FW_SUBSYS_INFO_OFFSET			42
+#define FW_SUBSYS_MAX_NUM				47
+
+#define ISP_MAX_BUFFERSIZE				4096
+
+#define FW_PID_LEN						8
+#define FW_VID_LEN						4
+#define FLASH_CMD_LEN					11
+
+#define FW_FILE_CHECKSUM_OFFSET			8
+#define CONFIG_DATA_TYPE				4
+
+#define ISP_RAM_ADDR_BRA				0x18400
+#define ISP_RAM_ADDR_BRB				0x57000
+#define ISP_RAM_ADDR_BRD				0x23800
+#define HW_REG_CPU_RUN_FROM				0x10000
+#define FLASH_CMD_REG_BRA				0x10400
+#define FLASH_CMD_REG_BRB				0x13400
+#define FLASH_CMD_REG_BRD				0x12400
+#define HW_REG_ISP_BUFFER_BRA			0x10410
+#define HW_REG_ISP_BUFFER_BRB			0x13410
+#define HW_REG_ISP_BUFFER_BRD			0x12410
+#define CONFIG_DATA_ADDR_BRA			0x3E000
+#define CONFIG_DATA_ADDR_BRB			0x40000
+#define CONFIG_DATA_ADDR_BRD			0x3E000
+#define GOODIX_CFG_ID_ADDR_BRA			0x1006E
+#define GOODIX_CFG_ID_ADDR_BRB			0x10076
+#define GOODIX_CFG_ID_ADDR_BRD			0x10076
+
+#define HOLD_CPU_REG_W					0x0002
+#define HOLD_CPU_REG_R					0x2000
+#define MISCTL_REG_BRA					0xD807
+#define MISCTL_REG_BRB					0xD80B
+#define MISCTL_REG_BRD					0xD804
+#define ENABLE_MISCTL_BRA				0x08
+#define ENABLE_MISCTL_BRB				0x40
+#define ENABLE_MISCTL_BRD				0x20700000
+#define ESD_KEY_REG						0xCC58
+#define WATCH_DOG_REG_BRA				0xCC54
+#define WATCH_DOG_REG_BRB				0xD054
+#define WATCH_DOG_REG_BRD				0xD040
+
+#define FLASH_CMD_TYPE_READ				0xAA
+#define FLASH_CMD_TYPE_WRITE			0xBB
+#define FLASH_CMD_ACK_CHK_PASS			0xEE
+#define FLASH_CMD_ACK_CHK_ERROR			0x33
+#define FLASH_CMD_ACK_IDLE				0x11
+#define FLASH_CMD_W_STATUS_CHK_PASS		0x22
+#define FLASH_CMD_W_STATUS_CHK_FAIL		0x33
+#define FLASH_CMD_W_STATUS_ADDR_ERR		0x44
+#define FLASH_CMD_W_STATUS_WRITE_ERR	0x55
+#define FLASH_CMD_W_STATUS_WRITE_OK		0xEE
+
+#define CHIP_TYPE_BRA					0x96
+#define CHIP_TYPE_BRB					0x97
+#define CHIP_TYPE_BRD					0x98
+
+
+struct update_info_t {
+	int header_size;
+	int subsys_info_offset;
+	u32 isp_ram_reg;
+	u32 flash_cmd_reg;
+	u32 isp_buffer_reg;
+	u32 config_data_reg;
+	u32 misctl_reg;
+	u32 watch_dog_reg;
+	u32 config_id_reg;
+	u32 enable_misctl_val;
+};
+
+/* berlinA update into */
+struct update_info_t update_bra = {
+	FW_HEADER_SIZE_BRA,
+	FW_SUBSYS_INFO_OFFSET_BRA,
+	ISP_RAM_ADDR_BRA,
+	FLASH_CMD_REG_BRA,
+	HW_REG_ISP_BUFFER_BRA,
+	CONFIG_DATA_ADDR_BRA,
+	MISCTL_REG_BRA,
+	WATCH_DOG_REG_BRA,
+	GOODIX_CFG_ID_ADDR_BRA,
+	ENABLE_MISCTL_BRA,
+};
+
+/* berlinB update info */
+struct update_info_t update_brb = {
+	FW_HEADER_SIZE,
+	FW_SUBSYS_INFO_OFFSET,
+	ISP_RAM_ADDR_BRB,
+	FLASH_CMD_REG_BRB,
+	HW_REG_ISP_BUFFER_BRB,
+	CONFIG_DATA_ADDR_BRB,
+	MISCTL_REG_BRB,
+	WATCH_DOG_REG_BRB,
+	GOODIX_CFG_ID_ADDR_BRB,
+	ENABLE_MISCTL_BRB,
+};
+
+/* berlinD update info */
+struct update_info_t update_brd = {
+	FW_HEADER_SIZE,
+	FW_SUBSYS_INFO_OFFSET,
+	ISP_RAM_ADDR_BRD,
+	FLASH_CMD_REG_BRD,
+	HW_REG_ISP_BUFFER_BRD,
+	CONFIG_DATA_ADDR_BRD,
+	MISCTL_REG_BRD,
+	WATCH_DOG_REG_BRD,
+	GOODIX_CFG_ID_ADDR_BRD,
+	ENABLE_MISCTL_BRD,
+};
+
+/**
+ * fw_subsys_info - subsytem firmware information
+ * @type: sybsystem type
+ * @size: firmware size
+ * @flash_addr: flash address
+ * @data: firmware data
+ */
+struct fw_subsys_info {
+	u8 type;
+	u32 size;
+	u32 flash_addr;
+	const u8 *data;
+};
+
+/**
+ *  firmware_summary
+ * @size: fw total length
+ * @checksum: checksum of fw
+ * @hw_pid: mask pid string
+ * @hw_pid: mask vid code
+ * @fw_pid: fw pid string
+ * @fw_vid: fw vid code
+ * @subsys_num: number of fw subsystem
+ * @chip_type: chip type
+ * @protocol_ver: firmware packing
+ *   protocol version
+ * @bus_type: 0 represent I2C, 1 for SPI
+ * @subsys: sybsystem info
+ */
+#pragma pack(1)
+struct  firmware_summary {
+	u32 size;
+	u32 checksum;
+	u8 hw_pid[6];
+	u8 hw_vid[3];
+	u8 fw_pid[FW_PID_LEN];
+	u8 fw_vid[FW_VID_LEN];
+	u8 subsys_num;
+	u8 chip_type;
+	u8 protocol_ver;
+	u8 bus_type;
+	u8 flash_protect;
+	// u8 reserved[8];
+	struct fw_subsys_info subsys[FW_SUBSYS_MAX_NUM];
+};
+#pragma pack()
+
+/**
+ * firmware_data - firmware data structure
+ * @fw_summary: firmware information
+ * @firmware: firmware data structure
+ */
+struct firmware_data {
+	struct  firmware_summary fw_summary;
+	const struct firmware *firmware;
+};
+
+struct config_data {
+	u8 *data;
+	int size;
+};
+
+#pragma pack(1)
+struct goodix_flash_cmd {
+	union {
+		struct {
+			u8 status;
+			u8 ack;
+			u8 len;
+			u8 cmd;
+			u8 fw_type;
+			u16 fw_len;
+			u32 fw_addr;
+			//u16 checksum;
+		};
+		u8 buf[16];
+	};
+};
+#pragma pack()
+
+enum update_status {
+	UPSTA_NOTWORK = 0,
+	UPSTA_PREPARING,
+	UPSTA_UPDATING,
+	UPSTA_SUCCESS,
+	UPSTA_FAILED
+};
+
+enum compare_status {
+	COMPARE_EQUAL = 0,
+	COMPARE_NOCODE,
+	COMPARE_PIDMISMATCH,
+	COMPARE_FW_NOTEQUAL,
+	COMPARE_CFG_NOTEQUAL,
+};
+
+/**
+ * fw_update_ctrl - structure used to control the
+ *  firmware update process
+ * @initialized: struct init state
+ * @mode: indicate weather reflash config or not, fw data source,
+ *        and run on block mode or not.
+ * @status: update status
+ * @progress: indicate the progress of update
+ * @fw_data: firmware data
+ * @fw_name: firmware name
+ * @attr_fwimage: sysfs bin attrs, for storing fw image
+ * @fw_data_src: firmware data source form sysfs, request or head file
+ * @kobj: pointer to the sysfs kobject
+ */
+struct fw_update_ctrl {
+	struct mutex mutex;
+	int initialized;
+	char fw_name[GOODIX_MAX_STR_LABLE_LEN];
+	int mode;
+	enum update_status status;
+	int spend_time;
+
+	struct firmware_data fw_data;
+	struct goodix_ic_config *ic_config;
+	struct goodix_ts_core *core_data;
+	struct update_info_t *update_info;
+
+	struct bin_attribute attr_fwimage;
+	struct kobject *kobj;
+};
+static struct fw_update_ctrl goodix_fw_update_ctrl;
+
+static int goodix_fw_update_reset(int delay)
+{
+	struct goodix_ts_hw_ops *hw_ops;
+
+	hw_ops = goodix_fw_update_ctrl.core_data->hw_ops;
+	return hw_ops->reset(goodix_fw_update_ctrl.core_data, delay);
+}
+
+static int get_fw_version_info(struct goodix_fw_version *fw_version)
+{
+	struct goodix_ts_hw_ops *hw_ops =
+		goodix_fw_update_ctrl.core_data->hw_ops;
+
+	return hw_ops->read_version(goodix_fw_update_ctrl.core_data,
+				fw_version);
+}
+
+static int goodix_reg_write(unsigned int addr,
+			unsigned char *data, unsigned int len)
+{
+	struct goodix_ts_hw_ops *hw_ops =
+			goodix_fw_update_ctrl.core_data->hw_ops;
+
+	return hw_ops->write(goodix_fw_update_ctrl.core_data,
+			addr, data, len);
+}
+
+static int goodix_reg_read(unsigned int addr,
+			unsigned char *data, unsigned int len)
+{
+	struct goodix_ts_hw_ops *hw_ops =
+			goodix_fw_update_ctrl.core_data->hw_ops;
+
+	return hw_ops->read(goodix_fw_update_ctrl.core_data,
+			addr, data, len);
+}
+
+/**
+ * goodix_parse_firmware - parse firmware header information
+ *	and subsystem information from firmware data buffer
+ *
+ * @fw_data: firmware struct, contains firmware header info
+ *	and firmware data.
+ * return: 0 - OK, < 0 - error
+ */
+/* sizeof(length) + sizeof(checksum) */
+
+static int goodix_parse_firmware(struct firmware_data *fw_data)
+{
+	const struct firmware *firmware;
+	struct  firmware_summary *fw_summary;
+	unsigned int i, fw_offset, info_offset;
+	u32 checksum;
+	int ic_type =
+		goodix_fw_update_ctrl.core_data->bus->ic_type;
+	int subsys_info_offset =
+		goodix_fw_update_ctrl.update_info->subsys_info_offset;
+	int header_size =
+		goodix_fw_update_ctrl.update_info->header_size;
+	int r = 0;
+
+	fw_summary = &fw_data->fw_summary;
+
+	/* copy firmware head info */
+	firmware = fw_data->firmware;
+	if (firmware->size < subsys_info_offset) {
+		ts_err("Invalid firmware size:%zu", firmware->size);
+		r = -EINVAL;
+		goto err_size;
+	}
+	memcpy(fw_summary, firmware->data, sizeof(*fw_summary));
+
+	/* check firmware size */
+	fw_summary->size = le32_to_cpu(fw_summary->size);
+	if (firmware->size != fw_summary->size + FW_FILE_CHECKSUM_OFFSET) {
+		ts_err("Bad firmware, size not match, %zu != %d",
+			firmware->size, fw_summary->size + 6);
+		r = -EINVAL;
+		goto err_size;
+	}
+
+	for (i = FW_FILE_CHECKSUM_OFFSET, checksum = 0;
+	     i < firmware->size; i += 2)
+		checksum += firmware->data[i] + (firmware->data[i+1] << 8);
+
+	/* byte order change, and check */
+	fw_summary->checksum = le32_to_cpu(fw_summary->checksum);
+	if (checksum != fw_summary->checksum) {
+		ts_err("Bad firmware, cheksum error");
+		r = -EINVAL;
+		goto err_size;
+	}
+
+	if (fw_summary->subsys_num > FW_SUBSYS_MAX_NUM) {
+		ts_err("Bad firmware, invalid subsys num: %d",
+		       fw_summary->subsys_num);
+		r = -EINVAL;
+		goto err_size;
+	}
+
+	/* parse subsystem info */
+	fw_offset = header_size;
+	for (i = 0; i < fw_summary->subsys_num; i++) {
+		info_offset = subsys_info_offset +
+					i * FW_SUBSYS_INFO_SIZE;
+
+		fw_summary->subsys[i].type = firmware->data[info_offset];
+		fw_summary->subsys[i].size =
+		    le32_to_cpup((__le32 *)&firmware->data[info_offset + 1]);
+
+		fw_summary->subsys[i].flash_addr =
+		    le32_to_cpup((__le32 *)&firmware->data[info_offset + 5]);
+		if (fw_offset > firmware->size) {
+			ts_err("Sybsys offset exceed Firmware size");
+			goto err_size;
+		}
+
+		fw_summary->subsys[i].data = firmware->data + fw_offset;
+		fw_offset += fw_summary->subsys[i].size;
+	}
+
+	ts_info("Firmware package protocol: V%u", fw_summary->protocol_ver);
+	ts_info("Firmware PID:GT%s", fw_summary->fw_pid);
+	ts_info("Firmware VID:%*ph", 4, fw_summary->fw_vid);
+	ts_info("Firmware chip type:0x%02X", fw_summary->chip_type);
+	ts_info("Firmware bus type:%s",
+		(fw_summary->bus_type & BUS_TYPE_SPI) ? "SPI" : "I2C");
+	ts_info("Firmware size:%u", fw_summary->size);
+	ts_info("Firmware subsystem num:%u", fw_summary->subsys_num);
+
+	for (i = 0; i < fw_summary->subsys_num; i++) {
+		ts_debug("------------------------------------------");
+		ts_debug("Index:%d", i);
+		ts_debug("Subsystem type:%02X", fw_summary->subsys[i].type);
+		ts_debug("Subsystem size:%u", fw_summary->subsys[i].size);
+		ts_debug("Subsystem flash_addr:%08X",
+				fw_summary->subsys[i].flash_addr);
+		ts_debug("Subsystem Ptr:%p", fw_summary->subsys[i].data);
+	}
+
+	if (fw_summary->chip_type == CHIP_TYPE_BRA &&
+		ic_type != IC_TYPE_BERLIN_A) {
+		ts_err("ic type mismatch!");
+		r = -EINVAL;
+	} else if (fw_summary->chip_type == CHIP_TYPE_BRB &&
+		ic_type != IC_TYPE_BERLIN_B) {
+		ts_err("ic type mismatch!");
+		r = -EINVAL;
+	} else if (fw_summary->chip_type == CHIP_TYPE_BRD &&
+		ic_type != IC_TYPE_BERLIN_D) {
+		ts_err("ic type mismatch!");
+		r = -EINVAL;
+	}
+
+err_size:
+	return r;
+}
+
+/**
+ * goodix_fw_version_compare - compare the active version with
+ * firmware file version.
+ * @fwu_ctrl: firmware information to be compared
+ * return: 0 equal, < 0 unequal
+ */
+#define GOODIX_NOCODE "NOCODE"
+static int goodix_fw_version_compare(struct fw_update_ctrl *fwu_ctrl)
+{
+	int ret = 0;
+	struct goodix_fw_version fw_version;
+	struct firmware_summary *fw_summary = &fwu_ctrl->fw_data.fw_summary;
+	u32 config_id_reg = goodix_fw_update_ctrl.update_info->config_id_reg;
+	u32 file_cfg_id;
+	u32 ic_cfg_id;
+
+	/* compare fw_version */
+	ret = get_fw_version_info(&fw_version);
+	if (ret)
+		return -EINVAL;
+
+	if (!memcmp(fw_version.rom_pid, GOODIX_NOCODE, 6) ||
+		!memcmp(fw_version.patch_pid, GOODIX_NOCODE, 6)) {
+		ts_info("there is no code in the chip");
+		return COMPARE_NOCODE;
+	}
+
+	if (memcmp(fw_version.patch_pid, fw_summary->fw_pid, FW_PID_LEN)) {
+		ts_err("Product ID mismatch:%s != %s",
+			fw_version.patch_pid, fw_summary->fw_pid);
+		return COMPARE_PIDMISMATCH;
+	}
+
+	ret = memcmp(fw_version.patch_vid, fw_summary->fw_vid, FW_VID_LEN);
+	if (ret) {
+		ts_info("active firmware version:%*ph", FW_VID_LEN,
+				fw_version.patch_vid);
+		ts_info("firmware file version: %*ph", FW_VID_LEN,
+				fw_summary->fw_vid);
+		return COMPARE_FW_NOTEQUAL;
+	}
+	ts_info("fw_version equal");
+
+	/* compare config id */
+	if (fwu_ctrl->ic_config && fwu_ctrl->ic_config->len > 0) {
+		file_cfg_id =
+			goodix_get_file_config_id(fwu_ctrl->ic_config->data);
+		goodix_reg_read(config_id_reg,
+			(u8 *)&ic_cfg_id, sizeof(ic_cfg_id));
+		if (ic_cfg_id != file_cfg_id) {
+			ts_info("ic_cfg_id:0x%x != file_cfg_id:0x%x",
+				ic_cfg_id, file_cfg_id);
+			return COMPARE_CFG_NOTEQUAL;
+		}
+		ts_info("config_id equal");
+	}
+
+	return COMPARE_EQUAL;
+}
+
+/**
+ * goodix_reg_write_confirm - write register and confirm the value
+ *  in the register.
+ * @dev: pointer to touch device
+ * @addr: register address
+ * @data: pointer to data buffer
+ * @len: data length
+ * return: 0 write success and confirm ok
+ *		   < 0 failed
+ */
+static int goodix_reg_write_confirm(unsigned int addr,
+		unsigned char *data, unsigned int len)
+{
+	u8 *cfm = NULL;
+	u8 cfm_buf[32];
+	int r, i;
+
+	if (len > sizeof(cfm_buf)) {
+		cfm = kzalloc(len, GFP_KERNEL);
+		if (!cfm)
+			return -ENOMEM;
+	} else {
+		cfm = &cfm_buf[0];
+	}
+
+	for (i = 0; i < GOODIX_BUS_RETRY_TIMES; i++) {
+		r = goodix_reg_write(addr, data, len);
+		if (r < 0)
+			goto exit;
+
+		r = goodix_reg_read(addr, cfm, len);
+		if (r < 0)
+			goto exit;
+
+		if (memcmp(data, cfm, len)) {
+			r = -EINVAL;
+			continue;
+		} else {
+			r = 0;
+			break;
+		}
+	}
+
+exit:
+	if (cfm != &cfm_buf[0])
+		kfree(cfm);
+	return r;
+}
+
+
+/**
+ * goodix_load_isp - load ISP program to device ram
+ * @dev: pointer to touch device
+ * @fw_data: firmware data
+ * return 0 ok, <0 error
+ */
+static int goodix_load_isp(struct firmware_data *fw_data)
+{
+	struct goodix_fw_version isp_fw_version;
+	struct fw_subsys_info *fw_isp;
+	u32 isp_ram_reg = goodix_fw_update_ctrl.update_info->isp_ram_reg;
+	u8 reg_val[8] = {0x00};
+	int r;
+
+	memset(&isp_fw_version, 0, sizeof(isp_fw_version));
+	fw_isp = &fw_data->fw_summary.subsys[0];
+
+	ts_info("Loading ISP start");
+	r = goodix_reg_write_confirm(isp_ram_reg,
+					(u8 *)fw_isp->data, fw_isp->size);
+	if (r < 0) {
+		ts_err("Loading ISP error");
+		return r;
+	}
+
+	ts_info("Success send ISP data");
+
+	/* SET BOOT OPTION TO 0X55 */
+	memset(reg_val, 0x55, 8);
+	r = goodix_reg_write_confirm(HW_REG_CPU_RUN_FROM, reg_val, 8);
+	if (r < 0) {
+		ts_err("Failed set REG_CPU_RUN_FROM flag");
+		return r;
+	}
+	ts_info("Success write [8]0x55 to 0x%x", HW_REG_CPU_RUN_FROM);
+
+	if (goodix_fw_update_reset(100))
+		ts_err("reset abnormal");
+	/*check isp state */
+	if (get_fw_version_info(&isp_fw_version)) {
+		ts_err("failed read isp version");
+		return -2;
+	}
+	if (memcmp(&isp_fw_version.patch_pid[3], "ISP", 3)) {
+		ts_err("patch id error %c%c%c != %s",
+		isp_fw_version.patch_pid[3], isp_fw_version.patch_pid[4],
+		isp_fw_version.patch_pid[5], "ISP");
+		return -3;
+	}
+	ts_info("ISP running successfully");
+	return 0;
+}
+
+/**
+ * goodix_update_prepare - update prepare, loading ISP program
+ *  and make sure the ISP is running.
+ * @fwu_ctrl: pointer to fimrware control structure
+ * return: 0 ok, <0 error
+ */
+static int goodix_update_prepare(struct fw_update_ctrl *fwu_ctrl)
+{
+	u32 misctl_reg = fwu_ctrl->update_info->misctl_reg;
+	u32 watch_dog_reg = fwu_ctrl->update_info->watch_dog_reg;
+	u32 enable_misctl_val = fwu_ctrl->update_info->enable_misctl_val;
+	u8 reg_val[4] = {0};
+	u8 temp_buf[64] = {0};
+	int retry = 20;
+	int r;
+
+	/*reset IC*/
+	ts_info("firmware update, reset");
+	if (goodix_fw_update_reset(5))
+		ts_err("reset abnormal");
+
+	retry = 100;
+	/* Hold cpu*/
+	do {
+		reg_val[0] = 0x01;
+		reg_val[1] = 0x00;
+		r = goodix_reg_write(HOLD_CPU_REG_W, reg_val, 2);
+		r |= goodix_reg_read(HOLD_CPU_REG_R, &temp_buf[0], 4);
+		r |= goodix_reg_read(HOLD_CPU_REG_R, &temp_buf[4], 4);
+		r |= goodix_reg_read(HOLD_CPU_REG_R, &temp_buf[8], 4);
+		if (!r && !memcmp(&temp_buf[0], &temp_buf[4], 4) &&
+			!memcmp(&temp_buf[4], &temp_buf[8], 4) &&
+			!memcmp(&temp_buf[0], &temp_buf[8], 4)) {
+			break;
+		}
+		usleep_range(1000, 1100);
+		ts_info("retry hold cpu %d", retry);
+		ts_debug("data:%*ph", 12, temp_buf);
+	} while (--retry);
+	if (!retry) {
+		ts_err("Failed to hold CPU, return =%d", r);
+		return -1;
+	}
+	ts_info("Success hold CPU");
+
+	/* enable misctl clock */
+	if (fwu_ctrl->core_data->bus->ic_type == IC_TYPE_BERLIN_D)
+		goodix_reg_write(misctl_reg, (u8 *)&enable_misctl_val, 4);
+	else
+		goodix_reg_write(misctl_reg, (u8 *)&enable_misctl_val, 1);
+	ts_info("enbale misctl clock");
+
+	if (fwu_ctrl->core_data->bus->ic_type == IC_TYPE_BERLIN_A) {
+		/* open ESD_KEY */
+		retry = 20;
+		do {
+			reg_val[0] = 0x95;
+			r = goodix_reg_write(ESD_KEY_REG, reg_val, 1);
+			r |= goodix_reg_read(ESD_KEY_REG, temp_buf, 1);
+			if (!r && temp_buf[0] == 0x01)
+				break;
+			usleep_range(1000, 1100);
+			ts_info("retry %d enable esd key, 0x%x",
+				retry, temp_buf[0]);
+		} while (--retry);
+		if (!retry) {
+			ts_err("Failed to enable esd key, return =%d", r);
+			return -2;
+		}
+		ts_info("success enable esd key");
+	}
+
+	/* disable watch dog */
+	reg_val[0] = 0x00;
+	r = goodix_reg_write(watch_dog_reg, reg_val, 1);
+	ts_info("disable watch dog");
+
+	/* load ISP code and run form isp */
+	r = goodix_load_isp(&fwu_ctrl->fw_data);
+	if (r < 0)
+		ts_err("Failed load and run isp");
+
+	return r;
+}
+
+/*	goodix_send_flash_cmd: send command to read or write flash data
+ *	@flash_cmd: command need to send.
+ */
+static int goodix_send_flash_cmd(struct goodix_flash_cmd *flash_cmd)
+{
+	int i, ret, retry;
+	struct goodix_flash_cmd tmp_cmd;
+	u32 flash_cmd_reg = goodix_fw_update_ctrl.update_info->flash_cmd_reg;
+
+	ts_info("try send flash cmd:%*ph", (int)sizeof(flash_cmd->buf),
+		flash_cmd->buf);
+	memset(tmp_cmd.buf, 0, sizeof(tmp_cmd));
+	ret = goodix_reg_write(flash_cmd_reg,
+		flash_cmd->buf, sizeof(flash_cmd->buf));
+	if (ret) {
+		ts_err("failed send flash cmd %d", ret);
+		return ret;
+	}
+
+	retry = 5;
+	for (i = 0; i < retry; i++) {
+		ret = goodix_reg_read(flash_cmd_reg,
+			tmp_cmd.buf, sizeof(tmp_cmd.buf));
+		if (!ret && tmp_cmd.ack == FLASH_CMD_ACK_CHK_PASS)
+			break;
+		usleep_range(5000, 5100);
+		ts_info("flash cmd ack error retry %d, ack 0x%x, ret %d",
+			i, tmp_cmd.ack, ret);
+	}
+	if (tmp_cmd.ack != FLASH_CMD_ACK_CHK_PASS) {
+		ts_err("flash cmd ack error, ack 0x%x, ret %d",
+			tmp_cmd.ack, ret);
+		ts_err("data:%*ph", (int)sizeof(tmp_cmd.buf), tmp_cmd.buf);
+		return -EINVAL;
+	}
+	ts_info("flash cmd ack check pass");
+
+	msleep(80);
+	retry = 20;
+	for (i = 0; i < retry; i++) {
+		ret = goodix_reg_read(flash_cmd_reg,
+			tmp_cmd.buf, sizeof(tmp_cmd.buf));
+		if (!ret && tmp_cmd.ack == FLASH_CMD_ACK_CHK_PASS &&
+			tmp_cmd.status == FLASH_CMD_W_STATUS_WRITE_OK) {
+			ts_info("flash status check pass");
+			return 0;
+		}
+
+		ts_info("flash cmd status not ready, retry %d, ack 0x%x, status 0x%x, ret %d",
+			i, tmp_cmd.ack, tmp_cmd.status, ret);
+		msleep(20);
+	}
+
+	ts_err("flash cmd status error %d, ack 0x%x, status 0x%x, ret %d",
+		i, tmp_cmd.ack, tmp_cmd.status, ret);
+	if (ret) {
+		ts_info("reason: bus or paltform error");
+		return -EINVAL;
+	}
+
+	switch (tmp_cmd.status) {
+	case FLASH_CMD_W_STATUS_CHK_PASS:
+		ts_err("data check pass, but failed get follow-up results");
+		return -EFAULT;
+	case FLASH_CMD_W_STATUS_CHK_FAIL:
+		ts_err("data check failed, please retry");
+		return -EAGAIN;
+	case FLASH_CMD_W_STATUS_ADDR_ERR:
+		ts_err("flash target addr error, please check");
+		return -EFAULT;
+	case FLASH_CMD_W_STATUS_WRITE_ERR:
+		ts_err("flash data write err, please retry");
+		return -EAGAIN;
+	default:
+		ts_err("unknown status");
+		return -EFAULT;
+	}
+}
+
+static int goodix_flash_package(u8 subsys_type, u8 *pkg,
+	u32 flash_addr, u16 pkg_len)
+{
+	int ret, retry;
+	struct goodix_flash_cmd flash_cmd;
+	u32 isp_buffer_reg = goodix_fw_update_ctrl.update_info->isp_buffer_reg;
+
+	retry = 2;
+	do {
+		ret = goodix_reg_write(isp_buffer_reg, pkg, pkg_len);
+		if (ret < 0) {
+			ts_err("Failed to write firmware packet");
+			return ret;
+		}
+
+		flash_cmd.status = 0;
+		flash_cmd.ack = 0;
+		flash_cmd.len = FLASH_CMD_LEN;
+		flash_cmd.cmd = FLASH_CMD_TYPE_WRITE;
+		flash_cmd.fw_type = subsys_type;
+		flash_cmd.fw_len = cpu_to_le16(pkg_len);
+		flash_cmd.fw_addr = cpu_to_le32(flash_addr);
+
+		goodix_append_checksum(&(flash_cmd.buf[2]),
+				9, CHECKSUM_MODE_U8_LE);
+
+		ret = goodix_send_flash_cmd(&flash_cmd);
+		if (!ret) {
+			ts_info("success write package to 0x%x, len %d",
+				flash_addr, pkg_len - 4);
+			return 0;
+		}
+	} while (ret == -EAGAIN && --retry);
+
+	return ret;
+}
+
+/**
+ * goodix_flash_subsystem - flash subsystem firmware,
+ *  Main flow of flashing firmware.
+ *	Each firmware subsystem is divided into several
+ *	packets, the max size of packet is limited to
+ *	@{ISP_MAX_BUFFERSIZE}
+ * @dev: pointer to touch device
+ * @subsys: subsystem information
+ * return: 0 ok, < 0 error
+ */
+static int goodix_flash_subsystem(struct fw_subsys_info *subsys)
+{
+	u32 data_size, offset;
+	u32 total_size;
+	//TODO: confirm flash addr ,<< 8??
+	u32 subsys_base_addr = subsys->flash_addr;
+	u8 *fw_packet = NULL;
+	int r = 0;
+
+	/*
+	 * if bus(i2c/spi) error occued, then exit, we will do
+	 * hardware reset and re-prepare ISP and then retry
+	 * flashing
+	 */
+	total_size = subsys->size;
+	fw_packet = kzalloc(ISP_MAX_BUFFERSIZE + 4, GFP_KERNEL);
+	if (!fw_packet) {
+		ts_err("Failed alloc memory");
+		return -EINVAL;
+	}
+
+	offset = 0;
+	while (total_size > 0) {
+		data_size = total_size > ISP_MAX_BUFFERSIZE ?
+				ISP_MAX_BUFFERSIZE : total_size;
+		ts_info("Flash firmware to %08x,size:%u bytes",
+			subsys_base_addr + offset, data_size);
+
+		memcpy(fw_packet, &subsys->data[offset], data_size);
+		/* set checksum for package data */
+		goodix_append_checksum(fw_packet,
+				data_size, CHECKSUM_MODE_U16_LE);
+
+		r = goodix_flash_package(subsys->type, fw_packet,
+				subsys_base_addr + offset, data_size + 4);
+		if (r) {
+			ts_err("failed flash to %08x,size:%u bytes",
+			subsys_base_addr + offset, data_size);
+			break;
+		}
+		offset += data_size;
+		total_size -= data_size;
+	} /* end while */
+
+	kfree(fw_packet);
+	return r;
+}
+
+/**
+ * goodix_flash_firmware - flash firmware
+ * @dev: pointer to touch device
+ * @fw_data: firmware data
+ * return: 0 ok, < 0 error
+ */
+static int goodix_flash_firmware(struct fw_update_ctrl *fw_ctrl)
+{
+	struct firmware_data *fw_data = &fw_ctrl->fw_data;
+	struct  firmware_summary  *fw_summary;
+	struct fw_subsys_info *fw_x;
+	struct fw_subsys_info subsys_cfg = {0};
+	u32 config_data_reg = fw_ctrl->update_info->config_data_reg;
+	int retry = GOODIX_BUS_RETRY_TIMES;
+	int i, r = 0, fw_num;
+
+	/*	start from subsystem 1,
+	 *	subsystem 0 is the ISP program
+	 */
+
+	fw_summary = &fw_data->fw_summary;
+	fw_num = fw_summary->subsys_num;
+
+	/* flash config data first if we have */
+	if (fw_ctrl->ic_config && fw_ctrl->ic_config->len) {
+		subsys_cfg.data = fw_ctrl->ic_config->data;
+		subsys_cfg.size = fw_ctrl->ic_config->len;
+		subsys_cfg.flash_addr = config_data_reg;
+		subsys_cfg.type = CONFIG_DATA_TYPE;
+		r = goodix_flash_subsystem(&subsys_cfg);
+		if (r) {
+			ts_err("failed flash config with ISP, %d", r);
+			return r;
+		}
+		ts_info("success flash config with ISP");
+	}
+
+	for (i = 1; i < fw_num && retry;) {
+		ts_info("--- Start to flash subsystem[%d] ---", i);
+		fw_x = &fw_summary->subsys[i];
+		r = goodix_flash_subsystem(fw_x);
+		if (r == 0) {
+			ts_info("--- End flash subsystem[%d]: OK ---", i);
+			i++;
+		} else if (r == -EAGAIN) {
+			retry--;
+			ts_err("--- End flash subsystem%d: Fail, errno:%d, retry:%d ---",
+				i, r, GOODIX_BUS_RETRY_TIMES - retry);
+		} else if (r < 0) { /* bus error */
+			ts_err("--- End flash subsystem%d: Fatal error:%d exit ---",
+				i, r);
+			goto exit_flash;
+		}
+	}
+
+exit_flash:
+	return r;
+}
+
+/**
+ * goodix_update_finish - update finished, FREE resource
+ *  and reset flags---
+ * @fwu_ctrl: pointer to fw_update_ctrl structrue
+ * return: 0 ok, < 0 error
+ */
+static int goodix_update_finish(struct fw_update_ctrl *fwu_ctrl)
+{
+	int ret;
+
+	if (goodix_fw_update_reset(100))
+		ts_err("reset abnormal");
+	ret = goodix_fw_version_compare(fwu_ctrl);
+	if (ret == COMPARE_EQUAL || ret == COMPARE_CFG_NOTEQUAL)
+		return 0;
+
+	return -EINVAL;
+}
+
+/**
+ * goodix_fw_update_proc - firmware update process, the entry of
+ *  firmware update flow
+ * @fwu_ctrl: firmware control
+ * return: = 0 update ok, < 0 error or NO_NEED_UPDATE
+ */
+int goodix_fw_update_proc(struct fw_update_ctrl *fwu_ctrl)
+{
+#define FW_UPDATE_RETRY		2
+	int retry0 = FW_UPDATE_RETRY;
+	int retry1 = FW_UPDATE_RETRY;
+	int ret = 0;
+
+	ret = goodix_parse_firmware(&fwu_ctrl->fw_data);
+	if (ret < 0)
+		return ret;
+
+	if (!(fwu_ctrl->mode & UPDATE_MODE_FORCE)) {
+		ret = goodix_fw_version_compare(fwu_ctrl);
+		if (!ret) {
+			ts_info("firmware upgraded");
+			return 0;
+		} else
+			ts_info("need to upgrade");
+	}
+
+start_update:
+	fwu_ctrl->status = UPSTA_PREPARING;
+	do {
+		ret = goodix_update_prepare(fwu_ctrl);
+		if (ret) {
+			ts_err("failed prepare ISP, retry %d",
+				FW_UPDATE_RETRY - retry0);
+		}
+	} while (ret && --retry0 > 0);
+	if (ret) {
+		ts_err("Failed to prepare ISP, exit update:%d", ret);
+		goto err_fw_prepare;
+	}
+
+	/* progress: 20%~100% */
+	fwu_ctrl->status = UPSTA_UPDATING;
+	ret = goodix_flash_firmware(fwu_ctrl);
+	if (ret < 0 && --retry1 > 0) {
+		ts_err("Bus error, retry firmware update:%d",
+				FW_UPDATE_RETRY - retry1);
+		goto start_update;
+	}
+	if (ret)
+		ts_err("flash fw data enter error, ret:%d", ret);
+	else
+		ts_info("flash fw data success, need check version");
+
+err_fw_prepare:
+	ret = goodix_update_finish(fwu_ctrl);
+	if (!ret)
+		ts_info("Firmware update successfully");
+	else
+		ts_err("Firmware update failed, ret:%d", ret);
+
+	return ret;
+}
+
+/*
+ * goodix_sysfs_update_en_store: start fw update manually
+ * @buf: '1'[001] update in blocking mode with fwdata from sysfs
+ *       '2'[010] update in blocking mode with fwdata from request
+ *       '5'[101] update in unblocking mode with fwdata from sysfs
+ *       '6'[110] update in unblocking mode with fwdata from request
+ */
+static ssize_t goodix_sysfs_update_en_store(
+		struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	int ret = 0;
+	int mode = 0;
+	struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl;
+
+	if (!buf || count <= 0) {
+		ts_err("invalid params");
+		return -EINVAL;
+	}
+	if (!fw_ctrl || !fw_ctrl->initialized) {
+		ts_err("fw module uninit");
+		return -EINVAL;
+	}
+
+	ts_info("set update mode:0x%x", buf[0]);
+	if (buf[0] == '1') {
+		mode = UPDATE_MODE_FORCE | UPDATE_MODE_BLOCK |
+			UPDATE_MODE_SRC_SYSFS;
+	} else if (buf[0] == '2') {
+		mode = UPDATE_MODE_FORCE | UPDATE_MODE_BLOCK |
+			UPDATE_MODE_SRC_REQUEST;
+	} else if (buf[0] == '5') {
+		mode = UPDATE_MODE_FORCE | UPDATE_MODE_SRC_SYSFS;
+	} else if (buf[0] == '6') {
+		mode = UPDATE_MODE_FORCE | UPDATE_MODE_SRC_REQUEST;
+	} else {
+		ts_err("invalid update mode:0x%x", buf[0]);
+		return -EINVAL;
+	}
+
+	ret = goodix_do_fw_update(NULL, mode);
+	if (!ret) {
+		ts_info("success do update work");
+		return count;
+	}
+	ts_err("failed do fw update work");
+	return -EINVAL;
+}
+
+static ssize_t goodix_sysfs_fwsize_show(
+		struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl;
+	int r = -EINVAL;
+
+	if (fw_ctrl && fw_ctrl->fw_data.firmware)
+		r = snprintf(buf, PAGE_SIZE, "%zu\n",
+				fw_ctrl->fw_data.firmware->size);
+	return r;
+}
+
+static ssize_t goodix_sysfs_fwsize_store(
+		struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl;
+	struct firmware *fw;
+	u8 **data;
+	size_t size = 0;
+
+	if (!fw_ctrl)
+		return -EINVAL;
+
+	if (sscanf(buf, "%zu", &size) < 0 || !size) {
+		ts_err("Failed to get fwsize");
+		return -EFAULT;
+	}
+
+	/* use vmalloc to alloc huge memory */
+	fw = vmalloc(sizeof(*fw) + size);
+	if (!fw)
+		return -ENOMEM;
+	mutex_lock(&fw_ctrl->mutex);
+	memset(fw, 0x00, sizeof(*fw) + size);
+	data = (u8 **)&fw->data;
+	*data = (u8 *)fw + sizeof(struct firmware);
+	fw->size = size;
+	fw_ctrl->fw_data.firmware = fw;
+	fw_ctrl->mode = UPDATE_MODE_SRC_SYSFS;
+	mutex_unlock(&fw_ctrl->mutex);
+	return count;
+}
+
+static ssize_t goodix_sysfs_fwimage_store(struct file *file,
+		struct kobject *kobj, struct bin_attribute *attr,
+		char *buf, loff_t pos, size_t count)
+{
+	struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl;
+	struct firmware_data *fw_data;
+
+	fw_data = &fw_ctrl->fw_data;
+
+	if (!fw_data->firmware) {
+		ts_err("Need set fw image size first");
+		return -ENOMEM;
+	}
+
+	if (fw_data->firmware->size == 0) {
+		ts_err("Invalid firmware size");
+		return -EINVAL;
+	}
+
+	if (pos + count > fw_data->firmware->size)
+		return -EFAULT;
+	mutex_lock(&fw_ctrl->mutex);
+	memcpy((u8 *)&fw_data->firmware->data[pos], buf, count);
+	mutex_unlock(&fw_ctrl->mutex);
+	return count;
+}
+
+/* return fw_update result */
+static ssize_t goodix_sysfs_result_show(
+		struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl;
+	char str[GOODIX_MAX_STR_LABLE_LEN] = {0};
+	int r = -EINVAL;
+
+	if (!fw_ctrl)
+		return r;
+
+	switch (fw_ctrl->status) {
+	case UPSTA_PREPARING:
+		sprintf(str, "preparing");
+		break;
+	case UPSTA_UPDATING:
+		sprintf(str, "updating");
+		break;
+	case UPSTA_SUCCESS:
+		sprintf(str, "success");
+		break;
+	case UPSTA_FAILED:
+		sprintf(str, "failed");
+		break;
+	case UPSTA_NOTWORK:
+	default:
+		sprintf(str, "notwork");
+		break;
+	}
+
+	r = snprintf(buf, PAGE_SIZE, "result:%s  spend_time:%dms\n",
+			str, fw_ctrl->spend_time);
+
+	return r;
+}
+
+static DEVICE_ATTR(update_en, 0220, NULL, goodix_sysfs_update_en_store);
+static DEVICE_ATTR(fwsize, 0664, goodix_sysfs_fwsize_show,
+		goodix_sysfs_fwsize_store);
+static DEVICE_ATTR(result, 0664, goodix_sysfs_result_show, NULL);
+
+static struct attribute *goodix_fwu_attrs[] = {
+	&dev_attr_update_en.attr,
+	&dev_attr_fwsize.attr,
+	&dev_attr_result.attr
+};
+
+static int goodix_fw_sysfs_init(struct goodix_ts_core *core_data,
+		struct fw_update_ctrl *fw_ctrl)
+{
+	int ret = 0, i;
+
+	fw_ctrl->kobj = kobject_create_and_add("fwupdate",
+					&core_data->pdev->dev.kobj);
+	if (!fw_ctrl->kobj) {
+		ts_err("failed create sub dir for fwupdate");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs) && !ret; i++)
+		ret = sysfs_create_file(fw_ctrl->kobj, goodix_fwu_attrs[i]);
+
+	if (ret) {
+		ts_err("failed create fwu sysfs files");
+		while (--i >= 0)
+			sysfs_remove_file(fw_ctrl->kobj, goodix_fwu_attrs[i]);
+
+		kobject_put(fw_ctrl->kobj);
+		return -EINVAL;
+	}
+
+	fw_ctrl->attr_fwimage.attr.name = "fwimage";
+	fw_ctrl->attr_fwimage.attr.mode = 0666;
+	fw_ctrl->attr_fwimage.size = 0;
+	fw_ctrl->attr_fwimage.write = goodix_sysfs_fwimage_store;
+	ret = sysfs_create_bin_file(fw_ctrl->kobj, &fw_ctrl->attr_fwimage);
+	if (ret) {
+		ts_err("failed create fwimage bin node, %d", ret);
+		for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs); i++)
+			sysfs_remove_file(fw_ctrl->kobj, goodix_fwu_attrs[i]);
+		kobject_put(fw_ctrl->kobj);
+	}
+
+	return ret;
+}
+
+static void goodix_fw_sysfs_remove(void)
+{
+	struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl;
+	int i;
+
+	sysfs_remove_bin_file(fw_ctrl->kobj, &fw_ctrl->attr_fwimage);
+
+	for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs); i++)
+		sysfs_remove_file(fw_ctrl->kobj,
+				goodix_fwu_attrs[i]);
+
+	kobject_put(fw_ctrl->kobj);
+}
+
+
+/**
+ * goodix_request_firmware - request firmware data from user space
+ *
+ * @fw_data: firmware struct, contains firmware header info
+ *	and firmware data pointer.
+ * return: 0 - OK, < 0 - error
+ */
+static int goodix_request_firmware(struct firmware_data *fw_data,
+				const char *name)
+{
+	struct fw_update_ctrl *fw_ctrl =
+		container_of(fw_data, struct fw_update_ctrl, fw_data);
+	struct device *dev = &(fw_ctrl->core_data->pdev->dev);
+	int r;
+	int retry = GOODIX_RETRY_3;
+
+	ts_info("Request firmware image [%s]", name);
+
+	while (retry--) {
+		r = request_firmware(&fw_data->firmware, name, dev);
+		if (!r)
+			break;
+		ts_info("get fw bin retry:[%d]", GOODIX_RETRY_3 - retry);
+		msleep(200);
+	}
+	if (retry < 0) {
+		ts_err("Firmware image [%s] not available,errno:%d", name, r);
+		return r;
+	}
+
+	ts_info("Firmware image [%s] is ready", name);
+	return 0;
+}
+
+/**
+ * relase firmware resources
+ *
+ */
+static inline void goodix_release_firmware(struct firmware_data *fw_data)
+{
+	if (fw_data->firmware) {
+		release_firmware(fw_data->firmware);
+		fw_data->firmware = NULL;
+	}
+}
+
+static int goodix_fw_update_thread(void *data)
+{
+	struct fw_update_ctrl *fwu_ctrl = data;
+	struct firmware *temp_firmware = NULL;
+	ktime_t start, end;
+	int r = -EINVAL;
+
+	start = ktime_get();
+	fwu_ctrl->spend_time = 0;
+	fwu_ctrl->status = UPSTA_NOTWORK;
+	mutex_lock(&fwu_ctrl->mutex);
+
+	if (fwu_ctrl->mode & UPDATE_MODE_SRC_REQUEST) {
+		ts_info("Firmware request update starts");
+		r = goodix_request_firmware(&fwu_ctrl->fw_data,
+						fwu_ctrl->fw_name);
+		if (r < 0)
+			goto out;
+
+	} else if (fwu_ctrl->mode & UPDATE_MODE_SRC_SYSFS) {
+		if (!fwu_ctrl->fw_data.firmware) {
+			ts_err("Invalid firmware from sysfs");
+			r = -EINVAL;
+			goto out;
+		}
+	} else {
+		ts_err("unknown update mode 0x%x", fwu_ctrl->mode);
+		r = -EINVAL;
+		goto out;
+	}
+
+	ts_debug("notify update start");
+	goodix_ts_blocking_notify(NOTIFY_FWUPDATE_START, NULL);
+
+	/* ready to update */
+	ts_debug("start update proc");
+	r = goodix_fw_update_proc(fwu_ctrl);
+
+	/* clean */
+	if (fwu_ctrl->mode & UPDATE_MODE_SRC_HEAD) {
+		kfree(fwu_ctrl->fw_data.firmware);
+		fwu_ctrl->fw_data.firmware = NULL;
+		temp_firmware = NULL;
+	} else if (fwu_ctrl->mode & UPDATE_MODE_SRC_REQUEST) {
+		goodix_release_firmware(&fwu_ctrl->fw_data);
+	}
+out:
+	fwu_ctrl->mode = UPDATE_MODE_DEFAULT;
+	mutex_unlock(&fwu_ctrl->mutex);
+
+	if (r) {
+		ts_err("fw update failed, %d", r);
+		fwu_ctrl->status = UPSTA_FAILED;
+		goodix_ts_blocking_notify(NOTIFY_FWUPDATE_FAILED, NULL);
+	} else {
+		ts_info("fw update success");
+		fwu_ctrl->status = UPSTA_SUCCESS;
+		goodix_ts_blocking_notify(NOTIFY_FWUPDATE_SUCCESS, NULL);
+	}
+
+	end = ktime_get();
+	fwu_ctrl->spend_time = ktime_to_ms(ktime_sub(end, start));
+
+	return r;
+}
+
+int goodix_do_fw_update(struct goodix_ic_config *ic_config, int mode)
+{
+	struct task_struct *fwu_thrd;
+	struct fw_update_ctrl *fwu_ctrl = &goodix_fw_update_ctrl;
+	int ret;
+
+	if (!fwu_ctrl->initialized) {
+		ts_err("fw mode uninit");
+		return -EINVAL;
+	}
+
+	fwu_ctrl->mode = mode;
+	fwu_ctrl->ic_config = ic_config;
+	ts_debug("fw update mode 0x%x", mode);
+	if (fwu_ctrl->mode & UPDATE_MODE_BLOCK) {
+		ret = goodix_fw_update_thread(fwu_ctrl);
+		ts_info("fw update return %d", ret);
+		return ret;
+	}
+	/* create and run update thread */
+	fwu_thrd = kthread_run(goodix_fw_update_thread,
+			fwu_ctrl, "goodix-fwu");
+	if (IS_ERR_OR_NULL(fwu_thrd)) {
+		ts_err("Failed to create update thread:%ld",
+				PTR_ERR(fwu_thrd));
+		return -EFAULT;
+	}
+	ts_info("success create fw update thread");
+	return 0;
+}
+
+int goodix_fw_update_init(struct goodix_ts_core *core_data)
+{
+	int ret;
+
+	if (!core_data || !core_data->hw_ops) {
+		ts_err("core_data && hw_ops cann't be null");
+		return -ENODEV;
+	}
+
+	mutex_init(&goodix_fw_update_ctrl.mutex);
+	goodix_fw_update_ctrl.core_data = core_data;
+	goodix_fw_update_ctrl.mode = 0;
+
+	strlcpy(goodix_fw_update_ctrl.fw_name, core_data->board_data.fw_name,
+		sizeof(goodix_fw_update_ctrl.fw_name));
+
+	ret = goodix_fw_sysfs_init(core_data, &goodix_fw_update_ctrl);
+	if (ret) {
+		ts_err("failed create fwupate sysfs node");
+		return ret;
+	}
+	if (core_data->bus->ic_type == IC_TYPE_BERLIN_A)
+		goodix_fw_update_ctrl.update_info = &update_bra;
+	else if (core_data->bus->ic_type == IC_TYPE_BERLIN_B)
+		goodix_fw_update_ctrl.update_info = &update_brb;
+	else
+		goodix_fw_update_ctrl.update_info = &update_brd;
+
+	goodix_fw_update_ctrl.initialized = 1;
+	return 0;
+}
+
+void goodix_fw_update_uninit(void)
+{
+	if (!goodix_fw_update_ctrl.initialized)
+		return;
+
+	mutex_lock(&goodix_fw_update_ctrl.mutex);
+	goodix_fw_sysfs_remove();
+	goodix_fw_update_ctrl.initialized = 0;
+	mutex_unlock(&goodix_fw_update_ctrl.mutex);
+	mutex_destroy(&goodix_fw_update_ctrl.mutex);
+}

+ 1434 - 0
goodix_brl_hw.c

@@ -0,0 +1,1434 @@
+ /*
+  * Goodix Touchscreen Driver
+  * Copyright (C) 2020 - 2021 Goodix, Inc.
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be a reference
+  * to you, when you are integrating the GOODiX's CTP IC into your system,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  */
+#include "goodix_ts_core.h"
+
+/* berlin_A SPI mode setting */
+#define GOODIX_SPI_MODE_REG			0xC900
+#define GOODIX_SPI_NORMAL_MODE_0	0x01
+
+/* berlin_A D12 setting */
+#define GOODIX_REG_CLK_STA0			0xD807
+#define GOODIX_CLK_STA0_ENABLE		0xFF
+#define GOODIX_REG_CLK_STA1			0xD806
+#define GOODIX_CLK_STA1_ENABLE		0x77
+#define GOODIX_REG_TRIM_D12			0xD006
+#define GOODIX_TRIM_D12_LEVEL		0x3C
+#define GOODIX_REG_RESET			0xD808
+#define GOODIX_RESET_EN				0xFA
+#define HOLD_CPU_REG_W				0x0002
+#define HOLD_CPU_REG_R				0x2000
+
+#define DEV_CONFIRM_VAL				0xAA
+#define BOOTOPTION_ADDR				0x10000
+#define FW_VERSION_INFO_ADDR_BRA	0x1000C
+#define FW_VERSION_INFO_ADDR		0x10014
+
+#define GOODIX_IC_INFO_MAX_LEN		1024
+#define GOODIX_IC_INFO_ADDR_BRA		0x10068
+#define GOODIX_IC_INFO_ADDR			0x10070
+
+
+enum brl_request_code {
+	BRL_REQUEST_CODE_CONFIG = 0x01,
+	BRL_REQUEST_CODE_REF_ERR = 0x02,
+	BRL_REQUEST_CODE_RESET = 0x03,
+	BRL_REQUEST_CODE_CLOCK = 0x04,
+};
+
+static int brl_select_spi_mode(struct goodix_ts_core *cd)
+{
+	int ret;
+	int i;
+	u8 w_value = GOODIX_SPI_NORMAL_MODE_0;
+	u8 r_value;
+
+	if (cd->bus->bus_type == GOODIX_BUS_TYPE_I2C ||
+			cd->bus->ic_type != IC_TYPE_BERLIN_A)
+		return 0;
+
+	for (i = 0; i < GOODIX_RETRY_5; i++) {
+		cd->hw_ops->write(cd, GOODIX_SPI_MODE_REG,
+				&w_value, 1);
+		ret = cd->hw_ops->read(cd, GOODIX_SPI_MODE_REG,
+				&r_value, 1);
+		if (!ret && r_value == w_value)
+			return 0;
+	}
+	ts_err("failed switch SPI mode after reset, ret:%d r_value:%02x", ret, r_value);
+	return -EINVAL;
+}
+
+static int brl_dev_confirm(struct goodix_ts_core *cd)
+{
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+	int ret = 0;
+	int retry = GOODIX_RETRY_3;
+	u8 tx_buf[8] = {0};
+	u8 rx_buf[8] = {0};
+
+	memset(tx_buf, DEV_CONFIRM_VAL, sizeof(tx_buf));
+	while (retry--) {
+		ret = hw_ops->write(cd, BOOTOPTION_ADDR,
+			tx_buf, sizeof(tx_buf));
+		if (ret < 0)
+			return ret;
+		ret = hw_ops->read(cd, BOOTOPTION_ADDR,
+			rx_buf, sizeof(rx_buf));
+		if (ret < 0)
+			return ret;
+		if (!memcmp(tx_buf, rx_buf, sizeof(tx_buf)))
+			break;
+		usleep_range(5000, 5100);
+	}
+
+	if (retry < 0) {
+		ret = -EINVAL;
+		ts_err("device confirm failed, rx_buf:%*ph", 8, rx_buf);
+	}
+
+	ts_info("device connected");
+	return ret;
+}
+
+static int brl_reset_after(struct goodix_ts_core *cd)
+{
+	u8 reg_val[2] = {0};
+	u8 temp_buf[12] = {0};
+	int ret;
+	int retry;
+
+	if (cd->bus->ic_type != IC_TYPE_BERLIN_A)
+		return 0;
+
+	ts_info("IN");
+
+	/* select spi mode */
+	ret = brl_select_spi_mode(cd);
+	if (ret < 0)
+		return ret;
+
+	/* hold cpu */
+	retry = GOODIX_RETRY_10;
+	while (retry--) {
+		reg_val[0] = 0x01;
+		reg_val[1] = 0x00;
+		ret = cd->hw_ops->write(cd, HOLD_CPU_REG_W, reg_val, 2);
+		ret |= cd->hw_ops->read(cd, HOLD_CPU_REG_R, &temp_buf[0], 4);
+		ret |= cd->hw_ops->read(cd, HOLD_CPU_REG_R, &temp_buf[4], 4);
+		ret |= cd->hw_ops->read(cd, HOLD_CPU_REG_R, &temp_buf[8], 4);
+		if (!ret && !memcmp(&temp_buf[0], &temp_buf[4], 4) &&
+			!memcmp(&temp_buf[4], &temp_buf[8], 4) &&
+			!memcmp(&temp_buf[0], &temp_buf[8], 4)) {
+			break;
+		}
+	}
+	if (retry < 0) {
+		ts_err("failed to hold cpu, status:%*ph", 12, temp_buf);
+		return -EINVAL;
+	}
+
+	/* enable sta0 clk */
+	retry = GOODIX_RETRY_5;
+	while (retry--) {
+		reg_val[0] = GOODIX_CLK_STA0_ENABLE;
+		ret = cd->hw_ops->write(cd, GOODIX_REG_CLK_STA0, reg_val, 1);
+		ret |= cd->hw_ops->read(cd, GOODIX_REG_CLK_STA0, temp_buf, 1);
+		if (!ret && temp_buf[0] == GOODIX_CLK_STA0_ENABLE)
+			break;
+	}
+	if (retry < 0) {
+		ts_err("failed to enable group0 clock, ret:%d status:%02x", ret, temp_buf[0]);
+		return -EINVAL;
+	}
+
+	/* enable sta1 clk */
+	retry = GOODIX_RETRY_5;
+	while (retry--) {
+		reg_val[0] = GOODIX_CLK_STA1_ENABLE;
+		ret = cd->hw_ops->write(cd, GOODIX_REG_CLK_STA1, reg_val, 1);
+		ret |= cd->hw_ops->read(cd, GOODIX_REG_CLK_STA1, temp_buf, 1);
+		if (!ret && temp_buf[0] == GOODIX_CLK_STA1_ENABLE)
+			break;
+	}
+	if (retry < 0) {
+		ts_err("failed to enable group1 clock, ret:%d status:%02x", ret, temp_buf[0]);
+		return -EINVAL;
+	}
+
+	/* set D12 level */
+	retry = GOODIX_RETRY_5;
+	while (retry--) {
+		reg_val[0] = GOODIX_TRIM_D12_LEVEL;
+		ret = cd->hw_ops->write(cd, GOODIX_REG_TRIM_D12, reg_val, 1);
+		ret |= cd->hw_ops->read(cd, GOODIX_REG_TRIM_D12, temp_buf, 1);
+		if (!ret && temp_buf[0] == GOODIX_TRIM_D12_LEVEL)
+			break;
+	}
+	if (retry < 0) {
+		ts_err("failed to set D12, ret:%d status:%02x", ret, temp_buf[0]);
+		return -EINVAL;
+	}
+
+	usleep_range(5000, 5100);
+	/* soft reset */
+	reg_val[0] = GOODIX_RESET_EN;
+	ret = cd->hw_ops->write(cd, GOODIX_REG_RESET, reg_val, 1);
+	if (ret < 0)
+		return ret;
+
+	/* select spi mode */
+	ret = brl_select_spi_mode(cd);
+	if (ret < 0)
+		return ret;
+
+	ts_info("OUT");
+
+	return 0;
+}
+
+static int brl_power_on(struct goodix_ts_core *cd, bool on)
+{
+	int ret = 0;
+	int iovdd_gpio = cd->board_data.iovdd_gpio;
+	int avdd_gpio = cd->board_data.avdd_gpio;
+	int reset_gpio = cd->board_data.reset_gpio;
+
+	if (on) {
+		if (iovdd_gpio > 0) {
+			gpio_direction_output(iovdd_gpio, 1);
+		} else if (cd->iovdd) {
+			ret = regulator_enable(cd->iovdd);
+			if (ret < 0) {
+				ts_err("Failed to enable iovdd:%d", ret);
+				goto power_off;
+			}
+		}
+		usleep_range(3000, 3100);
+		if (avdd_gpio > 0) {
+			gpio_direction_output(avdd_gpio, 1);
+		} else if (cd->avdd) {
+			ret = regulator_enable(cd->avdd);
+			if (ret < 0) {
+				ts_err("Failed to enable avdd:%d", ret);
+				goto power_off;
+			}
+		}
+		usleep_range(15000, 15100);
+		gpio_direction_output(reset_gpio, 1);
+		usleep_range(4000, 4100);
+		ret = brl_dev_confirm(cd);
+		if (ret < 0)
+			goto power_off;
+		ret = brl_reset_after(cd);
+		if (ret < 0)
+			goto power_off;
+
+		msleep(GOODIX_NORMAL_RESET_DELAY_MS);
+		return 0;
+	}
+
+power_off:
+	gpio_direction_output(reset_gpio, 0);
+	if (iovdd_gpio > 0)
+		gpio_direction_output(iovdd_gpio, 0);
+	else if (cd->iovdd)
+		regulator_disable(cd->iovdd);
+	if (avdd_gpio > 0)
+		gpio_direction_output(avdd_gpio, 0);
+	else if (cd->avdd)
+		regulator_disable(cd->avdd);
+	return ret;
+}
+
+#define GOODIX_SLEEP_CMD	0x84
+int brl_suspend(struct goodix_ts_core *cd)
+{
+	struct goodix_ts_cmd sleep_cmd;
+
+	sleep_cmd.cmd = GOODIX_SLEEP_CMD;
+	sleep_cmd.len = 4;
+	if (cd->hw_ops->send_cmd(cd, &sleep_cmd))
+		ts_err("failed send sleep cmd");
+
+	return 0;
+}
+
+int brl_resume(struct goodix_ts_core *cd)
+{
+	return cd->hw_ops->reset(cd, GOODIX_NORMAL_RESET_DELAY_MS);
+}
+
+#define GOODIX_GESTURE_CMD	0x12
+int brl_gesture(struct goodix_ts_core *cd, int gesture_type)
+{
+	struct goodix_ts_cmd cmd;
+
+	cmd.cmd = GOODIX_GESTURE_CMD;
+	cmd.len = 5;
+	cmd.data[0] = gesture_type;
+	if (cd->hw_ops->send_cmd(cd, &cmd))
+		ts_err("failed send gesture cmd");
+
+	return 0;
+}
+
+static int brl_reset(struct goodix_ts_core *cd, int delay)
+{
+	ts_info("chip_reset");
+
+	gpio_direction_output(cd->board_data.reset_gpio, 0);
+	usleep_range(2000, 2100);
+	gpio_direction_output(cd->board_data.reset_gpio, 1);
+	if (delay < 20)
+		usleep_range(delay * 1000, delay * 1000 + 100);
+	else
+		msleep(delay);
+
+	return brl_select_spi_mode(cd);
+}
+
+static int brl_irq_enbale(struct goodix_ts_core *cd, bool enable)
+{
+	if (enable && !atomic_cmpxchg(&cd->irq_enabled, 0, 1)) {
+		enable_irq(cd->irq);
+		ts_debug("Irq enabled");
+		return 0;
+	}
+
+	if (!enable && atomic_cmpxchg(&cd->irq_enabled, 1, 0)) {
+		disable_irq(cd->irq);
+		ts_debug("Irq disabled");
+		return 0;
+	}
+	ts_info("warnning: irq deepth inbalance!");
+	return 0;
+}
+
+static int brl_read(struct goodix_ts_core *cd, unsigned int addr,
+		unsigned char *data, unsigned int len)
+{
+	struct goodix_bus_interface *bus = cd->bus;
+
+	return bus->read(bus->dev, addr, data, len);
+}
+
+static int brl_write(struct goodix_ts_core *cd, unsigned int addr,
+		 unsigned char *data, unsigned int len)
+{
+	struct goodix_bus_interface *bus = cd->bus;
+
+	return bus->write(bus->dev, addr, data, len);
+}
+
+/* command ack info */
+#define CMD_ACK_IDLE             0x01
+#define CMD_ACK_BUSY             0x02
+#define CMD_ACK_BUFFER_OVERFLOW  0x03
+#define CMD_ACK_CHECKSUM_ERROR   0x04
+#define CMD_ACK_OK               0x80
+
+#define GOODIX_CMD_RETRY 6
+static int brl_send_cmd(struct goodix_ts_core *cd,
+	struct goodix_ts_cmd *cmd)
+{
+	int ret, retry, i;
+	struct goodix_ts_cmd cmd_ack;
+	struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+
+	cmd->state = 0;
+	cmd->ack = 0;
+	goodix_append_checksum(&(cmd->buf[2]), cmd->len - 2,
+		CHECKSUM_MODE_U8_LE);
+	ts_debug("cmd data %*ph", cmd->len, &(cmd->buf[2]));
+
+	retry = 0;
+	while (retry++ < GOODIX_CMD_RETRY) {
+		ret = hw_ops->write(cd, misc->cmd_addr,
+				    cmd->buf, sizeof(*cmd));
+		if (ret < 0) {
+			ts_err("failed write command");
+			return ret;
+		}
+		for (i = 0; i < GOODIX_CMD_RETRY; i++) {
+			/* check command result */
+			ret = hw_ops->read(cd, misc->cmd_addr,
+				cmd_ack.buf, sizeof(cmd_ack));
+			if (ret < 0) {
+				ts_err("failed read command ack, %d", ret);
+				return ret;
+			}
+			ts_debug("cmd ack data %*ph",
+				 (int)sizeof(cmd_ack), cmd_ack.buf);
+			if (cmd_ack.ack == CMD_ACK_OK) {
+				usleep_range(2000, 2100);
+				return 0;
+			}
+			if (cmd_ack.ack == CMD_ACK_BUSY ||
+			    cmd_ack.ack == 0x00) {
+				usleep_range(1000, 1100);
+				continue;
+			}
+			if (cmd_ack.ack == CMD_ACK_BUFFER_OVERFLOW)
+				usleep_range(10000, 11000);
+			usleep_range(1000, 1100);
+			break;
+		}
+	}
+	ts_err("failed get valid cmd ack");
+	return -EINVAL;
+}
+
+#pragma  pack(1)
+struct goodix_config_head {
+	union {
+		struct {
+			u8 panel_name[8];
+			u8 fw_pid[8];
+			u8 fw_vid[4];
+			u8 project_name[8];
+			u8 file_ver[2];
+			u32 cfg_id;
+			u8 cfg_ver;
+			u8 cfg_time[8];
+			u8 reserved[15];
+			u8 flag;
+			u16 cfg_len;
+			u8 cfg_num;
+			u16 checksum;
+		};
+		u8 buf[64];
+	};
+};
+#pragma pack()
+
+#define CONFIG_CND_LEN			4
+#define CONFIG_CMD_START		0x04
+#define CONFIG_CMD_WRITE		0x05
+#define CONFIG_CMD_EXIT			0x06
+#define CONFIG_CMD_READ_START	0x07
+#define CONFIG_CMD_READ_EXIT	0x08
+
+#define CONFIG_CMD_STATUS_PASS	0x80
+#define CONFIG_CMD_WAIT_RETRY	20
+
+static int wait_cmd_status(struct goodix_ts_core *cd,
+	u8 target_status, int retry)
+{
+	struct goodix_ts_cmd cmd_ack;
+	struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+	int i, ret;
+
+	for (i = 0; i < retry; i++) {
+		ret = hw_ops->read(cd, misc->cmd_addr, cmd_ack.buf,
+			sizeof(cmd_ack));
+		if (!ret && cmd_ack.state == target_status) {
+			ts_debug("status check pass");
+			return 0;
+		}
+		ts_debug("cmd buf %*ph", (int)sizeof(cmd_ack), cmd_ack.buf);
+		msleep(20);
+	}
+
+	ts_err("cmd status not ready, retry %d, ack 0x%x, status 0x%x, ret %d",
+			i, cmd_ack.ack, cmd_ack.state, ret);
+	return -EINVAL;
+}
+
+static int send_cfg_cmd(struct goodix_ts_core *cd,
+	struct goodix_ts_cmd *cfg_cmd)
+{
+	int ret;
+
+	ret = cd->hw_ops->send_cmd(cd, cfg_cmd);
+	if (ret) {
+		ts_err("failed write cfg prepare cmd %d", ret);
+		return ret;
+	}
+	ret = wait_cmd_status(cd, CONFIG_CMD_STATUS_PASS,
+		CONFIG_CMD_WAIT_RETRY);
+	if (ret) {
+		ts_err("failed wait for fw ready for config, %d", ret);
+		return ret;
+	}
+	return 0;
+}
+
+static int brl_send_config(struct goodix_ts_core *cd, u8 *cfg, int len)
+{
+	int ret;
+	u8 *tmp_buf;
+	struct goodix_ts_cmd cfg_cmd;
+	struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+
+	if (len > misc->fw_buffer_max_len) {
+		ts_err("config len exceed limit %d > %d",
+			len, misc->fw_buffer_max_len);
+		return -EINVAL;
+	}
+
+	tmp_buf = kzalloc(len, GFP_KERNEL);
+	if (!tmp_buf)
+		return -ENOMEM;
+
+	cfg_cmd.len = CONFIG_CND_LEN;
+	cfg_cmd.cmd = CONFIG_CMD_START;
+	ret = send_cfg_cmd(cd, &cfg_cmd);
+	if (ret) {
+		ts_err("failed write cfg prepare cmd %d", ret);
+		goto exit;
+	}
+
+	ts_debug("try send config to 0x%x, len %d", misc->fw_buffer_addr, len);
+	ret = hw_ops->write(cd, misc->fw_buffer_addr, cfg, len);
+	if (ret) {
+		ts_err("failed write config data, %d", ret);
+		goto exit;
+	}
+	ret = hw_ops->read(cd, misc->fw_buffer_addr, tmp_buf, len);
+	if (ret) {
+		ts_err("failed read back config data");
+		goto exit;
+	}
+
+	if (memcmp(cfg, tmp_buf, len)) {
+		ts_err("config data read back compare file");
+		ret = -EINVAL;
+		goto exit;
+	}
+	/* notify fw for recive config */
+	memset(cfg_cmd.buf, 0, sizeof(cfg_cmd));
+	cfg_cmd.len = CONFIG_CND_LEN;
+	cfg_cmd.cmd = CONFIG_CMD_WRITE;
+	ret = send_cfg_cmd(cd, &cfg_cmd);
+	if (ret)
+		ts_err("failed send config data ready cmd %d", ret);
+
+exit:
+	memset(cfg_cmd.buf, 0, sizeof(cfg_cmd));
+	cfg_cmd.len = CONFIG_CND_LEN;
+	cfg_cmd.cmd = CONFIG_CMD_EXIT;
+	if (send_cfg_cmd(cd, &cfg_cmd)) {
+		ts_err("failed send config write end command");
+		ret = -EINVAL;
+	}
+
+	if (!ret) {
+		ts_info("success send config");
+		msleep(100);
+	}
+
+	kfree(tmp_buf);
+	return ret;
+}
+
+/*
+ * return: return config length on succes, other wise return < 0
+ **/
+static int brl_read_config(struct goodix_ts_core *cd, u8 *cfg, int size)
+{
+	int ret;
+	struct goodix_ts_cmd cfg_cmd;
+	struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+	struct goodix_config_head cfg_head;
+
+	if (!cfg)
+		return -EINVAL;
+
+	cfg_cmd.len = CONFIG_CND_LEN;
+	cfg_cmd.cmd = CONFIG_CMD_READ_START;
+	ret = send_cfg_cmd(cd, &cfg_cmd);
+	if (ret) {
+		ts_err("failed send config read prepare command");
+		return ret;
+	}
+
+	ret = hw_ops->read(cd, misc->fw_buffer_addr,
+			   cfg_head.buf, sizeof(cfg_head));
+	if (ret) {
+		ts_err("failed read config head %d", ret);
+		goto exit;
+	}
+
+	if (checksum_cmp(cfg_head.buf, sizeof(cfg_head), CHECKSUM_MODE_U8_LE)) {
+		ts_err("config head checksum error");
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	cfg_head.cfg_len = le16_to_cpu(cfg_head.cfg_len);
+	if (cfg_head.cfg_len > misc->fw_buffer_max_len ||
+	    cfg_head.cfg_len > size) {
+		ts_err("cfg len exceed buffer size %d > %d", cfg_head.cfg_len,
+			 misc->fw_buffer_max_len);
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	memcpy(cfg, cfg_head.buf, sizeof(cfg_head));
+	ret = hw_ops->read(cd, misc->fw_buffer_addr + sizeof(cfg_head),
+			   cfg + sizeof(cfg_head), cfg_head.cfg_len);
+	if (ret) {
+		ts_err("failed read cfg pack, %d", ret);
+		goto exit;
+	}
+
+	ts_info("config len %d", cfg_head.cfg_len);
+	if (checksum_cmp(cfg + sizeof(cfg_head),
+			 cfg_head.cfg_len, CHECKSUM_MODE_U16_LE)) {
+		ts_err("config body checksum error");
+		ret = -EINVAL;
+		goto exit;
+	}
+	ts_info("success read config data: len %zu",
+		cfg_head.cfg_len + sizeof(cfg_head));
+exit:
+	memset(cfg_cmd.buf, 0, sizeof(cfg_cmd));
+	cfg_cmd.len = CONFIG_CND_LEN;
+	cfg_cmd.cmd = CONFIG_CMD_READ_EXIT;
+	if (send_cfg_cmd(cd, &cfg_cmd)) {
+		ts_err("failed send config read finish command");
+		ret = -EINVAL;
+	}
+	if (ret)
+		return -EINVAL;
+	return cfg_head.cfg_len + sizeof(cfg_head);
+}
+
+/*
+ *	return: 0 for no error.
+ *	GOODIX_EBUS when encounter a bus error
+ *	GOODIX_ECHECKSUM version checksum error
+ *	GOODIX_EVERSION  patch ID compare failed,
+ *	in this case the sensorID is valid.
+ */
+static int brl_read_version(struct goodix_ts_core *cd,
+			struct goodix_fw_version *version)
+{
+	int ret, i;
+	u32 fw_addr;
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+	u8 buf[sizeof(struct goodix_fw_version)] = {0};
+	u8 temp_pid[8] = {0};
+
+	if (cd->bus->ic_type == IC_TYPE_BERLIN_A)
+		fw_addr = FW_VERSION_INFO_ADDR_BRA;
+	else
+		fw_addr = FW_VERSION_INFO_ADDR;
+
+	for (i = 0; i < 2; i++) {
+		ret = hw_ops->read(cd, fw_addr, buf, sizeof(buf));
+		if (ret) {
+			ts_info("read fw version: %d, retry %d", ret, i);
+			ret = -GOODIX_EBUS;
+			usleep_range(5000, 5100);
+			continue;
+		}
+
+		if (!checksum_cmp(buf, sizeof(buf), CHECKSUM_MODE_U8_LE))
+			break;
+
+		ts_info("invalid fw version: checksum error!");
+		ts_info("fw version:%*ph", (int)sizeof(buf), buf);
+		ret = -GOODIX_ECHECKSUM;
+		usleep_range(10000, 11000);
+	}
+	if (ret) {
+		ts_err("failed get valied fw version");
+		return ret;
+	}
+	memcpy(version, buf, sizeof(*version));
+	memcpy(temp_pid, version->rom_pid, sizeof(version->rom_pid));
+	ts_info("rom_pid:%s", temp_pid);
+	ts_info("rom_vid:%*ph", (int)sizeof(version->rom_vid),
+		version->rom_vid);
+	ts_info("pid:%s", version->patch_pid);
+	ts_info("vid:%*ph", (int)sizeof(version->patch_vid),
+		version->patch_vid);
+	ts_info("sensor_id:%d", version->sensor_id);
+
+	return 0;
+}
+
+#define LE16_TO_CPU(x)  (x = le16_to_cpu(x))
+#define LE32_TO_CPU(x)  (x = le32_to_cpu(x))
+static int convert_ic_info(struct goodix_ic_info *info, const u8 *data)
+{
+	int i;
+	struct goodix_ic_info_version *version = &info->version;
+	struct goodix_ic_info_feature *feature = &info->feature;
+	struct goodix_ic_info_param *parm = &info->parm;
+	struct goodix_ic_info_misc *misc = &info->misc;
+
+	info->length = le16_to_cpup((__le16 *)data);
+
+	data += 2;
+	memcpy(version, data, sizeof(*version));
+	version->config_id = le32_to_cpu(version->config_id);
+
+	data += sizeof(struct goodix_ic_info_version);
+	memcpy(feature, data, sizeof(*feature));
+	feature->freqhop_feature =
+		le16_to_cpu(feature->freqhop_feature);
+	feature->calibration_feature =
+		le16_to_cpu(feature->calibration_feature);
+	feature->gesture_feature =
+		le16_to_cpu(feature->gesture_feature);
+	feature->side_touch_feature =
+		le16_to_cpu(feature->side_touch_feature);
+	feature->stylus_feature =
+		le16_to_cpu(feature->stylus_feature);
+
+	data += sizeof(struct goodix_ic_info_feature);
+	parm->drv_num = *(data++);
+	parm->sen_num = *(data++);
+	parm->button_num = *(data++);
+	parm->force_num = *(data++);
+	parm->active_scan_rate_num = *(data++);
+	if (parm->active_scan_rate_num > MAX_SCAN_RATE_NUM) {
+		ts_err("invalid scan rate num %d > %d",
+			parm->active_scan_rate_num, MAX_SCAN_RATE_NUM);
+		return -EINVAL;
+	}
+	for (i = 0; i < parm->active_scan_rate_num; i++)
+		parm->active_scan_rate[i] =
+			le16_to_cpup((__le16 *)(data + i * 2));
+
+	data += parm->active_scan_rate_num * 2;
+	parm->mutual_freq_num = *(data++);
+	if (parm->mutual_freq_num > MAX_SCAN_FREQ_NUM) {
+		ts_err("invalid mntual freq num %d > %d",
+			parm->mutual_freq_num, MAX_SCAN_FREQ_NUM);
+		return -EINVAL;
+	}
+	for (i = 0; i < parm->mutual_freq_num; i++)
+		parm->mutual_freq[i] =
+			le16_to_cpup((__le16 *)(data + i * 2));
+
+	data += parm->mutual_freq_num * 2;
+	parm->self_tx_freq_num = *(data++);
+	if (parm->self_tx_freq_num > MAX_SCAN_FREQ_NUM) {
+		ts_err("invalid tx freq num %d > %d",
+			parm->self_tx_freq_num, MAX_SCAN_FREQ_NUM);
+		return -EINVAL;
+	}
+	for (i = 0; i < parm->self_tx_freq_num; i++)
+		parm->self_tx_freq[i] =
+			le16_to_cpup((__le16 *)(data + i * 2));
+
+	data += parm->self_tx_freq_num * 2;
+	parm->self_rx_freq_num = *(data++);
+	if (parm->self_rx_freq_num > MAX_SCAN_FREQ_NUM) {
+		ts_err("invalid rx freq num %d > %d",
+			parm->self_rx_freq_num, MAX_SCAN_FREQ_NUM);
+		return -EINVAL;
+	}
+	for (i = 0; i < parm->self_rx_freq_num; i++)
+		parm->self_rx_freq[i] =
+			le16_to_cpup((__le16 *)(data + i * 2));
+
+	data += parm->self_rx_freq_num * 2;
+	parm->stylus_freq_num = *(data++);
+	if (parm->stylus_freq_num > MAX_FREQ_NUM_STYLUS) {
+		ts_err("invalid stylus freq num %d > %d",
+			parm->stylus_freq_num, MAX_FREQ_NUM_STYLUS);
+		return -EINVAL;
+	}
+	for (i = 0; i < parm->stylus_freq_num; i++)
+		parm->stylus_freq[i] =
+			le16_to_cpup((__le16 *)(data + i * 2));
+
+	data += parm->stylus_freq_num * 2;
+	memcpy(misc, data, sizeof(*misc));
+	misc->cmd_addr = le32_to_cpu(misc->cmd_addr);
+	misc->cmd_max_len = le16_to_cpu(misc->cmd_max_len);
+	misc->cmd_reply_addr = le32_to_cpu(misc->cmd_reply_addr);
+	misc->cmd_reply_len = le16_to_cpu(misc->cmd_reply_len);
+	misc->fw_state_addr = le32_to_cpu(misc->fw_state_addr);
+	misc->fw_state_len = le16_to_cpu(misc->fw_state_len);
+	misc->fw_buffer_addr = le32_to_cpu(misc->fw_buffer_addr);
+	misc->fw_buffer_max_len = le16_to_cpu(misc->fw_buffer_max_len);
+	misc->frame_data_addr = le32_to_cpu(misc->frame_data_addr);
+	misc->frame_data_head_len = le16_to_cpu(misc->frame_data_head_len);
+
+	misc->fw_attr_len = le16_to_cpu(misc->fw_attr_len);
+	misc->fw_log_len = le16_to_cpu(misc->fw_log_len);
+	misc->stylus_struct_len = le16_to_cpu(misc->stylus_struct_len);
+	misc->mutual_struct_len = le16_to_cpu(misc->mutual_struct_len);
+	misc->self_struct_len = le16_to_cpu(misc->self_struct_len);
+	misc->noise_struct_len = le16_to_cpu(misc->noise_struct_len);
+	misc->touch_data_addr = le32_to_cpu(misc->touch_data_addr);
+	misc->touch_data_head_len = le16_to_cpu(misc->touch_data_head_len);
+	misc->point_struct_len = le16_to_cpu(misc->point_struct_len);
+	LE32_TO_CPU(misc->mutual_rawdata_addr);
+	LE32_TO_CPU(misc->mutual_diffdata_addr);
+	LE32_TO_CPU(misc->mutual_refdata_addr);
+	LE32_TO_CPU(misc->self_rawdata_addr);
+	LE32_TO_CPU(misc->self_diffdata_addr);
+	LE32_TO_CPU(misc->self_refdata_addr);
+	LE32_TO_CPU(misc->iq_rawdata_addr);
+	LE32_TO_CPU(misc->iq_refdata_addr);
+	LE32_TO_CPU(misc->im_rawdata_addr);
+	LE16_TO_CPU(misc->im_readata_len);
+	LE32_TO_CPU(misc->noise_rawdata_addr);
+	LE16_TO_CPU(misc->noise_rawdata_len);
+	LE32_TO_CPU(misc->stylus_rawdata_addr);
+	LE16_TO_CPU(misc->stylus_rawdata_len);
+	LE32_TO_CPU(misc->noise_data_addr);
+	LE32_TO_CPU(misc->esd_addr);
+
+	return 0;
+}
+
+static void print_ic_info(struct goodix_ic_info *ic_info)
+{
+	struct goodix_ic_info_version *version = &ic_info->version;
+	struct goodix_ic_info_feature *feature = &ic_info->feature;
+	struct goodix_ic_info_param *parm = &ic_info->parm;
+	struct goodix_ic_info_misc *misc = &ic_info->misc;
+
+	ts_info("ic_info_length:                %d",
+		ic_info->length);
+	ts_info("info_customer_id:              0x%01X",
+		version->info_customer_id);
+	ts_info("info_version_id:               0x%01X",
+		version->info_version_id);
+	ts_info("ic_die_id:                     0x%01X",
+		version->ic_die_id);
+	ts_info("ic_version_id:                 0x%01X",
+		version->ic_version_id);
+	ts_info("config_id:                     0x%4X",
+		version->config_id);
+	ts_info("config_version:                0x%01X",
+		version->config_version);
+	ts_info("frame_data_customer_id:        0x%01X",
+		version->frame_data_customer_id);
+	ts_info("frame_data_version_id:         0x%01X",
+		version->frame_data_version_id);
+	ts_info("touch_data_customer_id:        0x%01X",
+		version->touch_data_customer_id);
+	ts_info("touch_data_version_id:         0x%01X",
+		version->touch_data_version_id);
+
+	ts_info("freqhop_feature:               0x%04X",
+		feature->freqhop_feature);
+	ts_info("calibration_feature:           0x%04X",
+		feature->calibration_feature);
+	ts_info("gesture_feature:               0x%04X",
+		feature->gesture_feature);
+	ts_info("side_touch_feature:            0x%04X",
+		feature->side_touch_feature);
+	ts_info("stylus_feature:                0x%04X",
+		feature->stylus_feature);
+
+	ts_info("Drv*Sen,Button,Force num:      %d x %d, %d, %d",
+		parm->drv_num, parm->sen_num,
+		parm->button_num, parm->force_num);
+
+	ts_info("Cmd:                           0x%04X, %d",
+		misc->cmd_addr, misc->cmd_max_len);
+	ts_info("Cmd-Reply:                     0x%04X, %d",
+		misc->cmd_reply_addr, misc->cmd_reply_len);
+	ts_info("FW-State:                      0x%04X, %d",
+		misc->fw_state_addr, misc->fw_state_len);
+	ts_info("FW-Buffer:                     0x%04X, %d",
+		misc->fw_buffer_addr, misc->fw_buffer_max_len);
+	ts_info("Touch-Data:                    0x%04X, %d",
+		misc->touch_data_addr, misc->touch_data_head_len);
+	ts_info("point_struct_len:              %d",
+		misc->point_struct_len);
+	ts_info("mutual_rawdata_addr:           0x%04X",
+		misc->mutual_rawdata_addr);
+	ts_info("mutual_diffdata_addr:          0x%04X",
+		misc->mutual_diffdata_addr);
+	ts_info("self_rawdata_addr:             0x%04X",
+		misc->self_rawdata_addr);
+	ts_info("self_diffdata_addr:            0x%04X",
+		misc->self_diffdata_addr);
+	ts_info("stylus_rawdata_addr:           0x%04X, %d",
+		misc->stylus_rawdata_addr, misc->stylus_rawdata_len);
+	ts_info("esd_addr:                      0x%04X",
+		misc->esd_addr);
+}
+
+static int brl_get_ic_info(struct goodix_ts_core *cd,
+	struct goodix_ic_info *ic_info)
+{
+	int ret, i;
+	u16 length = 0;
+	u32 ic_addr;
+	u8 afe_data[GOODIX_IC_INFO_MAX_LEN] = {0};
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+
+	if (cd->bus->ic_type == IC_TYPE_BERLIN_A)
+		ic_addr = GOODIX_IC_INFO_ADDR_BRA;
+	else
+		ic_addr = GOODIX_IC_INFO_ADDR;
+
+	for (i = 0; i < GOODIX_RETRY_3; i++) {
+		ret = hw_ops->read(cd, ic_addr,
+				   (u8 *)&length, sizeof(length));
+		if (ret) {
+			ts_info("failed get ic info length, %d", ret);
+			usleep_range(5000, 5100);
+			continue;
+		}
+		length = le16_to_cpu(length);
+		if (length >= GOODIX_IC_INFO_MAX_LEN) {
+			ts_info("invalid ic info length %d, retry %d",
+				length, i);
+			continue;
+		}
+
+		ret = hw_ops->read(cd, ic_addr, afe_data, length);
+		if (ret) {
+			ts_info("failed get ic info data, %d", ret);
+			usleep_range(5000, 5100);
+			continue;
+		}
+		/* judge whether the data is valid */
+		if (is_risk_data((const uint8_t *)afe_data, length)) {
+			ts_info("fw info data invalid");
+			usleep_range(5000, 5100);
+			continue;
+		}
+		if (checksum_cmp((const uint8_t *)afe_data,
+					length, CHECKSUM_MODE_U8_LE)) {
+			ts_info("fw info checksum error!");
+			usleep_range(5000, 5100);
+			continue;
+		}
+		break;
+	}
+	if (i == GOODIX_RETRY_3) {
+		ts_err("failed get ic info");
+		return -EINVAL;
+	}
+
+	ret = convert_ic_info(ic_info, afe_data);
+	if (ret) {
+		ts_err("convert ic info encounter error");
+		return ret;
+	}
+
+	print_ic_info(ic_info);
+
+	/* check some key info */
+	if (!ic_info->misc.cmd_addr || !ic_info->misc.fw_buffer_addr ||
+	    !ic_info->misc.touch_data_addr) {
+		ts_err("cmd_addr fw_buf_addr and touch_data_addr is null");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+#define GOODIX_ESD_TICK_WRITE_DATA	0xAA
+static int brl_esd_check(struct goodix_ts_core *cd)
+{
+	int ret;
+	u32 esd_addr;
+	u8 esd_value;
+
+	if (!cd->ic_info.misc.esd_addr)
+		return 0;
+
+	esd_addr = cd->ic_info.misc.esd_addr;
+	ret = cd->hw_ops->read(cd, esd_addr, &esd_value, 1);
+	if (ret) {
+		ts_err("failed get esd value, %d", ret);
+		return ret;
+	}
+
+	if (esd_value == GOODIX_ESD_TICK_WRITE_DATA) {
+		ts_err("esd check failed, 0x%x", esd_value);
+		return -EINVAL;
+	}
+	esd_value = GOODIX_ESD_TICK_WRITE_DATA;
+	ret = cd->hw_ops->write(cd, esd_addr, &esd_value, 1);
+	if (ret) {
+		ts_err("failed refrash esd value");
+		return ret;
+	}
+	return 0;
+}
+
+#define IRQ_EVENT_HEAD_LEN			8
+#define BYTES_PER_POINT				8
+#define COOR_DATA_CHECKSUM_SIZE		2
+
+#define GOODIX_TOUCH_EVENT			0x80
+#define GOODIX_REQUEST_EVENT		0x40
+#define GOODIX_GESTURE_EVENT		0x20
+#define POINT_TYPE_STYLUS_HOVER		0x01
+#define POINT_TYPE_STYLUS			0x03
+
+static void goodix_parse_finger(struct goodix_touch_data *touch_data,
+				u8 *buf, int touch_num)
+{
+	unsigned int id = 0, x = 0, y = 0, w = 0;
+	u8 *coor_data;
+	int i;
+
+	coor_data = &buf[IRQ_EVENT_HEAD_LEN];
+	for (i = 0; i < touch_num; i++) {
+		id = (coor_data[0] >> 4) & 0x0F;
+		if (id >= GOODIX_MAX_TOUCH) {
+			ts_info("invalid finger id =%d", id);
+			touch_data->touch_num = 0;
+			return;
+		}
+		x = le16_to_cpup((__le16 *)(coor_data + 2));
+		y = le16_to_cpup((__le16 *)(coor_data + 4));
+		w = le16_to_cpup((__le16 *)(coor_data + 6));
+		touch_data->coords[id].status = TS_TOUCH;
+		touch_data->coords[id].x = x;
+		touch_data->coords[id].y = y;
+		touch_data->coords[id].w = w;
+		coor_data += BYTES_PER_POINT;
+	}
+	touch_data->touch_num = touch_num;
+}
+
+static unsigned int goodix_pen_btn_code[] = {BTN_STYLUS, BTN_STYLUS2};
+static void goodix_parse_pen(struct goodix_pen_data *pen_data,
+	u8 *buf, int touch_num)
+{
+	unsigned int id = 0;
+	u8 cur_key_map = 0;
+	u8 *coor_data;
+	int16_t x_angle, y_angle;
+	int i;
+
+	pen_data->coords.tool_type = BTN_TOOL_PEN;
+
+	if (touch_num) {
+		pen_data->coords.status = TS_TOUCH;
+		coor_data = &buf[IRQ_EVENT_HEAD_LEN];
+
+		id = (coor_data[0] >> 4) & 0x0F;
+		pen_data->coords.x = le16_to_cpup((__le16 *)(coor_data + 2));
+		pen_data->coords.y = le16_to_cpup((__le16 *)(coor_data + 4));
+		pen_data->coords.p = le16_to_cpup((__le16 *)(coor_data + 6));
+		x_angle = le16_to_cpup((__le16 *)(coor_data + 8));
+		y_angle = le16_to_cpup((__le16 *)(coor_data + 10));
+		pen_data->coords.tilt_x = x_angle / 100;
+		pen_data->coords.tilt_y = y_angle / 100;
+	} else {
+		pen_data->coords.status = TS_RELEASE;
+	}
+
+	cur_key_map = (buf[3] & 0x0F) >> 1;
+	for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) {
+		pen_data->keys[i].code = goodix_pen_btn_code[i];
+		if (!(cur_key_map & (1 << i)))
+			continue;
+		pen_data->keys[i].status = TS_TOUCH;
+	}
+}
+
+static int goodix_touch_handler(struct goodix_ts_core *cd,
+				struct goodix_ts_event *ts_event,
+				u8 *pre_buf, u32 pre_buf_len)
+{
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+	struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
+	struct goodix_touch_data *touch_data = &ts_event->touch_data;
+	struct goodix_pen_data *pen_data = &ts_event->pen_data;
+	static u8 buffer[IRQ_EVENT_HEAD_LEN +
+			 BYTES_PER_POINT * GOODIX_MAX_TOUCH + 2];
+	u8 touch_num = 0;
+	int ret = 0;
+	u8 point_type = 0;
+	static u8 pre_finger_num;
+	static u8 pre_pen_num;
+
+	/* clean event buffer */
+	memset(ts_event, 0, sizeof(*ts_event));
+	/* copy pre-data to buffer */
+	memcpy(buffer, pre_buf, pre_buf_len);
+
+	touch_num = buffer[2] & 0x0F;
+
+	if (touch_num > GOODIX_MAX_TOUCH) {
+		ts_debug("invalid touch num %d", touch_num);
+		return -EINVAL;
+	}
+
+	if (unlikely(touch_num > 2)) {
+		ret = hw_ops->read(cd,
+				misc->touch_data_addr + pre_buf_len,
+				&buffer[pre_buf_len],
+				(touch_num - 2) * BYTES_PER_POINT);
+		if (ret) {
+			ts_debug("failed get touch data");
+			return ret;
+		}
+	}
+
+	if (touch_num > 0) {
+		point_type = buffer[IRQ_EVENT_HEAD_LEN] & 0x0F;
+		if (point_type == POINT_TYPE_STYLUS ||
+				point_type == POINT_TYPE_STYLUS_HOVER) {
+			ret = checksum_cmp(&buffer[IRQ_EVENT_HEAD_LEN],
+					BYTES_PER_POINT * 2 + 2, CHECKSUM_MODE_U8_LE);
+			if (ret) {
+				ts_debug("touch data checksum error");
+				ts_debug("data:%*ph", BYTES_PER_POINT * 2 + 2,
+						&buffer[IRQ_EVENT_HEAD_LEN]);
+				return -EINVAL;
+			}
+		} else {
+			ret = checksum_cmp(&buffer[IRQ_EVENT_HEAD_LEN],
+					touch_num * BYTES_PER_POINT + 2, CHECKSUM_MODE_U8_LE);
+			if (ret) {
+				ts_debug("touch data checksum error");
+				ts_debug("data:%*ph", touch_num * BYTES_PER_POINT + 2,
+						&buffer[IRQ_EVENT_HEAD_LEN]);
+				return -EINVAL;
+			}
+		}
+	}
+
+	if (touch_num > 0 && (point_type == POINT_TYPE_STYLUS
+				|| point_type == POINT_TYPE_STYLUS_HOVER)) {
+		/* stylus info */
+		if (pre_finger_num) {
+			ts_event->event_type = EVENT_TOUCH;
+			goodix_parse_finger(touch_data, buffer, 0);
+			pre_finger_num = 0;
+		} else {
+			pre_pen_num = 1;
+			ts_event->event_type = EVENT_PEN;
+			goodix_parse_pen(pen_data, buffer, touch_num);
+		}
+	} else {
+		/* finger info */
+		if (pre_pen_num) {
+			ts_event->event_type = EVENT_PEN;
+			goodix_parse_pen(pen_data, buffer, 0);
+			pre_pen_num = 0;
+		} else {
+			ts_event->event_type = EVENT_TOUCH;
+			goodix_parse_finger(touch_data,
+					    buffer, touch_num);
+			pre_finger_num = touch_num;
+		}
+	}
+
+	/* process custom info */
+	if (buffer[3] & 0x01)
+		ts_debug("TODO add custom info process function");
+
+	return 0;
+}
+
+static int brl_event_handler(struct goodix_ts_core *cd,
+			 struct goodix_ts_event *ts_event)
+{
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+	struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
+	int pre_read_len;
+	u8 pre_buf[32];
+	u8 event_status;
+	int ret;
+
+	pre_read_len = IRQ_EVENT_HEAD_LEN +
+		BYTES_PER_POINT * 2 + COOR_DATA_CHECKSUM_SIZE;
+	ret = hw_ops->read(cd, misc->touch_data_addr,
+			   pre_buf, pre_read_len);
+	if (ret) {
+		ts_debug("failed get event head data");
+		return ret;
+	}
+
+	if (checksum_cmp(pre_buf, IRQ_EVENT_HEAD_LEN, CHECKSUM_MODE_U8_LE)) {
+		ts_debug("touch head checksum err");
+		ts_debug("touch_head %*ph", IRQ_EVENT_HEAD_LEN, pre_buf);
+		ts_event->retry = 1;
+		return -EINVAL;
+	}
+
+	event_status = pre_buf[0];
+	if (event_status & GOODIX_TOUCH_EVENT)
+		return goodix_touch_handler(cd, ts_event,
+					    pre_buf, pre_read_len);
+
+	if (event_status & GOODIX_REQUEST_EVENT) {
+		ts_event->event_type = EVENT_REQUEST;
+		if (pre_buf[2] == BRL_REQUEST_CODE_CONFIG)
+			ts_event->request_code = REQUEST_TYPE_CONFIG;
+		else if (pre_buf[2] == BRL_REQUEST_CODE_RESET)
+			ts_event->request_code = REQUEST_TYPE_RESET;
+		else
+			ts_debug("unsupported request code 0x%x", pre_buf[2]);
+	}
+	if (event_status & GOODIX_GESTURE_EVENT) {
+		ts_event->event_type = EVENT_GESTURE;
+		ts_event->gesture_type = pre_buf[4];
+	}
+	return 0;
+}
+
+static int brl_after_event_handler(struct goodix_ts_core *cd)
+{
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+	struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
+	u8 sync_clean = 0;
+
+	return hw_ops->write(cd, misc->touch_data_addr,
+		&sync_clean, 1);
+}
+
+static int brld_get_framedata(struct goodix_ts_core *cd,
+		struct ts_rawdata_info *info)
+{
+	int ret;
+	unsigned char val;
+	int retry = 20;
+	struct frame_head *frame_head;
+	unsigned char frame_buf[GOODIX_MAX_FRAMEDATA_LEN];
+	unsigned char *cur_ptr;
+	unsigned int flag_addr = cd->ic_info.misc.frame_data_addr;
+
+	/* clean touch event flag */
+	val = 0;
+	ret = brl_write(cd, flag_addr, &val, 1);
+	if (ret < 0) {
+		ts_err("clean touch event failed, exit!");
+		return ret;
+	}
+
+	while (retry--) {
+		usleep_range(2000, 2100);
+		ret = brl_read(cd, flag_addr, &val, 1);
+		if (!ret && (val & GOODIX_TOUCH_EVENT))
+			break;
+	}
+	if (retry < 0) {
+		ts_err("framedata is not ready val:0x%02x, exit!", val);
+		return -EINVAL;
+	}
+
+	ret = brl_read(cd, flag_addr, frame_buf, GOODIX_MAX_FRAMEDATA_LEN);
+	if (ret < 0) {
+		ts_err("read frame data failed");
+		return ret;
+	}
+
+	if (checksum_cmp(frame_buf, cd->ic_info.misc.frame_data_head_len, CHECKSUM_MODE_U8_LE)) {
+		ts_err("frame head checksum error");
+		return -EINVAL;
+	}
+
+	frame_head = (struct frame_head *)frame_buf;
+	if (checksum_cmp(frame_buf, frame_head->cur_frame_len, CHECKSUM_MODE_U16_LE)) {
+		ts_err("frame body checksum error");
+		return -EINVAL;
+	}
+	cur_ptr = frame_buf;
+	cur_ptr += cd->ic_info.misc.frame_data_head_len;
+	cur_ptr += cd->ic_info.misc.fw_attr_len;
+	cur_ptr += cd->ic_info.misc.fw_log_len;
+	memcpy((u8 *)(info->buff + info->used_size), cur_ptr + 8,
+			cd->ic_info.misc.mutual_struct_len - 8);
+
+	return 0;
+}
+
+static int brld_get_cap_data(struct goodix_ts_core *cd,
+		struct ts_rawdata_info *info)
+{
+	struct goodix_ts_cmd temp_cmd;
+	int tx = cd->ic_info.parm.drv_num;
+	int rx = cd->ic_info.parm.sen_num;
+	int size = tx * rx;
+	int ret;
+
+	/* disable irq & close esd */
+	brl_irq_enbale(cd, false);
+	goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
+
+	info->buff[0] = rx;
+	info->buff[1] = tx;
+	info->used_size = 2;
+
+	temp_cmd.cmd = 0x90;
+	temp_cmd.data[0] = 0x81;
+	temp_cmd.len = 5;
+	ret = brl_send_cmd(cd, &temp_cmd);
+	if (ret < 0) {
+		ts_err("report rawdata failed, exit!");
+		goto exit;
+	}
+
+	ret = brld_get_framedata(cd, info);
+	if (ret < 0) {
+		ts_err("brld get rawdata failed");
+		goto exit;
+	}
+	goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size]);
+	info->used_size += size;
+
+	temp_cmd.cmd = 0x90;
+	temp_cmd.data[0] = 0x82;
+	temp_cmd.len = 5;
+	ret = brl_send_cmd(cd, &temp_cmd);
+	if (ret < 0) {
+		ts_err("report diffdata failed, exit!");
+		goto exit;
+	}
+
+	ret = brld_get_framedata(cd, info);
+	if (ret < 0) {
+		ts_err("brld get diffdata failed");
+		goto exit;
+	}
+	goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size]);
+	info->used_size += size;
+
+exit:
+	temp_cmd.cmd = 0x90;
+	temp_cmd.data[0] = 0;
+	temp_cmd.len = 5;
+	brl_send_cmd(cd, &temp_cmd);
+	/* enable irq & esd */
+	brl_irq_enbale(cd, true);
+	goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
+	return ret;
+}
+
+#define GOODIX_CMD_RAWDATA	2
+#define GOODIX_CMD_COORD	0
+static int brl_get_capacitance_data(struct goodix_ts_core *cd,
+		struct ts_rawdata_info *info)
+{
+	int ret;
+	int retry = 20;
+	struct goodix_ts_cmd temp_cmd;
+	u32 flag_addr = cd->ic_info.misc.touch_data_addr;
+	u32 raw_addr = cd->ic_info.misc.mutual_rawdata_addr;
+	u32 diff_addr = cd->ic_info.misc.mutual_diffdata_addr;
+	int tx = cd->ic_info.parm.drv_num;
+	int rx = cd->ic_info.parm.sen_num;
+	int size = tx * rx;
+	u8 val;
+
+	if (!info) {
+		ts_err("input null ptr");
+		return -EIO;
+	}
+
+	if (cd->bus->ic_type == IC_TYPE_BERLIN_D)
+		return brld_get_cap_data(cd, info);
+
+	/* disable irq & close esd */
+	brl_irq_enbale(cd, false);
+	goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
+
+    /* switch rawdata mode */
+	temp_cmd.cmd = GOODIX_CMD_RAWDATA;
+	temp_cmd.len = 4;
+	ret = brl_send_cmd(cd, &temp_cmd);
+	if (ret < 0) {
+		ts_err("switch rawdata mode failed, exit!");
+		goto exit;
+	}
+
+	/* clean touch event flag */
+	val = 0;
+	ret = brl_write(cd, flag_addr, &val, 1);
+	if (ret < 0) {
+		ts_err("clean touch event failed, exit!");
+		goto exit;
+	}
+
+	while (retry--) {
+		usleep_range(5000, 5100);
+		ret = brl_read(cd, flag_addr, &val, 1);
+		if (!ret && (val & GOODIX_TOUCH_EVENT))
+			break;
+	}
+	if (retry < 0) {
+		ts_err("rawdata is not ready val:0x%02x, exit!", val);
+		goto exit;
+	}
+
+	/* obtain rawdata & diff_rawdata */
+	info->buff[0] = rx;
+	info->buff[1] = tx;
+	info->used_size = 2;
+
+	ret = brl_read(cd, raw_addr, (u8 *)&info->buff[info->used_size],
+			size * sizeof(s16));
+	if (ret < 0) {
+		ts_err("obtian raw_data failed, exit!");
+		goto exit;
+	}
+	goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size]);
+	info->used_size += size;
+
+	ret = brl_read(cd, diff_addr, (u8 *)&info->buff[info->used_size],
+			size * sizeof(s16));
+	if (ret < 0) {
+		ts_err("obtian diff_data failed, exit!");
+		goto exit;
+	}
+	goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size]);
+	info->used_size += size;
+
+exit:
+	/* switch coor mode */
+	temp_cmd.cmd = GOODIX_CMD_COORD;
+	temp_cmd.len = 4;
+	brl_send_cmd(cd, &temp_cmd);
+	/* clean touch event flag */
+	val = 0;
+	brl_write(cd, flag_addr, &val, 1);
+	/* enable irq & esd */
+	brl_irq_enbale(cd, true);
+	goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
+	return ret;
+}
+
+static struct goodix_ts_hw_ops brl_hw_ops = {
+	.power_on = brl_power_on,
+	.resume = brl_resume,
+	.suspend = brl_suspend,
+	.gesture = brl_gesture,
+	.reset = brl_reset,
+	.irq_enable = brl_irq_enbale,
+	.read = brl_read,
+	.write = brl_write,
+	.send_cmd = brl_send_cmd,
+	.send_config = brl_send_config,
+	.read_config = brl_read_config,
+	.read_version = brl_read_version,
+	.get_ic_info = brl_get_ic_info,
+	.esd_check = brl_esd_check,
+	.event_handler = brl_event_handler,
+	.after_event_handler = brl_after_event_handler,
+	.get_capacitance_data = brl_get_capacitance_data,
+};
+
+struct goodix_ts_hw_ops *goodix_get_hw_ops(void)
+{
+	return &brl_hw_ops;
+}

+ 264 - 0
goodix_brl_i2c.c

@@ -0,0 +1,264 @@
+ /*
+  * Goodix Touchscreen Driver
+  * Copyright (C) 2020 - 2021 Goodix, Inc.
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be a reference
+  * to you, when you are integrating the GOODiX's CTP IC into your system,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+
+#include "goodix_ts_core.h"
+
+#define TS_DRIVER_NAME				"gtx8_i2c"
+#define I2C_MAX_TRANSFER_SIZE		256
+#define GOODIX_BUS_RETRY_TIMES		2
+#define GOODIX_REG_ADDR_SIZE		4
+
+static struct platform_device *goodix_pdev;
+struct goodix_bus_interface goodix_i2c_bus;
+
+static int goodix_i2c_read(struct device *dev, unsigned int reg,
+			 unsigned char *data, unsigned int len)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned int transfer_length = 0;
+	unsigned int pos = 0, address = reg;
+	unsigned char get_buf[128], addr_buf[GOODIX_REG_ADDR_SIZE];
+	int retry, r = 0;
+	struct i2c_msg msgs[] = {
+		{
+			.addr = client->addr,
+			.flags = !I2C_M_RD,
+			.buf = &addr_buf[0],
+			.len = GOODIX_REG_ADDR_SIZE,
+		}, {
+			.addr = client->addr,
+			.flags = I2C_M_RD,
+		}
+	};
+
+	if (likely(len < sizeof(get_buf))) {
+		/* code optimize, use stack memory */
+		msgs[1].buf = &get_buf[0];
+	} else {
+		msgs[1].buf = kzalloc(len, GFP_KERNEL);
+		if (msgs[1].buf == NULL)
+			return -ENOMEM;
+	}
+
+	while (pos != len) {
+		if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE))
+			transfer_length = I2C_MAX_TRANSFER_SIZE;
+		else
+			transfer_length = len - pos;
+
+		msgs[0].buf[0] = (address >> 24) & 0xFF;
+		msgs[0].buf[1] = (address >> 16) & 0xFF;
+		msgs[0].buf[2] = (address >> 8) & 0xFF;
+		msgs[0].buf[3] = address & 0xFF;
+		msgs[1].len = transfer_length;
+
+		for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) {
+			if (likely(i2c_transfer(client->adapter,
+						msgs, 2) == 2)) {
+				memcpy(&data[pos], msgs[1].buf,
+				       transfer_length);
+				pos += transfer_length;
+				address += transfer_length;
+				break;
+			}
+			ts_info("I2c read retry[%d]:0x%x", retry + 1, reg);
+			usleep_range(2000, 2100);
+		}
+		if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) {
+			ts_err("I2c read failed,dev:%02x,reg:%04x,size:%u",
+			       client->addr, reg, len);
+			r = -EAGAIN;
+			goto read_exit;
+		}
+	}
+
+read_exit:
+	if (unlikely(len >= sizeof(get_buf)))
+		kfree(msgs[1].buf);
+	return r;
+}
+
+static int goodix_i2c_write(struct device *dev, unsigned int reg,
+			unsigned char *data, unsigned int len)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned int pos = 0, transfer_length = 0;
+	unsigned int address = reg;
+	unsigned char put_buf[128];
+	int retry, r = 0;
+	struct i2c_msg msg = {
+			.addr = client->addr,
+			.flags = !I2C_M_RD,
+	};
+
+	if (likely(len + GOODIX_REG_ADDR_SIZE < sizeof(put_buf))) {
+		/* code optimize,use stack memory*/
+		msg.buf = &put_buf[0];
+	} else {
+		msg.buf = kmalloc(len + GOODIX_REG_ADDR_SIZE, GFP_KERNEL);
+		if (msg.buf == NULL)
+			return -ENOMEM;
+	}
+
+	while (pos != len) {
+		if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE -
+			     GOODIX_REG_ADDR_SIZE))
+			transfer_length = I2C_MAX_TRANSFER_SIZE -
+			     GOODIX_REG_ADDR_SIZE;
+		else
+			transfer_length = len - pos;
+		msg.buf[0] = (address >> 24) & 0xFF;
+		msg.buf[1] = (address >> 16) & 0xFF;
+		msg.buf[2] = (address >> 8) & 0xFF;
+		msg.buf[3] = address & 0xFF;
+
+		msg.len = transfer_length + GOODIX_REG_ADDR_SIZE;
+		memcpy(&msg.buf[GOODIX_REG_ADDR_SIZE],
+			&data[pos], transfer_length);
+
+		for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) {
+			if (likely(i2c_transfer(client->adapter,
+						&msg, 1) == 1)) {
+				pos += transfer_length;
+				address += transfer_length;
+				break;
+			}
+			ts_debug("I2c write retry[%d]", retry + 1);
+			msleep(20);
+		}
+		if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) {
+			ts_err("I2c write failed,dev:%02x,reg:%04x,size:%u",
+				client->addr, reg, len);
+			r = -EAGAIN;
+			goto write_exit;
+		}
+	}
+
+write_exit:
+	if (likely(len + GOODIX_REG_ADDR_SIZE >= sizeof(put_buf)))
+		kfree(msg.buf);
+	return r;
+}
+
+static void goodix_pdev_release(struct device *dev)
+{
+	ts_info("goodix pdev released");
+	kfree(goodix_pdev);
+}
+
+static int goodix_i2c_probe(struct i2c_client *client,
+	const struct i2c_device_id *dev_id)
+{
+	int ret = 0;
+
+	ts_info("goodix i2c probe in");
+	ret = i2c_check_functionality(client->adapter,
+		I2C_FUNC_I2C);
+	if (!ret)
+		return -EIO;
+
+	/* get ic type */
+	ret = goodix_get_ic_type(client->dev.of_node);
+	if (ret < 0)
+		return ret;
+
+	goodix_i2c_bus.ic_type = ret;
+	goodix_i2c_bus.bus_type = GOODIX_BUS_TYPE_I2C;
+	goodix_i2c_bus.dev = &client->dev;
+	goodix_i2c_bus.read = goodix_i2c_read;
+	goodix_i2c_bus.write = goodix_i2c_write;
+	/* ts core device */
+	goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL);
+	if (!goodix_pdev)
+		return -ENOMEM;
+
+	goodix_pdev->name = GOODIX_CORE_DRIVER_NAME;
+	goodix_pdev->id = 0;
+	goodix_pdev->num_resources = 0;
+	/*
+	 * you can find this platform dev in
+	 * /sys/devices/platform/goodix_ts.0
+	 * goodix_pdev->dev.parent = &client->dev;
+	 */
+	goodix_pdev->dev.platform_data = &goodix_i2c_bus;
+	goodix_pdev->dev.release = goodix_pdev_release;
+
+	/* register platform device, then the goodix_ts_core
+	 * module will probe the touch device.
+	 */
+	ret = platform_device_register(goodix_pdev);
+	if (ret) {
+		ts_err("failed register goodix platform device, %d", ret);
+		goto err_pdev;
+	}
+	ts_info("i2c probe out");
+	return ret;
+
+err_pdev:
+	kfree(goodix_pdev);
+	goodix_pdev = NULL;
+	ts_info("i2c probe out, %d", ret);
+	return ret;
+}
+
+static int goodix_i2c_remove(struct i2c_client *client)
+{
+	platform_device_unregister(goodix_pdev);
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id i2c_matchs[] = {
+	{.compatible = "goodix,gt9897",},
+	{.compatible = "goodix,gt9966",},
+	{.compatible = "goodix,gt9916",},
+	{},
+};
+MODULE_DEVICE_TABLE(of, i2c_matchs);
+#endif
+
+static const struct i2c_device_id i2c_id_table[] = {
+	{TS_DRIVER_NAME, 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, i2c_id_table);
+
+static struct i2c_driver goodix_i2c_driver = {
+	.driver = {
+		.name = TS_DRIVER_NAME,
+		//.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(i2c_matchs),
+	},
+	.probe = goodix_i2c_probe,
+	.remove = goodix_i2c_remove,
+	.id_table = i2c_id_table,
+};
+
+int goodix_i2c_bus_init(void)
+{
+	ts_info("Goodix i2c driver init");
+	return i2c_add_driver(&goodix_i2c_driver);
+}
+
+void goodix_i2c_bus_exit(void)
+{
+	ts_info("Goodix i2c driver exit");
+	i2c_del_driver(&goodix_i2c_driver);
+}

+ 299 - 0
goodix_brl_spi.c

@@ -0,0 +1,299 @@
+ /*
+  * Goodix Touchscreen Driver
+  * Copyright (C) 2020 - 2021 Goodix, Inc.
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be a reference
+  * to you, when you are integrating the GOODiX's CTP IC into your system,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include "goodix_ts_core.h"
+#define TS_DRIVER_NAME		"gtx8_spi"
+
+#define SPI_TRANS_PREFIX_LEN    1
+#define REGISTER_WIDTH          4
+#define SPI_READ_DUMMY_LEN      4
+#define SPI_READ_PREFIX_LEN  (SPI_TRANS_PREFIX_LEN + REGISTER_WIDTH + SPI_READ_DUMMY_LEN)
+#define SPI_WRITE_PREFIX_LEN (SPI_TRANS_PREFIX_LEN + REGISTER_WIDTH)
+
+#define SPI_WRITE_FLAG  0xF0
+#define SPI_READ_FLAG   0xF1
+
+static struct platform_device *goodix_pdev;
+struct goodix_bus_interface goodix_spi_bus;
+
+/**
+ * goodix_spi_read_bra- read device register through spi bus
+ * @dev: pointer to device data
+ * @addr: register address
+ * @data: read buffer
+ * @len: bytes to read
+ * return: 0 - read ok, < 0 - spi transter error
+ */
+static int goodix_spi_read_bra(struct device *dev, unsigned int addr,
+	unsigned char *data, unsigned int len)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	u8 *rx_buf = NULL;
+	u8 *tx_buf = NULL;
+	struct spi_transfer xfers;
+	struct spi_message spi_msg;
+	int ret = 0;
+
+	rx_buf = kzalloc(SPI_READ_PREFIX_LEN + len, GFP_KERNEL);
+	tx_buf = kzalloc(SPI_READ_PREFIX_LEN + len, GFP_KERNEL);
+	if (!rx_buf || !tx_buf) {
+		ts_err("alloc tx/rx_buf failed, size:%d",
+			SPI_READ_PREFIX_LEN + len);
+		return -ENOMEM;
+	}
+
+	spi_message_init(&spi_msg);
+	memset(&xfers, 0, sizeof(xfers));
+
+	/*spi_read tx_buf format: 0xF1 + addr(4bytes) + data*/
+	tx_buf[0] = SPI_READ_FLAG;
+	tx_buf[1] = (addr >> 24) & 0xFF;
+	tx_buf[2] = (addr >> 16) & 0xFF;
+	tx_buf[3] = (addr >> 8) & 0xFF;
+	tx_buf[4] = addr & 0xFF;
+	tx_buf[5] = 0xFF;
+	tx_buf[6] = 0xFF;
+	tx_buf[7] = 0xFF;
+	tx_buf[8] = 0xFF;
+
+	xfers.tx_buf = tx_buf;
+	xfers.rx_buf = rx_buf;
+	xfers.len = SPI_READ_PREFIX_LEN + len;
+	xfers.cs_change = 0;
+	spi_message_add_tail(&xfers, &spi_msg);
+	ret = spi_sync(spi, &spi_msg);
+	if (ret < 0) {
+		ts_err("spi transfer error:%d",ret);
+		goto exit;
+	}
+	memcpy(data, &rx_buf[SPI_READ_PREFIX_LEN], len);
+
+exit:
+	kfree(rx_buf);
+	kfree(tx_buf);
+	return ret;
+}
+
+static int goodix_spi_read(struct device *dev, unsigned int addr,
+	unsigned char *data, unsigned int len)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	u8 *rx_buf = NULL;
+	u8 *tx_buf = NULL;
+	struct spi_transfer xfers;
+	struct spi_message spi_msg;
+	int ret = 0;
+
+	rx_buf = kzalloc(SPI_READ_PREFIX_LEN - 1 + len, GFP_KERNEL);
+	tx_buf = kzalloc(SPI_READ_PREFIX_LEN - 1 + len, GFP_KERNEL);
+	if (!rx_buf || !tx_buf) {
+		ts_err("alloc tx/rx_buf failed, size:%d",
+			SPI_READ_PREFIX_LEN - 1 + len);
+		return -ENOMEM;
+	}
+
+	spi_message_init(&spi_msg);
+	memset(&xfers, 0, sizeof(xfers));
+
+	/*spi_read tx_buf format: 0xF1 + addr(4bytes) + data*/
+	tx_buf[0] = SPI_READ_FLAG;
+	tx_buf[1] = (addr >> 24) & 0xFF;
+	tx_buf[2] = (addr >> 16) & 0xFF;
+	tx_buf[3] = (addr >> 8) & 0xFF;
+	tx_buf[4] = addr & 0xFF;
+	tx_buf[5] = 0xFF;
+	tx_buf[6] = 0xFF;
+	tx_buf[7] = 0xFF;
+
+	xfers.tx_buf = tx_buf;
+	xfers.rx_buf = rx_buf;
+	xfers.len = SPI_READ_PREFIX_LEN - 1 + len;
+	xfers.cs_change = 0;
+	spi_message_add_tail(&xfers, &spi_msg);
+	ret = spi_sync(spi, &spi_msg);
+	if (ret < 0) {
+		ts_err("spi transfer error:%d",ret);
+		goto exit;
+	}
+	memcpy(data, &rx_buf[SPI_READ_PREFIX_LEN - 1], len);
+
+exit:
+	kfree(rx_buf);
+	kfree(tx_buf);
+	return ret;
+}
+
+/**
+ * goodix_spi_write- write device register through spi bus
+ * @dev: pointer to device data
+ * @addr: register address
+ * @data: write buffer
+ * @len: bytes to write
+ * return: 0 - write ok; < 0 - spi transter error.
+ */
+static int goodix_spi_write(struct device *dev, unsigned int addr,
+		unsigned char *data, unsigned int len)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	u8 *tx_buf = NULL;
+	struct spi_transfer xfers;
+	struct spi_message spi_msg;
+	int ret = 0;
+
+	tx_buf = kzalloc(SPI_WRITE_PREFIX_LEN + len, GFP_KERNEL);
+	if (!tx_buf) {
+		ts_err("alloc tx_buf failed, size:%d",
+			SPI_WRITE_PREFIX_LEN + len);
+		return -ENOMEM;
+	}
+
+	spi_message_init(&spi_msg);
+	memset(&xfers, 0, sizeof(xfers));
+
+	tx_buf[0] = SPI_WRITE_FLAG;
+	tx_buf[1] = (addr >> 24) & 0xFF;
+	tx_buf[2] = (addr >> 16) & 0xFF;
+	tx_buf[3] = (addr >> 8) & 0xFF;
+	tx_buf[4] = addr & 0xFF;
+	memcpy(&tx_buf[SPI_WRITE_PREFIX_LEN], data, len);
+	xfers.tx_buf = tx_buf;
+	xfers.len = SPI_WRITE_PREFIX_LEN + len;
+	xfers.cs_change = 0;
+	spi_message_add_tail(&xfers, &spi_msg);
+	ret = spi_sync(spi, &spi_msg);
+	if (ret < 0)
+		ts_err("spi transfer error:%d",ret);
+
+	kfree(tx_buf);
+	return ret;
+}
+
+static void goodix_pdev_release(struct device *dev)
+{
+	ts_info("goodix pdev released");
+	kfree(goodix_pdev);
+}
+
+static int goodix_spi_probe(struct spi_device *spi)
+{
+	int ret = 0;
+
+	ts_info("goodix spi probe in");
+
+	/* init spi_device */
+	spi->mode            = SPI_MODE_0;
+	spi->bits_per_word   = 8;
+
+	ret = spi_setup(spi);
+	if (ret) {
+		ts_err("failed set spi mode, %d", ret);
+		return ret;
+	}
+
+	/* get ic type */
+	ret = goodix_get_ic_type(spi->dev.of_node);
+	if (ret < 0)
+		return ret;
+
+	goodix_spi_bus.ic_type = ret;
+	goodix_spi_bus.bus_type = GOODIX_BUS_TYPE_SPI;
+	goodix_spi_bus.dev = &spi->dev;
+	if (goodix_spi_bus.ic_type == IC_TYPE_BERLIN_A)
+		goodix_spi_bus.read = goodix_spi_read_bra;
+	else
+		goodix_spi_bus.read = goodix_spi_read;
+	goodix_spi_bus.write = goodix_spi_write;
+	/* ts core device */
+	goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL);
+	if (!goodix_pdev)
+		return -ENOMEM;
+
+	goodix_pdev->name = GOODIX_CORE_DRIVER_NAME;
+	goodix_pdev->id = 0;
+	goodix_pdev->num_resources = 0;
+	/*
+	 * you can find this platform dev in
+	 * /sys/devices/platfrom/goodix_ts.0
+	 * goodix_pdev->dev.parent = &client->dev;
+	 */
+	goodix_pdev->dev.platform_data = &goodix_spi_bus;
+	goodix_pdev->dev.release = goodix_pdev_release;
+
+	/* register platform device, then the goodix_ts_core
+	 * module will probe the touch deivce.
+	 */
+	ret = platform_device_register(goodix_pdev);
+	if (ret) {
+		ts_err("failed register goodix platform device, %d", ret);
+		goto err_pdev;
+	}
+	ts_info("spi probe out");
+	return 0;
+
+err_pdev:
+	kfree(goodix_pdev);
+	goodix_pdev = NULL;
+	ts_info("spi probe out, %d", ret);
+	return ret;
+}
+
+static int goodix_spi_remove(struct spi_device *spi)
+{
+	platform_device_unregister(goodix_pdev);
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id spi_matchs[] = {
+	{.compatible = "goodix,gt9897S",},
+	{.compatible = "goodix,gt9897T",},
+	{.compatible = "goodix,gt9966S",},
+	{.compatible = "goodix,gt9916S",},
+	{},
+};
+#endif
+
+static const struct spi_device_id spi_id_table[] = {
+	{TS_DRIVER_NAME, 0},
+	{},
+};
+
+static struct spi_driver goodix_spi_driver = {
+	.driver = {
+		.name = TS_DRIVER_NAME,
+		//.owner = THIS_MODULE,
+		.of_match_table = spi_matchs,
+	},
+	.id_table = spi_id_table,
+	.probe = goodix_spi_probe,
+	.remove = goodix_spi_remove,
+};
+
+int goodix_spi_bus_init(void)
+{
+	ts_info("Goodix spi driver init");
+	return spi_register_driver(&goodix_spi_driver);
+}
+
+void goodix_spi_bus_exit(void)
+{
+	ts_info("Goodix spi driver exit");
+	spi_unregister_driver(&goodix_spi_driver);
+}

+ 329 - 0
goodix_cfg_bin.c

@@ -0,0 +1,329 @@
+ /*
+  * Goodix Touchscreen Driver
+  * Copyright (C) 2020 - 2021 Goodix, Inc.
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be a reference
+  * to you, when you are integrating the GOODiX's CTP IC into your system,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  */
+#include "goodix_ts_core.h"
+
+#define TS_BIN_VERSION_START_INDEX		5
+#define TS_BIN_VERSION_LEN				4
+#define TS_CFG_BIN_HEAD_RESERVED_LEN	6
+#define TS_CFG_OFFSET_LEN				2
+#define TS_IC_TYPE_NAME_MAX_LEN			15
+#define TS_CFG_BIN_HEAD_LEN		(sizeof(struct goodix_cfg_bin_head) + \
+					TS_CFG_BIN_HEAD_RESERVED_LEN)
+#define TS_PKG_CONST_INFO_LEN	(sizeof(struct goodix_cfg_pkg_const_info))
+#define TS_PKG_REG_INFO_LEN		(sizeof(struct goodix_cfg_pkg_reg_info))
+#define TS_PKG_HEAD_LEN			(TS_PKG_CONST_INFO_LEN + TS_PKG_REG_INFO_LEN)
+
+/*cfg block definitin*/
+#define TS_CFG_BLOCK_PID_LEN		8
+#define TS_CFG_BLOCK_VID_LEN		8
+#define TS_CFG_BLOCK_FW_MASK_LEN	9
+#define TS_CFG_BLOCK_FW_PATCH_LEN	4
+#define TS_CFG_BLOCK_RESERVED_LEN	9
+
+#define TS_NORMAL_CFG 				0x01
+#define TS_HIGH_SENSE_CFG 			0x03
+#define TS_RQST_FW_RETRY_TIMES 		2
+
+#pragma pack(1)
+struct goodix_cfg_pkg_reg {
+	u16 addr;
+	u8 reserved1;
+	u8 reserved2;
+};
+
+struct goodix_cfg_pkg_const_info {
+	u32 pkg_len;
+	u8 ic_type[TS_IC_TYPE_NAME_MAX_LEN];
+	u8 cfg_type;
+	u8 sensor_id;
+	u8 hw_pid[TS_CFG_BLOCK_PID_LEN];
+	u8 hw_vid[TS_CFG_BLOCK_VID_LEN];
+	u8 fw_mask[TS_CFG_BLOCK_FW_MASK_LEN];
+	u8 fw_patch[TS_CFG_BLOCK_FW_PATCH_LEN];
+	u16 x_res_offset;
+	u16 y_res_offset;
+	u16 trigger_offset;
+};
+
+struct goodix_cfg_pkg_reg_info {
+	struct goodix_cfg_pkg_reg cfg_send_flag;
+	struct goodix_cfg_pkg_reg version_base;
+	struct goodix_cfg_pkg_reg pid;
+	struct goodix_cfg_pkg_reg vid;
+	struct goodix_cfg_pkg_reg sensor_id;
+	struct goodix_cfg_pkg_reg fw_mask;
+	struct goodix_cfg_pkg_reg fw_status;
+	struct goodix_cfg_pkg_reg cfg_addr;
+	struct goodix_cfg_pkg_reg esd;
+	struct goodix_cfg_pkg_reg command;
+	struct goodix_cfg_pkg_reg coor;
+	struct goodix_cfg_pkg_reg gesture;
+	struct goodix_cfg_pkg_reg fw_request;
+	struct goodix_cfg_pkg_reg proximity;
+	u8 reserved[TS_CFG_BLOCK_RESERVED_LEN];
+};
+
+struct goodix_cfg_bin_head {
+	u32 bin_len;
+	u8 checksum;
+	u8 bin_version[TS_BIN_VERSION_LEN];
+	u8 pkg_num;
+};
+
+#pragma pack()
+
+struct goodix_cfg_package {
+	struct goodix_cfg_pkg_const_info cnst_info;
+	struct goodix_cfg_pkg_reg_info reg_info;
+	const u8 *cfg;
+	u32 pkg_len;
+};
+
+struct goodix_cfg_bin {
+	unsigned char *bin_data;
+	unsigned int bin_data_len;
+	struct goodix_cfg_bin_head head;
+	struct goodix_cfg_package *cfg_pkgs;
+};
+
+static int goodix_read_cfg_bin(struct device *dev, const char *cfg_name, 
+			struct goodix_cfg_bin *cfg_bin)
+{
+	const struct firmware *firmware = NULL;
+	int ret;
+	int retry = GOODIX_RETRY_3;
+
+	ts_info("cfg_bin_name:%s", cfg_name);
+
+	while (retry--) {
+		ret = request_firmware(&firmware, cfg_name, dev);
+		if (!ret)
+			break;
+		ts_info("get cfg bin retry:[%d]", GOODIX_RETRY_3 - retry);
+		msleep(200);
+	}
+	if (retry < 0) {
+		ts_err("failed get cfg bin[%s] error:%d", cfg_name, ret);
+		return ret;
+	}
+
+	if (firmware->size <= 0) {
+		ts_err("request_firmware, cfg_bin length ERROR,len:%zu",
+		       firmware->size);
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	cfg_bin->bin_data_len = firmware->size;
+	/* allocate memory for cfg_bin->bin_data */
+	cfg_bin->bin_data = kzalloc(cfg_bin->bin_data_len, GFP_KERNEL);
+	if (!cfg_bin->bin_data) {
+		ret = -ENOMEM;
+		goto exit;
+	}
+	memcpy(cfg_bin->bin_data, firmware->data, cfg_bin->bin_data_len);
+
+exit:
+	release_firmware(firmware);
+	return ret;
+}
+
+static int goodix_parse_cfg_bin(struct goodix_cfg_bin *cfg_bin)
+{
+	u16 offset1, offset2;
+	u8 checksum;
+	int i;
+
+	/* copy cfg_bin head info */
+	if (cfg_bin->bin_data_len < sizeof(struct goodix_cfg_bin_head)) {
+		ts_err("Invalid cfg_bin size:%d", cfg_bin->bin_data_len);
+		return -EINVAL;
+	}
+
+	memcpy(&cfg_bin->head, cfg_bin->bin_data,
+	       sizeof(struct goodix_cfg_bin_head));
+	cfg_bin->head.bin_len = le32_to_cpu(cfg_bin->head.bin_len);
+
+	/*check length*/
+	if (cfg_bin->bin_data_len != cfg_bin->head.bin_len) {
+		ts_err("cfg_bin len check failed,%d != %d",
+		       cfg_bin->head.bin_len, cfg_bin->bin_data_len);
+		return -EINVAL;
+	}
+
+	/*check cfg_bin valid*/
+	checksum = 0;
+	for (i = TS_BIN_VERSION_START_INDEX; i < cfg_bin->bin_data_len; i++) {
+		checksum += cfg_bin->bin_data[i];
+	}
+	if (checksum != cfg_bin->head.checksum) {
+		ts_err("cfg_bin checksum check filed 0x%02x != 0x%02x",
+		       cfg_bin->head.checksum, checksum);
+		return -EINVAL;
+	}
+
+	/*allocate memory for cfg packages*/
+	cfg_bin->cfg_pkgs = kzalloc(sizeof(struct goodix_cfg_package) *
+				    cfg_bin->head.pkg_num, GFP_KERNEL);
+	if (!cfg_bin->cfg_pkgs) {
+		ts_err("cfg_pkgs, allocate memory ERROR");
+		return -ENOMEM;
+	}
+
+	/*get cfg_pkg's info*/
+	for (i = 0; i < cfg_bin->head.pkg_num; i++) {
+		/*get cfg pkg length*/
+		if (i == cfg_bin->head.pkg_num - 1) {
+			offset1 = cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN] +
+				(cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN + 1] << 8);
+
+			cfg_bin->cfg_pkgs[i].pkg_len = cfg_bin->bin_data_len - offset1;
+		} else {
+			offset1 = cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN] +
+				(cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN + 1] << 8);
+
+			offset2 = cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN + 2] +
+				(cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN + 3] << 8);
+
+			if (offset2 <= offset1) {
+				ts_err("offset error,pkg:%d, offset1:%d, offset2:%d", i, offset1, offset2);
+				goto exit;
+			}
+
+			cfg_bin->cfg_pkgs[i].pkg_len = offset2 - offset1;
+		}
+		/*get cfg pkg head*/
+		memcpy(&cfg_bin->cfg_pkgs[i].cnst_info,
+		       &cfg_bin->bin_data[offset1], TS_PKG_CONST_INFO_LEN);
+		memcpy(&cfg_bin->cfg_pkgs[i].reg_info,
+		       &cfg_bin->bin_data[offset1 + TS_PKG_CONST_INFO_LEN],
+		       TS_PKG_REG_INFO_LEN);
+
+		/*get configuration data*/
+		cfg_bin->cfg_pkgs[i].cfg = &cfg_bin->bin_data[offset1 + TS_PKG_HEAD_LEN];
+	}
+
+	/*debug, print pkg information*/
+	ts_info("Driver bin info: ver %s, len %d, pkgs %d", cfg_bin->head.bin_version,
+		cfg_bin->head.bin_len, cfg_bin->head.pkg_num);
+
+	return 0;
+exit:
+	kfree(cfg_bin->cfg_pkgs);
+	return -EINVAL;
+}
+
+static int goodix_get_reg_and_cfg(struct goodix_ts_core *cd, u8 sensor_id,
+			   struct goodix_cfg_bin *cfg_bin)
+{
+	int i;
+	u8 cfg_type;
+	u32 cfg_len;
+	struct goodix_cfg_package *cfg_pkg;
+
+	if (!cfg_bin->head.pkg_num || !cfg_bin->cfg_pkgs) {
+		ts_err("there is none cfg package, pkg_num:%d",
+			cfg_bin->head.pkg_num);
+		return -EINVAL;
+	}
+
+	/* find cfg packages with same sensor_id */
+	for (i = 0; i < cfg_bin->head.pkg_num; i++) {
+		cfg_pkg = &cfg_bin->cfg_pkgs[i];
+		if (sensor_id != cfg_pkg->cnst_info.sensor_id) {
+			ts_info("pkg:%d, sensor id contrast FAILED, bin %d != %d",
+			       i, cfg_pkg->cnst_info.sensor_id, sensor_id);
+			continue;
+		}
+		cfg_type = cfg_pkg->cnst_info.cfg_type;
+		if (cfg_type >= GOODIX_MAX_CONFIG_GROUP) {
+			ts_err("usupported config type %d",
+				cfg_pkg->cnst_info.cfg_type);
+			goto err_out;
+		}
+
+		cfg_len = cfg_pkg->pkg_len - TS_PKG_CONST_INFO_LEN -
+			    TS_PKG_REG_INFO_LEN;
+		if (cfg_len > GOODIX_CFG_MAX_SIZE) {
+			ts_err("config len exceed limit %d > %d",
+				cfg_len, GOODIX_CFG_MAX_SIZE);
+			goto err_out;
+		}
+		if (cd->ic_configs[cfg_type]) {
+			ts_err("found same type config twice for sensor id %d, skiped",
+				sensor_id);
+			continue;
+		}
+		cd->ic_configs[cfg_type] = kzalloc(sizeof(struct goodix_ic_config), GFP_KERNEL);
+		if (!cd->ic_configs[cfg_type])
+			goto err_out;
+		cd->ic_configs[cfg_type]->len = cfg_len;
+		memcpy(cd->ic_configs[cfg_type]->data, cfg_pkg->cfg, cfg_len);
+		ts_info("get config type %d, len %d, for sensor id %d",
+			cfg_type, cfg_len, sensor_id);
+	}
+	return 0;
+
+err_out:
+	/* parse config enter error, release memory alloced */
+	for (i = 0; i < GOODIX_MAX_CONFIG_GROUP; i++) {
+		if (cd->ic_configs[i])
+			kfree(cd->ic_configs[i]);
+		cd->ic_configs[i] = NULL;
+	}
+	return -EINVAL;
+}
+
+static int goodix_get_config_data(struct goodix_ts_core *cd, u8 sensor_id)
+{
+	struct goodix_cfg_bin cfg_bin = {0};
+	char *cfg_name = cd->board_data.cfg_bin_name;
+	int ret;
+
+	/*get cfg_bin from file system*/
+	ret = goodix_read_cfg_bin(&cd->pdev->dev, cfg_name, &cfg_bin);
+	if (ret) {
+		ts_err("failed get valid config bin data");
+		return ret;
+	}
+
+	/*parse cfg bin*/
+	ret = goodix_parse_cfg_bin(&cfg_bin);
+	if (ret) {
+		ts_err("failed parse cfg bin");
+		goto err_out;
+	}
+
+	/*get register address and configuration from cfg bin*/
+	ret = goodix_get_reg_and_cfg(cd, sensor_id, &cfg_bin);
+	if (!ret)
+		ts_info("success get reg and cfg info from cfg bin");
+	else
+		ts_err("failed get cfg and reg info, update fw then retry");
+
+	kfree(cfg_bin.cfg_pkgs);
+err_out:
+	kfree(cfg_bin.bin_data);
+	return ret;
+}
+
+int goodix_get_config_proc(struct goodix_ts_core *cd)
+{
+	return goodix_get_config_data(cd, cd->fw_version.sensor_id);
+}
+
+

+ 2263 - 0
goodix_ts_core.c

@@ -0,0 +1,2263 @@
+ /*
+  * Goodix Touchscreen Driver
+  * Copyright (C) 2020 - 2021 Goodix, Inc.
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be a reference
+  * to you, when you are integrating the GOODiX's CTP IC into your system,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  */
+#include <linux/version.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 38)
+#include <linux/input/mt.h>
+#define INPUT_TYPE_B_PROTOCOL
+#endif
+
+#include "goodix_ts_core.h"
+
+#define GOODIX_DEFAULT_CFG_NAME 	"goodix_cfg_group.cfg"
+#define GOOIDX_INPUT_PHYS			"goodix_ts/input0"
+
+struct goodix_module goodix_modules;
+int core_module_prob_sate = CORE_MODULE_UNPROBED;
+
+static int goodix_send_ic_config(struct goodix_ts_core *cd, int type);
+/**
+ * __do_register_ext_module - register external module
+ * to register into touch core modules structure
+ * return 0 on success, otherwise return < 0
+ */
+static int __do_register_ext_module(struct goodix_ext_module *module)
+{
+	struct goodix_ext_module *ext_module, *next;
+	struct list_head *insert_point = &goodix_modules.head;
+
+	/* prority level *must* be set */
+	if (module->priority == EXTMOD_PRIO_RESERVED) {
+		ts_err("Priority of module [%s] needs to be set",
+		       module->name);
+		return -EINVAL;
+	}
+	mutex_lock(&goodix_modules.mutex);
+	/* find insert point for the specified priority */
+	if (!list_empty(&goodix_modules.head)) {
+		list_for_each_entry_safe(ext_module, next,
+					 &goodix_modules.head, list) {
+			if (ext_module == module) {
+				ts_info("Module [%s] already exists",
+					module->name);
+				mutex_unlock(&goodix_modules.mutex);
+				return 0;
+			}
+		}
+
+		/* smaller priority value with higher priority level */
+		list_for_each_entry_safe(ext_module, next,
+					 &goodix_modules.head, list) {
+			if (ext_module->priority >= module->priority) {
+				insert_point = &ext_module->list;
+				break;
+			}
+		}
+	}
+
+	if (module->funcs && module->funcs->init) {
+		if (module->funcs->init(goodix_modules.core_data,
+					module) < 0) {
+			ts_err("Module [%s] init error",
+			       module->name ? module->name : " ");
+			mutex_unlock(&goodix_modules.mutex);
+			return -EFAULT;
+		}
+	}
+
+	list_add(&module->list, insert_point->prev);
+	mutex_unlock(&goodix_modules.mutex);
+
+	ts_info("Module [%s] registered,priority:%u", module->name,
+		module->priority);
+	return 0;
+}
+
+static void goodix_register_ext_module_work(struct work_struct *work) {
+	struct goodix_ext_module *module =
+			container_of(work, struct goodix_ext_module, work);
+
+	ts_info("module register work IN");
+
+	/* driver probe failed */
+	if (core_module_prob_sate != CORE_MODULE_PROB_SUCCESS) {
+		ts_err("Can't register ext_module core error");
+		return;
+	}
+
+	if (__do_register_ext_module(module))
+		ts_err("failed register module: %s", module->name);
+	else
+		ts_info("success register module: %s", module->name);
+}
+
+static void goodix_core_module_init(void)
+{
+	if (goodix_modules.initilized)
+		return;
+	goodix_modules.initilized = true;
+	INIT_LIST_HEAD(&goodix_modules.head);
+	mutex_init(&goodix_modules.mutex);
+}
+
+/**
+ * goodix_register_ext_module - interface for register external module
+ * to the core. This will create a workqueue to finish the real register
+ * work and return immediately. The user need to check the final result
+ * to make sure registe is success or fail.
+ *
+ * @module: pointer to external module to be register
+ * return: 0 ok, <0 failed
+ */
+int goodix_register_ext_module(struct goodix_ext_module *module)
+{
+	if (!module)
+		return -EINVAL;
+
+	ts_info("goodix_register_ext_module IN");
+
+	goodix_core_module_init();
+	INIT_WORK(&module->work, goodix_register_ext_module_work);
+	schedule_work(&module->work);
+
+	ts_info("goodix_register_ext_module OUT");
+	return 0;
+}
+
+/**
+ * goodix_register_ext_module_no_wait
+ * return: 0 ok, <0 failed
+ */
+int goodix_register_ext_module_no_wait(struct goodix_ext_module *module)
+{
+	if (!module)
+		return -EINVAL;
+	ts_info("goodix_register_ext_module_no_wait IN");
+	goodix_core_module_init();
+	/* driver probe failed */
+	if (core_module_prob_sate != CORE_MODULE_PROB_SUCCESS) {
+		ts_err("Can't register ext_module core error");
+		return -EINVAL;
+	}
+	return __do_register_ext_module(module);
+}
+
+/**
+ * goodix_unregister_ext_module - interface for external module
+ * to unregister external modules
+ *
+ * @module: pointer to external module
+ * return: 0 ok, <0 failed
+ */
+int goodix_unregister_ext_module(struct goodix_ext_module *module)
+{
+	struct goodix_ext_module *ext_module, *next;
+	bool found = false;
+
+	if (!module)
+		return -EINVAL;
+
+	if (!goodix_modules.initilized)
+		return -EINVAL;
+
+	if (!goodix_modules.core_data)
+		return -ENODEV;
+
+	mutex_lock(&goodix_modules.mutex);
+	if (!list_empty(&goodix_modules.head)) {
+		list_for_each_entry_safe(ext_module, next,
+					 &goodix_modules.head, list) {
+			if (ext_module == module) {
+				found = true;
+				break;
+			}
+		}
+	} else {
+		mutex_unlock(&goodix_modules.mutex);
+		return 0;
+	}
+
+	if (!found) {
+		ts_debug("Module [%s] never registed",
+				module->name);
+		mutex_unlock(&goodix_modules.mutex);
+		return 0;
+	}
+
+	list_del(&module->list);
+	mutex_unlock(&goodix_modules.mutex);
+
+	if (module->funcs && module->funcs->exit)
+		module->funcs->exit(goodix_modules.core_data, module);
+
+	ts_info("Moudle [%s] unregistered",
+		module->name ? module->name : " ");
+	return 0;
+}
+
+static void goodix_ext_sysfs_release(struct kobject *kobj)
+{
+	ts_info("Kobject released!");
+}
+
+#define to_ext_module(kobj)	container_of(kobj,\
+				struct goodix_ext_module, kobj)
+#define to_ext_attr(attr)	container_of(attr,\
+				struct goodix_ext_attribute, attr)
+
+static ssize_t goodix_ext_sysfs_show(struct kobject *kobj,
+		struct attribute *attr, char *buf)
+{
+	struct goodix_ext_module *module = to_ext_module(kobj);
+	struct goodix_ext_attribute *ext_attr = to_ext_attr(attr);
+
+	if (ext_attr->show)
+		return ext_attr->show(module, buf);
+
+	return -EIO;
+}
+
+static ssize_t goodix_ext_sysfs_store(struct kobject *kobj,
+		struct attribute *attr, const char *buf, size_t count)
+{
+	struct goodix_ext_module *module = to_ext_module(kobj);
+	struct goodix_ext_attribute *ext_attr = to_ext_attr(attr);
+
+	if (ext_attr->store)
+		return ext_attr->store(module, buf, count);
+
+	return -EIO;
+}
+
+static const struct sysfs_ops goodix_ext_ops = {
+	.show = goodix_ext_sysfs_show,
+	.store = goodix_ext_sysfs_store
+};
+
+static struct kobj_type goodix_ext_ktype = {
+	.release = goodix_ext_sysfs_release,
+	.sysfs_ops = &goodix_ext_ops,
+};
+
+struct kobj_type *goodix_get_default_ktype(void)
+{
+	return &goodix_ext_ktype;
+}
+
+struct kobject *goodix_get_default_kobj(void)
+{
+	struct kobject *kobj = NULL;
+
+	if (goodix_modules.core_data &&
+			goodix_modules.core_data->pdev)
+		kobj = &goodix_modules.core_data->pdev->dev.kobj;
+	return kobj;
+}
+
+/* show driver infomation */
+static ssize_t goodix_ts_driver_info_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "DriverVersion:%s\n",
+			GOODIX_DRIVER_VERSION);
+}
+
+/* show chip infoamtion */
+static ssize_t goodix_ts_chip_info_show(struct device  *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct goodix_ts_core *core_data = dev_get_drvdata(dev);
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+	struct goodix_fw_version chip_ver;
+	u8 temp_pid[8] = {0};
+	int ret;
+	int cnt = -EINVAL;
+
+	if (hw_ops->read_version) {
+		ret = hw_ops->read_version(core_data, &chip_ver);
+		if (!ret) {
+			memcpy(temp_pid, chip_ver.rom_pid, sizeof(chip_ver.rom_pid));
+			cnt = snprintf(&buf[0], PAGE_SIZE,
+				"rom_pid:%s\nrom_vid:%02x%02x%02x\n",
+				temp_pid, chip_ver.rom_vid[0],
+				chip_ver.rom_vid[1], chip_ver.rom_vid[2]);
+			cnt += snprintf(&buf[cnt], PAGE_SIZE,
+				"patch_pid:%s\npatch_vid:%02x%02x%02x%02x\n",
+				chip_ver.patch_pid, chip_ver.patch_vid[0],
+				chip_ver.patch_vid[1], chip_ver.patch_vid[2],
+				chip_ver.patch_vid[3]);
+			cnt += snprintf(&buf[cnt], PAGE_SIZE,
+				"sensorid:%d\n", chip_ver.sensor_id);
+		}
+	}
+
+	if (hw_ops->get_ic_info) {
+		ret = hw_ops->get_ic_info(core_data, &core_data->ic_info);
+		if (!ret) {
+			cnt += snprintf(&buf[cnt], PAGE_SIZE,
+				"config_id:%x\n", core_data->ic_info.version.config_id);
+			cnt += snprintf(&buf[cnt], PAGE_SIZE,
+				"config_version:%x\n", core_data->ic_info.version.config_version);
+		}
+	}
+
+	return cnt;
+}
+
+/* reset chip */
+static ssize_t goodix_ts_reset_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf,
+				     size_t count)
+{
+	struct goodix_ts_core *core_data = dev_get_drvdata(dev);
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+
+	if (!buf || count <= 0)
+		return -EINVAL;
+	if (buf[0] != '0')
+		hw_ops->reset(core_data, GOODIX_NORMAL_RESET_DELAY_MS);
+	return count;
+}
+
+/* read config */
+static ssize_t goodix_ts_read_cfg_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct goodix_ts_core *core_data = dev_get_drvdata(dev);
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+	int ret;
+	int i;
+	int offset;
+	char *cfg_buf = NULL;
+
+	cfg_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!cfg_buf)
+		return -ENOMEM;
+
+	if (hw_ops->read_config)
+		ret = hw_ops->read_config(core_data, cfg_buf, PAGE_SIZE);
+	else
+		ret = -EINVAL;
+
+	if (ret > 0) {
+		offset = 0;
+		for (i = 0; i < 200; i++) { // only print 200 bytes
+			offset += snprintf(&buf[offset], PAGE_SIZE - offset,
+					"%02x,", cfg_buf[i]);
+			if ((i + 1) % 20 == 0)
+				buf[offset++] = '\n';
+		}
+	}
+
+	kfree(cfg_buf);
+	if (ret <= 0)
+		return ret;
+
+	return offset;
+}
+
+static u8 ascii2hex(u8 a)
+{
+	s8 value = 0;
+
+	if (a >= '0' && a <= '9')
+		value = a - '0';
+	else if (a >= 'A' && a <= 'F')
+		value = a - 'A' + 0x0A;
+	else if (a >= 'a' && a <= 'f')
+		value = a - 'a' + 0x0A;
+	else
+		value = 0xff;
+
+	return value;
+}
+
+static int goodix_ts_convert_0x_data(const u8 *buf, int buf_size,
+				     u8 *out_buf, int *out_buf_len)
+{
+	int i, m_size = 0;
+	int temp_index = 0;
+	u8 high, low;
+
+	for (i = 0; i < buf_size; i++) {
+		if (buf[i] == 'x' || buf[i] == 'X')
+			m_size++;
+	}
+
+	if (m_size <= 1) {
+		ts_err("cfg file ERROR, valid data count:%d", m_size);
+		return -EINVAL;
+	}
+	*out_buf_len = m_size;
+
+	for (i = 0; i < buf_size; i++) {
+		if (buf[i] != 'x' && buf[i] != 'X')
+			continue;
+
+		if (temp_index >= m_size) {
+			ts_err("exchange cfg data error, overflow,"
+			       "temp_index:%d,m_size:%d",
+			       temp_index, m_size);
+			return -EINVAL;
+		}
+		high = ascii2hex(buf[i + 1]);
+		low = ascii2hex(buf[i + 2]);
+		if (high == 0xff || low == 0xff) {
+			ts_err("failed convert: 0x%x, 0x%x",
+				buf[i + 1], buf[i + 2]);
+			return -EINVAL;
+		}
+		out_buf[temp_index++] = (high << 4) + low;
+	}
+	return 0;
+}
+
+/* send config */
+static ssize_t goodix_ts_send_cfg_store(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t count)
+{
+	struct goodix_ts_core *core_data = dev_get_drvdata(dev);
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+	struct goodix_ic_config *config = NULL;
+	const struct firmware *cfg_img = NULL;
+	int en;
+	int ret;
+
+	if (sscanf(buf, "%d", &en) != 1)
+		return -EINVAL;
+
+	if (en != 1)
+		return -EINVAL;
+
+	hw_ops->irq_enable(core_data, false);
+
+	ret = request_firmware(&cfg_img, GOODIX_DEFAULT_CFG_NAME, dev);
+	if (ret < 0) {
+		ts_err("cfg file [%s] not available,errno:%d",
+			GOODIX_DEFAULT_CFG_NAME, ret);
+		goto exit;
+	} else {
+		ts_info("cfg file [%s] is ready", GOODIX_DEFAULT_CFG_NAME);
+	}
+
+	config = kzalloc(sizeof(*config), GFP_KERNEL);
+	if (!config)
+		goto exit;
+
+	if (goodix_ts_convert_0x_data(cfg_img->data, cfg_img->size,
+			config->data, &config->len)) {
+		ts_err("convert config data FAILED");
+		goto exit;
+	}
+
+	if (hw_ops->send_config) {
+		ret = hw_ops->send_config(core_data, config->data, config->len);
+		if (ret < 0)
+			ts_err("send config failed");
+	}
+		
+exit:
+	hw_ops->irq_enable(core_data, true);
+	kfree(config);
+	if (cfg_img)
+		release_firmware(cfg_img);
+
+	return count;
+}
+
+/* reg read/write */
+static u32 rw_addr;
+static u32 rw_len;
+static u8 rw_flag;
+static u8 store_buf[32];
+static u8 show_buf[PAGE_SIZE];
+static ssize_t goodix_ts_reg_rw_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct goodix_ts_core *core_data = dev_get_drvdata(dev);
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+	int ret;
+
+	if (!rw_addr || !rw_len) {
+		ts_err("address(0x%x) and length(%d) can't be null",
+			rw_addr, rw_len);
+		return -EINVAL;
+	}
+
+	if (rw_flag != 1) {
+		ts_err("invalid rw flag %d, only support [1/2]", rw_flag);
+		return -EINVAL;
+	}
+
+	ret = hw_ops->read(core_data, rw_addr, show_buf, rw_len);
+	if (ret < 0) {
+		ts_err("failed read addr(%x) length(%d)", rw_addr, rw_len);
+		return snprintf(buf, PAGE_SIZE, "failed read addr(%x), len(%d)\n",
+			rw_addr, rw_len);
+	}
+
+	return snprintf(buf, PAGE_SIZE, "0x%x,%d {%*ph}\n",
+		rw_addr, rw_len, rw_len, show_buf);
+}
+
+static ssize_t goodix_ts_reg_rw_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct goodix_ts_core *core_data = dev_get_drvdata(dev);
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+	char *pos = NULL;
+	char *token = NULL;
+	long result = 0;
+	int ret;
+	int i;
+
+	if (!buf || !count) {
+		ts_err("invalid parame");
+		goto err_out;
+	}
+
+	if (buf[0] == 'r') {
+		rw_flag = 1;
+	} else if (buf[0] == 'w') {
+		rw_flag = 2;
+	} else {
+		ts_err("string must start with 'r/w'");
+		goto err_out;
+	}
+
+	/* get addr */
+	pos = (char *)buf;
+	pos += 2;
+	token = strsep(&pos, ":");
+	if (!token) {
+		ts_err("invalid address info");
+		goto err_out;
+	} else {
+		if (kstrtol(token, 16, &result)) {
+			ts_err("failed get addr info");
+			goto err_out;
+		}
+		rw_addr = (u32)result;
+		ts_info("rw addr is 0x%x", rw_addr);
+	}
+
+	/* get length */
+	token = strsep(&pos, ":");
+	if (!token) {
+		ts_err("invalid length info");
+		goto err_out;
+	} else {
+		if (kstrtol(token, 0, &result)) {
+			ts_err("failed get length info");
+			goto err_out;
+		}
+		rw_len = (u32)result;
+		ts_info("rw length info is %d", rw_len);
+		if (rw_len > sizeof(store_buf)) {
+			ts_err("data len > %lu", sizeof(store_buf));
+			goto err_out;
+		}
+	}
+
+	if (rw_flag == 1)
+		return count;
+
+	for (i = 0; i < rw_len; i++) {
+		token = strsep(&pos, ":");
+		if (!token) {
+			ts_err("invalid data info");
+			goto err_out;
+		} else {
+			if (kstrtol(token, 16, &result)) {
+				ts_err("failed get data[%d] info", i);
+				goto err_out;
+			}
+			store_buf[i] = (u8)result;
+			ts_info("get data[%d]=0x%x", i, store_buf[i]);
+		}
+	}
+	ret = hw_ops->write(core_data, rw_addr, store_buf, rw_len);
+	if (ret < 0) {
+		ts_err("failed write addr(%x) data %*ph", rw_addr,
+			rw_len, store_buf);
+		goto err_out;
+	}
+
+	ts_info("%s write to addr (%x) with data %*ph",
+		"success", rw_addr, rw_len, store_buf);
+
+	return count;
+err_out:
+	snprintf(show_buf, PAGE_SIZE, "%s\n",
+		"invalid params, format{r/w:4100:length:[41:21:31]}");
+	return -EINVAL;
+
+}
+
+/* show irq infomation */
+static ssize_t goodix_ts_irq_info_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct goodix_ts_core *core_data = dev_get_drvdata(dev);
+	struct irq_desc *desc;
+	size_t offset = 0;
+	int r;
+
+	r = snprintf(&buf[offset], PAGE_SIZE, "irq:%u\n", core_data->irq);
+	if (r < 0)
+		return -EINVAL;
+
+	offset += r;
+	r = snprintf(&buf[offset], PAGE_SIZE - offset, "state:%s\n",
+		     atomic_read(&core_data->irq_enabled) ?
+		     "enabled" : "disabled");
+	if (r < 0)
+		return -EINVAL;
+
+	desc = irq_to_desc(core_data->irq);
+	offset += r;
+	r = snprintf(&buf[offset], PAGE_SIZE - offset, "disable-depth:%d\n",
+		     desc->depth);
+	if (r < 0)
+		return -EINVAL;
+
+	offset += r;
+	r = snprintf(&buf[offset], PAGE_SIZE - offset, "trigger-count:%zu\n",
+		core_data->irq_trig_cnt);
+	if (r < 0)
+		return -EINVAL;
+
+	offset += r;
+	r = snprintf(&buf[offset], PAGE_SIZE - offset,
+		     "echo 0/1 > irq_info to disable/enable irq\n");
+	if (r < 0)
+		return -EINVAL;
+
+	offset += r;
+	return offset;
+}
+
+/* enable/disable irq */
+static ssize_t goodix_ts_irq_info_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct goodix_ts_core *core_data = dev_get_drvdata(dev);
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+
+	if (!buf || count <= 0)
+		return -EINVAL;
+
+	if (buf[0] != '0')
+		hw_ops->irq_enable(core_data, true);
+	else
+		hw_ops->irq_enable(core_data, false);
+	return count;
+}
+
+/* show esd status */
+static ssize_t goodix_ts_esd_info_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct goodix_ts_core *core_data = dev_get_drvdata(dev);
+	struct goodix_ts_esd *ts_esd = &core_data->ts_esd;
+	int r = 0;
+
+	r = snprintf(buf, PAGE_SIZE, "state:%s\n",
+		     atomic_read(&ts_esd->esd_on) ?
+		     "enabled" : "disabled");
+
+	return r;
+}					   
+
+/* enable/disable esd */
+static ssize_t goodix_ts_esd_info_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	if (!buf || count <= 0)
+		return -EINVAL;
+
+	if (buf[0] != '0')
+		goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
+	else
+		goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
+	return count;
+}
+
+/* debug level show */
+static ssize_t goodix_ts_debug_log_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	int r = 0;
+
+	r = snprintf(buf, PAGE_SIZE, "state:%s\n",
+		    debug_log_flag ?
+		    "enabled" : "disabled");
+
+	return r;
+}
+
+/* debug level store */
+static ssize_t goodix_ts_debug_log_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	if (!buf || count <= 0)
+		return -EINVAL;
+
+	if (buf[0] != '0')
+		debug_log_flag = true;
+	else
+		debug_log_flag = false;
+	return count;
+}					
+
+static DEVICE_ATTR(driver_info, 0444, goodix_ts_driver_info_show, NULL);
+static DEVICE_ATTR(chip_info, 0444, goodix_ts_chip_info_show, NULL);
+static DEVICE_ATTR(reset, 0220, NULL, goodix_ts_reset_store);
+static DEVICE_ATTR(send_cfg, 0220, NULL, goodix_ts_send_cfg_store);
+static DEVICE_ATTR(read_cfg, 0444, goodix_ts_read_cfg_show, NULL);
+static DEVICE_ATTR(reg_rw, 0664, goodix_ts_reg_rw_show, goodix_ts_reg_rw_store);
+static DEVICE_ATTR(irq_info, 0664, goodix_ts_irq_info_show, goodix_ts_irq_info_store);
+static DEVICE_ATTR(esd_info, 0664, goodix_ts_esd_info_show, goodix_ts_esd_info_store);
+static DEVICE_ATTR(debug_log, 0664, goodix_ts_debug_log_show, goodix_ts_debug_log_store);
+
+static struct attribute *sysfs_attrs[] = {
+	&dev_attr_driver_info.attr,
+	&dev_attr_chip_info.attr,
+	&dev_attr_reset.attr,
+	&dev_attr_send_cfg.attr,
+	&dev_attr_read_cfg.attr,
+	&dev_attr_reg_rw.attr,
+	&dev_attr_irq_info.attr,
+	&dev_attr_esd_info.attr,
+	&dev_attr_debug_log.attr,
+	NULL,
+};
+
+static const struct attribute_group sysfs_group = {
+	.attrs = sysfs_attrs,
+};
+
+static int goodix_ts_sysfs_init(struct goodix_ts_core *core_data)
+{
+	int ret;
+
+	ret = sysfs_create_group(&core_data->pdev->dev.kobj, &sysfs_group);
+	if (ret) {
+		ts_err("failed create core sysfs group");
+		return ret;
+	}
+
+	return ret;
+}
+
+static void goodix_ts_sysfs_exit(struct goodix_ts_core *core_data)
+{
+	sysfs_remove_group(&core_data->pdev->dev.kobj, &sysfs_group);
+}
+
+/* prosfs create */
+static int rawdata_proc_show(struct seq_file *m, void *v)
+{
+	struct ts_rawdata_info *info;
+	struct goodix_ts_core *cd = m->private;
+	int tx;
+	int rx;
+	int ret;
+	int i;
+	int index;
+
+	if (!m || !v || !cd) {
+		ts_err("rawdata_proc_show, input null ptr");
+		return -EIO;
+	}
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		ts_err("Failed to alloc rawdata info memory");
+		return -ENOMEM;
+	}
+
+	ret = cd->hw_ops->get_capacitance_data(cd, info);
+	if (ret < 0) {
+		ts_err("failed to get_capacitance_data, exit!");
+		goto exit;
+	}
+
+	rx = info->buff[0];
+	tx = info->buff[1];
+	seq_printf(m, "TX:%d  RX:%d\n", tx, rx);
+	seq_printf(m, "mutual_rawdata:\n");
+	index = 2;
+	for (i = 0; i < tx * rx; i++) {
+		seq_printf(m, "%5d,", info->buff[index + i]);
+		if ((i + 1) % tx == 0)
+			seq_printf(m, "\n");
+	}
+	seq_printf(m, "mutual_diffdata:\n");
+	index += tx * rx;
+	for (i = 0; i < tx * rx; i++) {
+		seq_printf(m, "%3d,", info->buff[index + i]);
+		if ((i + 1) % tx == 0)
+			seq_printf(m, "\n");
+	}
+
+exit:
+	kfree(info);
+	return ret;
+}
+
+static int rawdata_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open_size(file, rawdata_proc_show, PDE_DATA(inode), PAGE_SIZE * 10);
+}
+
+static const struct file_operations rawdata_proc_fops = {
+	.open = rawdata_proc_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static void goodix_ts_procfs_init(struct goodix_ts_core *core_data)
+{
+	if (!proc_mkdir("goodix_ts", NULL))
+		return;
+	proc_create_data("goodix_ts/tp_capacitance_data",
+			0666, NULL, &rawdata_proc_fops, core_data);
+}
+
+static void goodix_ts_procfs_exit(struct goodix_ts_core *core_data)
+{
+	remove_proc_entry("goodix_ts/tp_capacitance_data", NULL);
+	remove_proc_entry("goodix_ts", NULL);
+}
+
+/* event notifier */
+static BLOCKING_NOTIFIER_HEAD(ts_notifier_list);
+/**
+ * goodix_ts_register_client - register a client notifier
+ * @nb: notifier block to callback on events
+ *  see enum ts_notify_event in goodix_ts_core.h
+ */
+int goodix_ts_register_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_register(&ts_notifier_list, nb);
+}
+
+/**
+ * goodix_ts_unregister_client - unregister a client notifier
+ * @nb: notifier block to callback on events
+ *	see enum ts_notify_event in goodix_ts_core.h
+ */
+int goodix_ts_unregister_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_unregister(&ts_notifier_list, nb);
+}
+
+/**
+ * fb_notifier_call_chain - notify clients of fb_events
+ *	see enum ts_notify_event in goodix_ts_core.h
+ */
+int goodix_ts_blocking_notify(enum ts_notify_event evt, void *v)
+{
+	int ret;
+
+	ret = blocking_notifier_call_chain(&ts_notifier_list,
+			(unsigned long)evt, v);
+	return ret;
+}
+
+#ifdef CONFIG_OF
+/**
+ * goodix_parse_dt_resolution - parse resolution from dt
+ * @node: devicetree node
+ * @board_data: pointer to board data structure
+ * return: 0 - no error, <0 error
+ */
+static int goodix_parse_dt_resolution(struct device_node *node,
+		struct goodix_ts_board_data *board_data)
+{
+	int ret;
+
+	ret = of_property_read_u32(node, "goodix,panel-max-x",
+				 &board_data->panel_max_x);
+	if (ret) {
+		ts_err("failed get panel-max-x");
+		return ret;
+	}
+
+	ret = of_property_read_u32(node, "goodix,panel-max-y",
+				 &board_data->panel_max_y);
+	if (ret) {
+		ts_err("failed get panel-max-y");
+		return ret;
+	}
+
+	ret = of_property_read_u32(node, "goodix,panel-max-w",
+				 &board_data->panel_max_w);
+	if (ret) {
+		ts_err("failed get panel-max-w");
+		return ret;
+	}
+
+	ret = of_property_read_u32(node, "goodix,panel-max-p",
+				 &board_data->panel_max_p);
+	if (ret) {
+		ts_err("failed get panel-max-p, use default");
+		board_data->panel_max_p = GOODIX_PEN_MAX_PRESSURE;
+	}
+
+	return 0;
+}
+
+/**
+ * goodix_parse_dt - parse board data from dt
+ * @dev: pointer to device
+ * @board_data: pointer to board data structure
+ * return: 0 - no error, <0 error
+ */
+static int goodix_parse_dt(struct device_node *node,
+	struct goodix_ts_board_data *board_data)
+{
+	const char *name_tmp;
+	int r;
+
+	if (!board_data) {
+		ts_err("invalid board data");
+		return -EINVAL;
+	}
+
+	r = of_get_named_gpio(node, "goodix,avdd-gpio", 0);
+	if (r < 0) {
+		ts_info("can't find avdd-gpio, use other power supply");
+		board_data->avdd_gpio = 0;
+	} else {
+		ts_info("get avdd-gpio[%d] from dt", r);
+		board_data->avdd_gpio = r;
+	}
+
+	r = of_get_named_gpio(node, "goodix,iovdd-gpio", 0);
+	if (r < 0) {
+		ts_info("can't find iovdd-gpio, use other power supply");
+		board_data->iovdd_gpio = 0;
+	} else {
+		ts_info("get iovdd-gpio[%d] from dt", r);
+		board_data->iovdd_gpio = r;
+	}
+
+	r = of_get_named_gpio(node, "goodix,reset-gpio", 0);
+	if (r < 0) {
+		ts_err("invalid reset-gpio in dt: %d", r);
+		return -EINVAL;
+	}
+	ts_info("get reset-gpio[%d] from dt", r);
+	board_data->reset_gpio = r;
+
+	r = of_get_named_gpio(node, "goodix,irq-gpio", 0);
+	if (r < 0) {
+		ts_err("invalid irq-gpio in dt: %d", r);
+		return -EINVAL;
+	}
+	ts_info("get irq-gpio[%d] from dt", r);
+	board_data->irq_gpio = r;
+
+	r = of_property_read_u32(node, "goodix,irq-flags",
+			&board_data->irq_flags);
+	if (r) {
+		ts_err("invalid irq-flags");
+		return -EINVAL;
+	}
+
+	memset(board_data->avdd_name, 0, sizeof(board_data->avdd_name));
+	r = of_property_read_string(node, "goodix,avdd-name", &name_tmp);
+	if (!r) {
+		ts_info("avdd name from dt: %s", name_tmp);
+		if (strlen(name_tmp) < sizeof(board_data->avdd_name))
+			strncpy(board_data->avdd_name,
+				name_tmp, sizeof(board_data->avdd_name));
+		else
+			ts_info("invalied avdd name length: %ld > %ld",
+				strlen(name_tmp),
+				sizeof(board_data->avdd_name));
+	}
+
+	memset(board_data->iovdd_name, 0, sizeof(board_data->iovdd_name));
+	r = of_property_read_string(node, "goodix,iovdd-name", &name_tmp);
+	if (!r) {
+		ts_info("iovdd name from dt: %s", name_tmp);
+		if (strlen(name_tmp) < sizeof(board_data->iovdd_name))
+			strncpy(board_data->iovdd_name,
+				name_tmp, sizeof(board_data->iovdd_name));
+		else
+			ts_info("invalied iovdd name length: %ld > %ld",
+				strlen(name_tmp),
+				sizeof(board_data->iovdd_name));
+	}
+
+	/* get firmware file name */
+	r = of_property_read_string(node, "goodix,firmware-name", &name_tmp);
+	if (!r) {
+		ts_info("firmware name from dt: %s", name_tmp);
+		strncpy(board_data->fw_name, name_tmp, sizeof(board_data->fw_name));
+	} else {
+		ts_info("can't find firmware name, use default: %s", TS_DEFAULT_FIRMWARE);
+		strncpy(board_data->fw_name, TS_DEFAULT_FIRMWARE, sizeof(board_data->fw_name));
+	}
+
+	/* get config file name */
+	r = of_property_read_string(node, "goodix,config-name", &name_tmp);
+	if (!r) {
+		ts_info("config name from dt: %s", name_tmp);
+		strncpy(board_data->cfg_bin_name, name_tmp, sizeof(board_data->cfg_bin_name));
+	} else {
+		ts_info("can't find config name, use default: %s", TS_DEFAULT_CFG_BIN);
+		strncpy(board_data->cfg_bin_name, TS_DEFAULT_CFG_BIN, sizeof(board_data->cfg_bin_name));
+	}
+
+	/* get xyz resolutions */
+	r = goodix_parse_dt_resolution(node, board_data);
+	if (r) {
+		ts_err("Failed to parse resolutions:%d", r);
+		return r;
+	}
+
+
+	/*get pen-enable switch and pen keys, must after "key map"*/
+	board_data->pen_enable = of_property_read_bool(node,
+					"goodix,pen-enable");
+	if (board_data->pen_enable)
+		ts_info("goodix pen enabled");
+
+	ts_debug("[DT]x:%d, y:%d, w:%d, p:%d", board_data->panel_max_x,
+		 board_data->panel_max_y, board_data->panel_max_w,
+		 board_data->panel_max_p);
+	return 0;
+}
+#endif
+
+static void goodix_ts_report_pen(struct input_dev *dev,
+		struct goodix_pen_data *pen_data)
+{
+	int i;
+
+	mutex_lock(&dev->mutex);
+
+	if (pen_data->coords.status == TS_TOUCH) {
+		input_report_key(dev, BTN_TOUCH, 1);
+		input_report_key(dev, pen_data->coords.tool_type, 1);
+		input_report_abs(dev, ABS_X, pen_data->coords.x);
+		input_report_abs(dev, ABS_Y, pen_data->coords.y);
+		input_report_abs(dev, ABS_PRESSURE, pen_data->coords.p);
+		input_report_abs(dev, ABS_TILT_X, pen_data->coords.tilt_x);
+		input_report_abs(dev, ABS_TILT_Y, pen_data->coords.tilt_y);
+		ts_debug("pen_data:x %d, y %d, p %d, tilt_x %d tilt_y %d key[%d %d]",
+				pen_data->coords.x, pen_data->coords.y,
+				pen_data->coords.p, pen_data->coords.tilt_x,
+				pen_data->coords.tilt_y, pen_data->keys[0].status == TS_TOUCH ? 1 : 0,
+				pen_data->keys[1].status == TS_TOUCH ? 1 : 0);
+	} else {
+		input_report_key(dev, BTN_TOUCH, 0);
+		input_report_key(dev, pen_data->coords.tool_type, 0);
+	}
+	/* report pen button */
+	for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) {
+		if (pen_data->keys[i].status == TS_TOUCH)
+			input_report_key(dev, pen_data->keys[i].code, 1);
+		else
+			input_report_key(dev, pen_data->keys[i].code, 0);
+	}
+
+	input_sync(dev);
+	mutex_unlock(&dev->mutex);
+}
+
+static void goodix_ts_report_finger(struct input_dev *dev,
+		struct goodix_touch_data *touch_data)
+{
+	unsigned int touch_num = touch_data->touch_num;
+	int i;
+
+	mutex_lock(&dev->mutex);
+
+	for (i = 0; i < GOODIX_MAX_TOUCH; i++) {
+		if (touch_data->coords[i].status == TS_TOUCH) {
+			ts_debug("report: id %d, x %d, y %d, w %d", i,
+				touch_data->coords[i].x, touch_data->coords[i].y,
+				touch_data->coords[i].w);
+			input_mt_slot(dev, i);
+			input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
+			input_report_abs(dev, ABS_MT_POSITION_X,
+					touch_data->coords[i].x);
+			input_report_abs(dev, ABS_MT_POSITION_Y,
+					touch_data->coords[i].y);
+			input_report_abs(dev, ABS_MT_TOUCH_MAJOR,
+					touch_data->coords[i].w);
+		} else {
+			input_mt_slot(dev, i);
+			input_mt_report_slot_state(dev, MT_TOOL_FINGER, false);
+		}
+	}
+
+	input_report_key(dev, BTN_TOUCH, touch_num > 0 ? 1 : 0);
+	input_sync(dev);
+
+	mutex_unlock(&dev->mutex);
+}
+
+static int goodix_ts_request_handle(struct goodix_ts_core *cd,
+	struct goodix_ts_event *ts_event)
+{
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+	int ret = -1;
+
+	if (ts_event->request_code == REQUEST_TYPE_CONFIG)
+		ret = goodix_send_ic_config(cd, CONFIG_TYPE_NORMAL);
+	else if (ts_event->request_code == REQUEST_TYPE_RESET)
+		ret = hw_ops->reset(cd, GOODIX_NORMAL_RESET_DELAY_MS);
+	else
+		ts_info("can not handle request type 0x%x",
+			  ts_event->request_code);
+	if (ret)
+		ts_err("failed handle request 0x%x",
+			 ts_event->request_code);
+	else
+		ts_info("success handle ic request 0x%x",
+			  ts_event->request_code);
+	return ret;
+}
+
+/**
+ * goodix_ts_threadirq_func - Bottom half of interrupt
+ * This functions is excuted in thread context,
+ * sleep in this function is permit.
+ *
+ * @data: pointer to touch core data
+ * return: 0 ok, <0 failed
+ */
+static irqreturn_t goodix_ts_threadirq_func(int irq, void *data)
+{
+	struct goodix_ts_core *core_data = data;
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+	struct goodix_ext_module *ext_module, *next;
+	struct goodix_ts_event *ts_event = &core_data->ts_event;
+	struct goodix_ts_esd *ts_esd = &core_data->ts_esd;
+	int ret;
+
+	ts_esd->irq_status = true;
+	core_data->irq_trig_cnt++;
+	/* inform external module */
+	mutex_lock(&goodix_modules.mutex);
+	list_for_each_entry_safe(ext_module, next,
+				 &goodix_modules.head, list) {
+		if (!ext_module->funcs->irq_event)
+			continue;
+		ret = ext_module->funcs->irq_event(core_data, ext_module);
+		if (ret == EVT_CANCEL_IRQEVT) {
+			mutex_unlock(&goodix_modules.mutex);
+			return IRQ_HANDLED;
+		}
+	}
+	mutex_unlock(&goodix_modules.mutex);
+
+	/* read touch data from touch device */
+	ret = hw_ops->event_handler(core_data, ts_event);
+	if (likely(!ret)) {
+		if (ts_event->event_type == EVENT_TOUCH) {
+			/* report touch */
+			goodix_ts_report_finger(core_data->input_dev,
+					&ts_event->touch_data);
+		}
+		if (core_data->board_data.pen_enable &&
+				ts_event->event_type == EVENT_PEN) {
+			goodix_ts_report_pen(core_data->pen_dev,
+					&ts_event->pen_data);
+		}
+		if (ts_event->event_type == EVENT_REQUEST) {
+			goodix_ts_request_handle(core_data, ts_event);
+		}
+	}
+
+	if (!core_data->tools_ctrl_sync && !ts_event->retry)
+		hw_ops->after_event_handler(core_data);
+	ts_event->retry = 0;
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * goodix_ts_init_irq - Requset interrput line from system
+ * @core_data: pointer to touch core data
+ * return: 0 ok, <0 failed
+ */
+static int goodix_ts_irq_setup(struct goodix_ts_core *core_data)
+{
+	const struct goodix_ts_board_data *ts_bdata = board_data(core_data);
+	int ret;
+
+	/* if ts_bdata-> irq is invalid */
+	core_data->irq = gpio_to_irq(ts_bdata->irq_gpio);
+	if (core_data->irq < 0) {
+		ts_err("failed get irq num %d", core_data->irq);
+		return -EINVAL;
+	}
+
+	ts_info("IRQ:%u,flags:%d", core_data->irq, (int)ts_bdata->irq_flags);
+	ret = devm_request_threaded_irq(&core_data->pdev->dev,
+				      core_data->irq, NULL,
+				      goodix_ts_threadirq_func,
+				      ts_bdata->irq_flags | IRQF_ONESHOT,
+				      GOODIX_CORE_DRIVER_NAME,
+				      core_data);
+	if (ret < 0)
+		ts_err("Failed to requeset threaded irq:%d", ret);
+	else
+		atomic_set(&core_data->irq_enabled, 1);
+
+	return ret;
+}
+
+/**
+ * goodix_ts_power_init - Get regulator for touch device
+ * @core_data: pointer to touch core data
+ * return: 0 ok, <0 failed
+ */
+static int goodix_ts_power_init(struct goodix_ts_core *core_data)
+{
+	struct goodix_ts_board_data *ts_bdata = board_data(core_data);
+	struct device *dev = core_data->bus->dev;
+	int ret = 0;
+
+	ts_info("Power init");
+	if (strlen(ts_bdata->avdd_name)) {
+		core_data->avdd = devm_regulator_get(dev,
+				 ts_bdata->avdd_name);
+		if (IS_ERR_OR_NULL(core_data->avdd)) {
+			ret = PTR_ERR(core_data->avdd);
+			ts_err("Failed to get regulator avdd:%d", ret);
+			core_data->avdd = NULL;
+			return ret;
+		}
+	} else {
+		ts_info("Avdd name is NULL");
+	}
+
+	if (strlen(ts_bdata->iovdd_name)) {
+		core_data->iovdd = devm_regulator_get(dev,
+				 ts_bdata->iovdd_name);
+		if (IS_ERR_OR_NULL(core_data->iovdd)) {
+			ret = PTR_ERR(core_data->iovdd);
+			ts_err("Failed to get regulator iovdd:%d", ret);
+			core_data->iovdd = NULL;
+		}
+	} else {
+		ts_info("iovdd name is NULL");
+	}
+
+	return ret;
+}
+
+/**
+ * goodix_ts_power_on - Turn on power to the touch device
+ * @core_data: pointer to touch core data
+ * return: 0 ok, <0 failed
+ */
+int goodix_ts_power_on(struct goodix_ts_core *cd)
+{
+	int ret = 0;
+
+	ts_info("Device power on");
+	if (cd->power_on)
+		return 0;
+
+	ret = cd->hw_ops->power_on(cd, true);
+	if (!ret)
+		cd->power_on = 1;
+	else
+		ts_err("failed power on, %d", ret);
+	return ret;
+}
+
+/**
+ * goodix_ts_power_off - Turn off power to the touch device
+ * @core_data: pointer to touch core data
+ * return: 0 ok, <0 failed
+ */
+int goodix_ts_power_off(struct goodix_ts_core *cd)
+{
+	int ret;
+
+	ts_info("Device power off");
+	if (!cd->power_on)
+		return 0;
+
+	ret = cd->hw_ops->power_on(cd, false);
+	if (!ret)
+		cd->power_on = 0;
+	else
+		ts_err("failed power off, %d", ret);
+
+	return ret;
+}
+
+/**
+ * goodix_ts_gpio_setup - Request gpio resources from GPIO subsysten
+ * @core_data: pointer to touch core data
+ * return: 0 ok, <0 failed
+ */
+static int goodix_ts_gpio_setup(struct goodix_ts_core *core_data)
+{
+	struct goodix_ts_board_data *ts_bdata = board_data(core_data);
+	int r = 0;
+
+	ts_info("GPIO setup,reset-gpio:%d, irq-gpio:%d",
+		ts_bdata->reset_gpio, ts_bdata->irq_gpio);
+	/*
+	 * after kenerl3.13, gpio_ api is deprecated, new
+	 * driver should use gpiod_ api.
+	 */
+	r = devm_gpio_request_one(&core_data->pdev->dev, ts_bdata->reset_gpio,
+				  GPIOF_OUT_INIT_LOW, "ts_reset_gpio");
+	if (r < 0) {
+		ts_err("Failed to request reset gpio, r:%d", r);
+		return r;
+	}
+
+	r = devm_gpio_request_one(&core_data->pdev->dev, ts_bdata->irq_gpio,
+				  GPIOF_IN, "ts_irq_gpio");
+	if (r < 0) {
+		ts_err("Failed to request irq gpio, r:%d", r);
+		return r;
+	}
+
+	if (ts_bdata->avdd_gpio > 0) {
+		r = devm_gpio_request_one(&core_data->pdev->dev, ts_bdata->avdd_gpio,
+				GPIOF_OUT_INIT_LOW, "ts_avdd_gpio");
+		if (r < 0) {
+			ts_err("Failed to request avdd-gpio, r:%d", r);
+			return r;
+		}
+	}
+
+	if (ts_bdata->iovdd_gpio > 0) {
+		r = devm_gpio_request_one(&core_data->pdev->dev, ts_bdata->iovdd_gpio,
+				GPIOF_OUT_INIT_LOW, "ts_iovdd_gpio");
+		if (r < 0) {
+			ts_err("Failed to request iovdd-gpio, r:%d", r);
+			return r;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * goodix_ts_input_dev_config - Requset and config a input device
+ *  then register it to input sybsystem.
+ * @core_data: pointer to touch core data
+ * return: 0 ok, <0 failed
+ */
+static int goodix_ts_input_dev_config(struct goodix_ts_core *core_data)
+{
+	struct goodix_ts_board_data *ts_bdata = board_data(core_data);
+	struct input_dev *input_dev = NULL;
+	int r;
+
+	input_dev = input_allocate_device();
+	if (!input_dev) {
+		ts_err("Failed to allocated input device");
+		return -ENOMEM;
+	}
+
+	core_data->input_dev = input_dev;
+	input_set_drvdata(input_dev, core_data);
+
+	input_dev->name = GOODIX_CORE_DRIVER_NAME;
+	input_dev->phys = GOOIDX_INPUT_PHYS;
+	input_dev->id.product = 0xDEAD;
+	input_dev->id.vendor = 0xBEEF;
+	input_dev->id.version = 10427;
+
+	__set_bit(EV_SYN, input_dev->evbit);
+	__set_bit(EV_KEY, input_dev->evbit);
+	__set_bit(EV_ABS, input_dev->evbit);
+	__set_bit(BTN_TOUCH, input_dev->keybit);
+	__set_bit(BTN_TOOL_FINGER, input_dev->keybit);
+
+#ifdef INPUT_PROP_DIRECT
+	__set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+#endif
+
+	/* set input parameters */
+	input_set_abs_params(input_dev, ABS_MT_POSITION_X,
+			     0, ts_bdata->panel_max_x, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
+			     0, ts_bdata->panel_max_y, 0, 0);
+	input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
+			     0, ts_bdata->panel_max_w, 0, 0);
+#ifdef INPUT_TYPE_B_PROTOCOL
+#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 7, 0)
+	input_mt_init_slots(input_dev, GOODIX_MAX_TOUCH,
+			    INPUT_MT_DIRECT);
+#else
+	input_mt_init_slots(input_dev, GOODIX_MAX_TOUCH);
+#endif
+#endif
+
+	input_set_capability(input_dev, EV_KEY, KEY_POWER);
+
+	r = input_register_device(input_dev);
+	if (r < 0) {
+		ts_err("Unable to register input device");
+		input_free_device(input_dev);
+		return r;
+	}
+
+	return 0;
+}
+
+static int goodix_ts_pen_dev_config(struct goodix_ts_core *core_data)
+{
+	struct goodix_ts_board_data *ts_bdata = board_data(core_data);
+	struct input_dev *pen_dev = NULL;
+	int r;
+
+	pen_dev = input_allocate_device();
+	if (!pen_dev) {
+		ts_err("Failed to allocated pen device");
+		return -ENOMEM;
+	}
+
+	core_data->pen_dev = pen_dev;
+	input_set_drvdata(pen_dev, core_data);
+
+	pen_dev->name = GOODIX_PEN_DRIVER_NAME;
+	pen_dev->id.product = 0xDEAD;
+	pen_dev->id.vendor = 0xBEEF;
+	pen_dev->id.version = 10427;
+
+	pen_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	__set_bit(ABS_X, pen_dev->absbit);
+	__set_bit(ABS_Y, pen_dev->absbit);
+	__set_bit(ABS_TILT_X, pen_dev->absbit);
+	__set_bit(ABS_TILT_Y, pen_dev->absbit);	
+	__set_bit(BTN_STYLUS, pen_dev->keybit);
+	__set_bit(BTN_STYLUS2, pen_dev->keybit);
+	__set_bit(BTN_TOUCH, pen_dev->keybit);
+	__set_bit(BTN_TOOL_PEN, pen_dev->keybit);
+	__set_bit(INPUT_PROP_DIRECT, pen_dev->propbit);
+	input_set_abs_params(pen_dev, ABS_X, 0, ts_bdata->panel_max_x, 0, 0);
+	input_set_abs_params(pen_dev, ABS_Y, 0, ts_bdata->panel_max_y, 0, 0);
+	input_set_abs_params(pen_dev, ABS_PRESSURE, 0,
+			     ts_bdata->panel_max_p, 0, 0);
+	input_set_abs_params(pen_dev, ABS_TILT_X,
+			-GOODIX_PEN_MAX_TILT, GOODIX_PEN_MAX_TILT, 0, 0);
+	input_set_abs_params(pen_dev, ABS_TILT_Y,
+			-GOODIX_PEN_MAX_TILT, GOODIX_PEN_MAX_TILT, 0, 0);				 
+
+	r = input_register_device(pen_dev);
+	if (r < 0) {
+		ts_err("Unable to register pen device");
+		input_free_device(pen_dev);
+		return r;
+	}
+
+	return 0;
+}
+
+void goodix_ts_input_dev_remove(struct goodix_ts_core *core_data)
+{
+	if (!core_data->input_dev)
+		return;
+	input_unregister_device(core_data->input_dev);
+	input_free_device(core_data->input_dev);
+	core_data->input_dev = NULL;
+}
+
+void goodix_ts_pen_dev_remove(struct goodix_ts_core *core_data)
+{
+	if (!core_data->pen_dev)
+		return;
+	input_unregister_device(core_data->pen_dev);
+	input_free_device(core_data->pen_dev);
+	core_data->pen_dev = NULL;
+}
+
+/**
+ * goodix_ts_esd_work - check hardware status and recovery
+ *  the hardware if needed.
+ */
+static void goodix_ts_esd_work(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct goodix_ts_esd *ts_esd = container_of(dwork,
+			struct goodix_ts_esd, esd_work);
+	struct goodix_ts_core *cd = container_of(ts_esd,
+			struct goodix_ts_core, ts_esd);
+	const struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+	int ret = 0;
+
+	if (ts_esd->irq_status)
+		goto exit;
+
+	if (!atomic_read(&ts_esd->esd_on))
+		return;
+
+	if (!hw_ops->esd_check)
+		return;
+
+	ret = hw_ops->esd_check(cd);
+	if (ret) {
+		ts_err("esd check failed");
+		goodix_ts_power_off(cd);
+		usleep_range(5000, 5100);
+		goodix_ts_power_on(cd);
+	}
+
+exit:
+	ts_esd->irq_status = false;
+	if (atomic_read(&ts_esd->esd_on))
+		schedule_delayed_work(&ts_esd->esd_work, 2 * HZ);
+}
+
+/**
+ * goodix_ts_esd_on - turn on esd protection
+ */
+static void goodix_ts_esd_on(struct goodix_ts_core *cd)
+{
+	struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
+	struct goodix_ts_esd *ts_esd = &cd->ts_esd;
+
+	if (!misc->esd_addr)
+		return;
+
+	if (atomic_read(&ts_esd->esd_on))
+		return;
+
+	atomic_set(&ts_esd->esd_on, 1);
+	if (!schedule_delayed_work(&ts_esd->esd_work, 2 * HZ)) {
+		ts_info("esd work already in workqueue");
+	}
+	ts_info("esd on");
+}
+
+/**
+ * goodix_ts_esd_off - turn off esd protection
+ */
+static void goodix_ts_esd_off(struct goodix_ts_core *cd)
+{
+	struct goodix_ts_esd *ts_esd = &cd->ts_esd;
+	int ret;
+
+	if (!atomic_read(&ts_esd->esd_on))
+		return;
+
+	atomic_set(&ts_esd->esd_on, 0);
+	ret = cancel_delayed_work_sync(&ts_esd->esd_work);
+	ts_info("Esd off, esd work state %d", ret);
+}
+
+/**
+ * goodix_esd_notifier_callback - notification callback
+ *  under certain condition, we need to turn off/on the esd
+ *  protector, we use kernel notify call chain to achieve this.
+ *
+ *  for example: before firmware update we need to turn off the
+ *  esd protector and after firmware update finished, we should
+ *  turn on the esd protector.
+ */
+static int goodix_esd_notifier_callback(struct notifier_block *nb,
+		unsigned long action, void *data)
+{
+	struct goodix_ts_esd *ts_esd = container_of(nb,
+			struct goodix_ts_esd, esd_notifier);
+
+	switch (action) {
+	case NOTIFY_FWUPDATE_START:
+	case NOTIFY_SUSPEND:
+	case NOTIFY_ESD_OFF:
+		goodix_ts_esd_off(ts_esd->ts_core);
+		break;
+	case NOTIFY_FWUPDATE_FAILED:
+	case NOTIFY_FWUPDATE_SUCCESS:
+	case NOTIFY_RESUME:
+	case NOTIFY_ESD_ON:
+		goodix_ts_esd_on(ts_esd->ts_core);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/**
+ * goodix_ts_esd_init - initialize esd protection
+ */
+int goodix_ts_esd_init(struct goodix_ts_core *cd)
+{
+	struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
+	struct goodix_ts_esd *ts_esd = &cd->ts_esd;
+
+	if (!cd->hw_ops->esd_check || !misc->esd_addr) {
+		ts_info("missing key info for esd check");
+		return 0;
+	}
+
+	INIT_DELAYED_WORK(&ts_esd->esd_work, goodix_ts_esd_work);
+	ts_esd->ts_core = cd;
+	atomic_set(&ts_esd->esd_on, 0);
+	ts_esd->esd_notifier.notifier_call = goodix_esd_notifier_callback;
+	goodix_ts_register_notifier(&ts_esd->esd_notifier);
+	goodix_ts_esd_on(cd);
+
+	return 0;
+}
+
+static void goodix_ts_release_connects(struct goodix_ts_core *core_data)
+{
+	struct input_dev *input_dev = core_data->input_dev;
+	int i;
+
+	mutex_lock(&input_dev->mutex);
+
+	for (i = 0; i < GOODIX_MAX_TOUCH; i++) {
+		input_mt_slot(input_dev, i);
+		input_mt_report_slot_state(input_dev,
+				MT_TOOL_FINGER,
+				false);
+	}
+	input_report_key(input_dev, BTN_TOUCH, 0);
+	input_mt_sync_frame(input_dev);
+	input_sync(input_dev);
+
+	mutex_unlock(&input_dev->mutex);
+}
+
+/**
+ * goodix_ts_suspend - Touchscreen suspend function
+ * Called by PM/FB/EARLYSUSPEN module to put the device to  sleep
+ */
+static int goodix_ts_suspend(struct goodix_ts_core *core_data)
+{
+	struct goodix_ext_module *ext_module, *next;
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+	int ret;
+
+	if (core_data->init_stage < CORE_INIT_STAGE2 ||
+			atomic_read(&core_data->suspended))
+		return 0;
+
+	ts_info("Suspend start");
+	atomic_set(&core_data->suspended, 1);
+	/* disable irq */
+	hw_ops->irq_enable(core_data, false);
+
+	/*
+	 * notify suspend event, inform the esd protector
+	 * and charger detector to turn off the work
+	 */
+	goodix_ts_blocking_notify(NOTIFY_SUSPEND, NULL);
+
+	/* inform external module */
+	mutex_lock(&goodix_modules.mutex);
+	if (!list_empty(&goodix_modules.head)) {
+		list_for_each_entry_safe(ext_module, next,
+					 &goodix_modules.head, list) {
+			if (!ext_module->funcs->before_suspend)
+				continue;
+
+			ret = ext_module->funcs->before_suspend(core_data,
+							      ext_module);
+			if (ret == EVT_CANCEL_SUSPEND) {
+				mutex_unlock(&goodix_modules.mutex);
+				ts_info("Canceled by module:%s",
+					ext_module->name);
+				goto out;
+			}
+		}
+	}
+	mutex_unlock(&goodix_modules.mutex);
+
+	/* enter sleep mode or power off */
+	if (hw_ops->suspend)
+		hw_ops->suspend(core_data);
+
+	/* inform exteranl modules */
+	mutex_lock(&goodix_modules.mutex);
+	if (!list_empty(&goodix_modules.head)) {
+		list_for_each_entry_safe(ext_module, next,
+					&goodix_modules.head, list) {
+			if (!ext_module->funcs->after_suspend)
+				continue;
+
+			ret = ext_module->funcs->after_suspend(core_data,
+							     ext_module);
+			if (ret == EVT_CANCEL_SUSPEND) {
+				mutex_unlock(&goodix_modules.mutex);
+				ts_info("Canceled by module:%s",
+					ext_module->name);
+				goto out;
+			}
+		}
+	}
+	mutex_unlock(&goodix_modules.mutex);
+
+out:
+	goodix_ts_release_connects(core_data);
+	ts_info("Suspend end");
+	return 0;
+}
+
+/**
+ * goodix_ts_resume - Touchscreen resume function
+ * Called by PM/FB/EARLYSUSPEN module to wakeup device
+ */
+static int goodix_ts_resume(struct goodix_ts_core *core_data)
+{
+	struct goodix_ext_module *ext_module, *next;
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+	int ret;
+
+	if (core_data->init_stage < CORE_INIT_STAGE2 ||
+			!atomic_read(&core_data->suspended))
+		return 0;
+
+	ts_info("Resume start");
+	atomic_set(&core_data->suspended, 0);
+
+	mutex_lock(&goodix_modules.mutex);
+	if (!list_empty(&goodix_modules.head)) {
+		list_for_each_entry_safe(ext_module, next,
+					 &goodix_modules.head, list) {
+			if (!ext_module->funcs->before_resume)
+				continue;
+
+			ret = ext_module->funcs->before_resume(core_data,
+					ext_module);
+			if (ret == EVT_CANCEL_RESUME) {
+				mutex_unlock(&goodix_modules.mutex);
+				ts_info("Canceled by module:%s",
+					ext_module->name);
+				goto out;
+			}
+		}
+	}
+	mutex_unlock(&goodix_modules.mutex);
+
+	/* reset device or power on*/
+	if (hw_ops->resume)
+		hw_ops->resume(core_data);
+
+	mutex_lock(&goodix_modules.mutex);
+	if (!list_empty(&goodix_modules.head)) {
+		list_for_each_entry_safe(ext_module, next,
+					 &goodix_modules.head, list) {
+			if (!ext_module->funcs->after_resume)
+				continue;
+
+			ret = ext_module->funcs->after_resume(core_data,
+							    ext_module);
+			if (ret == EVT_CANCEL_RESUME) {
+				mutex_unlock(&goodix_modules.mutex);
+				ts_info("Canceled by module:%s",
+					ext_module->name);
+				goto out;
+			}
+		}
+	}
+	mutex_unlock(&goodix_modules.mutex);
+
+out:
+	/* enable irq */
+	hw_ops->irq_enable(core_data, true);
+	/* open esd */
+	goodix_ts_blocking_notify(NOTIFY_RESUME, NULL);
+	ts_info("Resume end");
+	return 0;
+}
+
+#ifdef CONFIG_FB
+/**
+ * goodix_ts_fb_notifier_callback - Framebuffer notifier callback
+ * Called by kernel during framebuffer blanck/unblank phrase
+ */
+int goodix_ts_fb_notifier_callback(struct notifier_block *self,
+	unsigned long event, void *data)
+{
+	struct goodix_ts_core *core_data =
+		container_of(self, struct goodix_ts_core, fb_notifier);
+	struct fb_event *fb_event = data;
+
+	if (fb_event && fb_event->data && core_data) {
+		if (event == FB_EARLY_EVENT_BLANK) {
+			/* before fb blank */
+		} else if (event == FB_EVENT_BLANK) {
+			int *blank = fb_event->data;
+			if (*blank == FB_BLANK_UNBLANK)
+				goodix_ts_resume(core_data);
+			else if (*blank == FB_BLANK_POWERDOWN)
+				goodix_ts_suspend(core_data);
+		}
+	}
+
+	return 0;
+}
+#endif
+
+
+#ifdef CONFIG_PM
+#if !defined(CONFIG_FB) && !defined(CONFIG_HAS_EARLYSUSPEND)
+/**
+ * goodix_ts_pm_suspend - PM suspend function
+ * Called by kernel during system suspend phrase
+ */
+static int goodix_ts_pm_suspend(struct device *dev)
+{
+	struct goodix_ts_core *core_data =
+		dev_get_drvdata(dev);
+
+	return goodix_ts_suspend(core_data);
+}
+/**
+ * goodix_ts_pm_resume - PM resume function
+ * Called by kernel during system wakeup
+ */
+static int goodix_ts_pm_resume(struct device *dev)
+{
+	struct goodix_ts_core *core_data =
+		dev_get_drvdata(dev);
+
+	return goodix_ts_resume(core_data);
+}
+#endif
+#endif
+
+/**
+ * goodix_generic_noti_callback - generic notifier callback
+ *  for goodix touch notification event.
+ */
+static int goodix_generic_noti_callback(struct notifier_block *self,
+		unsigned long action, void *data)
+{
+	struct goodix_ts_core *cd = container_of(self,
+			struct goodix_ts_core, ts_notifier);
+	const struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+
+	if (cd->init_stage < CORE_INIT_STAGE2)
+		return 0;
+
+	ts_info("notify event type 0x%x", (unsigned int)action);
+	switch (action) {
+	case NOTIFY_FWUPDATE_START:
+		hw_ops->irq_enable(cd, 0);
+		break;
+	case NOTIFY_FWUPDATE_SUCCESS:
+	case NOTIFY_FWUPDATE_FAILED:
+		if (hw_ops->read_version(cd, &cd->fw_version))
+			ts_info("failed read fw version info[ignore]");
+		hw_ops->irq_enable(cd, 1);
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+int goodix_ts_stage2_init(struct goodix_ts_core *cd)
+{
+	int ret;
+
+	/* alloc/config/register input device */
+	ret = goodix_ts_input_dev_config(cd);
+	if (ret < 0) {
+		ts_err("failed set input device");
+		return ret;
+	}
+
+	if (cd->board_data.pen_enable) {
+		ret = goodix_ts_pen_dev_config(cd);
+		if (ret < 0) {
+			ts_err("failed set pen device");
+			goto err_finger;
+		}
+	}
+	/* request irq line */
+	ret = goodix_ts_irq_setup(cd);
+	if (ret < 0) {
+		ts_info("failed set irq");
+		goto exit;
+	}
+	ts_info("success register irq");
+
+#ifdef CONFIG_FB
+	cd->fb_notifier.notifier_call = goodix_ts_fb_notifier_callback;
+	if (fb_register_client(&cd->fb_notifier))
+		ts_err("Failed to register fb notifier client:%d", ret);
+#endif
+	/* create sysfs files */
+	goodix_ts_sysfs_init(cd);
+
+	/* create procfs files */
+	goodix_ts_procfs_init(cd);
+
+	/* esd protector */
+	goodix_ts_esd_init(cd);
+
+	/* gesture init */
+	gesture_module_init();
+
+	/* inspect init */
+	inspect_module_init();
+
+	return 0;
+exit:
+	goodix_ts_pen_dev_remove(cd);
+err_finger:
+	goodix_ts_input_dev_remove(cd);
+	return ret;
+}
+
+/* try send the config specified with type */
+static int goodix_send_ic_config(struct goodix_ts_core *cd, int type)
+{
+	u32 config_id;
+	struct goodix_ic_config *cfg;
+
+	if (type >= GOODIX_MAX_CONFIG_GROUP) {
+		ts_err("unsupproted config type %d", type);
+		return -EINVAL;
+	}
+
+	cfg = cd->ic_configs[type];
+	if (!cfg || cfg->len <= 0) {
+		ts_info("no valid normal config found");
+		return -EINVAL;
+	}
+
+	config_id = goodix_get_file_config_id(cfg->data);
+	if (cd->ic_info.version.config_id == config_id) {
+		ts_info("config id is equal 0x%x, skiped", config_id);
+		return 0;
+	}
+
+	ts_info("try send config, id=0x%x", config_id);
+	return cd->hw_ops->send_config(cd, cfg->data, cfg->len);
+}
+
+/**
+ * goodix_later_init_thread - init IC fw and config
+ * @data: point to goodix_ts_core
+ *
+ * This function respond for get fw version and try upgrade fw and config.
+ * Note: when init encounter error, need release all resource allocated here.
+ */
+static int goodix_later_init_thread(void *data)
+{
+	int ret, i;
+	int update_flag = UPDATE_MODE_BLOCK | UPDATE_MODE_SRC_REQUEST;
+	struct goodix_ts_core *cd = data;
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+
+	/* step 1: read version */
+	ret = cd->hw_ops->read_version(cd, &cd->fw_version);
+	if (ret < 0) {
+		ts_err("failed to get version info, try to upgrade");
+		update_flag |= UPDATE_MODE_FORCE;
+		goto upgrade;
+	}
+
+	/* step 2: get config data from config bin */
+	ret = goodix_get_config_proc(cd);
+	if (ret)
+		ts_info("no valid ic config found");
+	else
+		ts_info("success get valid ic config");
+
+upgrade:
+	/* step 3: init fw struct add try do fw upgrade */
+	ret = goodix_fw_update_init(cd);
+	if (ret) {
+		ts_err("failed init fw update module");
+		goto err_out;
+	}
+
+	ts_info("update flag: 0x%X", update_flag);
+	ret = goodix_do_fw_update(cd->ic_configs[CONFIG_TYPE_NORMAL], update_flag);
+	if (ret)
+		ts_err("failed do fw update");
+
+	/* step 4: get fw version and ic_info
+	 * at this step we believe that the ic is in normal mode,
+	 * if the version info is invalid there must have some
+	 * problem we cann't cover so exit init directly.
+	 */
+	ret = hw_ops->read_version(cd, &cd->fw_version);
+	if (ret) {
+		ts_err("invalid fw version, abort");
+		goto uninit_fw;
+	}
+	ret = hw_ops->get_ic_info(cd, &cd->ic_info);
+	if (ret) {
+		ts_err("invalid ic info, abort");
+		goto uninit_fw;
+	}
+
+	/* the recomend way to update ic config is throuth ISP,
+	 * if not we will send config with interactive mode
+	 */
+	goodix_send_ic_config(cd, CONFIG_TYPE_NORMAL);
+
+	/* init other resources */
+	ret = goodix_ts_stage2_init(cd);
+	if (ret) {
+		ts_err("stage2 init failed");
+		goto uninit_fw;
+	}
+	cd->init_stage = CORE_INIT_STAGE2;
+
+	return 0;
+
+uninit_fw:
+	goodix_fw_update_uninit();
+err_out:
+	ts_err("stage2 init failed");
+	cd->init_stage = CORE_INIT_FAIL;
+	for (i = 0; i < GOODIX_MAX_CONFIG_GROUP; i++) {
+		if (cd->ic_configs[i])
+			kfree(cd->ic_configs[i]);
+		cd->ic_configs[i] = NULL;
+	}
+	return ret;
+}
+
+static int goodix_start_later_init(struct goodix_ts_core *ts_core)
+{
+	struct task_struct *init_thrd;
+	/* create and run update thread */
+	init_thrd = kthread_run(goodix_later_init_thread,
+				ts_core, "goodix_init_thread");
+	if (IS_ERR_OR_NULL(init_thrd)) {
+		ts_err("Failed to create update thread:%ld",
+		       PTR_ERR(init_thrd));
+		return -EFAULT;
+	}
+	return 0;
+}
+
+/**
+ * goodix_ts_probe - called by kernel when Goodix touch
+ *  platform driver is added.
+ */
+static int goodix_ts_probe(struct platform_device *pdev)
+{
+	struct goodix_ts_core *core_data = NULL;
+	struct goodix_bus_interface *bus_interface;
+	int ret;
+
+	ts_info("goodix_ts_probe IN");
+
+	bus_interface = pdev->dev.platform_data;
+	if (!bus_interface) {
+		ts_err("Invalid touch device");
+		core_module_prob_sate = CORE_MODULE_PROB_FAILED;
+		return -ENODEV;
+	}
+
+	core_data = devm_kzalloc(&pdev->dev,
+			sizeof(struct goodix_ts_core), GFP_KERNEL);
+	if (!core_data) {
+		ts_err("Failed to allocate memory for core data");
+		core_module_prob_sate = CORE_MODULE_PROB_FAILED;
+		return -ENOMEM;
+	}
+
+	if (IS_ENABLED(CONFIG_OF) && bus_interface->dev->of_node) {
+		/* parse devicetree property */
+		ret = goodix_parse_dt(bus_interface->dev->of_node,
+					&core_data->board_data);
+		if (ret) {
+			ts_err("failed parse device info form dts, %d", ret);
+			return -EINVAL;
+		}
+	} else {
+		ts_err("no valid device tree node found");
+		return -ENODEV;
+	}
+
+	core_data->hw_ops = goodix_get_hw_ops();
+	if (!core_data->hw_ops) {
+		ts_err("hw ops is NULL");
+		core_module_prob_sate = CORE_MODULE_PROB_FAILED;
+		return -EINVAL;
+	}
+	goodix_core_module_init();
+	/* touch core layer is a platform driver */
+	core_data->pdev = pdev;
+	core_data->bus = bus_interface;
+	platform_set_drvdata(pdev, core_data);
+
+	/* get GPIO resource */
+	ret = goodix_ts_gpio_setup(core_data);
+	if (ret) {
+		ts_err("failed init gpio");
+		goto err_out;
+	}
+
+	ret = goodix_ts_power_init(core_data);
+	if (ret) {
+		ts_err("failed init power");
+		goto err_out;
+	}
+
+	ret = goodix_ts_power_on(core_data);
+	if (ret) {
+		ts_err("failed power on");
+		goto err_out;
+	}
+
+	/* generic notifier callback */
+	core_data->ts_notifier.notifier_call = goodix_generic_noti_callback;
+	goodix_ts_register_notifier(&core_data->ts_notifier);
+
+	/* debug node init */
+	goodix_tools_init();
+
+	core_data->init_stage = CORE_INIT_STAGE1;
+	goodix_modules.core_data = core_data;
+	core_module_prob_sate = CORE_MODULE_PROB_SUCCESS;
+
+	/* Try start a thread to get config-bin info */
+	goodix_start_later_init(core_data);
+
+	ts_info("goodix_ts_core probe success");
+	return 0;
+
+err_out:
+	core_data->init_stage = CORE_INIT_FAIL;
+	core_module_prob_sate = CORE_MODULE_PROB_FAILED;
+	ts_err("goodix_ts_core failed, ret:%d", ret);
+	return ret;
+}
+
+static int goodix_ts_remove(struct platform_device *pdev)
+{
+	struct goodix_ts_core *core_data = platform_get_drvdata(pdev);
+	struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
+	struct goodix_ts_esd *ts_esd = &core_data->ts_esd;
+
+	goodix_ts_unregister_notifier(&core_data->ts_notifier);
+	goodix_tools_exit();
+
+	if (core_data->init_stage >= CORE_INIT_STAGE2) {
+		gesture_module_exit();
+		inspect_module_exit();
+		hw_ops->irq_enable(core_data, false);
+	#ifdef CONFIG_FB
+		fb_unregister_client(&core_data->fb_notifier);
+	#endif
+		core_module_prob_sate = CORE_MODULE_REMOVED;
+		if (atomic_read(&core_data->ts_esd.esd_on))
+			goodix_ts_esd_off(core_data);
+		goodix_ts_unregister_notifier(&ts_esd->esd_notifier);
+
+		goodix_fw_update_uninit();
+		goodix_ts_input_dev_remove(core_data);
+		goodix_ts_pen_dev_remove(core_data);
+		goodix_ts_sysfs_exit(core_data);
+		goodix_ts_procfs_exit(core_data);
+		goodix_ts_power_off(core_data);
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops dev_pm_ops = {
+#if !defined(CONFIG_FB) && !defined(CONFIG_HAS_EARLYSUSPEND)
+	.suspend = goodix_ts_pm_suspend,
+	.resume = goodix_ts_pm_resume,
+#endif
+};
+#endif
+
+static const struct platform_device_id ts_core_ids[] = {
+	{.name = GOODIX_CORE_DRIVER_NAME},
+	{}
+};
+MODULE_DEVICE_TABLE(platform, ts_core_ids);
+
+static struct platform_driver goodix_ts_driver = {
+	.driver = {
+		.name = GOODIX_CORE_DRIVER_NAME,
+		.owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm = &dev_pm_ops,
+#endif
+	},
+	.probe = goodix_ts_probe,
+	.remove = goodix_ts_remove,
+	.id_table = ts_core_ids,
+};
+
+static int __init goodix_ts_core_init(void)
+{
+	int ret;
+
+	ts_info("Core layer init:%s", GOODIX_DRIVER_VERSION);
+#ifdef CONFIG_TOUCHSCREEN_GOODIX_BRL_SPI
+	ret = goodix_spi_bus_init();
+#else
+	ret = goodix_i2c_bus_init();
+#endif
+	if (ret) {
+		ts_err("failed add bus driver");
+		return ret;
+	}
+	return platform_driver_register(&goodix_ts_driver);
+}
+
+static void __exit goodix_ts_core_exit(void)
+{
+	ts_info("Core layer exit");
+	platform_driver_unregister(&goodix_ts_driver);
+#ifdef CONFIG_TOUCHSCREEN_GOODIX_BRL_SPI
+	goodix_spi_bus_exit();
+#else
+	goodix_i2c_bus_exit();
+#endif
+}
+
+late_initcall(goodix_ts_core_init);
+module_exit(goodix_ts_core_exit);
+
+MODULE_DESCRIPTION("Goodix Touchscreen Core Module");
+MODULE_AUTHOR("Goodix, Inc.");
+MODULE_LICENSE("GPL v2");

+ 630 - 0
goodix_ts_core.h

@@ -0,0 +1,630 @@
+#ifndef _GOODIX_TS_CORE_H_
+#define _GOODIX_TS_CORE_H_
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+#include <linux/vmalloc.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/of_irq.h>
+#ifdef CONFIG_OF
+#include <linux/of_gpio.h>
+#include <linux/regulator/consumer.h>
+#endif
+#ifdef CONFIG_FB
+#include <linux/notifier.h>
+#include <linux/fb.h>
+#endif
+
+#define GOODIX_CORE_DRIVER_NAME			"goodix_ts"
+#define GOODIX_PEN_DRIVER_NAME			"goodix_ts,pen"
+#define GOODIX_DRIVER_VERSION			"v1.2.3"
+#define GOODIX_MAX_TOUCH				10
+#define GOODIX_PEN_MAX_PRESSURE			4096
+#define GOODIX_MAX_PEN_KEY 				2
+#define GOODIX_PEN_MAX_TILT				90
+#define GOODIX_CFG_MAX_SIZE				4096
+#define GOODIX_MAX_STR_LABLE_LEN		32
+#define GOODIX_MAX_FRAMEDATA_LEN		1700
+
+#define GOODIX_NORMAL_RESET_DELAY_MS	100
+#define GOODIX_HOLD_CPU_RESET_DELAY_MS  5
+
+#define GOODIX_RETRY_3					3
+#define GOODIX_RETRY_5					5
+#define GOODIX_RETRY_10					10
+
+#define TS_DEFAULT_FIRMWARE				"goodix_firmware.bin"
+#define TS_DEFAULT_CFG_BIN 				"goodix_cfg_group.bin"
+
+enum CORD_PROB_STA {
+	CORE_MODULE_UNPROBED = 0,
+	CORE_MODULE_PROB_SUCCESS = 1,
+	CORE_MODULE_PROB_FAILED = -1,
+	CORE_MODULE_REMOVED = -2,
+};
+
+enum GOODIX_ERR_CODE {
+	GOODIX_EBUS      = (1<<0),
+	GOODIX_ECHECKSUM = (1<<1),
+	GOODIX_EVERSION  = (1<<2),
+	GOODIX_ETIMEOUT  = (1<<3),
+	GOODIX_EMEMCMP   = (1<<4),
+
+	GOODIX_EOTHER    = (1<<7)
+};
+
+enum IC_TYPE_ID {
+	IC_TYPE_NONE,
+	IC_TYPE_NORMANDY,
+	IC_TYPE_NANJING,
+	IC_TYPE_YELLOWSTONE,
+	IC_TYPE_BERLIN_A,
+	IC_TYPE_BERLIN_B,
+	IC_TYPE_BERLIN_D
+};
+
+enum GOODIX_IC_CONFIG_TYPE {
+	CONFIG_TYPE_TEST = 0,
+	CONFIG_TYPE_NORMAL = 1,
+	CONFIG_TYPE_HIGHSENSE = 2,
+	CONFIG_TYPE_CHARGER = 3,
+	CONFIG_TYPE_CHARGER_HS = 4,
+	CONFIG_TYPE_HOLSTER = 5,
+	CONFIG_TYPE_HOSTER_CH = 6,
+	CONFIG_TYPE_OTHER = 7,
+	/* keep this at the last */
+	GOODIX_MAX_CONFIG_GROUP = 8,
+};
+
+enum CHECKSUM_MODE {
+	CHECKSUM_MODE_U8_LE,
+	CHECKSUM_MODE_U16_LE,
+};
+
+#define MAX_SCAN_FREQ_NUM            5
+#define MAX_SCAN_RATE_NUM            5
+#define MAX_FREQ_NUM_STYLUS          8
+#define MAX_STYLUS_SCAN_FREQ_NUM     6
+#pragma pack(1)
+struct frame_head {
+	uint8_t sync;
+	uint16_t frame_index;
+	uint16_t cur_frame_len;
+	uint16_t next_frame_len;
+	uint32_t data_en; /* 0- 7 for pack_en; 8 - 31 for type en */
+	uint8_t touch_pack_index;
+	uint8_t stylus_pack_index;
+	uint8_t res;
+	uint16_t checksum;
+};
+
+struct goodix_fw_version {
+	u8 rom_pid[6];               /* rom PID */
+	u8 rom_vid[3];               /* Mask VID */
+	u8 rom_vid_reserved;
+	u8 patch_pid[8];              /* Patch PID */
+	u8 patch_vid[4];              /* Patch VID */
+	u8 patch_vid_reserved;
+	u8 sensor_id;
+	u8 reserved[2];
+	u16 checksum;
+};
+
+struct goodix_ic_info_version {
+	u8 info_customer_id;
+	u8 info_version_id;
+	u8 ic_die_id;
+	u8 ic_version_id;
+	u32 config_id;
+	u8 config_version;
+	u8 frame_data_customer_id;
+	u8 frame_data_version_id;
+	u8 touch_data_customer_id;
+	u8 touch_data_version_id;
+	u8 reserved[3];
+};
+
+struct goodix_ic_info_feature { /* feature info*/
+	u16 freqhop_feature;
+	u16 calibration_feature;
+	u16 gesture_feature;
+	u16 side_touch_feature;
+	u16 stylus_feature;
+};
+
+struct goodix_ic_info_param { /* param */
+	u8 drv_num;
+	u8 sen_num;
+	u8 button_num;
+	u8 force_num;
+	u8 active_scan_rate_num;
+	u16 active_scan_rate[MAX_SCAN_RATE_NUM];
+	u8 mutual_freq_num;
+	u16 mutual_freq[MAX_SCAN_FREQ_NUM];
+	u8 self_tx_freq_num;
+	u16 self_tx_freq[MAX_SCAN_FREQ_NUM];
+	u8 self_rx_freq_num;
+	u16 self_rx_freq[MAX_SCAN_FREQ_NUM];
+	u8 stylus_freq_num;
+	u16 stylus_freq[MAX_FREQ_NUM_STYLUS];
+};
+
+struct goodix_ic_info_misc { /* other data */
+	u32 cmd_addr;
+	u16 cmd_max_len;
+	u32 cmd_reply_addr;
+	u16 cmd_reply_len;
+	u32 fw_state_addr;
+	u16 fw_state_len;
+	u32 fw_buffer_addr;
+	u16 fw_buffer_max_len;
+	u32 frame_data_addr;
+	u16 frame_data_head_len;
+	u16 fw_attr_len;
+	u16 fw_log_len;
+	u8 pack_max_num;
+	u8 pack_compress_version;
+	u16 stylus_struct_len;
+	u16 mutual_struct_len;
+	u16 self_struct_len;
+	u16 noise_struct_len;
+	u32 touch_data_addr;
+	u16 touch_data_head_len;
+	u16 point_struct_len;
+	u16 reserved1;
+	u16 reserved2;
+	u32 mutual_rawdata_addr;
+	u32 mutual_diffdata_addr;
+	u32 mutual_refdata_addr;
+	u32 self_rawdata_addr;
+	u32 self_diffdata_addr;
+	u32 self_refdata_addr;
+	u32 iq_rawdata_addr;
+	u32 iq_refdata_addr;
+	u32 im_rawdata_addr;
+	u16 im_readata_len;
+	u32 noise_rawdata_addr;
+	u16 noise_rawdata_len;
+	u32 stylus_rawdata_addr;
+	u16 stylus_rawdata_len;
+	u32 noise_data_addr;
+	u32 esd_addr;
+};
+
+struct goodix_ic_info {
+	u16 length;
+	struct goodix_ic_info_version version;
+	struct goodix_ic_info_feature feature;
+	struct goodix_ic_info_param parm;
+	struct goodix_ic_info_misc misc;
+};
+#pragma pack()
+
+/*
+ * struct ts_rawdata_info
+ *
+ */
+#define TS_RAWDATA_BUFF_MAX             7000
+#define TS_RAWDATA_RESULT_MAX           100
+struct ts_rawdata_info {
+	int used_size; //fill in rawdata size
+	s16 buff[TS_RAWDATA_BUFF_MAX];
+	char result[TS_RAWDATA_RESULT_MAX];
+};
+
+/*
+ * struct goodix_module - external modules container
+ * @head: external modules list
+ * @initilized: whether this struct is initilized
+ * @mutex: mutex lock
+ * @wq: workqueue to do register work
+ * @core_data: core_data pointer
+ */
+struct goodix_module {
+	struct list_head head;
+	bool initilized;
+	struct mutex mutex;
+	struct workqueue_struct *wq;
+	struct goodix_ts_core *core_data;
+};
+
+/*
+ * struct goodix_ts_board_data -  board data
+ * @avdd_name: name of analoy regulator
+ * @iovdd_name: name of analoy regulator
+ * @reset_gpio: reset gpio number
+ * @irq_gpio: interrupt gpio number
+ * @irq_flag: irq trigger type
+ * @swap_axis: whether swaw x y axis
+ * @panel_max_x/y/w/p: resolution and size
+ * @pannel_key_map: key map
+ * @fw_name: name of the firmware image
+ */
+struct goodix_ts_board_data {
+	char avdd_name[GOODIX_MAX_STR_LABLE_LEN];
+	char iovdd_name[GOODIX_MAX_STR_LABLE_LEN];
+	int reset_gpio;
+	int irq_gpio;
+	int avdd_gpio;
+	int iovdd_gpio;
+	unsigned int  irq_flags;
+
+	unsigned int swap_axis;
+	unsigned int panel_max_x;
+	unsigned int panel_max_y;
+	unsigned int panel_max_w; /*major and minor*/
+	unsigned int panel_max_p; /*pressure*/
+
+	bool pen_enable;
+	char fw_name[GOODIX_MAX_STR_LABLE_LEN];
+	char cfg_bin_name[GOODIX_MAX_STR_LABLE_LEN];
+};
+
+enum goodix_fw_update_mode {
+	UPDATE_MODE_DEFAULT = 0,
+	UPDATE_MODE_FORCE = (1<<0), /* force update mode */
+	UPDATE_MODE_BLOCK = (1<<1), /* update in block mode */
+	UPDATE_MODE_FLASH_CFG = (1<<2), /* reflash config */
+	UPDATE_MODE_SRC_SYSFS = (1<<4), /* firmware file from sysfs */
+	UPDATE_MODE_SRC_HEAD = (1<<5), /* firmware file from head file */
+	UPDATE_MODE_SRC_REQUEST = (1<<6), /* request firmware */
+	UPDATE_MODE_SRC_ARGS = (1<<7), /* firmware data from function args */
+};
+
+#define MAX_CMD_DATA_LEN 10
+#define MAX_CMD_BUF_LEN  16
+#pragma pack(1)
+struct goodix_ts_cmd {
+	union {
+		struct {
+			u8 state;
+			u8 ack;
+			u8 len;
+			u8 cmd;
+			u8 data[MAX_CMD_DATA_LEN];
+		};
+		u8 buf[MAX_CMD_BUF_LEN];
+	};
+};
+#pragma pack()
+
+/* interrupt event type */
+enum ts_event_type {
+	EVENT_INVALID = 0,
+	EVENT_TOUCH = (1 << 0), /* finger touch event */
+	EVENT_PEN = (1 << 1),   /* pen event */
+	EVENT_REQUEST = (1 << 2),
+	EVENT_GESTURE = (1 << 3),
+};
+
+enum ts_request_type {
+	REQUEST_TYPE_CONFIG = 1,
+	REQUEST_TYPE_RESET = 3,
+};
+
+/* notifier event */
+enum ts_notify_event {
+	NOTIFY_FWUPDATE_START,
+	NOTIFY_FWUPDATE_FAILED,
+	NOTIFY_FWUPDATE_SUCCESS,
+	NOTIFY_SUSPEND,
+	NOTIFY_RESUME,
+	NOTIFY_ESD_OFF,
+	NOTIFY_ESD_ON,
+	NOTIFY_CFG_BIN_FAILED,
+	NOTIFY_CFG_BIN_SUCCESS,
+};
+
+enum touch_point_status {
+	TS_NONE,
+	TS_RELEASE,
+	TS_TOUCH,
+};
+/* coordinate package */
+struct goodix_ts_coords {
+	int status; /* NONE, RELEASE, TOUCH */
+	unsigned int x, y, w, p;
+};
+
+struct goodix_pen_coords {
+	int status; /* NONE, RELEASE, TOUCH */
+	int tool_type;  /* BTN_TOOL_RUBBER BTN_TOOL_PEN */
+	unsigned int x, y, p;
+	signed char tilt_x;
+	signed char tilt_y;
+};
+
+/* touch event data */
+struct goodix_touch_data {
+	int touch_num;
+	struct goodix_ts_coords coords[GOODIX_MAX_TOUCH];
+};
+
+struct goodix_ts_key {
+	int status;
+	int code;
+};
+
+struct goodix_pen_data {
+	struct goodix_pen_coords coords;
+	struct goodix_ts_key keys[GOODIX_MAX_PEN_KEY];
+};
+
+/*
+ * struct goodix_ts_event - touch event struct
+ * @event_type: touch event type, touch data or
+ *	request event
+ * @event_data: event data
+ */
+struct goodix_ts_event {
+	int retry;
+	enum ts_event_type event_type;
+	u8 request_code; /* represent the request type */
+	u8 gesture_type;
+	struct goodix_touch_data touch_data;
+	struct goodix_pen_data pen_data;
+};
+
+enum goodix_ic_bus_type {
+	GOODIX_BUS_TYPE_I2C,
+	GOODIX_BUS_TYPE_SPI,
+	GOODIX_BUS_TYPE_I3C,
+};
+
+struct goodix_bus_interface {
+	int bus_type;
+	int ic_type;
+	struct device *dev;
+	int (*read)(struct device *dev, unsigned int addr,
+			 unsigned char *data, unsigned int len);
+	int (*write)(struct device *dev, unsigned int addr,
+			unsigned char *data, unsigned int len);	
+};
+
+struct goodix_ts_hw_ops {
+	int (*power_on)(struct goodix_ts_core *cd, bool on);
+	int (*resume)(struct goodix_ts_core *cd);
+	int (*suspend)(struct goodix_ts_core *cd);
+	int (*gesture)(struct goodix_ts_core *cd, int gesture_type);
+	int (*reset)(struct goodix_ts_core *cd, int delay_ms);
+	int (*irq_enable)(struct goodix_ts_core *cd, bool enable);
+	int (*read)(struct goodix_ts_core *cd, unsigned int addr,
+		    unsigned char *data, unsigned int len);
+	int (*write)(struct goodix_ts_core *cd, unsigned int addr,
+		     unsigned char *data, unsigned int len);
+	int (*send_cmd)(struct goodix_ts_core *cd, struct goodix_ts_cmd *cmd);
+	int (*send_config)(struct goodix_ts_core *cd, u8 *config, int len);
+	int (*read_config)(struct goodix_ts_core *cd, u8 *config_data, int size);
+	int (*read_version)(struct goodix_ts_core *cd, struct goodix_fw_version *version);
+	int (*get_ic_info)(struct goodix_ts_core *cd, struct goodix_ic_info *ic_info);
+	int (*esd_check)(struct goodix_ts_core *cd);
+	int (*event_handler)(struct goodix_ts_core *cd, struct goodix_ts_event *ts_event);
+	int (*after_event_handler)(struct goodix_ts_core *cd); /* clean sync flag */
+	int (*get_capacitance_data)(struct goodix_ts_core *cd, struct ts_rawdata_info *info);
+};
+
+/*
+ * struct goodix_ts_esd - esd protector structure
+ * @esd_work: esd delayed work
+ * @esd_on: 1 - turn on esd protection, 0 - turn
+ *  off esd protection
+ */
+struct goodix_ts_esd {
+	bool irq_status;
+	atomic_t esd_on;
+	struct delayed_work esd_work;
+	struct notifier_block esd_notifier;
+	struct goodix_ts_core *ts_core;
+};
+
+enum goodix_core_init_stage {
+	CORE_UNINIT,
+	CORE_INIT_FAIL,
+	CORE_INIT_STAGE1,
+	CORE_INIT_STAGE2
+};
+
+struct goodix_ic_config {
+	int len;
+	u8 data[GOODIX_CFG_MAX_SIZE];
+};
+
+struct goodix_ts_core {
+	int init_stage;
+	struct platform_device *pdev;
+	struct goodix_fw_version fw_version;
+	struct goodix_ic_info ic_info;
+	struct goodix_bus_interface *bus;
+	struct goodix_ts_board_data board_data;
+	struct goodix_ts_hw_ops *hw_ops;
+	struct input_dev *input_dev;
+	struct input_dev *pen_dev;
+ 	/* TODO counld we remove this from core data? */
+	struct goodix_ts_event ts_event;
+
+	/* every pointer of this array represent a kind of config */
+	struct goodix_ic_config *ic_configs[GOODIX_MAX_CONFIG_GROUP];
+	struct regulator *avdd;
+	struct regulator *iovdd;
+
+	int power_on;
+	int irq;
+	size_t irq_trig_cnt;
+
+	atomic_t irq_enabled;
+	atomic_t suspended;
+	/* when this flag is true, driver should not clean the sync flag */
+	bool tools_ctrl_sync;
+
+	struct notifier_block ts_notifier;
+	struct goodix_ts_esd ts_esd;
+
+#ifdef CONFIG_FB
+	struct notifier_block fb_notifier;
+#endif
+};
+
+/* external module structures */
+enum goodix_ext_priority {
+	EXTMOD_PRIO_RESERVED = 0,
+	EXTMOD_PRIO_FWUPDATE,
+	EXTMOD_PRIO_GESTURE,
+	EXTMOD_PRIO_HOTKNOT,
+	EXTMOD_PRIO_DBGTOOL,
+	EXTMOD_PRIO_DEFAULT,
+};
+
+#define EVT_HANDLED				0
+#define EVT_CONTINUE			0
+#define EVT_CANCEL				1
+#define EVT_CANCEL_IRQEVT		1
+#define EVT_CANCEL_SUSPEND		1
+#define EVT_CANCEL_RESUME		1
+#define EVT_CANCEL_RESET		1
+
+struct goodix_ext_module;
+/* external module's operations callback */
+struct goodix_ext_module_funcs {
+	int (*init)(struct goodix_ts_core *core_data,
+		    struct goodix_ext_module *module);
+	int (*exit)(struct goodix_ts_core *core_data,
+		    struct goodix_ext_module *module);
+	int (*before_reset)(struct goodix_ts_core *core_data,
+			    struct goodix_ext_module *module);
+	int (*after_reset)(struct goodix_ts_core *core_data,
+			   struct goodix_ext_module *module);
+	int (*before_suspend)(struct goodix_ts_core *core_data,
+			      struct goodix_ext_module *module);
+	int (*after_suspend)(struct goodix_ts_core *core_data,
+			     struct goodix_ext_module *module);
+	int (*before_resume)(struct goodix_ts_core *core_data,
+			     struct goodix_ext_module *module);
+	int (*after_resume)(struct goodix_ts_core *core_data,
+			    struct goodix_ext_module *module);
+	int (*irq_event)(struct goodix_ts_core *core_data,
+			 struct goodix_ext_module *module);
+};
+
+/*
+ * struct goodix_ext_module - external module struct
+ * @list: list used to link into modules manager
+ * @name: name of external module
+ * @priority: module priority vlaue, zero is invalid
+ * @funcs: operations callback
+ * @priv_data: private data region
+ * @kobj: kobject
+ * @work: used to queue one work to do registration
+ */
+struct goodix_ext_module {
+	struct list_head list;
+	char *name;
+	enum goodix_ext_priority priority;
+	const struct goodix_ext_module_funcs *funcs;
+	void *priv_data;
+	struct kobject kobj;
+	struct work_struct work;
+};
+
+/*
+ * struct goodix_ext_attribute - exteranl attribute struct
+ * @attr: attribute
+ * @show: show interface of external attribute
+ * @store: store interface of external attribute
+ */
+struct goodix_ext_attribute {
+	struct attribute attr;
+	ssize_t (*show)(struct goodix_ext_module *, char *);
+	ssize_t (*store)(struct goodix_ext_module *, const char *, size_t);
+};
+
+/* external attrs helper macro */
+#define __EXTMOD_ATTR(_name, _mode, _show, _store)	{	\
+	.attr = {.name = __stringify(_name), .mode = _mode },	\
+	.show   = _show,	\
+	.store  = _store,	\
+}
+
+/* external attrs helper macro, used to define external attrs */
+#define DEFINE_EXTMOD_ATTR(_name, _mode, _show, _store)	\
+static struct goodix_ext_attribute ext_attr_##_name = \
+	__EXTMOD_ATTR(_name, _mode, _show, _store);
+
+/* log macro */
+extern bool debug_log_flag;
+#define ts_info(fmt, arg...)	pr_info("[GTP-INF][%s:%d] "fmt"\n", __func__, __LINE__, ##arg)
+#define	ts_err(fmt, arg...)		pr_err("[GTP-ERR][%s:%d] "fmt"\n", __func__, __LINE__, ##arg)
+#define ts_debug(fmt, arg...)	{if (debug_log_flag) pr_info("[GTP-DBG][%s:%d] "fmt"\n", __func__, __LINE__, ##arg);}
+
+/*
+ * get board data pointer
+ */
+static inline struct goodix_ts_board_data *board_data(
+		struct goodix_ts_core *core)
+{
+	if (!core)
+		return NULL;
+	return &(core->board_data);
+}
+
+/**
+ * goodix_register_ext_module - interface for external module
+ * to register into touch core modules structure
+ *
+ * @module: pointer to external module to be register
+ * return: 0 ok, <0 failed
+ */
+int goodix_register_ext_module(struct goodix_ext_module *module);
+/* register module no wait */
+int goodix_register_ext_module_no_wait(struct goodix_ext_module *module);
+/**
+ * goodix_unregister_ext_module - interface for external module
+ * to unregister external modules
+ *
+ * @module: pointer to external module
+ * return: 0 ok, <0 failed
+ */
+int goodix_unregister_ext_module(struct goodix_ext_module *module);
+/* remove all registered ext module
+ * return 0 on success, otherwise return < 0
+ */
+int goodix_ts_blocking_notify(enum ts_notify_event evt, void *v);
+struct kobj_type *goodix_get_default_ktype(void);
+struct kobject *goodix_get_default_kobj(void);
+
+struct goodix_ts_hw_ops *goodix_get_hw_ops(void);
+int goodix_get_config_proc(struct goodix_ts_core *cd);
+
+int goodix_spi_bus_init(void);
+void goodix_spi_bus_exit(void);
+int goodix_i2c_bus_init(void);
+void goodix_i2c_bus_exit(void);
+
+u32 goodix_append_checksum(u8 *data, int len, int mode);
+int checksum_cmp(const u8 *data, int size, int mode);
+int is_risk_data(const u8 *data, int size);
+u32 goodix_get_file_config_id(u8 *ic_config);
+void goodix_rotate_abcd2cbad(int tx, int rx, s16 *data);
+int goodix_gesture_enable(int enable);
+
+int goodix_fw_update_init(struct goodix_ts_core *core_data);
+void goodix_fw_update_uninit(void);
+int goodix_do_fw_update(struct goodix_ic_config *ic_config, int mode);
+
+int goodix_get_ic_type(struct device_node *node);
+int gesture_module_init(void);
+void gesture_module_exit(void);
+int inspect_module_init(void);
+void inspect_module_exit(void);
+int goodix_tools_init(void);
+void goodix_tools_exit(void);
+
+#endif

+ 384 - 0
goodix_ts_gesture.c

@@ -0,0 +1,384 @@
+/*
+ * Goodix Gesture Module
+ *
+ * Copyright (C) 2019 - 2020 Goodix, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be a reference
+ * to you, when you are integrating the GOODiX's CTP IC into your system,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
+#include <linux/delay.h>
+#include <linux/atomic.h>
+#include "goodix_ts_core.h"
+
+#define QUERYBIT(longlong, bit) (!!(longlong[bit/8] & (1 << bit%8)))
+
+#define GSX_GESTURE_TYPE_LEN	32
+
+/*
+ * struct gesture_module - gesture module data
+ * @registered: module register state
+ * @sysfs_node_created: sysfs node state
+ * @gesture_type: valid gesture type, each bit represent one gesture type
+ * @gesture_data: store latest gesture code get from irq event
+ * @gesture_ts_cmd: gesture command data
+ */
+struct gesture_module {
+	atomic_t registered;
+	rwlock_t rwlock;
+	u8 gesture_type[GSX_GESTURE_TYPE_LEN];
+	u8 gesture_data;
+	struct goodix_ext_module module;
+};
+
+static struct gesture_module *gsx_gesture; /*allocated in gesture init module*/
+static bool module_initialized;
+
+
+int goodix_gesture_enable(int enable)
+{
+	int ret = 0;
+
+	if (!module_initialized)
+		return 0;		
+
+	if (enable) {
+		if (atomic_read(&gsx_gesture->registered))
+			ts_info("gesture module has been already registered");
+		else
+			ret = goodix_register_ext_module_no_wait(&gsx_gesture->module);
+	} else {
+		if (!atomic_read(&gsx_gesture->registered))
+			ts_info("gesture module has been already unregistered");
+		else
+			ret = goodix_unregister_ext_module(&gsx_gesture->module);
+	}
+
+	return ret;
+}
+
+/**
+ * gsx_gesture_type_show - show valid gesture type
+ *
+ * @module: pointer to goodix_ext_module struct
+ * @buf: pointer to output buffer
+ * Returns >=0 - succeed,< 0 - failed
+ */
+static ssize_t gsx_gesture_type_show(struct goodix_ext_module *module,
+				char *buf)
+{
+	int count = 0, i, ret = 0;
+	unsigned char *type;
+
+	type = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!type)
+		return -ENOMEM;
+	read_lock(&gsx_gesture->rwlock);
+	for (i = 0; i < 256; i++) {
+		if (QUERYBIT(gsx_gesture->gesture_type, i)) {
+			count += scnprintf(type + count,
+					   PAGE_SIZE, "%02x,", i);
+		}
+	}
+	if (count > 0)
+		ret = scnprintf(buf, PAGE_SIZE, "%s\n", type);
+	read_unlock(&gsx_gesture->rwlock);
+
+	kfree(type);
+	return ret;
+}
+
+/**
+ * gsx_gesture_type_store - set vailed gesture
+ *
+ * @module: pointer to goodix_ext_module struct
+ * @buf: pointer to valid gesture type
+ * @count: length of buf
+ * Returns >0 - valid gestures, < 0 - failed
+ */
+static ssize_t gsx_gesture_type_store(struct goodix_ext_module *module,
+		const char *buf, size_t count)
+{
+	int i;
+
+	if (count <= 0 || count > 256 || buf == NULL) {
+		ts_err("Parameter error");
+		return -EINVAL;
+	}
+
+	write_lock(&gsx_gesture->rwlock);
+	memset(gsx_gesture->gesture_type, 0, GSX_GESTURE_TYPE_LEN);
+	for (i = 0; i < count; i++)
+		gsx_gesture->gesture_type[buf[i]/8] |= (0x1 << buf[i]%8);
+	write_unlock(&gsx_gesture->rwlock);
+
+	return count;
+}
+
+static ssize_t gsx_gesture_enable_show(struct goodix_ext_module *module,
+		char *buf)
+{
+	return scnprintf(buf, PAGE_SIZE, "%d\n",
+			 atomic_read(&gsx_gesture->registered));
+}
+
+static ssize_t gsx_gesture_enable_store(struct goodix_ext_module *module,
+		const char *buf, size_t count)
+{
+	bool val;
+	int ret;
+
+	ret = strtobool(buf, &val);
+	if (ret < 0)
+		return ret;
+
+	if (val) {
+		ret = goodix_gesture_enable(1);
+		return ret ? ret : count;
+	} else {
+		ret = goodix_gesture_enable(0);
+		return ret ? ret : count;
+	}
+}
+
+static ssize_t gsx_gesture_data_show(struct goodix_ext_module *module,
+				char *buf)
+{
+	ssize_t count;
+
+	read_lock(&gsx_gesture->rwlock);
+	count = scnprintf(buf, PAGE_SIZE, "gesture type code:0x%x\n",
+			  gsx_gesture->gesture_data);
+	read_unlock(&gsx_gesture->rwlock);
+
+	return count;
+}
+
+const struct goodix_ext_attribute gesture_attrs[] = {
+	__EXTMOD_ATTR(type, 0666, gsx_gesture_type_show,
+		gsx_gesture_type_store),
+	__EXTMOD_ATTR(enable, 0666, gsx_gesture_enable_show,
+		gsx_gesture_enable_store),
+	__EXTMOD_ATTR(data, 0444, gsx_gesture_data_show, NULL)
+};
+
+static int gsx_gesture_init(struct goodix_ts_core *cd,
+		struct goodix_ext_module *module)
+{
+	if (!cd || !cd->hw_ops->gesture) {
+		ts_err("gesture unsupported");
+		return -EINVAL;
+	}
+
+	ts_info("gesture switch: ON");
+	ts_debug("enable all gesture type");
+	/* set all bit to 1 to enable all gesture wakeup */
+	memset(gsx_gesture->gesture_type, 0xff, GSX_GESTURE_TYPE_LEN);
+	atomic_set(&gsx_gesture->registered, 1);
+
+	return 0;
+}
+
+static int gsx_gesture_exit(struct goodix_ts_core *cd,
+		struct goodix_ext_module *module)
+{
+	if (!cd || !cd->hw_ops->gesture) {
+		ts_err("gesture unsupported");
+		return -EINVAL;
+	}
+
+	ts_info("gesture switch: OFF");
+	ts_debug("disable all gesture type");
+	memset(gsx_gesture->gesture_type, 0x00, GSX_GESTURE_TYPE_LEN);
+	atomic_set(&gsx_gesture->registered, 0);
+	
+	return 0;
+}
+
+/**
+ * gsx_gesture_ist - Gesture Irq handle
+ * This functions is excuted when interrupt happended and
+ * ic in doze mode.
+ *
+ * @cd: pointer to touch core data
+ * @module: pointer to goodix_ext_module struct
+ * return: 0 goon execute, EVT_CANCEL_IRQEVT  stop execute
+ */
+static int gsx_gesture_ist(struct goodix_ts_core *cd,
+	struct goodix_ext_module *module)
+{
+	struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+	struct goodix_ts_event gs_event = {0};
+	int ret;
+
+	if (atomic_read(&cd->suspended) == 0)
+		return EVT_CONTINUE;
+
+	ret = hw_ops->event_handler(cd, &gs_event);
+	if (ret) {
+		ts_err("failed get gesture data");
+		goto re_send_ges_cmd;
+	}
+	
+	if (!(gs_event.event_type & EVENT_GESTURE)) {
+		ts_err("invalid event type: 0x%x",
+			cd->ts_event.event_type);
+		goto re_send_ges_cmd;
+	}
+
+	if (QUERYBIT(gsx_gesture->gesture_type,
+		     gs_event.gesture_type)) {
+		gsx_gesture->gesture_data = gs_event.gesture_type;
+		/* do resume routine */
+		ts_info("got valid gesture type 0x%x",
+			gs_event.gesture_type);
+		input_report_key(cd->input_dev, KEY_POWER, 1);
+		input_sync(cd->input_dev);
+		input_report_key(cd->input_dev, KEY_POWER, 0);
+		input_sync(cd->input_dev);
+		goto gesture_ist_exit;
+	} else {
+		ts_info("unsupported gesture:%x", gs_event.gesture_type);
+	}
+
+re_send_ges_cmd:
+	if (hw_ops->gesture(cd, 0))
+		ts_info("warning: failed re_send gesture cmd");
+gesture_ist_exit:
+	if (!cd->tools_ctrl_sync)
+		hw_ops->after_event_handler(cd);
+	return EVT_CANCEL_IRQEVT;
+}
+
+/**
+ * gsx_gesture_before_suspend - execute gesture suspend routine
+ * This functions is excuted to set ic into doze mode
+ *
+ * @cd: pointer to touch core data
+ * @module: pointer to goodix_ext_module struct
+ * return: 0 goon execute, EVT_IRQCANCLED  stop execute
+ */
+static int gsx_gesture_before_suspend(struct goodix_ts_core *cd,
+	struct goodix_ext_module *module)
+{
+	int ret;
+	const struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+
+	ret = hw_ops->gesture(cd, 0);
+	if (ret)
+		ts_err("failed enter gesture mode");
+	else
+		ts_info("enter gesture mode");
+
+	hw_ops->irq_enable(cd, true);
+	enable_irq_wake(cd->irq);
+
+	return EVT_CANCEL_SUSPEND;
+}
+
+static int gsx_gesture_before_resume(struct goodix_ts_core *cd,
+	struct goodix_ext_module *module)
+{
+	const struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
+
+	hw_ops->irq_enable(cd, false);
+	disable_irq_wake(cd->irq);
+	hw_ops->reset(cd, GOODIX_NORMAL_RESET_DELAY_MS);
+
+	return EVT_CANCEL_RESUME;
+}
+
+static struct goodix_ext_module_funcs gsx_gesture_funcs = {
+	.irq_event = gsx_gesture_ist,
+	.init = gsx_gesture_init,
+	.exit = gsx_gesture_exit,
+	.before_suspend = gsx_gesture_before_suspend,
+	.before_resume = gsx_gesture_before_resume,
+};
+
+int gesture_module_init(void)
+{
+	int ret;
+	int i;
+	struct kobject *def_kobj = goodix_get_default_kobj();
+	struct kobj_type *def_kobj_type = goodix_get_default_ktype();
+
+	gsx_gesture = kzalloc(sizeof(struct gesture_module), GFP_KERNEL);
+	if (!gsx_gesture)
+		return -ENOMEM;
+
+	gsx_gesture->module.funcs = &gsx_gesture_funcs;
+	gsx_gesture->module.priority = EXTMOD_PRIO_GESTURE;
+	gsx_gesture->module.name = "Goodix_gsx_gesture";
+	gsx_gesture->module.priv_data = gsx_gesture;
+
+	atomic_set(&gsx_gesture->registered, 0);
+	rwlock_init(&gsx_gesture->rwlock);
+
+	/* gesture sysfs init */
+	ret = kobject_init_and_add(&gsx_gesture->module.kobj,
+			def_kobj_type, def_kobj, "gesture");
+	if (ret) {
+		ts_err("failed create gesture sysfs node!");
+		goto err_out;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(gesture_attrs) && !ret; i++)
+		ret = sysfs_create_file(&gsx_gesture->module.kobj,
+				&gesture_attrs[i].attr);
+	if (ret) {
+		ts_err("failed create gst sysfs files");
+		while (--i >= 0)
+			sysfs_remove_file(&gsx_gesture->module.kobj,
+					&gesture_attrs[i].attr);
+
+		kobject_put(&gsx_gesture->module.kobj);
+		goto err_out;
+	}
+
+	module_initialized = true;
+	ts_info("gesture module init success");
+
+	return 0;
+
+err_out:
+	ts_err("gesture module init failed!");
+	kfree(gsx_gesture);
+	return ret;
+}
+
+void gesture_module_exit(void)
+{
+	int i;
+
+	ts_info("gesture module exit");
+	if (!module_initialized)
+		return;
+
+	goodix_gesture_enable(0);
+
+	/* deinit sysfs */
+	for (i = 0; i < ARRAY_SIZE(gesture_attrs); i++)
+		sysfs_remove_file(&gsx_gesture->module.kobj,
+					&gesture_attrs[i].attr);
+
+	kobject_put(&gsx_gesture->module.kobj);
+	kfree(gsx_gesture);
+	module_initialized = false;
+}

+ 2919 - 0
goodix_ts_inspect.c

@@ -0,0 +1,2919 @@
+ /*
+  * Goodix Touchscreen Driver
+  * Copyright (C) 2020 - 2021 Goodix, Inc.
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be a reference
+  * to you, when you are integrating the GOODiX's CTP IC into your system,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  */
+
+#include "goodix_ts_core.h"
+#include <linux/rtc.h>
+#include <linux/timer.h>
+#include <linux/version.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+
+
+/* test config */
+#define TOTAL_FRAME_NUM 					1 /* rawdata test frames */
+#define NOISEDATA_TEST_TIMES				1  /* noise test frames */
+#define SAVE_IN_CSV
+
+#define GOODIX_RESULT_SAVE_PATH				"/vendor/etc/Test_Data.csv"
+#define GOODIX_TEST_FILE_NAME				"goodix"
+#define MAX_DATA_BUFFER						28000
+#define MAX_SHORT_NUM						15
+#define MAX_LINE_LEN                		(1024 * 3 * 7)
+#define MAX_DRV_NUM							52
+#define MAX_SEN_NUM							75
+
+#define STATISTICS_DATA_LEN					32
+#define MAX_STR_LEN				 			32
+#define MAX_TEST_ITEMS			    		10 /* 0P-1P-2P-3P-5P total test items */
+#define GTP_CAP_TEST						1
+#define GTP_DELTA_TEST						2
+#define GTP_NOISE_TEST						3
+#define GTP_SHORT_TEST						5
+#define GTP_SELFCAP_TEST					6
+#define GTP_SELFNOISE_TEST					7
+
+#define GTP_TEST_PASS						1 
+#define GTP_PANEL_REASON					2
+#define SYS_SOFTWARE_REASON					3
+
+#define CHN_VDD								0xFF
+#define CHN_GND								0x7F
+#define DRV_CHANNEL_FLAG		    		0x80
+
+#define CSV_TP_SPECIAL_RAW_MIN				"special_raw_min"
+#define CSV_TP_SPECIAL_RAW_MAX				"special_raw_max"
+#define CSV_TP_SPECIAL_RAW_DELTA			"special_raw_delta"
+#define CSV_TP_SHORT_THRESHOLD				"shortciurt_threshold"
+#define CSV_TP_SPECIAL_SELFRAW_MAX			"special_selfraw_max"
+#define CSV_TP_SPECIAL_SELFRAW_MIN			"special_selfraw_min"
+#define CSV_TP_NOISE_LIMIT					"noise_data_limit"
+#define CSV_TP_SELFNOISE_LIMIT				"noise_selfdata_limit"
+#define CSV_TP_TEST_CONFIG					"test_config"
+
+#define MAX_TEST_TIME_MS            		15000
+#define DEFAULT_TEST_TIME_MS				7000
+
+/* berlin A */
+#define MAX_DRV_NUM_BRA				    	21
+#define MAX_SEN_NUM_BRA				    	42
+#define SHORT_TEST_TIME_REG_BRA				0x11FF2
+#define DFT_ADC_DUMP_NUM_BRA				1396
+#define DFT_SHORT_THRESHOLD_BRA  			16
+#define DFT_DIFFCODE_SHORT_THRESHOLD_BRA	16
+#define SHORT_TEST_STATUS_REG_BRA			0x10400
+#define SHORT_TEST_RESULT_REG_BRA			0x10410
+#define DRV_DRV_SELFCODE_REG_BRA			0x1045E
+#define SEN_SEN_SELFCODE_REG_BRA 			0x1084E
+#define DRV_SEN_SELFCODE_REG_BRA			0x11712
+#define DIFF_CODE_DATA_REG_BRA				0x11F72
+
+/* berlin B */
+#define MAX_DRV_NUM_BRB				    	52
+#define MAX_SEN_NUM_BRB				    	75
+#define SHORT_TEST_TIME_REG_BRB				0x26AE0
+#define DFT_ADC_DUMP_NUM_BRB				762
+#define DFT_SHORT_THRESHOLD_BRB				100
+#define DFT_DIFFCODE_SHORT_THRESHOLD_BRB	32
+#define SHORT_TEST_STATUS_REG_BRB			0x20400
+#define SHORT_TEST_RESULT_REG_BRB			0x20410
+#define DRV_DRV_SELFCODE_REG_BRB			0x2049A
+#define SEN_SEN_SELFCODE_REG_BRB 			0x21AF2
+#define DRV_SEN_SELFCODE_REG_BRB			0x248A6
+#define DIFF_CODE_DATA_REG_BRB				0x269E0
+
+/* berlinD */
+#define MAX_DRV_NUM_BRD				    	20
+#define MAX_SEN_NUM_BRD				    	40
+#define SHORT_TEST_TIME_REG_BRD				0x14D7A
+#define DFT_ADC_DUMP_NUM_BRD				762
+#define DFT_SHORT_THRESHOLD_BRD				100
+#define DFT_DIFFCODE_SHORT_THRESHOLD_BRD	32
+#define SHORT_TEST_STATUS_REG_BRD			0x13400
+#define SHORT_TEST_RESULT_REG_BRD			0x13408
+#define DRV_DRV_SELFCODE_REG_BRD			0x1344E
+#define SEN_SEN_SELFCODE_REG_BRD 			0x137E6
+#define DRV_SEN_SELFCODE_REG_BRD			0x14556
+#define DIFF_CODE_DATA_REG_BRD				0x14D00
+
+
+#define ABS(val)			((val < 0)? -(val) : val)
+#define MAX(a, b)			((a > b)? a : b)
+
+static bool module_initialized;
+
+/* berlin A drv-sen map */
+static u8 brl_a_drv_map[] = {
+    42, 43, 44, 45, 46, 47, 48, 49,
+    50, 51, 52, 53, 54, 55, 56, 57,
+    58, 59, 60, 61, 62
+};
+
+static u8 brl_a_sen_map[] = {
+    0, 1, 2, 3, 4, 5, 6, 7,
+	8, 9, 10, 11, 12, 13, 14, 15,
+	16, 17, 18, 19, 20, 21, 22, 23,
+	24, 25, 26, 27, 28, 29, 30, 31,
+	32, 33, 34, 35, 36, 37, 38, 39,
+	40, 41
+};
+
+/* berlin B drv-sen map */
+static u8 brl_b_drv_map[] = {
+	75, 76, 77, 78, 79, 80, 81, 82,
+	83, 84, 85, 86, 87, 88, 89, 90,
+	91, 92, 93, 94, 95, 96, 97, 98,
+	99, 100, 101, 102, 103, 104, 105,
+	106, 107, 108, 109, 110, 111, 112,
+	113, 114, 115, 116, 117, 118, 119,
+	120, 121, 122, 123, 124, 125, 126
+};
+
+static u8 brl_b_sen_map[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+	11, 12, 13, 14, 15, 16, 17, 18,
+	19, 20, 21, 22, 23, 24, 25, 26,
+	27, 28, 29, 30, 31, 32, 33, 34,
+	35, 36, 37, 38, 39, 40, 41, 42,
+	43, 44, 45, 46, 47, 48, 49, 50,
+	51, 52, 53, 54, 55, 56, 57, 58,
+	59, 60, 61, 62, 63, 64, 65, 66,
+	67, 68, 69, 70, 71, 72, 73, 74
+};
+
+/* berlin D drv-sen map */
+static u8 brl_d_drv_map[] = {
+	40, 41, 42, 43, 44, 45, 46, 47,
+	48, 49, 50, 51, 52, 53, 54, 55,
+	56, 57, 58, 59,
+};
+
+static u8 brl_d_sen_map[] = {
+    0, 1, 2, 3, 4, 5, 6, 7,
+	8, 9, 10, 11, 12, 13, 14, 15,
+	16, 17, 18, 19, 20, 21, 22, 23,
+	24, 25, 26, 27, 28, 29, 30, 31,
+	32, 33, 34, 35, 36, 37, 38, 39,
+};
+
+typedef struct __attribute__((packed)) {
+    u8 result;
+	u8 drv_drv_num;
+	u8 sen_sen_num;
+	u8 drv_sen_num;
+	u8 drv_gnd_avdd_num;
+	u8 sen_gnd_avdd_num;
+	u16 checksum;
+} test_result_t;
+
+struct params_info_t {
+	u32 max_drv_num;
+	u32 max_sen_num;
+	u8 *drv_map;
+	u8 *sen_map;
+	u32 short_test_time_reg;
+	u32 short_test_status_reg;
+	u32 short_test_result_reg;
+	u32 drv_drv_selfcode_reg;
+	u32 sen_sen_selfcode_reg;
+	u32 drv_sen_selfcode_reg;
+	u32 diffcode_data_reg;
+	u16 short_test_dump_num;
+	u16 dft_short_threshold;
+	u16 short_diffcode_threshold;
+};
+
+struct params_info_t params_bra = {
+	MAX_DRV_NUM_BRA,
+	MAX_SEN_NUM_BRA,
+	brl_a_drv_map,
+	brl_a_sen_map,
+	SHORT_TEST_TIME_REG_BRA,
+	SHORT_TEST_STATUS_REG_BRA,
+	SHORT_TEST_RESULT_REG_BRA,
+	DRV_DRV_SELFCODE_REG_BRA,
+	SEN_SEN_SELFCODE_REG_BRA,
+	DRV_SEN_SELFCODE_REG_BRA,
+	DIFF_CODE_DATA_REG_BRA,
+	DFT_ADC_DUMP_NUM_BRA,
+	DFT_SHORT_THRESHOLD_BRA,
+	DFT_DIFFCODE_SHORT_THRESHOLD_BRA,
+};
+
+struct params_info_t params_brb = {
+	MAX_DRV_NUM_BRB,
+	MAX_SEN_NUM_BRB,
+	brl_b_drv_map,
+	brl_b_sen_map,
+	SHORT_TEST_TIME_REG_BRB,
+	SHORT_TEST_STATUS_REG_BRB,
+	SHORT_TEST_RESULT_REG_BRB,
+	DRV_DRV_SELFCODE_REG_BRB,
+	SEN_SEN_SELFCODE_REG_BRB,
+	DRV_SEN_SELFCODE_REG_BRB,
+	DIFF_CODE_DATA_REG_BRB,
+	DFT_ADC_DUMP_NUM_BRB,
+	DFT_SHORT_THRESHOLD_BRB,
+	DFT_DIFFCODE_SHORT_THRESHOLD_BRB,
+};
+
+struct params_info_t params_brd = {
+	MAX_DRV_NUM_BRD,
+	MAX_SEN_NUM_BRD,
+	brl_d_drv_map,
+	brl_d_sen_map,
+	SHORT_TEST_TIME_REG_BRD,
+	SHORT_TEST_STATUS_REG_BRD,
+	SHORT_TEST_RESULT_REG_BRD,
+	DRV_DRV_SELFCODE_REG_BRD,
+	SEN_SEN_SELFCODE_REG_BRD,
+	DRV_SEN_SELFCODE_REG_BRD,
+	DIFF_CODE_DATA_REG_BRD,
+	DFT_ADC_DUMP_NUM_BRD,
+	DFT_SHORT_THRESHOLD_BRD,
+	DFT_DIFFCODE_SHORT_THRESHOLD_BRD,
+};
+
+struct ts_test_params {
+	bool test_items[MAX_TEST_ITEMS];
+
+	u32 rawdata_addr;
+	u32 noisedata_addr;
+	u32 self_rawdata_addr;
+	u32 self_noisedata_addr;
+
+	u32 drv_num;
+	u32 sen_num;
+
+	struct params_info_t *params_info;
+
+	s32 cfg_buf[GOODIX_CFG_MAX_SIZE];
+	s32 max_limits[MAX_DRV_NUM * MAX_SEN_NUM];
+	s32 min_limits[MAX_DRV_NUM * MAX_SEN_NUM];
+	s32 deviation_limits[MAX_DRV_NUM * MAX_SEN_NUM];
+	s32 self_max_limits[MAX_DRV_NUM + MAX_SEN_NUM];
+	s32 self_min_limits[MAX_DRV_NUM + MAX_SEN_NUM];
+	s32 noise_threshold;
+	s32 self_noise_threshold;
+
+	u32 short_threshold;
+	u32 r_drv_drv_threshold;
+	u32 r_drv_sen_threshold;
+	u32 r_sen_sen_threshold;
+	u32 r_drv_gnd_threshold;
+	u32 r_sen_gnd_threshold;
+	u32 avdd_value;
+};
+
+struct ts_test_rawdata {
+	s16 data[MAX_DRV_NUM * MAX_SEN_NUM];
+	u32 size;
+};
+
+struct ts_test_self_rawdata {
+	s16 data[MAX_DRV_NUM + MAX_SEN_NUM];
+	u32 size;
+};
+
+struct ts_short_res {
+	u8 short_num;
+	s16 short_msg[4 * MAX_SHORT_NUM];
+};
+
+struct ts_open_res {
+	u8 beyond_max_limit_cnt[MAX_DRV_NUM * MAX_SEN_NUM];
+	u8 beyond_min_limit_cnt[MAX_DRV_NUM * MAX_SEN_NUM];
+	u8 beyond_accord_limit_cnt[MAX_DRV_NUM * MAX_SEN_NUM];
+};
+
+struct goodix_ts_test {
+	struct goodix_ts_core *ts;
+	struct ts_test_params test_params;
+	struct ts_test_rawdata rawdata[TOTAL_FRAME_NUM];
+	struct ts_test_rawdata accord_arr[TOTAL_FRAME_NUM];
+	struct ts_test_rawdata noisedata[NOISEDATA_TEST_TIMES];
+	struct goodix_ic_config test_config;
+	struct ts_test_self_rawdata self_rawdata;
+	struct ts_test_self_rawdata self_noisedata;
+	struct ts_short_res short_res;
+	struct ts_open_res open_res;
+
+	/*[0][0][0][0][0]..  0 without test; 1 pass, 2 panel failed; 3 software failed */
+	char test_result[MAX_TEST_ITEMS];
+	char test_info[TS_RAWDATA_RESULT_MAX];
+};
+
+static int cal_cha_to_cha_res(struct goodix_ts_test *ts_test, int v1, int v2)
+{
+	if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_A)
+		return (v1 - v2) * 63 / v2;
+	else if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_B)
+		return (v1 - v2) * 74 / v2 + 20;
+	else
+		return (v1 / v2 - 1) * 70 + 59;
+}
+
+static int cal_cha_to_avdd_res(struct goodix_ts_test *ts_test, int v1, int v2)
+{
+	if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_A)
+		return 125 * 1024 * (100 * v2 - 125) * 40 / (10000 * v1) - 40;
+	else if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_B)
+		return 125 * 1024 * (100 * v2 - 125) * 99 / (10000 * v1) - 60;
+	else
+		return 125 * 1024 * (100 * v2 - 125) * 93 / (10000 * v1) - 20;
+}
+
+static int cal_cha_to_gnd_res(struct goodix_ts_test *ts_test, int v)
+{
+	if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_A)
+		return 64148 / v - 40;
+	else if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_B)
+		return 150500 / v - 60;
+	else
+		return 145000 / v - 15;
+}
+
+static int ts_test_reset(struct goodix_ts_test *ts_test,
+    u32 delay_ms)
+{
+    return ts_test->ts->hw_ops->reset(ts_test->ts, delay_ms);
+}
+
+static int ts_test_read(struct goodix_ts_test *ts_test,
+    u32 addr, u8 *data, u32 len)
+{
+    return ts_test->ts->hw_ops->read(ts_test->ts, addr, data, len);
+}
+
+static int ts_test_write(struct goodix_ts_test *ts_test,
+    u32 addr, u8 *data, u32 len)
+{
+    return ts_test->ts->hw_ops->write(ts_test->ts, addr, data, len);
+}
+
+static int ts_test_send_cmd(struct goodix_ts_test *ts_test,
+    struct goodix_ts_cmd *cmd)
+{
+    return ts_test->ts->hw_ops->send_cmd(ts_test->ts, cmd);
+}
+
+static int ts_test_irq_enable(struct goodix_ts_test *ts_test,
+	bool flag)
+{
+	return ts_test->ts->hw_ops->irq_enable(ts_test->ts, flag);
+}
+
+static int ts_test_send_config(struct goodix_ts_test *ts_test,
+    int type)
+{
+    struct goodix_ic_config *cfg;
+
+	if (type >= GOODIX_MAX_CONFIG_GROUP) {
+		ts_err("unsupproted config type %d", type);
+		return -EINVAL;
+	}
+    cfg = ts_test->ts->ic_configs[type];
+	if (!cfg || cfg->len <= 0) {
+		ts_err("no valid normal config found");
+		return -EINVAL;
+	}
+    
+    return ts_test->ts->hw_ops->send_config(ts_test->ts, cfg->data, cfg->len);
+}
+
+static int ts_test_read_version(struct goodix_ts_test *ts_test,
+    struct goodix_fw_version *version)
+{
+    return ts_test->ts->hw_ops->read_version(ts_test->ts, version);
+}
+
+static void goto_next_line(char **ptr)
+{
+    do {
+        *ptr = *ptr + 1;
+    } while (**ptr != '\n' && **ptr != '\0');
+    if (**ptr == '\0') {
+        return;
+    }
+    *ptr = *ptr + 1;
+}
+
+static void copy_this_line(char *dest, char *src)
+{
+	char *copy_from;
+	char *copy_to;
+
+	copy_from = src;
+	copy_to = dest;
+	do {
+		*copy_to = *copy_from;
+		copy_from++;
+		copy_to++;
+	} while((*copy_from != '\n') && (*copy_from != '\r') && (*copy_from != '\0'));
+	*copy_to = '\0';
+}
+
+static int getrid_space(s8* data, s32 len)
+{
+	u8* buf = NULL;
+	s32 i;
+	u32 count = 0;
+
+	buf = (char*)kzalloc(len + 5, GFP_KERNEL);
+	if (buf == NULL){
+		ts_err("get space kzalloc error");
+		return -ESRCH;
+	}
+
+	for (i = 0; i < len; i++)
+	{
+		if (data[i] == ' ' || data[i] == '\r' || data[i] == '\n')
+		{
+			continue;
+		}
+		buf[count++] = data[i];
+	}
+
+	buf[count++] = '\0';
+
+	memcpy(data, buf, count);
+	kfree(buf);
+
+	return count;
+}
+
+static int parse_valid_data(char *buf_start, loff_t buf_size,
+    char *ptr, s32 *data, s32 rows)
+{
+    int i = 0;
+    int j = 0;
+    char *token = NULL;
+    char *tok_ptr = NULL;
+    char *row_data = NULL;
+	long temp_val;
+
+    if (!ptr) {
+        ts_err("ptr is NULL");
+        return -EINVAL;
+    }
+	if (!data) {
+		ts_err("data is NULL");
+		return -EINVAL;
+	}
+
+    row_data = (char *)kzalloc(MAX_LINE_LEN, GFP_KERNEL);
+    if (!row_data) {
+        ts_err("alloc bytes %d failed.", MAX_LINE_LEN);
+        return -ENOMEM;
+    }
+
+    for (i = 0; i < rows; i++) {
+        memset(row_data, 0, MAX_LINE_LEN);
+        copy_this_line(row_data, ptr);
+        getrid_space(row_data, strlen(row_data));
+        tok_ptr = row_data;
+		while ((token = strsep(&tok_ptr,","))) {
+			if (strlen(token) == 0)
+				continue;
+			if (kstrtol(token, 0, &temp_val)) {
+				kfree(row_data);
+				return -EINVAL;
+			}
+			data[j++] = (s32)temp_val;
+		}
+		if (i == rows - 1)
+			break;
+		goto_next_line(&ptr);				//next row
+		if(!ptr || (0 == strlen(ptr)) || (ptr >= (buf_start + buf_size))) {
+			ts_info("invalid ptr, return");
+			kfree(row_data);
+			row_data = NULL;
+			return -EPERM;
+		}        
+    }
+    kfree(row_data);
+    return j;
+}
+
+static int parse_csvfile(char *buf, size_t size, char *target_name,
+        s32 *data, s32 rows, s32 col)
+{
+    int ret = 0;
+    char *ptr = NULL;
+    int read_ret;
+
+    read_ret = size;
+    if (read_ret > 0) {
+        ptr = buf;
+        ptr = strstr(ptr, target_name);
+        if (!ptr) {
+			ts_info("load %s failed 1, maybe not this item", target_name);
+            return -EINTR;
+        }
+
+        goto_next_line(&ptr);
+        if (!ptr || (0 == strlen(ptr))) {
+            ts_err("load %s failed 2!", target_name);
+            return -EIO;
+        }
+        
+        if (data) {
+            ret = parse_valid_data(buf, size, ptr, data, rows);
+        } else {
+            ts_err("load %s failed 3!", target_name);
+            return -EINTR;
+        }
+    } else {
+        ts_err("ret=%d, read_ret=%d", ret, read_ret);
+        ret = -ENXIO;
+    }
+
+    return ret;
+}
+
+
+static void goodix_init_params(struct goodix_ts_test *ts_test)
+{
+	struct goodix_ts_core *ts = ts_test->ts;
+	struct ts_test_params *test_params = &ts_test->test_params;
+
+	test_params->rawdata_addr = ts->ic_info.misc.mutual_rawdata_addr;
+	test_params->noisedata_addr = ts->ic_info.misc.mutual_diffdata_addr;
+	test_params->self_rawdata_addr = ts->ic_info.misc.self_rawdata_addr;
+	test_params->self_noisedata_addr = ts->ic_info.misc.self_diffdata_addr;
+
+	test_params->drv_num = ts->ic_info.parm.drv_num;
+	test_params->sen_num = ts->ic_info.parm.sen_num;
+
+	if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_A)
+		test_params->params_info = &params_bra;
+	else if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_B)
+		test_params->params_info = &params_brb;
+	else
+		test_params->params_info = &params_brd;
+}
+
+static int goodix_init_testlimits(struct goodix_ts_test *ts_test)
+{
+    int ret;
+	int i;
+    u32 data_buf[10] = {0};
+    char *temp_buf = NULL;
+    struct ts_test_params *test_params = &ts_test->test_params;
+    struct goodix_ts_core *ts_core = ts_test->ts;
+    const struct firmware *firmware = NULL;
+    struct device *dev = &ts_core->pdev->dev;
+    char limit_file[100] = {0};
+    u32 tx = test_params->drv_num;
+    u32 rx = test_params->sen_num;
+
+    sprintf(limit_file, "%s_test_limits_%d.csv", GOODIX_TEST_FILE_NAME,
+			ts_core->fw_version.sensor_id);
+    ts_info("limit_file_name:%s", limit_file);
+
+    ret = request_firmware(&firmware, limit_file, dev);
+    if (ret < 0) {
+        ts_err("limits file [%s] not available", limit_file);
+        return -EINVAL;
+    }
+    if (firmware->size <= 0) {
+        ts_err("request_firmware, limits param length error,len:%zu",
+            firmware->size);
+        ret = -EINVAL;
+        goto exit_free;
+    }
+    temp_buf = kzalloc(firmware->size + 1, GFP_KERNEL);
+    if (!temp_buf) {
+        ts_err("kzalloc bytes failed.");
+        ret = -ENOMEM;
+        goto exit_free;
+    }
+    memcpy(temp_buf, firmware->data, firmware->size);
+
+	/* obtain config data */
+	ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_TEST_CONFIG,
+		test_params->cfg_buf, 1, GOODIX_CFG_MAX_SIZE);
+	if (ret < 0) {
+		ts_info("Can't find %s", CSV_TP_TEST_CONFIG);
+	} else {
+		ts_info("parse_csvfile %s OK, cfg_len:%d", CSV_TP_TEST_CONFIG, ret);
+		for (i = 0; i < ret; i++)
+			ts_test->test_config.data[i] = (u8)test_params->cfg_buf[i];
+		ts_test->test_config.len = ret;
+	}
+	
+    /* obtain mutual_raw min */
+    ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SPECIAL_RAW_MIN,
+        test_params->min_limits, rx, tx);
+    if (ret < 0) {
+        ts_err("Failed get min_limits");
+        goto exit_free;
+    } else {
+        ts_info("parse_csvfile %s OK", CSV_TP_SPECIAL_RAW_MIN);
+    }
+    /* obtain mutual_raw max */
+    ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SPECIAL_RAW_MAX,
+        test_params->max_limits, rx, tx);
+    if (ret < 0) {
+        ts_err("Failed get max_limits");
+        goto exit_free;
+    } else {
+        ts_info("parse_csvfile %s OK", CSV_TP_SPECIAL_RAW_MAX);
+    }
+	/* obtain delta limit */
+	ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SPECIAL_RAW_DELTA,
+		test_params->deviation_limits, rx, tx);
+	if (ret < 0) {
+		ts_err("Failed get delta limit");
+		goto exit_free;
+	} else {
+		ts_info("parse_csvfile %s OK", CSV_TP_SPECIAL_RAW_DELTA);
+	}
+
+    /* obtain self_raw min */
+    ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SPECIAL_SELFRAW_MIN,
+        test_params->self_min_limits, 1, tx + rx);
+	/* obtain self_raw max */
+	ret |= parse_csvfile(temp_buf, firmware->size, CSV_TP_SPECIAL_SELFRAW_MAX,
+		test_params->self_max_limits, 1, tx + rx);
+    if (ret < 0) {
+        ts_info("Can't find self_min_max_limits, ship this item");
+		ret = 0;
+        test_params->test_items[GTP_SELFCAP_TEST] = false;
+    } else {
+        ts_info("parse_csvfile %s OK", CSV_TP_SPECIAL_SELFRAW_MIN);
+		ts_info("parse_csvfile %s OK", CSV_TP_SPECIAL_SELFRAW_MAX);
+		test_params->test_items[GTP_SELFCAP_TEST] = true;
+    }
+
+    /* obtain noise_threshold */
+    ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_NOISE_LIMIT,
+        &test_params->noise_threshold, 1, 1);
+    if (ret < 0) {
+        ts_info("Can't find noise_threshold, skip this item");
+		ret = 0;
+        test_params->test_items[GTP_NOISE_TEST] = false;
+    } else {
+        ts_info("parse_csvfile %s OK", CSV_TP_NOISE_LIMIT);
+		test_params->test_items[GTP_NOISE_TEST] = true;
+    }
+
+    /* obtain self_noise_threshold */
+    ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SELFNOISE_LIMIT,
+        &test_params->self_noise_threshold, 1, 1);
+    if (ret < 0) {
+        ts_info("Can't find self_noise_threshold, skip this item");
+		ret = 0;
+        test_params->test_items[GTP_SELFNOISE_TEST] = false;
+    } else {
+        ts_info("parse_csvfile %s OK", CSV_TP_SELFNOISE_LIMIT);
+		test_params->test_items[GTP_SELFNOISE_TEST] = true;
+    }
+
+	 /* obtain short_params */
+    ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SHORT_THRESHOLD,
+        (s32 *)data_buf, 1, 7);
+    if (ret < 0) {
+		ts_info("Can't find short shortciurt_threshold, skip this item");
+		ret = 0;
+		test_params->test_items[GTP_SHORT_TEST] = false;
+    } else {
+        ts_info("parse_csvfile %s OK", CSV_TP_SHORT_THRESHOLD);
+		test_params->test_items[GTP_SHORT_TEST] = true;
+        test_params->short_threshold = data_buf[0];
+        test_params->r_drv_drv_threshold = data_buf[1];
+        test_params->r_drv_sen_threshold = data_buf[2];
+        test_params->r_sen_sen_threshold = data_buf[3];
+        test_params->r_drv_gnd_threshold = data_buf[4];
+        test_params->r_sen_gnd_threshold = data_buf[5];
+        test_params->avdd_value = data_buf[6];
+    }
+
+exit_free:
+    kfree(temp_buf);
+    if (firmware)
+        release_firmware(firmware);
+    return ret;
+}
+
+static int goodix_tptest_prepare(struct goodix_ts_test *ts_test)
+{
+    int ret;
+	struct goodix_ic_config *cfg = &ts_test->test_config;
+
+	ts_info("TP test prepare IN");
+
+    goodix_init_params(ts_test);
+    /* parse test limits from csv */
+    ret = goodix_init_testlimits(ts_test);
+    if (ret < 0) {
+        ts_err("Failed to init testlimits from csv.");
+        return ret;
+    }
+
+    /* disable irq */
+    ts_test_irq_enable(ts_test, false);
+    /* close esd */
+    goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
+
+    /* send test config if exist */
+	if (cfg->len > 0) {
+		ts_info("Test config exists and send it");
+		ret = ts_test->ts->hw_ops->send_config(ts_test->ts, cfg->data, cfg->len);
+		if (ret < 0) {
+			ts_err("Send test config failed, exit");
+			goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
+			ts_test_irq_enable(ts_test, true);
+			return ret;
+		}
+	}
+
+    return 0;
+}
+
+static void goodix_tptest_finish(struct goodix_ts_test *ts_test)
+{
+	ts_info("TP test finish IN");
+    /* reset chip */
+    ts_test_reset(ts_test, 100);
+    /* send normal config */
+	if (ts_test->test_config.len > 0) {
+		if (ts_test_send_config(ts_test, CONFIG_TYPE_NORMAL))
+			ts_err("Send normal config failed");
+	}
+
+    /* open esd */
+    goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
+    /* enable irq */
+    ts_test_irq_enable(ts_test, true);
+}
+
+#define SHORT_TEST_RUN_REG			0x10400
+#define SHORT_TEST_RUN_FLAG			0xAA
+#define INSPECT_FW_SWITCH_CMD		0x85
+#define TEST_FW_PID  				"OST"
+static int goodix_short_test_prepare(struct goodix_ts_test *ts_test)
+{
+    struct goodix_ts_cmd tmp_cmd;
+    struct goodix_fw_version fw_ver;
+    int ret;
+    int retry = 3;
+	u8 status;
+
+	ts_info("short test prepare IN");
+	ts_test->test_result[GTP_SHORT_TEST] = SYS_SOFTWARE_REASON;
+    tmp_cmd.len = 4;
+    tmp_cmd.cmd = INSPECT_FW_SWITCH_CMD;
+
+    ret = ts_test_send_cmd(ts_test, &tmp_cmd);
+    if (ret < 0) {
+        ts_err("send test mode failed");
+        return ret;
+    }
+
+    while (retry--) {
+        msleep(40);
+		if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_A) {
+			ret = ts_test_read_version(ts_test, &fw_ver);
+			if (ret < 0) {
+				ts_err("read test version failed");
+				return ret;
+			}
+			ret = memcmp(&(fw_ver.patch_pid[3]), TEST_FW_PID, strlen(TEST_FW_PID));
+			if (ret == 0)
+				return 0;
+			else
+				ts_info("patch ID dismatch %s != %s", fw_ver.patch_pid, TEST_FW_PID);
+		} else {
+			ret = ts_test_read(ts_test, SHORT_TEST_RUN_REG, &status, 1);
+			if (!ret && status == SHORT_TEST_RUN_FLAG)
+				return 0;
+			ts_info("short_mode_status=0x%02x ret=%d", status, ret);
+		}
+    }
+
+    return -EINVAL;
+}
+
+static u32 map_die2pin(struct ts_test_params *test_params, u32 chn_num)
+{
+	int i = 0;
+	u32 res = 255;
+
+	if (chn_num & DRV_CHANNEL_FLAG)
+		chn_num = (chn_num & ~DRV_CHANNEL_FLAG) + test_params->params_info->max_sen_num;
+
+	for (i = 0; i < test_params->params_info->max_sen_num; i++) {
+		if (test_params->params_info->sen_map[i] == chn_num) {
+			res = i;
+			break;
+		}
+	}
+	/* res != 255 mean found the corresponding channel num */
+	if (res != 255)
+		return res;
+	/* if cannot find in SenMap try find in DrvMap */
+	for (i = 0; i < test_params->params_info->max_drv_num; i++) {
+		if (test_params->params_info->drv_map[i] == chn_num) {
+			res = i;
+			break;
+		}
+	}
+	if (i >= test_params->params_info->max_drv_num)
+		ts_err("Faild found corrresponding channel num:%d", chn_num);
+	else
+		res |= DRV_CHANNEL_FLAG;
+
+	return res;   
+}
+
+static void goodix_save_short_res(struct ts_test_params *params,
+	u16 chn1, u16 chn2, int r)
+{
+	int i;
+	u8 repeat_cnt = 0;
+	u8 repeat = 0;
+	struct goodix_ts_test *ts_test = container_of(params, 
+		struct goodix_ts_test, test_params);
+	struct ts_short_res *short_res = &ts_test->short_res;
+
+	if (chn1 == chn2 || short_res->short_num >= MAX_SHORT_NUM)
+		return;
+	
+	for (i = 0; i < short_res->short_num; i++) {
+		repeat_cnt = 0;
+		if (short_res->short_msg[4 * i] == chn1)
+			repeat_cnt++;
+		if (short_res->short_msg[4 * i] == chn2)
+			repeat_cnt++;
+		if (short_res->short_msg[4 * i + 1] == chn1)
+			repeat_cnt++;
+		if (short_res->short_msg[4 * i + 1] == chn2)
+			repeat_cnt++;
+		if (repeat_cnt >= 2){
+			repeat = 1;
+			break;
+		}
+	}
+	if (repeat == 0) {
+		short_res->short_msg[4 * short_res->short_num + 0] = chn1;
+		short_res->short_msg[4 * short_res->short_num + 1] = chn2;
+		short_res->short_msg[4 * short_res->short_num + 2] = (r >> 8) & 0xFF;
+		short_res->short_msg[4 * short_res->short_num + 3] = r & 0xFF;
+		if (short_res->short_num < MAX_SHORT_NUM)
+			short_res->short_num++;
+	}
+}
+
+static int gdix_check_tx_tx_shortcircut(struct goodix_ts_test *ts_test,
+        u8 short_ch_num)
+{
+	int ret = 0, err = 0;
+	u32 r_threshold = 0, short_r = 0;
+	int size = 0, i = 0, j = 0;
+	u16 adc_signal = 0;
+	u8 master_pin_num, slave_pin_num;
+	u8 *data_buf;
+	u32 data_reg;
+	struct ts_test_params *test_params = &ts_test->test_params;
+	int max_drv_num = test_params->params_info->max_drv_num;
+	int max_sen_num = test_params->params_info->max_sen_num;
+	u16 self_capdata, short_die_num = 0;
+
+	size = 4 + max_drv_num * 2 + 2;
+	data_buf = kzalloc(size, GFP_KERNEL);
+	if (!data_buf) {
+		ts_err("Failed to alloc memory");
+		return -ENOMEM;
+	}
+    /* drv&drv shortcircut check */
+	data_reg = test_params->params_info->drv_drv_selfcode_reg;
+	for (i = 0; i < short_ch_num; i++) {
+		ret = ts_test_read(ts_test, data_reg, data_buf, size);
+		if (ret < 0) {
+			ts_err("Failed read Drv-to-Drv short rawdata");
+			err = -EINVAL;
+			break;
+		}
+
+		if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) {
+			ts_err("Drv-to-Drv adc data checksum error");
+			err = -EINVAL;
+			break;
+		}
+
+		r_threshold = test_params->r_drv_drv_threshold;
+		short_die_num = le16_to_cpup((__le16 *)&data_buf[0]);
+		short_die_num -= max_sen_num;
+		if (short_die_num >= max_drv_num) {
+			ts_info("invalid short pad num:%d",
+				short_die_num + max_sen_num);
+			continue;
+		}
+
+		/* TODO: j start position need recheck */
+		self_capdata = le16_to_cpup((__le16 *)&data_buf[2]);
+		if (self_capdata == 0xffff || self_capdata == 0) {
+			ts_info("invalid self_capdata:0x%x", self_capdata);
+			continue;
+		}
+
+		for (j = short_die_num + 1; j < max_drv_num; j++) {
+			adc_signal = le16_to_cpup((__le16 *)&data_buf[4 + j * 2]);
+
+			if (adc_signal < test_params->short_threshold)
+				continue;
+
+			short_r = (u32)cal_cha_to_cha_res(ts_test, self_capdata, adc_signal);
+			if (short_r < r_threshold) {
+				master_pin_num =
+					map_die2pin(test_params, short_die_num + max_sen_num);
+				slave_pin_num =
+					map_die2pin(test_params, j + max_sen_num);
+				if (master_pin_num == 0xFF || slave_pin_num == 0xFF) {
+					ts_info("WARNNING invalid pin");
+					continue;
+				}
+				goodix_save_short_res(test_params, master_pin_num,
+					slave_pin_num, short_r);
+				ts_err("short circut:R=%dK,R_Threshold=%dK",
+							short_r, r_threshold);
+				ts_err("%s%d--%s%d shortcircut",
+					(master_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN",
+					(master_pin_num & ~DRV_CHANNEL_FLAG),
+					(slave_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN",
+					(slave_pin_num & ~DRV_CHANNEL_FLAG));
+				err = -EINVAL;
+			}
+		}
+		data_reg += size;
+	}
+
+	kfree(data_buf);
+	return err;
+}
+
+static int gdix_check_rx_rx_shortcircut(struct goodix_ts_test *ts_test,
+        u8 short_ch_num)
+{
+	int ret = 0, err = 0;
+	u32 r_threshold = 0, short_r = 0;
+	int size = 0, i = 0, j = 0;
+	u16 adc_signal = 0;
+	u8 master_pin_num, slave_pin_num;
+	u8 *data_buf;
+	u32 data_reg;
+	struct ts_test_params *test_params = &ts_test->test_params;
+	int max_sen_num = test_params->params_info->max_sen_num;
+	u16 self_capdata, short_die_num = 0;
+
+	size = 4 + max_sen_num * 2 + 2;
+	data_buf = kzalloc(size, GFP_KERNEL);
+	if (!data_buf) {
+		ts_err("Failed to alloc memory");
+		return -ENOMEM;
+	}
+	/* drv&drv shortcircut check */
+	data_reg = test_params->params_info->sen_sen_selfcode_reg;
+    for (i = 0; i < short_ch_num; i++) {
+		ret = ts_test_read(ts_test, data_reg, data_buf, size);
+		if (ret) {
+			ts_err("Failed read Sen-to-Sen short rawdata");
+			err = -EINVAL;
+			break;
+		}
+
+		if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) {
+			ts_err("Sen-to-Sen adc data checksum error");				
+			err = -EINVAL;
+			break;		
+		}
+
+		r_threshold = test_params->r_sen_sen_threshold;
+		short_die_num = le16_to_cpup((__le16 *)&data_buf[0]);
+		if (short_die_num >= max_sen_num) {
+			ts_info("invalid short pad num:%d",	short_die_num);
+			continue;
+		}
+
+		/* TODO: j start position need recheck */
+		self_capdata = le16_to_cpup((__le16 *)&data_buf[2]);
+		if (self_capdata == 0xffff || self_capdata == 0) {
+			ts_info("invalid self_capdata:0x%x", self_capdata);
+			continue;
+		}
+
+		for (j = short_die_num + 1; j < max_sen_num; j++) {
+			adc_signal = le16_to_cpup((__le16 *)&data_buf[4 + j * 2]);
+			
+			if (adc_signal < test_params->short_threshold)
+				continue;
+
+			short_r = (u32)cal_cha_to_cha_res(ts_test, self_capdata, adc_signal);
+			if (short_r < r_threshold) {
+				master_pin_num = map_die2pin(test_params, short_die_num);
+				slave_pin_num = map_die2pin(test_params, j);
+				if (master_pin_num == 0xFF || slave_pin_num == 0xFF) {
+					ts_info("WARNNING invalid pin");
+					continue;
+				}
+				goodix_save_short_res(test_params, master_pin_num,
+					slave_pin_num, short_r);
+				ts_err("short circut:R=%dK,R_Threshold=%dK",
+							short_r, r_threshold);
+				ts_err("%s%d--%s%d shortcircut",
+					(master_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN",
+					(master_pin_num & ~DRV_CHANNEL_FLAG),
+					(slave_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN",
+					(slave_pin_num & ~DRV_CHANNEL_FLAG));
+				err = -EINVAL;
+			}
+		}
+		data_reg += size;
+	}
+
+	kfree(data_buf);
+	return err;    
+}
+
+static int gdix_check_tx_rx_shortcircut(struct goodix_ts_test *ts_test,
+        u8 short_ch_num)
+{
+	int ret = 0, err = 0;
+	u32 r_threshold = 0, short_r = 0;	
+	int size = 0, i = 0, j = 0;
+	u16 adc_signal = 0;
+	u8 master_pin_num, slave_pin_num;	
+	u8 *data_buf = NULL;
+	u32 data_reg;
+	struct ts_test_params *test_params = &ts_test->test_params;
+	int max_drv_num = test_params->params_info->max_drv_num;
+	int max_sen_num = test_params->params_info->max_sen_num;
+	u16 self_capdata, short_die_num = 0;
+
+	size = 4 + max_drv_num * 2 + 2;
+	data_buf = kzalloc(size, GFP_KERNEL);
+	if (!data_buf) {
+		ts_err("Failed to alloc memory");
+		return -ENOMEM;
+	}
+	/* drv&sen shortcircut check */
+	data_reg = test_params->params_info->drv_sen_selfcode_reg;
+	for (i = 0; i < short_ch_num; i++) {
+		ret = ts_test_read(ts_test, data_reg, data_buf, size);
+		if (ret) {
+			ts_err("Failed read Drv-to-Sen short rawdata");
+			err = -EINVAL;
+			break;
+		}
+
+		if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) {
+			ts_err("Drv-to-Sen adc data checksum error");
+			err = -EINVAL;
+			break;
+		}
+
+		r_threshold = test_params->r_drv_sen_threshold;
+		short_die_num = le16_to_cpup((__le16 *)&data_buf[0]);
+		if (short_die_num >= max_sen_num) {
+			ts_info("invalid short pad num:%d",	short_die_num);
+			continue;
+		}
+
+		/* TODO: j start position need recheck */
+		self_capdata = le16_to_cpup((__le16 *)&data_buf[2]);
+		if (self_capdata == 0xffff || self_capdata == 0) {
+			ts_info("invalid self_capdata:0x%x", self_capdata);
+			continue;
+		}
+
+		for (j = 0; j < max_drv_num; j++) {
+			adc_signal = le16_to_cpup((__le16 *)&data_buf[4 + j * 2]);
+
+			if (adc_signal < test_params->short_threshold)
+				continue;
+
+			short_r = (u32)cal_cha_to_cha_res(ts_test, self_capdata, adc_signal);
+			if (short_r < r_threshold) {
+				master_pin_num = map_die2pin(test_params, short_die_num);
+				slave_pin_num = map_die2pin(test_params, j + max_sen_num);
+				if (master_pin_num == 0xFF || slave_pin_num == 0xFF) {
+					ts_info("WARNNING invalid pin");
+					continue;
+				}
+				goodix_save_short_res(test_params, master_pin_num,
+					slave_pin_num, short_r);
+				ts_err("short circut:R=%dK,R_Threshold=%dK",
+							short_r, r_threshold);
+				ts_err("%s%d--%s%d shortcircut",
+					(master_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN",
+					(master_pin_num & ~DRV_CHANNEL_FLAG),
+					(slave_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN",
+					(slave_pin_num & ~DRV_CHANNEL_FLAG));
+				err = -EINVAL;
+			}
+		}
+		data_reg += size;
+	}
+
+	kfree(data_buf);
+	return err;    
+}
+
+static int gdix_check_resistance_to_gnd(struct ts_test_params *test_params,
+        u16 adc_signal, u32 pos)
+{
+	long r = 0;
+	u16 r_th = 0, avdd_value = 0;
+	u16 chn_id_tmp = 0;
+	u8 pin_num = 0;
+	struct goodix_ts_test *ts_test = container_of(test_params,
+		struct goodix_ts_test, test_params);
+	int max_drv_num = test_params->params_info->max_drv_num;
+	int max_sen_num = test_params->params_info->max_sen_num;
+
+	avdd_value = test_params->avdd_value;
+	if (adc_signal == 0 || adc_signal == 0x8000)
+		adc_signal |= 1;
+
+	if ((adc_signal & 0x8000) == 0) {
+		/* short to GND */
+		r = cal_cha_to_gnd_res(ts_test, adc_signal);
+	} else {
+		/* short to VDD */
+		r = cal_cha_to_avdd_res(ts_test, adc_signal & ~0x8000, avdd_value);
+	}
+
+	if (pos < max_drv_num)
+		r_th = test_params->r_drv_gnd_threshold;
+	else
+		r_th = test_params->r_sen_gnd_threshold;
+
+	chn_id_tmp = pos;
+	if (chn_id_tmp < max_drv_num)
+		chn_id_tmp += max_sen_num;
+	else
+		chn_id_tmp -= max_drv_num;
+
+	if (r < r_th) {
+		pin_num = map_die2pin(test_params, chn_id_tmp);
+		goodix_save_short_res(test_params, pin_num,
+			(adc_signal & 0x8000)? CHN_VDD : CHN_GND, r);
+		ts_err("%s%d shortcircut to %s,R=%ldK,R_Threshold=%dK",
+				(pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN",
+				(pin_num & ~DRV_CHANNEL_FLAG),
+				(adc_signal & 0x8000) ? "VDD" : "GND",
+				r, r_th);
+
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int gdix_check_gndvdd_shortcircut(struct goodix_ts_test *ts_test)
+{
+	int ret = 0, err = 0;
+	int size = 0, i = 0;
+	u16 adc_signal = 0;
+    u32 data_reg;
+	u8 *data_buf = NULL;
+	int max_drv_num = ts_test->test_params.params_info->max_drv_num;
+	int max_sen_num = ts_test->test_params.params_info->max_sen_num;
+
+	size = (max_drv_num + max_sen_num) * 2 + 2;
+	data_buf = kzalloc(size, GFP_KERNEL);
+	if (!data_buf) {
+		ts_err("Failed to alloc memory");
+		return -ENOMEM;
+	}
+	/* read diff code, diff code will be used to calculate
+		* resistance between channel and GND */
+	data_reg = ts_test->test_params.params_info->diffcode_data_reg;
+	ret = ts_test_read(ts_test, data_reg, data_buf, size);
+	if (ret < 0) {
+		ts_err("Failed read to-gnd rawdata");
+		err = -EINVAL;
+		goto err_out;
+	}
+
+	if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) {
+		ts_err("diff code checksum error");
+		err = -EINVAL;
+		goto err_out;		
+	}
+
+	for (i = 0; i < max_drv_num + max_sen_num; i++) {
+		adc_signal = le16_to_cpup((__le16 *)&data_buf[i * 2]);
+		ret = gdix_check_resistance_to_gnd(&ts_test->test_params,
+					adc_signal, i);
+		if (ret != 0) {
+			ts_err("Resistance to-gnd/vdd short");
+			err = ret;
+		}
+	}
+
+err_out:
+	kfree(data_buf);
+	return err;
+}
+
+static int goodix_shortcircut_analysis(struct goodix_ts_test *ts_test)
+{
+    int ret;
+    int err = 0;
+    test_result_t test_result;
+
+    ret = ts_test_read(ts_test, ts_test->test_params.params_info->short_test_result_reg,
+        (u8 *)&test_result, sizeof(test_result));
+    if (ret < 0) {
+        ts_err("Read TEST_RESULT_REG failed");
+        return ret;
+    }
+
+	if (checksum_cmp((u8 *)&test_result, sizeof(test_result),
+		CHECKSUM_MODE_U8_LE)) {
+		ts_err("shrot result checksum err");
+		return -EINVAL;
+	}
+
+	if (!(test_result.result & 0x0F)) {
+		ts_info(">>>>> No shortcircut");
+		return 0;
+	}     
+	ts_info("short flag 0x%02x, drv&drv:%d, sen&sen:%d, drv&sen:%d, drv/GNDVDD:%d, sen/GNDVDD:%d",
+		test_result.result, test_result.drv_drv_num, test_result.sen_sen_num,
+		test_result.drv_sen_num, test_result.drv_gnd_avdd_num, test_result.sen_gnd_avdd_num);
+
+	if (test_result.drv_drv_num)
+		err |= gdix_check_tx_tx_shortcircut(ts_test, test_result.drv_drv_num);
+	if (test_result.sen_sen_num)
+		err |= gdix_check_rx_rx_shortcircut(ts_test, test_result.sen_sen_num);
+	if (test_result.drv_sen_num)
+		err |= gdix_check_tx_rx_shortcircut(ts_test, test_result.drv_sen_num);
+	if (test_result.drv_gnd_avdd_num || test_result.sen_gnd_avdd_num)
+		err |= gdix_check_gndvdd_shortcircut(ts_test);
+
+    ts_info(">>>>> short check return 0x%x", err);
+
+    return err;
+}
+
+#define SHORT_FW_CMD_REG				0x10400
+static int send_test_cmd(struct goodix_ts_test *ts_test,
+    struct goodix_ts_cmd *cmd)
+{
+    int ret;
+    u32 reg = SHORT_FW_CMD_REG;
+    cmd->state = 0;
+    cmd->ack = 0;
+    goodix_append_checksum(&(cmd->buf[2]), cmd->len - 2,
+            CHECKSUM_MODE_U8_LE);
+    ret = ts_test_write(ts_test, reg, cmd->buf, cmd->len + 2);
+    if (ret < 0)
+        return ret;
+    usleep_range(10000, 11000);
+    return ret;
+}
+
+
+#define INSPECT_PARAM_CMD				0xAA
+#define SHORT_TEST_FINISH_FLAG  		0x88
+#define SHORT_TEST_THRESHOLD_REG		0x20402
+static void goodix_shortcircut_test(struct goodix_ts_test *ts_test)
+{
+    int ret = 0;
+    int retry;
+    u16 test_time;
+	u8 status;
+	int ic_type = ts_test->ts->bus->ic_type;
+    struct goodix_ts_cmd test_parm_cmd;
+	// u8 test_param[6];
+
+	ts_info("---------------------- short_test begin ----------------------");
+    ret = goodix_short_test_prepare(ts_test);
+    if (ret < 0) {
+        ts_err("Failed enter short test mode");
+        return;
+    }
+
+	/* get short test time */
+    ret = ts_test_read(ts_test, ts_test->test_params.params_info->short_test_time_reg, (u8 *)&test_time, 2);
+    if (ret < 0) {
+        ts_err("Failed to get test_time, default %dms", DEFAULT_TEST_TIME_MS);
+        test_time = DEFAULT_TEST_TIME_MS;
+    } else {
+		if (ic_type == IC_TYPE_BERLIN_A)
+        	test_time /= 10;
+        if (test_time > MAX_TEST_TIME_MS) {
+            ts_info("test time too long %d > %d",
+                test_time, MAX_TEST_TIME_MS);
+            test_time = MAX_TEST_TIME_MS;
+        }
+        ts_info("get test time %dms", test_time);
+    }
+
+	/* start short test */
+	if (ic_type == IC_TYPE_BERLIN_A) {
+		test_parm_cmd.len = 0x0A;
+		test_parm_cmd.cmd = INSPECT_PARAM_CMD;
+		test_parm_cmd.data[0] = ts_test->test_params.params_info->dft_short_threshold & 0xFF;
+		test_parm_cmd.data[1] = (ts_test->test_params.params_info->dft_short_threshold >> 8) & 0xFF;
+		test_parm_cmd.data[2] = ts_test->test_params.params_info->short_diffcode_threshold & 0xFF;
+		test_parm_cmd.data[3] = (ts_test->test_params.params_info->short_diffcode_threshold >> 8) & 0xFF;
+		test_parm_cmd.data[4] = ts_test->test_params.params_info->short_test_dump_num & 0xFF;
+		test_parm_cmd.data[5] = (ts_test->test_params.params_info->short_test_dump_num >> 8) & 0xFF;
+		ret = send_test_cmd(ts_test, &test_parm_cmd);
+		if (ret < 0) {
+			ts_err("send INSPECT_PARAM_CMD failed");
+			return;
+		}
+	} else {
+		// test_param[0] = ts_test->test_params.params_info->dft_short_threshold & 0xFF;
+		// test_param[1] = (ts_test->test_params.params_info->dft_short_threshold >> 8) & 0xFF;
+		// test_param[2] = ts_test->test_params.params_info->short_diffcode_threshold & 0xFF;
+		// test_param[3] = (ts_test->test_params.params_info->short_diffcode_threshold >> 8) & 0xFF;
+		// test_param[4] = ts_test->test_params.params_info->short_test_dump_num & 0xFF;
+		// test_param[5] = (ts_test->test_params.params_info->short_test_dump_num >> 8) & 0xFF;
+		// ts_test_write(ts_test, SHORT_TEST_THRESHOLD_REG, test_param, sizeof(test_param));
+		status = 0;
+		ts_test_write(ts_test, SHORT_TEST_RUN_REG, &status, 1);
+	}
+
+	/* wait short test finish */
+    msleep(test_time);
+    retry = 50;
+	while (retry--) {
+		ret = ts_test_read(ts_test, ts_test->test_params.params_info->short_test_status_reg, &status, 1);
+		if (!ret && status == SHORT_TEST_FINISH_FLAG)
+			break;
+		msleep(50);
+	}
+	if (retry < 0) {
+		ts_err("short test failed, status:0x%02x", status);
+		return;
+	}
+
+	/* start analysis short result */
+	ts_info("short_test finished, start analysis");
+	ret = goodix_shortcircut_analysis(ts_test);
+	if (ret < 0)
+		ts_test->test_result[GTP_SHORT_TEST] = GTP_PANEL_REASON;
+	else
+		ts_test->test_result[GTP_SHORT_TEST] = GTP_TEST_PASS;
+}
+
+#define GOODIX_CMD_RAWDATA	2
+#define GOODIX_TOUCH_EVENT	0x80
+static int goodix_cap_test_prepare(struct goodix_ts_test *ts_test)
+{
+    int ret;
+    struct goodix_ts_cmd temp_cmd;
+
+	ts_info("cap test prepare IN");
+	ts_test->test_result[GTP_CAP_TEST] = SYS_SOFTWARE_REASON;
+	ts_test->test_result[GTP_DELTA_TEST] = SYS_SOFTWARE_REASON;
+	if (ts_test->test_params.test_items[GTP_SELFCAP_TEST])
+		ts_test->test_result[GTP_SELFCAP_TEST] = SYS_SOFTWARE_REASON;
+	if (ts_test->test_params.test_items[GTP_NOISE_TEST])
+		ts_test->test_result[GTP_NOISE_TEST] = SYS_SOFTWARE_REASON;
+	if (ts_test->test_params.test_items[GTP_SELFNOISE_TEST])
+		ts_test->test_result[GTP_SELFNOISE_TEST] = SYS_SOFTWARE_REASON;
+
+    /* switch rawdata mode */
+	if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_D) {
+		temp_cmd.cmd = 0x90;
+		temp_cmd.data[0] = 0x81;
+		temp_cmd.len = 5;
+	} else {
+		temp_cmd.cmd = GOODIX_CMD_RAWDATA;
+		temp_cmd.len = 4;
+	}
+	ret = ts_test_send_cmd(ts_test, &temp_cmd);
+    if (ret < 0)
+        ts_err("Enter rawdata mode failed");
+
+    return ret;
+}
+
+static int goodix_cap_test_finish(struct goodix_ts_test *ts_test)
+{
+	ts_info("cap_test finished");
+    /* switch coor mode */
+	ts_test_reset(ts_test, 100);
+	return 0;
+}
+
+static int goodix_cache_rawdata(struct goodix_ts_test *ts_test)
+{
+	int ret;
+	int i;
+	int retry;
+	u8 val;
+	unsigned char frame_buf[GOODIX_MAX_FRAMEDATA_LEN];
+	struct frame_head *frame_head;
+	struct goodix_ts_core *cd = ts_test->ts;
+	unsigned char *cur_ptr;
+	u32 sen_num = ts_test->test_params.sen_num;
+	u32 drv_num = ts_test->test_params.drv_num;
+	u32 data_size = sen_num * drv_num;
+	u32 data_addr = ts_test->test_params.rawdata_addr;
+	u32 flag_addr = ts_test->ts->ic_info.misc.touch_data_addr;
+
+	if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_D)
+		flag_addr = ts_test->ts->ic_info.misc.frame_data_addr;
+
+	for (i = 0; i < TOTAL_FRAME_NUM; i++) {
+		val = 0;
+		ret = ts_test_write(ts_test, flag_addr, &val, 1);
+		if (ret < 0) {
+			ts_err("clean touch event failed, exit");
+			return -EAGAIN;
+		}
+		retry = 20;
+		while (retry--) {
+			usleep_range(5000, 5100);
+			ret = ts_test_read(ts_test, flag_addr, &val, 1);
+			if (!ret && (val & 0x80))
+				break;
+		}
+		if (retry < 0) {
+			ts_err("rawdata is not ready val:0x%02x i:%d, exit", val, i);
+			return -EAGAIN;
+		}
+
+		if (cd->bus->ic_type == IC_TYPE_BERLIN_D) {
+			ret = ts_test_read(ts_test, flag_addr, frame_buf, sizeof(frame_buf));
+			if (ret < 0)
+				return ret;
+			if (checksum_cmp(frame_buf, cd->ic_info.misc.frame_data_head_len, CHECKSUM_MODE_U8_LE)) {
+				ts_err("frame head checksum error");
+				return -EINVAL; 
+			}
+			frame_head = (struct frame_head *)frame_buf;
+			if (checksum_cmp(frame_buf, frame_head->cur_frame_len, CHECKSUM_MODE_U16_LE)) {
+				ts_err("frame body checksum error");
+				return -EINVAL;
+			}
+			cur_ptr = frame_buf;
+			cur_ptr += cd->ic_info.misc.frame_data_head_len;
+			cur_ptr += cd->ic_info.misc.fw_attr_len;
+			cur_ptr += cd->ic_info.misc.fw_log_len;
+			memcpy((u8 *)ts_test->rawdata[i].data, cur_ptr + 8,
+					cd->ic_info.misc.mutual_struct_len - 8);
+		} else {
+			ret = ts_test_read(ts_test, data_addr,
+				(u8 *)ts_test->rawdata[i].data, data_size * sizeof(s16));
+			if (ret < 0)
+				return ret;
+		}
+
+		ts_test->rawdata[i].size = data_size;
+		goodix_rotate_abcd2cbad(drv_num, sen_num, ts_test->rawdata[i].data);
+	}
+
+	return ret;
+}
+
+static void goodix_cache_deltadata(struct goodix_ts_test *ts_test)
+{
+	u32 data_size;
+	int tx = ts_test->test_params.drv_num;
+	int i;
+	int j;
+	int max_val;
+	int raw;
+	int temp;
+
+	for (i = 0; i < TOTAL_FRAME_NUM; i++) {
+		data_size = ts_test->rawdata[i].size;
+		if (data_size == 0)
+			continue;
+		for (j = 0; j < data_size; j++) {
+			raw = ts_test->rawdata[i].data[j];
+			max_val = 0;
+			/* calcu delta with above node */
+			if (j - tx >= 0) {
+				temp = ts_test->rawdata[i].data[j - tx];
+				temp = ABS(temp - raw);
+				max_val = MAX(max_val, temp);
+			}
+			/* calcu delta with bellow node */
+			if (j + tx < data_size) {
+				temp = ts_test->rawdata[i].data[j + tx];
+				temp = ABS(temp - raw);
+				max_val = MAX(max_val, temp);
+			}
+			/* calcu delta with left node */
+			if (j % tx) {
+				temp = ts_test->rawdata[i].data[j - 1];
+				temp = ABS(temp - raw);
+				max_val = MAX(max_val, temp);
+			}
+			/* calcu delta with right node */
+			if ((j + 1) % tx) {
+				temp = ts_test->rawdata[i].data[j + 1];
+				temp = ABS(temp - raw);
+				max_val = MAX(max_val, temp);
+			}
+			ts_test->accord_arr[i].data[j] = max_val * 1000 / raw;
+		}
+		ts_test->accord_arr[i].size = data_size;
+	}
+}
+
+static int goodix_cache_self_rawdata(struct goodix_ts_test *ts_test)
+{
+	int ret;
+	u32 sen_num = ts_test->test_params.sen_num;
+	u32 drv_num = ts_test->test_params.drv_num;
+	u32 data_size = sen_num + drv_num;
+	u32 data_addr = ts_test->test_params.self_rawdata_addr;
+	u32 flag_addr = ts_test->ts->ic_info.misc.frame_data_addr;
+	struct frame_head *frame_head;
+	struct goodix_ts_core *cd = ts_test->ts;
+	unsigned char frame_buf[GOODIX_MAX_FRAMEDATA_LEN];
+	unsigned char *cur_ptr;	
+
+	if (cd->bus->ic_type == IC_TYPE_BERLIN_D) {
+		ret = ts_test_read(ts_test, flag_addr, frame_buf, sizeof(frame_buf));
+		if (ret < 0)
+			return ret;
+		if (checksum_cmp(frame_buf, cd->ic_info.misc.frame_data_head_len, CHECKSUM_MODE_U8_LE)) {
+			ts_err("frame head checksum error");
+			return -EINVAL;
+		}
+		frame_head = (struct frame_head *)frame_buf;
+		if (checksum_cmp(frame_buf, frame_head->cur_frame_len, CHECKSUM_MODE_U16_LE)) {
+			ts_err("frame body checksum error");
+			return -EINVAL;
+		}
+		cur_ptr = frame_buf;
+		cur_ptr += cd->ic_info.misc.frame_data_head_len;
+		cur_ptr += cd->ic_info.misc.fw_attr_len;
+		cur_ptr += cd->ic_info.misc.fw_log_len;
+		cur_ptr += cd->ic_info.misc.mutual_struct_len;
+		memcpy((u8 *)ts_test->self_rawdata.data, cur_ptr + 10,
+				cd->ic_info.misc.self_struct_len - 10);
+	} else {
+		ret = ts_test_read(ts_test, data_addr,
+			(u8 *)ts_test->self_rawdata.data, data_size * sizeof(s16));
+		if (ret < 0)
+			return ret;
+	}
+	ts_test->self_rawdata.size = data_size;
+
+	return ret;
+}
+
+static int goodix_cache_noisedata(struct goodix_ts_test *ts_test)
+{
+	int ret;
+	int i;
+	int cnt;
+	int retry;
+	u8 val;
+	unsigned char frame_buf[GOODIX_MAX_FRAMEDATA_LEN];
+	unsigned char *cur_ptr;
+	struct frame_head *frame_head;
+	struct goodix_ts_cmd temp_cmd;
+	struct goodix_ts_core *cd = ts_test->ts;		
+	u32 sen_num = ts_test->test_params.sen_num;
+	u32 drv_num = ts_test->test_params.drv_num;
+	u32 data_size = sen_num * drv_num;
+	u32 data_addr = ts_test->test_params.noisedata_addr;
+	u32 flag_addr = ts_test->ts->ic_info.misc.touch_data_addr;
+
+	if (cd->bus->ic_type == IC_TYPE_BERLIN_D) {
+		flag_addr = ts_test->ts->ic_info.misc.frame_data_addr;
+		temp_cmd.cmd = 0x90;
+		temp_cmd.data[0] = 0x82;
+		temp_cmd.len = 5;
+		ret = ts_test_send_cmd(ts_test, &temp_cmd);
+		if (ret < 0) {
+			ts_err("switch diffdata mode failed, exit!");
+			return ret;
+		}
+	}
+
+	for (cnt = 0; cnt < NOISEDATA_TEST_TIMES; cnt++) {
+		val = 0;
+		ret = ts_test_write(ts_test, flag_addr, &val, 1);
+		if (ret < 0) {
+			ts_err("clean touch event failed, exit");
+			return -EAGAIN;
+		}
+		retry = 20;
+		while (retry--) {
+			usleep_range(5000, 5100);
+			ret = ts_test_read(ts_test, flag_addr, &val, 1);
+			if (!ret && (val & 0x80))
+				break;
+		}
+		if (retry < 0) {
+			ts_err("noisedata is not ready val:0x%02x i:%d, exit", val, cnt);
+			return -EAGAIN;
+		}
+
+		if (cd->bus->ic_type == IC_TYPE_BERLIN_D) {
+			ret = ts_test_read(ts_test, flag_addr, frame_buf, sizeof(frame_buf));
+			if (ret < 0)
+				return ret;
+			if (checksum_cmp(frame_buf, cd->ic_info.misc.frame_data_head_len, CHECKSUM_MODE_U8_LE)) {
+				ts_err("frame head checksum error");
+				return -EINVAL; 
+			}
+			frame_head = (struct frame_head *)frame_buf;
+			if (checksum_cmp(frame_buf, frame_head->cur_frame_len, CHECKSUM_MODE_U16_LE)) {
+				ts_err("frame body checksum error");
+				return -EINVAL;
+			}
+			cur_ptr = frame_buf;
+			cur_ptr += cd->ic_info.misc.frame_data_head_len;
+			cur_ptr += cd->ic_info.misc.fw_attr_len;
+			cur_ptr += cd->ic_info.misc.fw_log_len;
+			memcpy((u8 *)ts_test->noisedata[cnt].data, cur_ptr + 8,
+					cd->ic_info.misc.mutual_struct_len - 8);
+		} else {
+			ret = ts_test_read(ts_test, data_addr,
+				(u8 *)ts_test->noisedata[cnt].data, data_size * sizeof(s16));
+			if (ret < 0)
+				return ret;
+		}
+
+		ts_test->noisedata[cnt].size = data_size;
+		goodix_rotate_abcd2cbad(drv_num, sen_num, ts_test->noisedata[cnt].data);
+		for (i = 0; i < data_size; i++)
+			ts_test->noisedata[cnt].data[i] = ABS(ts_test->noisedata[cnt].data[i]);
+	}
+
+	return ret;
+}
+
+static int goodix_cache_self_noisedata(struct goodix_ts_test *ts_test)
+{
+	int ret;
+	int i;
+	u32 sen_num = ts_test->test_params.sen_num;
+	u32 drv_num = ts_test->test_params.drv_num;
+	u32 data_size = sen_num + drv_num;
+	u32 data_addr = ts_test->test_params.self_noisedata_addr;
+	u32 flag_addr = ts_test->ts->ic_info.misc.frame_data_addr;
+	struct frame_head *frame_head;
+	struct goodix_ts_core *cd = ts_test->ts;
+	unsigned char frame_buf[GOODIX_MAX_FRAMEDATA_LEN];
+	unsigned char *cur_ptr;
+
+	if (cd->bus->ic_type == IC_TYPE_BERLIN_D) {
+		ret = ts_test_read(ts_test, flag_addr, frame_buf, sizeof(frame_buf));
+		if (ret < 0)
+			return ret;
+		if (checksum_cmp(frame_buf, cd->ic_info.misc.frame_data_head_len, CHECKSUM_MODE_U8_LE)) {
+			ts_err("frame head checksum error");
+			return -EINVAL;
+		}
+		frame_head = (struct frame_head *)frame_buf;
+		if (checksum_cmp(frame_buf, frame_head->cur_frame_len, CHECKSUM_MODE_U16_LE)) {
+			ts_err("frame body checksum error");
+			return -EINVAL;
+		}
+		cur_ptr = frame_buf;
+		cur_ptr += cd->ic_info.misc.frame_data_head_len;
+		cur_ptr += cd->ic_info.misc.fw_attr_len;
+		cur_ptr += cd->ic_info.misc.fw_log_len;
+		cur_ptr += cd->ic_info.misc.mutual_struct_len;
+		memcpy((u8 *)ts_test->self_noisedata.data, cur_ptr + 10,
+				cd->ic_info.misc.self_struct_len - 10);
+	} else {
+		ret = ts_test_read(ts_test, data_addr,
+			(u8 *)ts_test->self_noisedata.data, data_size * sizeof(s16));
+		if (ret < 0)
+			return ret;
+	}
+
+	ts_test->self_noisedata.size = data_size;
+	for (i = 0; i < data_size; i++) {
+		ts_test->self_noisedata.data[i] = ABS(ts_test->self_noisedata.data[i]);
+	}	
+
+	return ret;	
+}
+
+static int goodix_analysis_rawdata(struct goodix_ts_test *ts_test)
+{
+	int i;
+	int j;
+	bool fail_flag = false;
+	int err_cnt = 0;
+	int times = TOTAL_FRAME_NUM;
+	s16 val;
+	u32 data_size = ts_test->rawdata[0].size;
+
+	for (i = 0; i < times; i++) {
+		for (j = 0; j < data_size; j++) {
+			val = ts_test->rawdata[i].data[j];
+			if (val < ts_test->test_params.min_limits[j]) {
+				fail_flag = true;
+				ts_test->open_res.beyond_min_limit_cnt[j]++;
+			}
+			if (val > ts_test->test_params.max_limits[j]) {
+				fail_flag = true;
+				ts_test->open_res.beyond_max_limit_cnt[j]++;
+			}
+		}
+		if (fail_flag)
+			err_cnt++;
+		fail_flag = false;
+	}
+
+	if (err_cnt > 0)
+		ts_err("rawdata have %d frames out of range", err_cnt);
+
+	err_cnt *= 100;
+	if (err_cnt > times * 100 * 9 / 10)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int goodix_analysis_deltadata(struct goodix_ts_test *ts_test)
+{
+	int i;
+	int j;
+	int ret = 0;
+	s16 val;
+	u32 data_size = ts_test->accord_arr[0].size;
+
+	for (i = 0; i < TOTAL_FRAME_NUM; i++) {
+		for (j = 0; j < data_size; j++) {
+			val = ts_test->accord_arr[i].data[j];
+			if (val > ts_test->test_params.deviation_limits[j]) {
+				ts_test->open_res.beyond_accord_limit_cnt[j]++;
+				ret = -EINVAL;
+			}	
+		}
+	}
+
+	return ret;
+}
+
+static int goodix_analysis_self_rawdata(struct goodix_ts_test *ts_test)
+{
+	int i;
+	s16 val;
+	u32 data_size = ts_test->self_rawdata.size;
+
+	for (i = 0; i < data_size; i++) {
+		val = ts_test->self_rawdata.data[i];
+		if (val < ts_test->test_params.self_min_limits[i] ||
+			val > ts_test->test_params.self_max_limits[i]) {
+			ts_err("self_rawdata isn't in range, val:%d threshold:[%d,%d]",
+				val, ts_test->test_params.self_min_limits[i],
+				ts_test->test_params.self_max_limits[i]);
+			return -EINVAL;
+		}
+	}
+
+	return 0;	
+}
+
+static int goodix_analysis_noisedata(struct goodix_ts_test *ts_test)
+{
+	int cnt;
+	int i;
+	bool fail_flag = false;
+	int err_cnt = 0;
+	int times = NOISEDATA_TEST_TIMES;
+	s16 val;
+	u32 data_size = ts_test->noisedata[0].size;
+
+	for (cnt = 0; cnt < times; cnt++) {
+		for (i = 0; i < data_size; i++) {
+			val = ts_test->noisedata[cnt].data[i];
+			if (val > ts_test->test_params.noise_threshold)
+				fail_flag = true;
+		}
+		if (fail_flag)
+			err_cnt++;
+		fail_flag = false;
+	}
+
+	if (err_cnt > 0)
+		ts_err("noisedata have %d frames out of range", err_cnt);
+
+	err_cnt *= 100;
+	if (err_cnt > times * 100 * 2 / 10)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int goodix_analysis_self_noisedata(struct goodix_ts_test *ts_test)
+{
+	int i;
+	s16 val;
+	u32 data_size = ts_test->self_noisedata.size;
+
+	for (i = 0; i < data_size; i++) {
+		val = ts_test->self_noisedata.data[i];
+		if (val > ts_test->test_params.self_noise_threshold) {
+			ts_err("self noisedata isn't in range, val:%d threshold:[0,%d]",
+				val, ts_test->test_params.self_noise_threshold);
+			return -EINVAL;
+		}
+	}
+
+	return 0;		
+}
+
+static void goodix_capacitance_test(struct goodix_ts_test *ts_test)
+{
+	int ret;
+
+	ts_info("---------------------- cap_test begin ----------------------");
+    ret = goodix_cap_test_prepare(ts_test);
+    if (ret < 0) {
+		ts_err("cap_test prepare failed, exit");
+		goto exit;
+    }
+	ts_info("cap rawdata prepare OK");
+
+    /* obtain rawdata */
+    ret = goodix_cache_rawdata(ts_test);
+    if (ret < 0) {
+		if (ret == -EAGAIN) {
+			ts_err("Capacitance exit");
+			goto exit;
+		} else {
+			ts_err("Failed to read capdata");
+		}
+    } else {
+		ts_info("get rawdata finish, start analysis");
+		ret = goodix_analysis_rawdata(ts_test);
+		if (ret < 0)
+			ts_test->test_result[GTP_CAP_TEST] = GTP_PANEL_REASON;
+		else
+			ts_test->test_result[GTP_CAP_TEST] = GTP_TEST_PASS;
+    }
+
+	/* obtain delta_data */
+	goodix_cache_deltadata(ts_test);
+	ts_info("get deltadata finish, start analysis");
+	ret = goodix_analysis_deltadata(ts_test);
+	if (ret < 0)
+		ts_test->test_result[GTP_DELTA_TEST] = GTP_PANEL_REASON;
+	else
+		ts_test->test_result[GTP_DELTA_TEST] = GTP_TEST_PASS;
+
+    /* obtain self_rawdata */
+	if (ts_test->test_params.test_items[GTP_SELFCAP_TEST]) {
+		ret = goodix_cache_self_rawdata(ts_test);
+		if (ret < 0) {
+			ts_err("Failed to read self_capdata");
+		} else {
+			ts_info("get self_rawdata finish, start analysis");
+			ret = goodix_analysis_self_rawdata(ts_test);
+			if (ret < 0)
+				ts_test->test_result[GTP_SELFCAP_TEST] = GTP_PANEL_REASON;
+			else
+				ts_test->test_result[GTP_SELFCAP_TEST] = GTP_TEST_PASS;
+		}
+	}
+
+    /* obtain noisedata */
+	if (ts_test->test_params.test_items[GTP_NOISE_TEST]) {
+		ret = goodix_cache_noisedata(ts_test);
+		if (ret < 0) {
+			ts_err("Failed to read noisedata");
+		} else {
+			ts_info("get noisedata finish, start analysis");
+			ret = goodix_analysis_noisedata(ts_test);
+			if (ret < 0)
+				ts_test->test_result[GTP_NOISE_TEST] = GTP_PANEL_REASON;
+			else
+				ts_test->test_result[GTP_NOISE_TEST] = GTP_TEST_PASS;
+		}
+	}
+
+    /* obtain self_noisedata */
+	if (ts_test->test_params.test_items[GTP_SELFNOISE_TEST]) {
+		ret = goodix_cache_self_noisedata(ts_test);
+		if (ret < 0) {
+			ts_err("Failed to read self_noisedata");
+		} else {
+			ts_info("get self_noisedata finish, start analysis");
+			ret = goodix_analysis_self_noisedata(ts_test);
+			if (ret < 0)
+				ts_test->test_result[GTP_SELFNOISE_TEST] = GTP_PANEL_REASON;
+			else
+				ts_test->test_result[GTP_SELFNOISE_TEST] = GTP_TEST_PASS;
+		}		
+	}
+
+exit:
+	goodix_cap_test_finish(ts_test);
+}
+
+char *goodix_strncat(char *dest, char *src, size_t dest_size)
+{
+	size_t dest_len = 0;
+
+	dest_len = strnlen(dest, dest_size);
+	return strncat(&dest[dest_len], src, dest_size - dest_len - 1);
+}
+
+char *goodix_strncatint(char *dest, int src, char *format, size_t dest_size)
+{
+	char src_str[MAX_STR_LEN] = {0};
+
+	snprintf(src_str, MAX_STR_LEN, format, src);
+	return goodix_strncat(dest, src_str, dest_size);
+}
+
+static void goodix_data_cal(s16 *data, size_t data_size, s16 *stat_result)
+{
+	int i = 0;
+	s16 avg = 0;
+	s16 min = 0;
+	s16 max = 0;
+	long long sum = 0;
+
+	min = data[0];
+	max = data[0];
+	for (i = 0; i < data_size; i++) {
+		sum += data[i];
+		if (max < data[i])
+			max = data[i];
+		if (min > data[i])
+			min = data[i];
+	}
+	avg = div_s64(sum, data_size);
+	stat_result[0] = avg;
+	stat_result[1] = max;
+	stat_result[2] = min;
+}
+
+static void goodix_data_statistics(s16 *data, size_t data_size,
+		char *result, size_t res_size)
+{
+	s16 stat_value[3];
+
+	if (!data || !result) {
+		ts_err("parameters error please check *data and *result value");
+		return;
+	}
+
+	if (data_size <= 0 || res_size <= 0) {
+		ts_err("input parameter is illegva:data_size=%ld, res_size=%ld",
+			data_size, res_size);
+		return;
+	}
+	goodix_data_cal(data, data_size, stat_value);
+
+	memset(result, 0, res_size);
+	snprintf(result, res_size, "[%d,%d,%d]",
+			stat_value[0], stat_value[1], stat_value[2]);
+	return;
+}
+
+#ifdef SAVE_IN_CSV
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)
+static ssize_t fs_write(const void* buf, size_t size, struct file* fp)
+{
+    loff_t pos;
+    ssize_t len;
+
+    pos = fp->f_pos;
+    len = kernel_write(fp, buf, size, &pos);
+	fp->f_pos = pos;
+
+    return len;
+}
+#else
+static ssize_t fs_write(const void* buf, size_t size, struct file* fp)
+{
+	mm_segment_t old_fs;
+    loff_t pos;
+    ssize_t len;
+
+    pos = fp->f_pos;
+	old_fs = get_fs();
+	set_fs(KERNEL_DS);
+    len = vfs_write(fp, buf, size, &pos);
+	set_fs(old_fs);
+	fp->f_pos = pos;
+
+    return len;
+}
+#endif
+
+static int goodix_save_test_config(struct goodix_ts_test *ts_test,
+		struct file *fp)
+{
+	int ret = 0;
+	int i;
+	int bytes = 0;
+	char *data;
+	struct goodix_ic_config *cfg = &ts_test->test_config;
+
+	if (cfg->len <= 0) {
+		ts_info("Can't find vaild test config");
+		return 0;
+	}
+
+	data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL);
+	if (!data) {
+		ts_err("alloc memory failed");
+		return -ENOMEM;
+	}
+
+	bytes += sprintf(&data[bytes], "<OrderConfig>\n");
+	for (i = 0; i < cfg->len; i++) {
+		bytes += sprintf(&data[bytes], "0x%02x,", cfg->data[i]);
+	}
+	bytes += sprintf(&data[bytes], "\n");
+	bytes += sprintf(&data[bytes], "</OrderConfig>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0) {
+		ts_err("test config write failed");
+		goto save_end;
+	}
+	
+save_end:
+	kfree(data);
+	return ret;	
+}
+
+static int goodix_save_header(struct goodix_ts_test *ts_test,
+		struct file *fp)
+{
+	int ret;
+	int i;
+	int bytes = 0;
+	bool result = false;
+	char *data = NULL;
+	struct goodix_ts_core *ts = ts_test->ts;
+	
+	data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL);
+	if (!data) {
+		ts_err("alloc memory failed");
+		return -ENOMEM;
+	}
+
+	bytes += sprintf(&data[bytes], "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+	bytes += sprintf(&data[bytes], "<TESTLOG>\n");
+	bytes += sprintf(&data[bytes], "<Header>\n");
+	/* sava test result */
+	for (i = 0; i < MAX_TEST_ITEMS; i++) {
+		if ((ts_test->test_result[i] > 0) &&
+			(ts_test->test_result[i] != GTP_TEST_PASS)) {
+				result = true;
+				break;
+		}
+	}
+	if (result)
+		bytes += sprintf(&data[bytes], "<Result>NG</Result>\n");
+	else
+		bytes += sprintf(&data[bytes], "<Result>OK</Result>\n");
+	bytes += sprintf(&data[bytes], "<DeviceType>GT%s</DeviceType>\n",
+			ts->fw_version.patch_pid);
+	bytes += sprintf(&data[bytes], "<SensorId>%d</SensorId>\n",
+			ts_test->ts->fw_version.sensor_id);
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0) {
+		ts_err("header write failed");
+		goto save_end;
+	}
+	bytes = 0;
+	/* save test config */
+	ret = goodix_save_test_config(ts_test, fp);
+	if (ret < 0)  {
+		ts_err("save test config failed");
+		goto save_end;
+	}
+
+	bytes += sprintf(&data[bytes], "</Header>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0) {
+		ts_err("header write failed");
+		goto save_end;
+	}
+	bytes = 0;
+
+	/* item list */
+	bytes += sprintf(&data[bytes], "<ItemList>\n");
+	if (ts_test->test_result[GTP_CAP_TEST]) {
+		if (GTP_TEST_PASS == ts_test->test_result[GTP_CAP_TEST])
+			bytes += sprintf(&data[bytes], 
+					"<Item name=\"Rawdata MAX/MIN Test\" result=\"OK\"/>\n");
+		else
+			bytes += sprintf(&data[bytes], 
+					"<Item name=\"Rawdata MAX/MIN Test\" result=\"NG\"/>\n");
+	}
+
+	if (ts_test->test_result[GTP_DELTA_TEST]) {
+		if (GTP_TEST_PASS == ts_test->test_result[GTP_DELTA_TEST])
+			bytes += sprintf(&data[bytes],
+					"<Item name=\"Rawdata Adjcent Deviation Test\" result=\"OK\"/>\n");
+		else
+			bytes += sprintf(&data[bytes],
+					"<Item name=\"Rawdata Adjcent Deviation Test\" result=\"NG\"/>\n");
+	}
+
+	if (ts_test->test_result[GTP_NOISE_TEST]) {
+		if (GTP_TEST_PASS == ts_test->test_result[GTP_NOISE_TEST])
+			bytes += sprintf(&data[bytes], 
+					"<Item name=\"Diffdata Jitter Test\" result=\"OK\"/>\n");
+		else
+			bytes += sprintf(&data[bytes], 
+					"<Item name=\"Diffdata Jitter Test\" result=\"NG\"/>\n");
+	}
+
+	if (ts_test->test_result[GTP_SELFNOISE_TEST]) {
+		if (GTP_TEST_PASS == ts_test->test_result[GTP_SELFNOISE_TEST])
+			bytes += sprintf(&data[bytes], 
+					"<Item name=\"Self Diffdata Jitter Limit Test\" result=\"OK\"/>\n");
+		else
+			bytes += sprintf(&data[bytes], 
+					"<Item name=\"Self Diffdata Jitter Limit Test\" result=\"NG\"/>\n");
+	}
+
+	if (ts_test->test_result[GTP_SELFCAP_TEST]) {
+		if (GTP_TEST_PASS == ts_test->test_result[GTP_SELFCAP_TEST])
+			bytes += sprintf(&data[bytes], 
+					"<Item name=\"Self Rawdata Upper Limit Test\" result=\"OK\"/>\n");
+		else
+			bytes += sprintf(&data[bytes], 
+					"<Item name=\"Self Rawdata Upper Limit Test\" result=\"NG\"/>\n");
+	}
+
+	if (ts_test->test_result[GTP_SHORT_TEST]) {
+		if (GTP_TEST_PASS == ts_test->test_result[GTP_SHORT_TEST])
+			bytes += sprintf(&data[bytes], 
+					"<Item name=\"Short Test\" result=\"OK\"/>\n");
+		else
+			bytes += sprintf(&data[bytes], 
+					"<Item name=\"Short Test\" result=\"NG\"/>\n");
+	}
+
+	bytes += sprintf(&data[bytes], "</ItemList>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0) {
+		ts_err("item list write failed");
+		goto save_end;
+	}
+
+save_end:
+	kfree(data);
+	return ret;	
+}
+
+static int goodix_save_limits(struct goodix_ts_test *ts_test,
+		struct file *fp)
+{
+	int ret;
+	int i;
+	int bytes = 0;
+	char *data = NULL;
+	int tx = ts_test->test_params.drv_num;
+	int rx = ts_test->test_params.sen_num;
+	int chn1;
+	int chn2;
+	int r;
+
+	data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL);
+	if (!data) {
+		ts_err("alloc memory failed for ");
+		return -ENOMEM;
+	}
+
+	bytes += sprintf(&data[bytes], "<TestItems>\n");
+
+	/* save short result */
+	if (ts_test->test_result[GTP_SHORT_TEST]) {
+		bytes += sprintf(&data[bytes], "<Item name=\"Short Test\">\n");
+		bytes += sprintf(&data[bytes], "<ShortNum>%d</ShortNum>\n",
+				ts_test->short_res.short_num);
+		for (i = 0; i < ts_test->short_res.short_num; i++) {
+			chn1 = ts_test->short_res.short_msg[4 * i];
+			chn2 = ts_test->short_res.short_msg[4 * i + 1];
+			r = (ts_test->short_res.short_msg[4 * i + 2] << 8) +
+				ts_test->short_res.short_msg[4 * i + 3];
+			if (chn1 == CHN_VDD)
+				bytes += sprintf(&data[bytes], "<ShortMess Chn1=\"VDD\" ");
+			else if (chn1 == CHN_GND)
+				bytes += sprintf(&data[bytes], "<ShortMess Chn1=\"GND\" ");
+			else if (chn1 & DRV_CHANNEL_FLAG)
+				bytes += sprintf(&data[bytes], "<ShortMess Chn1=\"Tx%d\" ",
+						chn1 & 0x7f);
+			else
+				bytes += sprintf(&data[bytes], "<ShortMess Chn1=\"Rx%d\" ",
+						chn1 & 0x7f);
+			if (chn2 == CHN_VDD)
+				bytes += sprintf(&data[bytes], 
+						"Chn2=\"VDD\" ShortResistor= \"%dKom\"/>\n", r);
+			else if (chn2 == CHN_GND)
+				bytes += sprintf(&data[bytes],
+						"Chn2=\"GND\" ShortResistor= \"%dKom\"/>\n", r);
+			else if (chn2 & DRV_CHANNEL_FLAG)
+				bytes += sprintf(&data[bytes],
+						"Chn2=\"Tx%d\" ShortResistor= \"%dKom\"/>\n",
+						chn2 & 0x7f, r);
+			else
+				bytes += sprintf(&data[bytes],
+						"Chn2=\"Rx%d\" ShortResistor= \"%dKom\"/>\n",
+						chn2 & 0x7f, r);
+		}
+		bytes += sprintf(&data[bytes], "</Item>\n");
+		ret = fs_write(data, bytes, fp);
+		if (ret < 0) {
+			ts_err("short res write fail.");
+			goto save_end;
+		}
+		bytes = 0;
+	}
+
+	/* rawdata max limit */
+	bytes += sprintf(&data[bytes], "<Item name=\"Rawdata Test Sets\">\n");
+	bytes += sprintf(&data[bytes], "<TotalFrameCnt>%d</TotalFrameCnt>\n",
+			TOTAL_FRAME_NUM);
+	bytes += sprintf(&data[bytes], "<MaxRawLimit>\n");
+	for (i = 0; i < tx * rx; i++) {
+		bytes += sprintf(&data[bytes], "%d,",
+			ts_test->test_params.max_limits[i]);
+		if ((i + 1) % tx == 0)
+			bytes += sprintf(&data[bytes], "\n");
+	}
+	bytes += sprintf(&data[bytes], "</MaxRawLimit>\n");
+	/* BeyondRawdataUpperLimit */
+	bytes += sprintf(&data[bytes], "<BeyondRawdataUpperLimitCnt>\n");
+	for (i = 0; i < tx * rx; i++) {
+		bytes += sprintf(&data[bytes], "%d,",
+				ts_test->open_res.beyond_max_limit_cnt[i]);
+		if ((i + 1) % tx == 0)
+			bytes += sprintf(&data[bytes], "\n");
+	}
+	bytes += sprintf(&data[bytes], "</BeyondRawdataUpperLimitCnt>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0) {
+		ts_err("rawdata limit write failed");
+		goto save_end;
+	}
+	bytes = 0;
+
+	/* rawdata min limit */
+	bytes += sprintf(&data[bytes], "<MinRawLimit>\n");
+	for (i = 0; i < tx * rx; i++) {
+		bytes += sprintf(&data[bytes], "%d,",
+			ts_test->test_params.min_limits[i]);
+		if ((i + 1) % tx == 0)
+			bytes += sprintf(&data[bytes], "\n");
+	}
+	bytes += sprintf(&data[bytes], "</MinRawLimit>\n");
+	/* BeyondRawdataLower limit */
+	bytes += sprintf(&data[bytes], "<BeyondRawdataLowerLimitCnt>\n");
+	for (i = 0; i < tx * rx; i++) {
+		bytes += sprintf(&data[bytes], "%d,",
+				ts_test->open_res.beyond_min_limit_cnt[i]);
+		if ((i + 1) % tx == 0)
+			bytes += sprintf(&data[bytes], "\n");
+	}
+	bytes += sprintf(&data[bytes], "</BeyondRawdataLowerLimitCnt>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0) {
+		ts_err("rawdata limit write failed");
+		goto save_end;
+	}
+	bytes = 0;
+
+	/* Max Accord limit */
+	bytes += sprintf(&data[bytes], "<MaxAccordLimit>\n");
+	for (i = 0; i < tx * rx; i++) {
+		bytes += sprintf(&data[bytes], "%d,",
+			ts_test->test_params.deviation_limits[i]);
+		if ((i + 1) % tx == 0)
+			bytes += sprintf(&data[bytes], "\n");
+	}	
+	bytes += sprintf(&data[bytes], "</MaxAccordLimit>\n");
+	/* BeyondAccordLimitCnt */
+	bytes += sprintf(&data[bytes], "<BeyondAccordLimitCnt>\n");
+	for (i = 0; i < tx * rx; i++) {
+		bytes += sprintf(&data[bytes], "%d,",
+			ts_test->open_res.beyond_accord_limit_cnt[i]);
+		if ((i + 1) % tx == 0)
+			bytes += sprintf(&data[bytes], "\n");
+	}
+	bytes += sprintf(&data[bytes], "</BeyondAccordLimitCnt>\n");
+	bytes += sprintf(&data[bytes], "</Item>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0) {
+		ts_err("rawdata limit write failed");
+		goto save_end;
+	}
+	bytes = 0;
+
+	/* save noise limit */
+	if (ts_test->test_result[GTP_NOISE_TEST]) {
+		bytes += sprintf(&data[bytes], "<Item name=\"Diffdata Test Sets\">\n");
+		bytes += sprintf(&data[bytes], "<TotalFrameCnt>%d</TotalFrameCnt>\n",
+				NOISEDATA_TEST_TIMES);
+		bytes += sprintf(&data[bytes], "<MaxJitterLimit>%d</MaxJitterLimit>\n",
+				ts_test->test_params.noise_threshold);
+		bytes += sprintf(&data[bytes], "</Item>\n");
+		ret = fs_write(data, bytes, fp);
+		if (ret < 0) {
+			ts_err("noise limit write failed");
+			goto save_end;
+		}
+		bytes = 0;
+	}
+
+	/* save self rawdata limit */
+	if (ts_test->test_result[GTP_SELFCAP_TEST]) {
+		bytes += sprintf(&data[bytes], "<Item name=\"Self Rawdata Test Sets\">\n");
+		bytes += sprintf(&data[bytes], "<TotalFrameCnt>1</TotalFrameCnt>\n");
+		bytes += sprintf(&data[bytes], "<MaxRawLimit>\n");
+		for (i = 0; i < tx + rx; i++) {
+			bytes += sprintf(&data[bytes], "%d,",
+					ts_test->test_params.self_max_limits[i]);
+			if ((i + 1) % tx == 0)
+				bytes += sprintf(&data[bytes], "\n");
+		}
+		if ((tx + rx) % tx != 0)
+			bytes += sprintf(&data[bytes], "\n");
+		bytes += sprintf(&data[bytes], "</MaxRawLimit>\n");
+		bytes += sprintf(&data[bytes], "<MinRawLimit>\n");
+		for (i = 0; i < tx + rx; i++) {
+			bytes += sprintf(&data[bytes], "%d,",
+					ts_test->test_params.self_min_limits[i]);
+			if ((i + 1) % tx == 0)
+				bytes += sprintf(&data[bytes], "\n");
+		}
+		if ((tx + rx) % tx != 0)
+			bytes += sprintf(&data[bytes], "\n");
+		bytes += sprintf(&data[bytes], "</MinRawLimit>\n");
+		bytes += sprintf(&data[bytes], "</Item>\n");
+		ret = fs_write(data, bytes, fp);
+		if (ret < 0) {
+			ts_err("self rawdata limit write failed");
+			goto save_end;
+		}
+		bytes = 0;
+	}
+
+	/* save selfnoise limit */
+	if (ts_test->test_result[GTP_SELFNOISE_TEST]) {
+		bytes += sprintf(&data[bytes], "<Item name=\"Self Diffdata Test Sets\">\n");
+		bytes += sprintf(&data[bytes], "<TotalFrameCnt>1</TotalFrameCnt>\n");
+		bytes += sprintf(&data[bytes], "<MaxJitterLimit>%d</MaxJitterLimit>\n",
+				ts_test->test_params.self_noise_threshold);
+		bytes += sprintf(&data[bytes], "</Item>\n");
+		ret = fs_write(data, bytes, fp);
+		if (ret < 0) {
+			ts_err("raw limit write failed");
+			goto save_end;
+		}
+		bytes = 0;
+	}
+
+	bytes += sprintf(&data[bytes], "</TestItems>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0)
+		ts_err("limit write fail.");
+
+save_end:
+	kfree(data);
+	return ret;
+}
+
+static int goodix_save_rawdata(struct goodix_ts_test *ts_test,
+		struct file *fp)
+{
+	int i;
+	int j;
+	int ret;
+	int bytes = 0;
+	s16 stat_result[3];
+	char *data = NULL;
+	int tx = ts_test->test_params.drv_num;
+	int rx = ts_test->test_params.sen_num;
+	int len = tx * rx;
+
+	data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL);
+	if (!data) {
+		ts_err("alloc memory failed for ");
+		return -ENOMEM;
+	}
+
+	bytes += sprintf(&data[bytes], "<RawDataRecord>\n");
+	for (i = 0; i < TOTAL_FRAME_NUM; i++) {
+		goodix_data_cal(ts_test->rawdata[i].data, len, stat_result);
+		bytes += sprintf(&data[bytes],
+			"<DataContent No.=\"%d\" DataCount=\"%d\" Maximum=\"%d\" Minimum=\"%d\" Average=\"%d\">\n",
+			i, len, stat_result[1], stat_result[2], stat_result[0]);
+		for (j = 0; j < len; j++) {
+			bytes += sprintf(&data[bytes], "%d,", ts_test->rawdata[i].data[j]);
+			if ((j + 1) % tx == 0)
+				bytes += sprintf(&data[bytes], "\n");
+		}
+		bytes += sprintf(&data[bytes], "</DataContent>\n");
+		goodix_data_cal(ts_test->accord_arr[i].data, len, stat_result);
+		bytes += sprintf(&data[bytes],
+			"<RawAccord No.=\"%d\" DataCount=\"%d\" Maximum=\"%d\" Minimum=\"%d\" Average=\"%d\">\n",
+			i, len, stat_result[1], stat_result[2], stat_result[0]);
+		for (j = 0; j < len; j++) {
+			bytes += sprintf(&data[bytes], "%d,", ts_test->accord_arr[i].data[j]);
+			if ((j + 1) % tx == 0)
+				bytes += sprintf(&data[bytes], "\n");
+		}
+		bytes += sprintf(&data[bytes], "</RawAccord>\n");
+		ret = fs_write(data, bytes, fp);
+		if (ret < 0) {
+			ts_err("rawdata write fail.");
+			goto save_end;
+		}
+		bytes = 0;
+	}
+
+	bytes += sprintf(&data[bytes], "</RawDataRecord>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0)
+		ts_err("rawdata write fail.");
+
+save_end:
+	kfree(data);
+	return ret;
+}
+
+static int goodix_save_noise_data(struct goodix_ts_test *ts_test, struct file *fp)
+{
+	int i;
+	int j;
+	int ret = 0;
+	int bytes = 0;
+	s16 stat_result[3];
+	char *data = NULL;
+	int tx = ts_test->test_params.drv_num;
+	int rx = ts_test->test_params.sen_num;
+	int len = tx * rx;
+
+	data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL);
+	if (!data) {
+		ts_err("alloc memory failed for ");
+		return -ENOMEM;
+	}
+
+	bytes += sprintf(&data[bytes], "<DiffDataRecord>\n");
+	for (i = 0; i < NOISEDATA_TEST_TIMES; i++) {
+		goodix_data_cal(ts_test->noisedata[i].data, len, stat_result);
+		bytes += sprintf(&data[bytes],
+			"<DataContent No.=\"%d\" DataCount=\"%d\" Maximum=\"%d\" Minimum=\"%d\" Average=\"%d\">\n",
+			i, len, stat_result[1], stat_result[2], stat_result[0]);
+		for (j = 0; j < len; j++) {
+			bytes += sprintf(&data[bytes], "%d,", ts_test->noisedata[i].data[j]);
+			if ((j + 1) % tx == 0)
+				bytes += sprintf(&data[bytes], "\n");
+		}
+		bytes += sprintf(&data[bytes], "</DataContent>\n");
+		ret = fs_write(data, bytes, fp);
+		if (ret < 0) {
+			ts_err("noisedata write fail.");
+			goto save_end;
+		}
+		bytes = 0;
+	}
+
+	bytes += sprintf(&data[bytes], "</DiffDataRecord>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0)
+		ts_err("noisedata write fail.");
+
+save_end:
+	kfree(data);
+	return ret;	
+}
+
+static int goodix_save_self_data(struct goodix_ts_test *ts_test,
+		struct file *fp, s16 *src_data, u8 *title, int len)
+{
+	int i;
+	int ret = 0;
+	s32 bytes = 0;
+	char *data;
+	s16 stat_result[3];
+	int tx = ts_test->test_params.drv_num;
+
+	data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL);
+	if (!data) {
+		ts_err("alloc memory failed for ");
+		return -ENOMEM;
+	}
+
+	bytes += sprintf(&data[bytes], "<%s>\n",title);
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0) {
+		ts_err("rawdata write fail.");
+		goto save_end;
+	}
+	bytes = 0;
+
+	goodix_data_cal(src_data, len, stat_result);
+	bytes += sprintf(&data[bytes],
+		"<DataContent No.=\"0\" DataCount=\"%d\" Maximum=\"%d\" Minimum=\"%d\" Average=\"%d\">\n",
+		len, stat_result[1], stat_result[2], stat_result[0]);
+	for (i = 0; i < len; i++) {
+		bytes += sprintf(&data[bytes], "%d,", src_data[i]);
+		if ((i + 1) % tx == 0)
+			bytes += sprintf(&data[bytes], "\n");
+	}
+	if (len % tx != 0)
+		bytes += sprintf(&data[bytes], "\n");
+	bytes += sprintf(&data[bytes], "</DataContent>\n");
+	bytes += sprintf(&data[bytes], "</%s>\n",title);
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0)
+		ts_err("rawdata write fail.");
+
+save_end:
+	kfree(data);
+	return ret;
+}
+
+static int goodix_save_data(struct goodix_ts_test *ts_test,
+		struct file *fp)
+{
+	int ret;
+	int bytes = 0;
+	char *data = NULL;
+
+	data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL);
+	if (!data) {
+		ts_err("alloc memory failed for ");
+		return -ENOMEM;
+	}
+
+	bytes += sprintf(&data[bytes], "<DataRecord>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0) {
+		ts_err("rawdata record lable failed");
+		goto save_end;
+	}
+	bytes = 0;
+
+	ret = goodix_save_rawdata(ts_test, fp);
+	if (ret < 0)
+		goto save_end;
+
+	if (ts_test->test_result[GTP_NOISE_TEST]) {
+		ret = goodix_save_noise_data(ts_test, fp);
+		if (ret < 0)
+			goto save_end;
+	}
+
+	if (ts_test->test_result[GTP_SELFCAP_TEST]) {
+		ret = goodix_save_self_data(ts_test, fp, ts_test->self_rawdata.data,
+				"selfDataRecord", ts_test->self_rawdata.size);
+		if (ret < 0)
+			goto save_end;		
+	}
+
+	if (ts_test->test_result[GTP_SELFNOISE_TEST]) {
+		ret = goodix_save_self_data(ts_test, fp, ts_test->self_noisedata.data,
+				"selfDiffDataRecord", ts_test->self_noisedata.size);
+		if (ret < 0)
+			goto save_end;
+	}
+
+	bytes += sprintf(&data[bytes], "</DataRecord>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0)
+		ts_err("rawdata data record lable fail.");
+
+save_end:
+	kfree(data);
+	return ret;
+}
+
+/* save end tag in csv file */
+static int goodix_save_tail(struct goodix_ts_test *ts_test,
+		struct file *fp)
+{
+	int ret = 0;
+	int bytes = 0;
+	char *data = NULL;
+
+	data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL);
+	if (!data) {
+		ts_err("alloc memory failed for ");
+		return -ENOMEM;
+	}
+
+	bytes += sprintf(&data[bytes], "</TESTLOG>\n");
+	ret = fs_write(data, bytes, fp);
+	if (ret < 0)
+		ts_err("tail write failed");
+
+	kfree(data);
+	return ret;
+}
+
+static void goodix_save_result_data(struct goodix_ts_test *ts_test)
+{
+	int ret = 0;
+	char save_path[100];
+	struct file *fp = NULL;
+
+	/* format result file */
+	sprintf(save_path, GOODIX_RESULT_SAVE_PATH);
+	ts_info("save result IN, file_name:%s", save_path);
+
+	fp = filp_open(save_path, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+	if (IS_ERR(fp)) {
+		ts_err("create file:%s failed, fp:%ld", save_path, PTR_ERR(fp));
+		return;
+	}
+
+	/* save header */
+	ret = goodix_save_header(ts_test, fp);
+	if (ret < 0)
+		goto save_end;
+
+	/* save limits */
+	ret = goodix_save_limits(ts_test, fp);
+	if (ret < 0)
+		goto save_end;
+
+	/* save data */
+	ret = goodix_save_data(ts_test, fp);
+	if (ret < 0)
+		goto save_end;
+
+	/* save tail */
+	ret = goodix_save_tail(ts_test, fp);
+	if (ret < 0)
+		goto save_end;
+
+	ts_info("the test result save in %s", save_path);
+save_end:
+	filp_close(fp, NULL);
+}
+#endif // SAVE_IN_CSV
+
+static void goodix_put_test_result(struct goodix_ts_test *ts_test,
+		struct ts_rawdata_info *info)
+{
+	int i;
+	bool have_bus_error = false;
+	bool have_panel_error = false;
+	char statistics_data[STATISTICS_DATA_LEN] = {0};
+	struct goodix_ts_core *ts = ts_test->ts;
+
+	ts_info("put test result IN");
+
+	info->buff[0] = ts_test->test_params.sen_num;
+	info->buff[1] = ts_test->test_params.drv_num;
+	info->used_size = 2;
+	/* save rawdata to info->buff, only one frame */
+	if (ts_test->rawdata[0].size) {
+		for (i = 0; i < ts_test->rawdata[0].size; i++)
+			info->buff[info->used_size + i] = ts_test->rawdata[0].data[i];
+		info->used_size += ts_test->rawdata[0].size;
+	}
+
+	/* save noisedata to info->buff */
+	if (ts_test->noisedata[0].size) {
+		for (i = 0; i < ts_test->noisedata[0].size; i++)
+			info->buff[info->used_size + i] = ts_test->noisedata[0].data[i];
+		info->used_size += ts_test->noisedata[0].size;
+	}
+
+	/* save self_noisedata to info->buff */
+	if (ts_test->self_noisedata.size) {
+		for (i = 0; i < ts_test->self_noisedata.size; i++)
+			info->buff[info->used_size + i] = ts_test->self_noisedata.data[i];
+		info->used_size += ts_test->self_noisedata.size;
+	}
+
+	/* save self_rawdata to info->buff */
+	if (ts_test->self_rawdata.size) {
+		for (i = 0; i < ts_test->self_rawdata.size; i++)
+			info->buff[info->used_size + i] = ts_test->self_rawdata.data[i];
+		info->used_size += ts_test->self_rawdata.size;
+	}
+
+	/* check if there have bus error */
+	for (i = 0; i < MAX_TEST_ITEMS; i++) {
+		if (ts_test->test_result[i] == SYS_SOFTWARE_REASON)
+			have_bus_error = true;
+		else if (ts_test->test_result[i] == GTP_PANEL_REASON)
+			have_panel_error = true;
+	}
+	ts_info("Have bus error:%d", have_bus_error);
+	if (have_bus_error || have_panel_error)
+		goodix_strncat(ts_test->test_info, "[FAIL]-", TS_RAWDATA_RESULT_MAX);
+	else
+		goodix_strncat(ts_test->test_info, "[PASS]-", TS_RAWDATA_RESULT_MAX);
+
+	if (have_bus_error)
+		goodix_strncat(ts_test->test_info, "0F-", TS_RAWDATA_RESULT_MAX);
+	else
+		goodix_strncat(ts_test->test_info, "0P-", TS_RAWDATA_RESULT_MAX);
+
+	for (i = 0; i < MAX_TEST_ITEMS; i++) {
+		/* if have tested, show result */
+		if (ts_test->test_result[i]) {
+			if (GTP_TEST_PASS == ts_test->test_result[i])
+				goodix_strncatint(ts_test->test_info, i, "%dP-",
+					TS_RAWDATA_RESULT_MAX);
+			else
+				goodix_strncatint(ts_test->test_info, i, "%dF-",
+					TS_RAWDATA_RESULT_MAX);
+		}
+	}
+
+	/* calculate rawdata min avg max value*/
+	if (ts_test->rawdata[0].size) {
+		goodix_data_statistics(
+				ts_test->rawdata[0].data,
+				ts_test->rawdata[0].size,
+				statistics_data,
+				STATISTICS_DATA_LEN);
+		goodix_strncat(ts_test->test_info, statistics_data,
+			TS_RAWDATA_RESULT_MAX);
+	} else {
+		ts_err("NO valiable rawdata");
+		goodix_strncat(ts_test->test_info, "[0,0,0]",
+			TS_RAWDATA_RESULT_MAX);
+	}
+
+	/* calculate noisedata min avg max value*/
+	if (ts_test->test_params.test_items[GTP_NOISE_TEST]) {
+		if (ts_test->noisedata[0].size) {
+			goodix_data_statistics(
+					ts_test->noisedata[0].data,
+					ts_test->noisedata[0].size,
+					statistics_data,
+					STATISTICS_DATA_LEN);
+			goodix_strncat(ts_test->test_info, statistics_data,
+				TS_RAWDATA_RESULT_MAX);
+		} else {
+			ts_err("NO valiable noisedata");
+			goodix_strncat(ts_test->test_info, "[0,0,0]",
+				TS_RAWDATA_RESULT_MAX);
+		}
+	}
+
+	/* calculate self_rawdata min avg max value*/
+	if (ts_test->test_params.test_items[GTP_SELFCAP_TEST]) {
+		if (ts_test->self_rawdata.size) {
+			goodix_data_statistics(
+					ts_test->self_rawdata.data,
+					ts_test->self_rawdata.size,
+					statistics_data,
+					STATISTICS_DATA_LEN);
+			goodix_strncat(ts_test->test_info, statistics_data,
+				TS_RAWDATA_RESULT_MAX);
+		} else {
+			ts_err("NO valiable self_rawdata");
+			goodix_strncat(ts_test->test_info, "[0,0,0]",
+				TS_RAWDATA_RESULT_MAX);
+		}
+	}
+
+	/* calculate self_noisedata min avg max value*/
+	if (ts_test->test_params.test_items[GTP_SELFNOISE_TEST]) {
+		if (ts_test->self_noisedata.size) {
+			goodix_data_statistics(
+					ts_test->self_noisedata.data,
+					ts_test->self_noisedata.size,
+					statistics_data,
+					STATISTICS_DATA_LEN);
+			goodix_strncat(ts_test->test_info, statistics_data,
+				TS_RAWDATA_RESULT_MAX);
+		} else {
+			ts_err("NO valiable self_noisedata");
+			goodix_strncat(ts_test->test_info, "[0,0,0]",
+				TS_RAWDATA_RESULT_MAX);
+		}
+	}
+
+	goodix_strncat(ts_test->test_info, "-GT",
+		TS_RAWDATA_RESULT_MAX);
+	goodix_strncat(ts_test->test_info, ts->fw_version.patch_pid,
+		TS_RAWDATA_RESULT_MAX);
+	goodix_strncat(ts_test->test_info, "\n",
+		TS_RAWDATA_RESULT_MAX);
+	strncpy(info->result, ts_test->test_info, TS_RAWDATA_RESULT_MAX - 1);
+
+#ifdef SAVE_IN_CSV
+	/* save result to file */
+	goodix_save_result_data(ts_test);
+#endif	
+}
+
+static int goodix_do_inspect(struct goodix_ts_core *cd, struct ts_rawdata_info *info)
+{
+    int ret;
+    struct goodix_ts_test *ts_test = NULL;
+
+    if (!cd || !info) {
+        ts_err("core_data or info is NULL");
+        return -ENODEV;
+    }
+
+	ts_test = kzalloc(sizeof(*ts_test), GFP_KERNEL);
+	if (!ts_test) {
+		ts_err("Failed to alloc mem");
+		return -ENOMEM;
+	}
+
+    ts_test->ts = cd;
+    ret = goodix_tptest_prepare(ts_test);
+    if (ret < 0) {
+        ts_err("Failed to prepare TP test, exit");
+        strncpy(info->result, "[FAIL]-0F-software reason\n",
+			TS_RAWDATA_RESULT_MAX - 1);
+        goto exit_finish;
+    }
+    ts_info("TP test prepare OK");
+
+    goodix_capacitance_test(ts_test); /* 1F 3F 6F 7F test */
+	if (ts_test->test_params.test_items[GTP_SHORT_TEST])
+    	goodix_shortcircut_test(ts_test); /* 5F test */
+    goodix_put_test_result(ts_test, info);
+    goodix_tptest_finish(ts_test);
+
+exit_finish:
+	kfree(ts_test);
+    return ret;
+}
+
+/* show rawdata */
+static ssize_t goodix_ts_get_rawdata_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int ret = 0;
+	struct ts_rawdata_info *info = NULL;
+	struct goodix_ts_core *cd = dev_get_drvdata(dev);
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		ts_err("Failed to alloc rawdata info memory");
+		return -ENOMEM;
+	}
+
+	goodix_do_inspect(cd, info);
+
+	ret = snprintf(buf, PAGE_SIZE, "resultInfo: %s", info->result);
+
+	kfree(info);
+	return ret;
+}
+
+static DEVICE_ATTR(get_rawdata, S_IRUGO, goodix_ts_get_rawdata_show, NULL);
+
+int inspect_module_init(void)
+{
+	int ret;
+	struct kobject *def_kobj = goodix_get_default_kobj();
+
+	/* create sysfs */
+	ret = sysfs_create_file(def_kobj, &dev_attr_get_rawdata.attr);
+	if (ret < 0) {
+		ts_err("create sysfs of get_rawdata failed");
+		goto err_out;
+	}
+
+	module_initialized = true;
+	ts_info("inspect module init success");
+	return 0;
+
+err_out:
+	ts_err("inspect module init failed!");
+	return ret;	
+}
+
+void inspect_module_exit(void)
+{
+	struct kobject *def_kobj = goodix_get_default_kobj();
+
+	ts_info("inspect module exit");
+	if (!module_initialized)
+		return;
+
+	sysfs_remove_file(def_kobj, &dev_attr_get_rawdata.attr);
+	module_initialized = false;
+}

+ 503 - 0
goodix_ts_tools.c

@@ -0,0 +1,503 @@
+ /*
+  * Goodix Touchscreen Driver
+  * Copyright (C) 2020 - 2021 Goodix, Inc.
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be a reference
+  * to you, when you are integrating the GOODiX's CTP IC into your system,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/atomic.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/ioctl.h>
+#include <linux/wait.h>
+#include "goodix_ts_core.h"
+
+#define GOODIX_TOOLS_NAME		"gtp_tools"
+#define GOODIX_TOOLS_VER_MAJOR		1
+#define GOODIX_TOOLS_VER_MINOR		0
+static const u16 goodix_tools_ver = ((GOODIX_TOOLS_VER_MAJOR << 8) +
+			(GOODIX_TOOLS_VER_MINOR));
+
+#define GOODIX_TS_IOC_MAGIC		'G'
+#define NEGLECT_SIZE_MASK		(~(_IOC_SIZEMASK << _IOC_SIZESHIFT))
+
+#define GTP_IRQ_ENABLE	_IO(GOODIX_TS_IOC_MAGIC, 0)
+#define GTP_DEV_RESET	_IO(GOODIX_TS_IOC_MAGIC, 1)
+#define GTP_SEND_COMMAND (_IOW(GOODIX_TS_IOC_MAGIC, 2, u8) & NEGLECT_SIZE_MASK)
+#define GTP_SEND_CONFIG	(_IOW(GOODIX_TS_IOC_MAGIC, 3, u8) & NEGLECT_SIZE_MASK)
+#define GTP_ASYNC_READ	(_IOR(GOODIX_TS_IOC_MAGIC, 4, u8) & NEGLECT_SIZE_MASK)
+#define GTP_SYNC_READ	(_IOR(GOODIX_TS_IOC_MAGIC, 5, u8) & NEGLECT_SIZE_MASK)
+#define GTP_ASYNC_WRITE	(_IOW(GOODIX_TS_IOC_MAGIC, 6, u8) & NEGLECT_SIZE_MASK)
+#define GTP_READ_CONFIG	(_IOW(GOODIX_TS_IOC_MAGIC, 7, u8) & NEGLECT_SIZE_MASK)
+#define GTP_ESD_ENABLE	_IO(GOODIX_TS_IOC_MAGIC, 8)
+#define GTP_TOOLS_VER   (_IOR(GOODIX_TS_IOC_MAGIC, 9, u8) & NEGLECT_SIZE_MASK)
+#define GTP_TOOLS_CTRL_SYNC (_IOW(GOODIX_TS_IOC_MAGIC, 10, u8) & NEGLECT_SIZE_MASK)
+
+#define MAX_BUF_LENGTH		(16*1024)
+#define IRQ_FALG		(0x01 << 2)
+
+#define I2C_MSG_HEAD_LEN	20
+
+/*
+ * struct goodix_tools_dev - goodix tools device struct
+ * @ts_core: The core data struct of ts driver
+ * @ops_mode: represent device work mode
+ * @rawdiffcmd: Set slave device into rawdata mode
+ * @normalcmd: Set slave device into normal mode
+ * @wq: Wait queue struct use in synchronous data read
+ * @mutex: Protect goodix_tools_dev
+ * @in_use: device in use
+ */
+struct goodix_tools_dev {
+	struct goodix_ts_core *ts_core;
+	struct list_head head;
+	unsigned int ops_mode;
+	struct goodix_ts_cmd rawdiffcmd, normalcmd;
+	wait_queue_head_t wq;
+	struct mutex mutex;
+	atomic_t in_use;
+	struct goodix_ext_module module;
+} *goodix_tools_dev;
+
+
+/* read data asynchronous,
+ * success return data length, otherwise return < 0
+ */
+static int async_read(struct goodix_tools_dev *dev, void __user *arg)
+{
+	u8 *databuf = NULL;
+	int ret = 0;
+	u32 reg_addr, length;
+	u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
+	const struct goodix_ts_hw_ops *hw_ops = dev->ts_core->hw_ops;
+
+	ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
+	if (ret)
+		return -EFAULT;
+
+	reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8)
+			+ (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24);
+	length = i2c_msg_head[4] + (i2c_msg_head[5] << 8)
+			+ (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24);
+	if (length > MAX_BUF_LENGTH) {
+		ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH);
+		return -EINVAL;
+	}
+	databuf = kzalloc(length, GFP_KERNEL);
+	if (!databuf) {
+		ts_err("Alloc memory failed");
+		return -ENOMEM;
+	}
+
+	if (hw_ops->read(dev->ts_core, reg_addr, databuf, length)) {
+		ret = -EBUSY;
+		ts_err("Read i2c failed");
+		goto err_out;
+	}
+	ret = copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, databuf, length);
+	if (ret) {
+		ret = -EFAULT;
+		ts_err("Copy_to_user failed");
+		goto err_out;
+	}
+	ret = length;
+err_out:
+	kfree(databuf);
+	return ret;
+}
+
+/* if success return config data length */
+static int read_config_data(struct goodix_ts_core *ts_core, void __user *arg)
+{
+	int ret = 0;
+	u32 reg_addr, length;
+	u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
+	u8 *tmp_buf;
+
+	ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
+	if (ret) {
+		ts_err("Copy data from user failed");
+		return -EFAULT;
+	}
+	reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8)
+		   + (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24);
+	length = i2c_msg_head[4] + (i2c_msg_head[5] << 8)
+		 + (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24);
+	ts_info("read config,reg_addr=0x%x, length=%d", reg_addr, length);
+	if (length > MAX_BUF_LENGTH) {
+		ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH);
+		return -EINVAL;
+	}
+	tmp_buf = kzalloc(length, GFP_KERNEL);
+	if (!tmp_buf) {
+		ts_err("failed alloc memory");
+		return -ENOMEM;
+	}
+	/* if reg_addr == 0, read config data with specific flow */
+	if (!reg_addr) {
+		if (ts_core->hw_ops->read_config)
+			ret = ts_core->hw_ops->read_config(ts_core, tmp_buf, length);
+		else
+			ret = -EINVAL;
+	} else {
+		ret = ts_core->hw_ops->read(ts_core, reg_addr, tmp_buf, length);
+		if (!ret)
+			ret = length;
+	}
+	if (ret <= 0)
+		goto err_out;
+
+	if (copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, tmp_buf, ret)) {
+		ret = -EFAULT;
+		ts_err("Copy_to_user failed");
+	}
+
+err_out:
+	kfree(tmp_buf);
+	return ret;
+}
+
+/* write data to i2c asynchronous,
+ * success return bytes write, else return <= 0
+ */
+static int async_write(struct goodix_tools_dev *dev, void __user *arg)
+{
+	u8 *databuf;
+	int ret = 0;
+	u32 reg_addr, length;
+	u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
+	struct goodix_ts_core *ts_core = dev->ts_core;
+	const struct goodix_ts_hw_ops *hw_ops = ts_core->hw_ops;
+
+	ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
+	if (ret) {
+		ts_err("Copy data from user failed");
+		return -EFAULT;
+	}
+	reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8)
+			+ (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24);
+	length = i2c_msg_head[4] + (i2c_msg_head[5] << 8)
+			+ (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24);
+	if (length > MAX_BUF_LENGTH) {
+		ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH);
+		return -EINVAL;
+	}
+
+	databuf = kzalloc(length, GFP_KERNEL);
+	if (!databuf) {
+		ts_err("Alloc memory failed");
+		return -ENOMEM;
+	}
+	ret = copy_from_user(databuf, (u8 *)arg + I2C_MSG_HEAD_LEN, length);
+	if (ret) {
+		ret = -EFAULT;
+		ts_err("Copy data from user failed");
+		goto err_out;
+	}
+
+	if (hw_ops->write(ts_core, reg_addr, databuf, length)) {
+		ret = -EBUSY;
+		ts_err("Write data to device failed");
+	} else {
+		ret = length;
+	}
+
+err_out:
+	kfree(databuf);
+	return ret;
+}
+
+static int init_cfg_data(struct goodix_ic_config *cfg, void __user *arg)
+{
+	int ret = 0;
+	u32 length;
+	u8 i2c_msg_head[I2C_MSG_HEAD_LEN] = {0};
+
+	ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
+	if (ret) {
+		ts_err("Copy data from user failed");
+		return -EFAULT;
+	}
+
+	length = i2c_msg_head[4] + (i2c_msg_head[5] << 8)
+			+ (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24);
+	if (length > GOODIX_CFG_MAX_SIZE) {
+		ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH);
+		return -EINVAL;
+	}
+	ret = copy_from_user(cfg->data, (u8 *)arg + I2C_MSG_HEAD_LEN, length);
+	if (ret) {
+		ts_err("Copy data from user failed");
+		return -EFAULT;
+	}
+	cfg->len = length;
+	return 0;
+}
+
+/**
+ * goodix_tools_ioctl - ioctl implementation
+ *
+ * @filp: Pointer to file opened
+ * @cmd: Ioctl opertion command
+ * @arg: Command data
+ * Returns >=0 - succeed, else failed
+ */
+static long goodix_tools_ioctl(struct file *filp, unsigned int cmd,
+					unsigned long arg)
+{
+	int ret = 0;
+	struct goodix_tools_dev *dev = filp->private_data;
+	struct goodix_ts_core *ts_core;
+	const struct goodix_ts_hw_ops *hw_ops;
+	struct goodix_ic_config *temp_cfg = NULL;
+
+	if (dev->ts_core == NULL) {
+		ts_err("Tools module not register");
+		return -EINVAL;
+	}
+	ts_core = dev->ts_core;
+	hw_ops = ts_core->hw_ops;
+
+	if (_IOC_TYPE(cmd) != GOODIX_TS_IOC_MAGIC) {
+		ts_err("Bad magic num:%c", _IOC_TYPE(cmd));
+		return -ENOTTY;
+	}
+
+	switch (cmd & NEGLECT_SIZE_MASK) {
+	case GTP_IRQ_ENABLE:
+		if (arg == 1) {
+			hw_ops->irq_enable(ts_core, true);
+			mutex_lock(&dev->mutex);
+			dev->ops_mode |= IRQ_FALG;
+			mutex_unlock(&dev->mutex);
+			ts_info("IRQ enabled");
+		} else if (arg == 0) {
+			hw_ops->irq_enable(ts_core, false);
+			mutex_lock(&dev->mutex);
+			dev->ops_mode &= ~IRQ_FALG;
+			mutex_unlock(&dev->mutex);
+			ts_info("IRQ disabled");
+		} else {
+			ts_info("Irq aready set with, arg = %ld", arg);
+		}
+		ret = 0;
+		break;
+	case GTP_ESD_ENABLE:
+		if (arg == 0)
+			goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
+		else
+			goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
+		break;
+	case GTP_DEV_RESET:
+		hw_ops->reset(ts_core, GOODIX_NORMAL_RESET_DELAY_MS);
+		break;
+	case GTP_SEND_COMMAND:
+		/* deprecated command */
+		ts_err("the GTP_SEND_COMMAND function has been removed");
+		ret = -EINVAL;
+		break;
+	case GTP_SEND_CONFIG:
+		temp_cfg = kzalloc(sizeof(struct goodix_ic_config), GFP_KERNEL);
+		if (temp_cfg == NULL) {
+			ts_err("Memory allco err");
+			ret = -ENOMEM;
+			goto err_out;
+		}
+
+		ret = init_cfg_data(temp_cfg, (void __user *)arg);
+		if (!ret && hw_ops->send_config) {
+			ret = hw_ops->send_config(ts_core, temp_cfg->data, temp_cfg->len);
+			if (ret) {
+				ts_err("Failed send config");
+				ret = -EAGAIN;
+			} else {
+				ts_info("Send config success");
+				ret = 0;
+			}
+		}
+		kfree(temp_cfg);
+		temp_cfg = NULL;
+		break;
+	case GTP_READ_CONFIG:
+		ret = read_config_data(ts_core, (void __user *)arg);
+		if (ret > 0)
+			ts_info("success read config:len=%d", ret);
+		else
+			ts_err("failed read config:ret=0x%x", ret);
+		break;
+	case GTP_ASYNC_READ:
+		ret = async_read(dev, (void __user *)arg);
+		if (ret < 0)
+			ts_err("Async data read failed");
+		break;
+	case GTP_SYNC_READ:
+		ts_info("unsupport sync read");
+		break;
+	case GTP_ASYNC_WRITE:
+		ret = async_write(dev, (void __user *)arg);
+		if (ret < 0)
+			ts_err("Async data write failed");
+		break;
+	case GTP_TOOLS_VER:
+		ret = copy_to_user((u8 *)arg, &goodix_tools_ver,
+					sizeof(u16));
+		if (ret)
+			ts_err("failed copy driver version info to user");
+		break;
+	case GTP_TOOLS_CTRL_SYNC:
+		ts_core->tools_ctrl_sync = !!arg;
+		ts_info("set tools ctrl sync %d", ts_core->tools_ctrl_sync);
+		break;
+	default:
+		ts_info("Invalid cmd");
+		ret = -ENOTTY;
+		break;
+	}
+
+err_out:
+	return ret;
+}
+
+#ifdef CONFIG_COMPAT
+static long goodix_tools_compat_ioctl(struct file *file, unsigned int cmd,
+				unsigned long arg)
+{
+	void __user *arg32 = compat_ptr(arg);
+
+	if (!file->f_op || !file->f_op->unlocked_ioctl)
+		return -ENOTTY;
+	return file->f_op->unlocked_ioctl(file, cmd, (unsigned long)arg32);
+}
+#endif
+
+static int goodix_tools_open(struct inode *inode, struct file *filp)
+{
+	int ret = 0;
+
+	ts_info("try open tool");
+	/* Only the first time open device need to register module */
+	ret = goodix_register_ext_module_no_wait(&goodix_tools_dev->module);
+	if (ret) {
+		ts_info("failed register to core module");
+		return -EFAULT;
+	}
+	ts_info("success open tools");
+	goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
+	filp->private_data = goodix_tools_dev;
+	atomic_set(&goodix_tools_dev->in_use, 1);
+	return 0;
+}
+
+static int goodix_tools_release(struct inode *inode, struct file *filp)
+{
+	int ret = 0;
+	/* when the last close this dev node unregister the module */
+	goodix_tools_dev->ts_core->tools_ctrl_sync = false;
+	atomic_set(&goodix_tools_dev->in_use, 0);
+	goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
+	ret = goodix_unregister_ext_module(&goodix_tools_dev->module);
+	return ret;
+}
+
+static int goodix_tools_module_init(struct goodix_ts_core *core_data,
+			struct goodix_ext_module *module)
+{
+	struct goodix_tools_dev *tools_dev = module->priv_data;
+
+	if (core_data)
+		tools_dev->ts_core = core_data;
+	else
+		return -ENODEV;
+
+	return 0;
+}
+
+static int goodix_tools_module_exit(struct goodix_ts_core *core_data,
+		struct goodix_ext_module *module)
+{
+	struct goodix_tools_dev *tools_dev = module->priv_data;
+	ts_debug("tools module unregister");
+	if (atomic_read(&tools_dev->in_use)) {
+		ts_err("tools module busy, please close it then retry");
+		return -EBUSY;
+	}
+	return 0;
+}
+
+static const struct file_operations goodix_tools_fops = {
+	.owner		= THIS_MODULE,
+	.open		= goodix_tools_open,
+	.release	= goodix_tools_release,
+	.unlocked_ioctl	= goodix_tools_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = goodix_tools_compat_ioctl,
+#endif
+};
+
+static struct miscdevice goodix_tools_miscdev = {
+	.minor	= MISC_DYNAMIC_MINOR,
+	.name	= GOODIX_TOOLS_NAME,
+	.fops	= &goodix_tools_fops,
+};
+
+static struct goodix_ext_module_funcs goodix_tools_module_funcs = {
+	.init = goodix_tools_module_init,
+	.exit = goodix_tools_module_exit,
+};
+
+/**
+ * goodix_tools_init - init goodix tools device and register a miscdevice
+ *
+ * return: 0 success, else failed
+ */
+int goodix_tools_init(void)
+{
+	int ret;
+
+	goodix_tools_dev = kzalloc(sizeof(struct goodix_tools_dev), GFP_KERNEL);
+	if (goodix_tools_dev == NULL) {
+		ts_err("Memory allco err");
+		return -ENOMEM;
+	}
+
+	INIT_LIST_HEAD(&goodix_tools_dev->head);
+	goodix_tools_dev->ops_mode = 0;
+	goodix_tools_dev->ops_mode |= IRQ_FALG;
+	init_waitqueue_head(&goodix_tools_dev->wq);
+	mutex_init(&goodix_tools_dev->mutex);
+	atomic_set(&goodix_tools_dev->in_use, 0);
+
+	goodix_tools_dev->module.funcs = &goodix_tools_module_funcs;
+	goodix_tools_dev->module.name = GOODIX_TOOLS_NAME;
+	goodix_tools_dev->module.priv_data = goodix_tools_dev;
+	goodix_tools_dev->module.priority = EXTMOD_PRIO_DBGTOOL;
+
+	ret = misc_register(&goodix_tools_miscdev);
+	if (ret)
+		ts_err("Debug tools miscdev register failed");
+	else
+		ts_info("Debug tools miscdev register success");
+
+	return ret;
+}
+
+void goodix_tools_exit(void)
+{
+	misc_deregister(&goodix_tools_miscdev);
+	kfree(goodix_tools_dev);
+	ts_info("Debug tools miscdev exit");
+}

+ 179 - 0
goodix_ts_utils.c

@@ -0,0 +1,179 @@
+ /*
+  * Goodix Touchscreen Driver
+  * Copyright (C) 2020 - 2021 Goodix, Inc.
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be a reference
+  * to you, when you are integrating the GOODiX's CTP IC into your system,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * General Public License for more details.
+  *
+  */
+#include "goodix_ts_core.h"
+
+bool debug_log_flag = false;
+
+/*****************************************************************************
+* goodix_append_checksum
+* @summary
+*    Calcualte data checksum with the specified mode.
+*
+* @param data
+*   data need to be calculate
+* @param len
+*   data length
+* @param mode
+*   calculate for u8 or u16 checksum
+* @return
+*   return the data checksum value.
+*
+*****************************************************************************/
+u32 goodix_append_checksum(u8 *data, int len, int mode)
+{
+	u32 checksum = 0;
+	int i;
+
+	checksum = 0;
+	if (mode == CHECKSUM_MODE_U8_LE) {
+		for (i = 0; i < len; i++)
+			checksum += data[i];
+	} else {
+		for (i = 0; i < len; i+=2)
+			checksum += (data[i] + (data[i+1] << 8));
+	}
+
+	if (mode == CHECKSUM_MODE_U8_LE) {
+		data[len] = checksum & 0xff;
+		data[len + 1] = (checksum >> 8) & 0xff;
+		return 0xFFFF & checksum;
+	}
+	data[len] = checksum & 0xff;
+	data[len + 1] = (checksum >> 8) & 0xff;
+	data[len + 2] = (checksum >> 16) & 0xff;
+	data[len + 3] = (checksum >> 24) & 0xff;
+	return checksum;
+}
+
+/* checksum_cmp: check data valid or not
+ * @data: data need to be check
+ * @size: data length need to be check(include the checksum bytes)
+ * @mode: compare with U8 or U16 mode
+ * */
+int checksum_cmp(const u8 *data, int size, int mode)
+{
+	u32 cal_checksum = 0;
+	u32 r_checksum = 0;
+	u32 i;
+
+	if (mode == CHECKSUM_MODE_U8_LE) {
+		if (size < 2)
+			return 1;
+		for (i = 0; i < size - 2; i++)
+			cal_checksum += data[i];
+
+		r_checksum = data[size - 2] + (data[size - 1] << 8);
+		return (cal_checksum & 0xFFFF) == r_checksum ? 0 : 1;
+	}
+
+	if (size < 4)
+		return 1;
+	for (i = 0; i < size - 4; i += 2)
+		cal_checksum += data[i] + (data[i + 1] << 8);
+	r_checksum = data[size - 4] + (data[size - 3] << 8) +
+		(data[size - 2] << 16) + (data[size - 1] << 24);
+	return cal_checksum == r_checksum ? 0 : 1;
+}
+
+/* return 1 if all data is zero or ff
+ * else return 0
+ */
+int is_risk_data(const u8 *data, int size)
+{
+	int i;
+	int zero_count =  0;
+	int ff_count = 0;
+
+	for (i = 0; i < size; i++) {
+		if (data[i] == 0)
+			zero_count++;
+		else if (data[i] == 0xff)
+			ff_count++;
+	}
+	if (zero_count == size || ff_count == size) {
+		ts_info("warning data is all %s\n",
+			zero_count == size ? "zero" : "0xff");
+		return 1;
+	}
+
+	return 0;
+}
+
+/* get config id form config file */
+#define CONFIG_ID_OFFSET 		30
+u32 goodix_get_file_config_id(u8 *ic_config)
+{
+	if (!ic_config)
+		return 0;
+	return le32_to_cpup((__le32 *)&ic_config[CONFIG_ID_OFFSET]);
+}
+
+/* matrix transpose */
+void goodix_rotate_abcd2cbad(int tx, int rx, s16 *data)
+{
+	s16 *temp_buf = NULL;
+	int size = tx * rx;
+	int i;
+	int j;
+	int col;
+
+	temp_buf = kcalloc(size, sizeof(s16), GFP_KERNEL);
+	if (!temp_buf) {
+		ts_err("malloc failed");
+		return;
+	}
+
+	for (i = 0, j = 0, col = 0; i < size; i++) {
+		temp_buf[i] = data[j++ * rx + col];
+		if (j == tx) {
+			j = 0;
+			col++;
+		}
+	}
+
+	memcpy(data, temp_buf, size * sizeof(s16));
+	kfree(temp_buf);
+}
+
+/* get ic type */
+int goodix_get_ic_type(struct device_node *node)
+{
+	const char *name_tmp;
+	int ret;
+
+	ret = of_property_read_string(node, "compatible", &name_tmp);
+	if (ret < 0) {
+		ts_err("get compatible failed");
+		return ret;
+	}
+
+	if (strstr(name_tmp, "9897")) {
+		ts_info("ic type is BerlinA");
+		ret = IC_TYPE_BERLIN_A;
+	} else if (strstr(name_tmp, "9966") || strstr(name_tmp, "7986")) {
+		ts_info("ic type is BerlinB");
+		ret = IC_TYPE_BERLIN_B;
+	} else if (strstr(name_tmp, "9916")) {
+		ts_info("ic type is BerlinD");
+		ret = IC_TYPE_BERLIN_D;
+	} else {
+		ts_info("can't find valid ic_type");
+		ret = -EINVAL;
+	}
+
+	return ret;
+}