summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWendly Li <wendlyli@google.com>2021-12-29 08:25:53 +0000
committerWendly Li <wendlyli@google.com>2022-01-21 03:51:40 +0000
commitf4cbd1e784f777c544763bb0e2bdb65ad5c685cf (patch)
treeb46d9dba8ae66c85da61754fbdb3b9ce279c3789
parentaa0a75aa6af4b4513db1719e841973d3a50f59c3 (diff)
downloadgoodix_touch-f4cbd1e784f777c544763bb0e2bdb65ad5c685cf.tar.gz
Initial the driver from the original vender code
BYPASS_INCLUSIVE_LANGUAGE_REASON=master and slave are stardand of SPI Bug: 214018056 Bug: 214118475 Change-Id: Ib1e7bbdca701fe852f665ee2986824d71d5eebe2 Signed-off-by: Wendly Li <wendlyli@google.com>
-rw-r--r--Kconfig20
-rw-r--r--Makefile12
-rw-r--r--goodix_brl_fwupdate.c1381
-rw-r--r--goodix_brl_hw.c1416
-rw-r--r--goodix_brl_i2c.c268
-rw-r--r--goodix_brl_spi.c308
-rw-r--r--goodix_cfg_bin.c342
-rw-r--r--goodix_ts_core.c2253
-rw-r--r--goodix_ts_core.h643
-rw-r--r--goodix_ts_gesture.c381
-rw-r--r--goodix_ts_inspect.c3062
-rw-r--r--goodix_ts_tools.c505
-rw-r--r--goodix_ts_utils.c179
13 files changed, 10770 insertions, 0 deletions
diff --git a/Kconfig b/Kconfig
new file mode 100644
index 0000000..223d5ae
--- /dev/null
+++ b/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
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..903a1cc
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+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
diff --git a/goodix_brl_fwupdate.c b/goodix_brl_fwupdate.c
new file mode 100644
index 0000000..6bce047
--- /dev/null
+++ b/goodix_brl_fwupdate.c
@@ -0,0 +1,1381 @@
+/*
+ * 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_LABEL_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("enable 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 platform 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_LABEL_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;
+}
+
+/**
+ * release 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);
+}
diff --git a/goodix_brl_hw.c b/goodix_brl_hw.c
new file mode 100644
index 0000000..41bf117
--- /dev/null
+++ b/goodix_brl_hw.c
@@ -0,0 +1,1416 @@
+/*
+ * 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_enable(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 receiving 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 success, 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_enable(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_enable(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_enable(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_enable(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_enable,
+ .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;
+}
diff --git a/goodix_brl_i2c.c b/goodix_brl_i2c.c
new file mode 100644
index 0000000..be7d638
--- /dev/null
+++ b/goodix_brl_i2c.c
@@ -0,0 +1,268 @@
+/*
+ * 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/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.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_matches[] = {
+ {
+ .compatible = "goodix,gt9897",
+ },
+ {
+ .compatible = "goodix,gt9966",
+ },
+ {
+ .compatible = "goodix,gt9916",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, i2c_matches);
+#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_matches),
+ },
+ .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);
+}
diff --git a/goodix_brl_spi.c b/goodix_brl_spi.c
new file mode 100644
index 0000000..8f53776
--- /dev/null
+++ b/goodix_brl_spi.c
@@ -0,0 +1,308 @@
+/*
+ * 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/platform/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 device.
+ */
+ 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_matches[] = {
+ {
+ .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_matches,
+ },
+ .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);
+}
diff --git a/goodix_cfg_bin.c b/goodix_cfg_bin.c
new file mode 100644
index 0000000..922aee4
--- /dev/null
+++ b/goodix_cfg_bin.c
@@ -0,0 +1,342 @@
+/*
+ * 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);
+}
diff --git a/goodix_ts_core.c b/goodix_ts_core.c
new file mode 100644
index 0000000..54e18af
--- /dev/null
+++ b/goodix_ts_core.c
@@ -0,0 +1,2253 @@
+/*
+ * 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/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/version.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.initialized)
+ return;
+ goodix_modules.initialized = 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.initialized)
+ 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("Module [%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 information */
+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 information */
+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 proc_ops rawdata_proc_fops = {
+ .proc_open = rawdata_proc_open,
+ .proc_read = seq_read,
+ .proc_lseek = seq_lseek,
+ .proc_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 - Request 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 - Request 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("unsupported 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 recommend 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");
diff --git a/goodix_ts_core.h b/goodix_ts_core.h
new file mode 100644
index 0000000..a37fb50
--- /dev/null
+++ b/goodix_ts_core.h
@@ -0,0 +1,643 @@
+#ifndef _GOODIX_TS_CORE_H_
+#define _GOODIX_TS_CORE_H_
+#include <asm/unaligned.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#ifdef CONFIG_OF
+#include <linux/of_gpio.h>
+#include <linux/regulator/consumer.h>
+#endif
+#ifdef CONFIG_FB
+#include <linux/fb.h>
+#include <linux/notifier.h>
+#endif
+
+#define GOODIX_CORE_DRIVER_NAME "goodix_ts"
+#define GOODIX_PEN_DRIVER_NAME "goodix_ts,pen"
+#define GOODIX_DRIVER_VERSION "v2.0.0"
+#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_LABEL_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
+ * @initialized: whether this struct is initialized
+ * @mutex: mutex lock
+ * @wq: workqueue to do register work
+ * @core_data: core_data pointer
+ */
+struct goodix_module {
+ struct list_head head;
+ bool initialized;
+ 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_LABEL_LEN];
+ char iovdd_name[GOODIX_MAX_STR_LABEL_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_LABEL_LEN];
+ char cfg_bin_name[GOODIX_MAX_STR_LABEL_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 value, 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
diff --git a/goodix_ts_gesture.c b/goodix_ts_gesture.c
new file mode 100644
index 0000000..43899b4
--- /dev/null
+++ b/goodix_ts_gesture.c
@@ -0,0 +1,381 @@
+/*
+ * 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 "goodix_ts_core.h"
+#include <linux/atomic.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/version.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 happened 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;
+}
diff --git a/goodix_ts_inspect.c b/goodix_ts_inspect.c
new file mode 100644
index 0000000..6b6e942
--- /dev/null
+++ b/goodix_ts_inspect.c
@@ -0,0 +1,3062 @@
+/*
+ * 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 <asm/uaccess.h>
+#include <linux/fs.h>
+#include <linux/rtc.h>
+#include <linux/timer.h>
+#include <linux/version.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("unsupported 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 below 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 valid 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 label 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 label 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;
+}
diff --git a/goodix_ts_tools.c b/goodix_ts_tools.c
new file mode 100644
index 0000000..88e3917
--- /dev/null
+++ b/goodix_ts_tools.c
@@ -0,0 +1,505 @@
+/*
+ * 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/atomic.h>
+#include <linux/compat.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/wait.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");
+}
diff --git a/goodix_ts_utils.c b/goodix_ts_utils.c
new file mode 100644
index 0000000..2dc08b5
--- /dev/null
+++ b/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
+ * Calculate 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;
+}