diff options
author | Wendly Li <wendlyli@google.com> | 2021-12-29 08:25:53 +0000 |
---|---|---|
committer | Wendly Li <wendlyli@google.com> | 2022-01-21 03:51:40 +0000 |
commit | f4cbd1e784f777c544763bb0e2bdb65ad5c685cf (patch) | |
tree | b46d9dba8ae66c85da61754fbdb3b9ce279c3789 | |
parent | aa0a75aa6af4b4513db1719e841973d3a50f59c3 (diff) | |
download | goodix_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-- | Kconfig | 20 | ||||
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | goodix_brl_fwupdate.c | 1381 | ||||
-rw-r--r-- | goodix_brl_hw.c | 1416 | ||||
-rw-r--r-- | goodix_brl_i2c.c | 268 | ||||
-rw-r--r-- | goodix_brl_spi.c | 308 | ||||
-rw-r--r-- | goodix_cfg_bin.c | 342 | ||||
-rw-r--r-- | goodix_ts_core.c | 2253 | ||||
-rw-r--r-- | goodix_ts_core.h | 643 | ||||
-rw-r--r-- | goodix_ts_gesture.c | 381 | ||||
-rw-r--r-- | goodix_ts_inspect.c | 3062 | ||||
-rw-r--r-- | goodix_ts_tools.c | 505 | ||||
-rw-r--r-- | goodix_ts_utils.c | 179 |
13 files changed, 10770 insertions, 0 deletions
@@ -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 = ¶ms_bra; + else if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_B) + test_params->params_info = ¶ms_brb; + else + test_params->params_info = ¶ms_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; +} |