diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Android.mk b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Android.mk new file mode 100644 index 000000000000..d07d3de94780 --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Android.mk @@ -0,0 +1,8 @@ +DLKM_DIR := motorola/kernel/modules +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := goodix_v1510_mmi.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/AndroidKernelModule.mk diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Kbuild b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Kbuild new file mode 100644 index 000000000000..631430719e22 --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Kbuild @@ -0,0 +1,8 @@ +# add -Wall to try to catch everything we can. +EXTRA_CFLAGS += -Wall +EXTRA_CFLAGS += \ + -I$(TOP)/motorola/kernel/modules/include \ + -I$(TOP)/motorola/kernel/modules/drivers/input/touchscreen/goodix_gtx8_v1510_mmi + +obj-m := goodix_v1510_mmi.o +goodix_v1510_mmi-objs = goodix_ts_i2c.o goodix_ts_core.o goodix_ts_test.o goodix_cfg_bin.o goodix_gtx8_update.o diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Kconfig b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Kconfig new file mode 100644 index 000000000000..094bc47b0f8d --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Kconfig @@ -0,0 +1,47 @@ +# +# Goodix touchscreen driver configuration +# +menuconfig TOUCHSCREEN_GOODIX_GTX8_MMI + bool "Goodix GTx8 touchscreen" + depends on I2C + default y + help + Say Y here if you have a Goodix GTx5xx touchscreen connected + to your system. + + If unsure, say N. + +if TOUCHSCREEN_GOODIX_GTX8_MMI + + +config TOUCHSCREEN_GOODIX_GTX8_MMI_UPDATE + tristate "Goodix GTx8 firmware update module" + default y + help + Say Y here to enable support for doing firmware update. + + If unsure, say N. + + To compile this driver as a module, choose M here. + +config TOUCHSCREEN_GOODIX_GTX8_MMI_GESTURE + tristate "Goodix GTx8 gesture wakeup feature" + default n + help + Say Y here to enable support for gesture wakeup feature.. + + If unsure, say N. + + To compile this driver as a module, choose M here. + +config TOUCHSCREEN_GOODIX_GTX8_MMI_TOOLS + tristate "Goodix touch tools support" + default y + help + Say Y here to enable debug tools. + + If unsure, say N. + + To compile this driver as a module, choose M here. + +endif diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Makefile b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Makefile new file mode 100644 index 000000000000..4191dea22679 --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Makefile @@ -0,0 +1,10 @@ +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) modules $(KBUILD_OPTIONS) + +modules_install: + $(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(shell pwd) modules_install + +clean: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_cfg_bin.c b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_cfg_bin.c new file mode 100644 index 000000000000..d7ee6ab6029e --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_cfg_bin.c @@ -0,0 +1,650 @@ +#include "goodix_cfg_bin.h" + +extern struct goodix_module goodix_modules; +int goodix_ts_stage2_init(struct goodix_ts_core *core_data); + +static int goodix_parse_cfg_bin(struct goodix_cfg_bin *cfg_bin); +static int goodix_get_reg_and_cfg(struct goodix_ts_device *ts_dev, + struct goodix_cfg_bin *cfg_bin); +static int goodix_read_cfg_bin(struct device *dev, + struct goodix_cfg_bin *cfg_bin); +static void goodix_cfg_pkg_leToCpu(struct goodix_cfg_package *pkg); + +int goodix_get_cfg_value(struct goodix_ts_core *core_data, + u8 *config, u8 *buf, u8 len, u8 sub_bag_num, u8 offset) +{ + u8 *sub_bag_ptr = NULL; + u8 i = 0; + u8 chksum_len = 1; + u8 head_len = 0; + + if (core_data->ts_dev->ic_type == IC_TYPE_YELLOWSTONE) + chksum_len = 2; + head_len = 3 + chksum_len; + + sub_bag_ptr = &config[head_len]; + for (i = 0; i < config[2]; i++) { + if (sub_bag_ptr[0] == sub_bag_num) + break; + sub_bag_ptr += sub_bag_ptr[1] + 2 + chksum_len; + } + + if (i >= config[2]) { + ts_err("Cann't find the specifiled bag num %d", sub_bag_num); + return -EINVAL; + } + + if (sub_bag_ptr[1]+2+chksum_len < offset + len) { + ts_err("Sub bag len less then you want to read: %d < %d", sub_bag_ptr[1]+3, offset + len); + return -EINVAL; + } + + if (len) + memcpy(buf, sub_bag_ptr + offset, len); + else + memcpy(buf, sub_bag_ptr, sub_bag_ptr[1] + 2 + chksum_len); + return (sub_bag_ptr + offset - config); +} + +/* + * parse driver and sensor num only on screen + */ +#define MAX_DRV_NUM 40 +#define MAX_SEN_NUM 36 +static int goodix_get_channel_num(struct goodix_ts_core *core_data, + u32 *sen_num, u32 *drv_num, u8 *cfg_data) +{ + int ret = 0; + u8 buf[3] = {0}; + u8 *temp_cfg_data = cfg_data; + + if (!sen_num || !drv_num || !temp_cfg_data) { + ts_err("%s: invalid param", __func__); + return -EINVAL; + } + + ret = goodix_get_cfg_value(core_data, temp_cfg_data, buf, 2, 1, 14); + if (ret > 0) { + *drv_num = buf[0] + buf[1]; + } else { + ts_err("Failed read drv_num reg"); + ret = -EINVAL; + goto err_out; + } + + ret = goodix_get_cfg_value(core_data, temp_cfg_data, buf, 1, 1, 10); + if (ret > 0) { + *sen_num = buf[0]; + } else { + ts_err("Failed read sen_num reg"); + ret = -EINVAL; + goto err_out; + } + + ts_info("drv_num:%d,sen_num:%d", *drv_num, *sen_num); + + if (*drv_num > MAX_DRV_NUM || *drv_num <= 0 + || *sen_num > MAX_SEN_NUM || *sen_num <= 0) { + ts_err("invalid sensor or driver num"); + ret = -EINVAL; + } + +err_out: + return ret < 0 ? ret : 0; +} + +static int goodix_parse_cfg_bin(struct goodix_cfg_bin *cfg_bin) +{ + u8 checksum; + int i, r; + u16 offset1, offset2; + + if (!cfg_bin->bin_data || cfg_bin->bin_data_len == 0) { + ts_err("NO cfg_bin data, cfg_bin data length:%d", + cfg_bin->bin_data_len); + r = -EINVAL; + goto exit; + } + + /* 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); + r = -EINVAL; + goto exit; + } + 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); + r = -EINVAL; + goto exit; + } + + /*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); + r = -EINVAL; + goto exit; + } + + /*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"); + r = -ENOMEM; + goto exit; + } + + /*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); + r = -EINVAL; + 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); + /*compatible little edition and big edition*/ + goodix_cfg_pkg_leToCpu(&cfg_bin->cfg_pkgs[i]); + + /*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); + for (i = 0; i < cfg_bin->head.pkg_num; i++) { + ts_debug("---------------------------------------------"); + ts_debug("------package:%d------", i + 1); + ts_debug("package len:%04x", cfg_bin->cfg_pkgs[i].cnst_info.pkg_len); + ts_debug("package ic_type:%s", cfg_bin->cfg_pkgs[i].cnst_info.ic_type); + ts_debug("package cfg_type:%01x", cfg_bin->cfg_pkgs[i].cnst_info.cfg_type); + ts_debug("package sensor_id:%01x", cfg_bin->cfg_pkgs[i].cnst_info.sensor_id); + ts_debug("package hw_pid:%s", cfg_bin->cfg_pkgs[i].cnst_info.hw_pid); + ts_debug("package hw_vid:%s", cfg_bin->cfg_pkgs[i].cnst_info.hw_vid); + ts_debug("package fw_mask_version:%s", cfg_bin->cfg_pkgs[i].cnst_info.fw_mask); + ts_debug("package fw_patch_version:%s", cfg_bin->cfg_pkgs[i].cnst_info.fw_patch); + ts_debug("package x_res_offset:%02x", cfg_bin->cfg_pkgs[i].cnst_info.x_res_offset); + ts_debug("package y_res_offset:%02x", cfg_bin->cfg_pkgs[i].cnst_info.y_res_offset); + ts_debug("package trigger_offset:%02x", cfg_bin->cfg_pkgs[i].cnst_info.trigger_offset); + + ts_debug("reg info"); + ts_debug("send_cfg_flag reg:%02x", cfg_bin->cfg_pkgs[i].reg_info.cfg_send_flag.addr); + ts_debug("version base reg:%02x, len:%d", + cfg_bin->cfg_pkgs[i].reg_info.version_base.addr, + cfg_bin->cfg_pkgs[i].reg_info.version_base.reserved1); + ts_debug("pid reg:%02x:%d", cfg_bin->cfg_pkgs[i].reg_info.pid.addr, + cfg_bin->cfg_pkgs[i].reg_info.pid.reserved1); + ts_debug("vid reg:%02x:%d", cfg_bin->cfg_pkgs[i].reg_info.vid.addr, + cfg_bin->cfg_pkgs[i].reg_info.vid.reserved1); + ts_debug("sensor_id reg:%02x,mask:%x", cfg_bin->cfg_pkgs[i].reg_info.sensor_id.addr, + cfg_bin->cfg_pkgs[i].reg_info.sensor_id.addr); + ts_debug("fw_status reg:%02x", cfg_bin->cfg_pkgs[i].reg_info.fw_status.addr); + ts_debug("cfg_addr reg:%02x", cfg_bin->cfg_pkgs[i].reg_info.cfg_addr.addr); + ts_debug("esd reg:%02x", cfg_bin->cfg_pkgs[i].reg_info.esd.addr); + ts_debug("command reg:%02x", cfg_bin->cfg_pkgs[i].reg_info.command.addr); + ts_debug("coor:%02x", cfg_bin->cfg_pkgs[i].reg_info.coor.addr); + ts_debug("gesture:%02x", cfg_bin->cfg_pkgs[i].reg_info.gesture.addr); + ts_debug("fw_request:%02x", cfg_bin->cfg_pkgs[i].reg_info.fw_request.addr); + ts_debug("proximity:%02x", cfg_bin->cfg_pkgs[i].reg_info.proximity.addr); + + ts_debug("--------------------------------------------"); + } + r = 0; +exit: + return r; +} + +static int goodix_cfg_bin_proc(struct goodix_ts_core *core_data) +{ + int r; + struct goodix_ts_device *ts_dev = core_data->ts_dev; + struct goodix_cfg_bin *cfg_bin = kzalloc(sizeof(struct goodix_cfg_bin), + GFP_KERNEL); + if (!cfg_bin) { + return -ENOMEM; + } + if (!ts_dev) { + ts_err("ts device can't be null"); + return -EINVAL; + } + + /* when start init config bin with error state */ + ts_dev->cfg_bin_state = CFG_BIN_STATE_ERROR; + + /*get cfg_bin from file system*/ + r = goodix_read_cfg_bin(ts_dev->dev, cfg_bin); + if (r < 0) { + ts_err("Failed get valid config bin data"); + goto exit; + } + /*parse cfg bin*/ + r = goodix_parse_cfg_bin(cfg_bin); + if (!r) { + ts_info("parse cfg bin SUCCESS"); + } else { + ts_err("parse cfg bin FAILED"); + goto exit; + } + + /*get register address and configuration from cfg bin*/ + r = goodix_get_reg_and_cfg(ts_dev, cfg_bin); + if (!r) { + ts_info("success get reg and cfg info from cfg bin"); + } else { + ts_err("failed get cfg and reg info, update fw then retry"); + } + + /* get sen_num/drv_num */ + if (ts_dev->normal_cfg.initialized) { + r = goodix_get_channel_num(core_data, &core_data->sen_num, + &core_data->drv_num, ts_dev->normal_cfg.data); + if (r) + ts_err("failed get channel_num"); + } + + /*debug*/ + ts_info("cfg_send_flag:0x%04x", ts_dev->reg.cfg_send_flag); + ts_info("pid:0x%04x", ts_dev->reg.pid); + ts_info("vid:0x%04x", ts_dev->reg.vid); + ts_info("sensor_id:0x%04x", ts_dev->reg.sensor_id); + ts_info("fw_mask:0x%04x", ts_dev->reg.fw_mask); + ts_info("fw_status:0x%04x", ts_dev->reg.fw_status); + ts_info("cfg_addr:0x%04x", ts_dev->reg.cfg_addr); + ts_info("esd:0x%04x", ts_dev->reg.esd); + ts_info("command:0x%04x", ts_dev->reg.command); + ts_info("coor:0x%04x", ts_dev->reg.coor); + ts_info("gesture:0x%04x", ts_dev->reg.gesture); + ts_info("fw_request:0x%04x", ts_dev->reg.fw_request); + ts_info("proximity:0x%04x", ts_dev->reg.proximity); + +exit: + kfree(cfg_bin->cfg_pkgs); + kfree(cfg_bin->bin_data); + kfree(cfg_bin); + if (r) + goodix_ts_blocking_notify(NOTIFY_CFG_BIN_FAILED, &r); + else + goodix_ts_blocking_notify(NOTIFY_CFG_BIN_SUCCESS, &r); + + ts_info("cfg bin state %d, ret %d", ts_dev->cfg_bin_state, r); + return r; +} + +static int goodix_extract_cfg_pkg(struct goodix_ts_device *ts_dev, + struct goodix_cfg_package *cfg_pkg) +{ + struct goodix_ts_config *ts_cfg; + + if (cfg_pkg->cnst_info.cfg_type == TS_NORMAL_CFG) { + ts_cfg = &(ts_dev->normal_cfg); + } else if (cfg_pkg->cnst_info.cfg_type == TS_HIGH_SENSE_CFG) { + ts_cfg = &(ts_dev->highsense_cfg); + } else { + ts_err("unknown cfg type %d", cfg_pkg->cnst_info.cfg_type); + return -EINVAL; + } + + ts_cfg->length = cfg_pkg->pkg_len - + TS_PKG_CONST_INFO_LEN - TS_PKG_REG_INFO_LEN; + if (ts_cfg->length > sizeof(ts_cfg->data)) { + ts_err("illegal cfg length %d", ts_cfg->length); + return -EINVAL; + } + if (ts_cfg->length) { + ts_info("get config type %d, len %d", + cfg_pkg->cnst_info.cfg_type, ts_cfg->length); + memcpy(ts_cfg->data, cfg_pkg->cfg, ts_cfg->length); + ts_cfg->initialized = TS_CFG_STABLE; + mutex_init(&ts_cfg->lock); + } else { + ts_info("no config data"); + } + + /*get register info*/ + ts_dev->reg.cfg_send_flag = cfg_pkg->reg_info.cfg_send_flag.addr; + ts_dev->reg.version_base = cfg_pkg->reg_info.version_base.addr; + ts_dev->reg.version_len = cfg_pkg->reg_info.version_base.reserved1; + ts_dev->reg.pid = cfg_pkg->reg_info.pid.addr; + ts_dev->reg.pid_len = cfg_pkg->reg_info.pid.reserved1; + ts_dev->reg.vid = cfg_pkg->reg_info.vid.addr; + ts_dev->reg.vid_len = cfg_pkg->reg_info.vid.reserved1; + ts_dev->reg.sensor_id = cfg_pkg->reg_info.sensor_id.addr; + ts_dev->reg.sensor_id_mask = cfg_pkg->reg_info.sensor_id.reserved1; + ts_dev->reg.fw_mask = cfg_pkg->reg_info.fw_mask.addr; + ts_dev->reg.fw_status = cfg_pkg->reg_info.fw_status.addr; + ts_dev->reg.cfg_addr = cfg_pkg->reg_info.cfg_addr.addr; + ts_dev->reg.esd = cfg_pkg->reg_info.esd.addr; + ts_dev->reg.command = cfg_pkg->reg_info.command.addr; + ts_dev->reg.coor = cfg_pkg->reg_info.coor.addr; + ts_dev->reg.gesture = cfg_pkg->reg_info.gesture.addr; + ts_dev->reg.fw_request = cfg_pkg->reg_info.fw_request.addr; + ts_dev->reg.proximity = cfg_pkg->reg_info.proximity.addr; + + return 0; +} + +static int goodix_get_reg_and_cfg(struct goodix_ts_device *ts_dev, + struct goodix_cfg_bin *cfg_bin) +{ + int i; + u16 addr; + u8 read_len; + char temp_sensor_id = -1; + u8 temp_fw_mask[TS_CFG_BLOCK_FW_MASK_LEN] = {0x00}; + u8 temp_pid[TS_CFG_BLOCK_PID_LEN] = {0x00}; + int r = -EINVAL; + + 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; + } + + memset(&ts_dev->normal_cfg, 0, sizeof(ts_dev->normal_cfg)); + memset(&ts_dev->highsense_cfg, 0, sizeof(ts_dev->highsense_cfg)); + + /* find suitable cfg packages */ + for (i = 0; i < cfg_bin->head.pkg_num; i++) { + /*get ic type*/ + if (!strncmp(cfg_bin->cfg_pkgs[i].cnst_info.ic_type, + "normandy", strlen("normandy"))) { + ts_dev->ic_type = IC_TYPE_NORMANDY; + } else if (!strncmp(cfg_bin->cfg_pkgs[i].cnst_info.ic_type, + "yellowstone", strlen("yellowstone"))) { + ts_dev->ic_type = IC_TYPE_YELLOWSTONE; + } else { + ts_err("unknow ic type of cfg_bin:%s", + cfg_bin->cfg_pkgs[i].cnst_info.ic_type); + continue; + } + + ts_info("ic_type:%d", ts_dev->ic_type); + + /* contrast sensor id */ + addr = cfg_bin->cfg_pkgs[i].reg_info.sensor_id.addr; + if (!addr) { + ts_info("pkg:%d, sensor_id reg is NULL", i); + continue; + } + + r = ts_dev->hw_ops->read(ts_dev, addr, &temp_sensor_id, 1); + if (r < 0) { + ts_err("failed get sensor of pkg:%d, reg:0x%02x", i, addr); + goto get_default_pkg; + } + ts_info("sensor id is %d", temp_sensor_id); + /*sensor.reserved1 is a mask, if it's not ZERO, use it*/ + if (cfg_bin->cfg_pkgs[i].reg_info.sensor_id.reserved1 != 0) + temp_sensor_id &= cfg_bin->cfg_pkgs[i].reg_info.sensor_id.reserved1; + + if (temp_sensor_id != cfg_bin->cfg_pkgs[i].cnst_info.sensor_id) { + ts_err("pkg:%d, sensor id contrast FAILED, reg:0x%02x", + i, addr); + ts_err("sensor_id from i2c:%d, sensor_id of cfg bin:%d", + temp_sensor_id, + cfg_bin->cfg_pkgs[i].cnst_info.sensor_id); + continue; + } + + /*contrast fw_mask, if this reg is null, skip this step*/ + addr = cfg_bin->cfg_pkgs[i].reg_info.fw_mask.addr; + if (addr && cfg_bin->cfg_pkgs[i].cnst_info.fw_mask[0]) { + r = ts_dev->hw_ops->read(ts_dev, addr, temp_fw_mask, + sizeof(temp_fw_mask)); + if (r < 0) { + ts_err("failed read fw_mask pkg:%d, reg:0x%02x", + i, addr); + goto get_default_pkg; + } + if (strncmp(temp_fw_mask, cfg_bin->cfg_pkgs[i].cnst_info.fw_mask, + sizeof(temp_fw_mask))) { + ts_err("pkg:%d, fw_mask contrast FAILED, reg:0x%02x,", i, addr); + ts_err("mask from i2c:%s, mask of cfg bin:%s", + temp_fw_mask, + cfg_bin->cfg_pkgs[i].cnst_info.fw_mask); + continue; + } + } + + /*contrast pid*/ + addr = cfg_bin->cfg_pkgs[i].reg_info.pid.addr; + read_len = cfg_bin->cfg_pkgs[i].reg_info.pid.reserved1; + if (!addr) { + ts_err("pkg:%d, pid reg is NULL", i); + continue; + } + if (read_len <= 0 || read_len > TS_CFG_BLOCK_PID_LEN) { + ts_err("pkg:%d, hw_pid length ERROR, len:%d", + i, read_len); + continue; + } + r = ts_dev->hw_ops->read(ts_dev, addr, temp_pid, read_len); + if (r < 0) { + ts_err("failed read pid pkg:%d, pid reg:0x%02x", i, addr); + goto get_default_pkg; + } + if (strncmp(temp_pid, cfg_bin->cfg_pkgs[i].cnst_info.hw_pid, read_len)) { + ts_err("pkg:%d, pid contrast FAILED, reg:0x%02x", i, addr); + ts_err("pid from i2c:%s, pid of cfg bin:%s", + temp_pid, cfg_bin->cfg_pkgs[i].cnst_info.hw_pid); + continue; + } + + ts_info("try get package info: ic type %s, cfg type %d", + cfg_bin->cfg_pkgs[i].cnst_info.ic_type, + cfg_bin->cfg_pkgs[i].cnst_info.cfg_type); + /* currently only support normal and high_sense config */ + if (cfg_bin->cfg_pkgs[i].cnst_info.cfg_type == TS_NORMAL_CFG || + cfg_bin->cfg_pkgs[i].cnst_info.cfg_type == TS_HIGH_SENSE_CFG) { + r = goodix_extract_cfg_pkg(ts_dev, &cfg_bin->cfg_pkgs[i]); + if (!r) { + ts_dev->cfg_bin_state = CFG_BIN_STATE_INITIALIZED; + ts_info("success parse cfg bin"); + } else { + ts_err("failed parse cfg bin"); + break; + } + } + } + +get_default_pkg: + if (ts_dev->cfg_bin_state != CFG_BIN_STATE_INITIALIZED) { + ts_err("no valid normal cfg, use cfg_pkg 0 as default"); + /* Foo code for recover dead IC. + * force set package 0 config type to normal config, this will + * config will use to recover IC. + */ + cfg_bin->cfg_pkgs[0].cnst_info.cfg_type = TS_NORMAL_CFG; + if (goodix_extract_cfg_pkg(ts_dev, &cfg_bin->cfg_pkgs[0])) { + ts_err("failed get valid config for IC recover"); + ts_dev->cfg_bin_state = CFG_BIN_STATE_ERROR; + } else { + ts_dev->cfg_bin_state = CFG_BIN_STATE_TEMP; + ts_info("get temp config data"); + } + + r = -EINVAL; + } + + return r; +} + +static int goodix_read_cfg_bin(struct device *dev, struct goodix_cfg_bin *cfg_bin) +{ + const struct firmware *firmware = NULL; + char cfg_bin_name[32] = {0x00}; + int i = 0, r; + + /*get cfg_bin_name*/ + strlcpy(cfg_bin_name, TS_DEFAULT_CFG_BIN, sizeof(cfg_bin_name)); + + ts_info("cfg_bin_name:%s", cfg_bin_name); + + for (i = 0; i < TS_RQST_FW_RETRY_TIMES; i++) { + r = request_firmware(&firmware, cfg_bin_name, dev); + if (r < 0) { + ts_err("failed get cfg bin[%s] error:%d, try_times:%d", + cfg_bin_name, r, i + 1); + msleep(1000); + } else { + ts_info("Cfg_bin image [%s] is ready, try_times:%d", + cfg_bin_name, i + 1); + break; + } + } + if (i >= TS_RQST_FW_RETRY_TIMES) { + ts_err("get cfg_bin FAILED"); + goto exit; + } + + if (firmware->size <= 0) { + ts_err("request_firmware, cfg_bin length ERROR,len:%zu", + firmware->size); + r = -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) + r = -ENOMEM; + + memcpy(cfg_bin->bin_data, firmware->data, cfg_bin->bin_data_len); + + r = 0; +exit: + if (firmware) { + release_firmware(firmware); + firmware = NULL; + } + return r; +} + +static void goodix_cfg_pkg_leToCpu(struct goodix_cfg_package *pkg) +{ + if (!pkg) { + ts_err("cfg package is NULL"); + return; + } + /*package const_info*/ + pkg->cnst_info.pkg_len = le32_to_cpu(pkg->cnst_info.pkg_len); + pkg->cnst_info.x_res_offset = le16_to_cpu(pkg->cnst_info.x_res_offset); + pkg->cnst_info.y_res_offset = le16_to_cpu(pkg->cnst_info.y_res_offset); + pkg->cnst_info.trigger_offset = le16_to_cpu(pkg->cnst_info.trigger_offset); + + /*package reg_info*/ + pkg->reg_info.cfg_send_flag.addr = le16_to_cpu(pkg->reg_info.cfg_send_flag.addr); + pkg->reg_info.pid.addr = le16_to_cpu(pkg->reg_info.pid.addr); + pkg->reg_info.vid.addr = le16_to_cpu(pkg->reg_info.vid.addr); + pkg->reg_info.sensor_id.addr = le16_to_cpu(pkg->reg_info.sensor_id.addr); + pkg->reg_info.fw_status.addr = le16_to_cpu(pkg->reg_info.fw_status.addr); + pkg->reg_info.cfg_addr.addr = le16_to_cpu(pkg->reg_info.cfg_addr.addr); + pkg->reg_info.esd.addr = le16_to_cpu(pkg->reg_info.esd.addr); + pkg->reg_info.command.addr = le16_to_cpu(pkg->reg_info.command.addr); + pkg->reg_info.coor.addr = le16_to_cpu(pkg->reg_info.coor.addr); + pkg->reg_info.gesture.addr = le16_to_cpu(pkg->reg_info.gesture.addr); + pkg->reg_info.fw_request.addr = le16_to_cpu(pkg->reg_info.fw_request.addr); + pkg->reg_info.proximity.addr = le16_to_cpu(pkg->reg_info.proximity.addr); +} + +static int goodix_later_init_thread(void *data) +{ + int ret; + struct goodix_ts_core *ts_core = data; + struct goodix_ts_device *ts_dev = ts_core->ts_dev; + + ret = goodix_cfg_bin_proc(ts_core); + if (ret) + ts_err("parse cfg bin encounter error, %d", ret); + else + ts_info("success get cfg bin"); + + if (ts_dev->cfg_bin_state == CFG_BIN_STATE_ERROR) { + ts_err("parse cfg bin encounter fatal err"); + goto release_core; + } + + if (ts_dev->cfg_bin_state == CFG_BIN_STATE_TEMP) { + ts_err("failed get valid config data, retry after fwupdate"); + ret = goodix_do_fw_update(UPDATE_MODE_BLOCK|UPDATE_MODE_FORCE| + UPDATE_MODE_FLASH_CFG| + UPDATE_MODE_SRC_REQUEST); + if (ret) { + ts_err("fw update failed, %d", ret); + goto release_core; + } + ts_info("fw update success retry parse cfg bin"); + ret = goodix_cfg_bin_proc(ts_core); + if (ret) { + ts_err("failed parse cfg bin after fw update"); + goto release_core; + } + } else { + ts_info("success parse config bin"); + ret = goodix_do_fw_update(UPDATE_MODE_BLOCK| + UPDATE_MODE_FLASH_CFG| + UPDATE_MODE_SRC_REQUEST); + if (ret) { + ts_info("fw update failed, %d[ignore]", ret); + ret = 0; + } + } + ret = goodix_ts_stage2_init(ts_core); + if (!ret) { + ts_info("stage2 init success"); + ts_core->init_stage = CORE_INIT_STAGE2; + return ret; + } + ts_err("stage2 init failed, %d", ret); + +release_core: + ts_err("stage2 init failed"); + return ret; +} + +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; +} diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_cfg_bin.h b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_cfg_bin.h new file mode 100644 index 000000000000..c1aa93a82bba --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_cfg_bin.h @@ -0,0 +1,92 @@ +#ifndef _GOODIX_CFG_BIN_H_ +#define _GOODIX_CFG_BIN_H_ + +#include "goodix_ts_core.h" + +#define TS_DEFAULT_CFG_BIN "goodix_cfg_group.bin" +#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; +}; + +#endif diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_default_fw.h b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_default_fw.h new file mode 100644 index 000000000000..1632e6ef3c76 --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_default_fw.h @@ -0,0 +1,6 @@ +#ifndef _GOODIX_FIRMWARE_FW_H_ +#define _GOODIX_FIRMWARE_FW_H_ + +u8 goodix_default_fw[] = { 0x00}; + +#endif diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_gtx8_update.c b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_gtx8_update.c new file mode 100644 index 000000000000..b258b5d51207 --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_gtx8_update.c @@ -0,0 +1,1505 @@ +/* + * Goodix Firmware Update Driver. + * + * 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 "goodix_cfg_bin.h" +#include "goodix_default_fw.h" +/* COMMON PART - START */ +#define TS_DEFAULT_FIRMWARE "goodix_firmware.bin" + +#define FW_HEADER_SIZE 256 +#define FW_SUBSYS_INFO_SIZE 8 +#define FW_SUBSYS_INFO_OFFSET 32 +#define FW_SUBSYS_MAX_NUM 28 + +#define ISP_MAX_BUFFERSIZE (1024 * 4) + +#define HW_REG_CPU_CTRL 0x2180 +#define HW_REG_DSP_MCU_POWER 0x2010 +#define HW_REG_RESET 0x2184 +#define HW_REG_SCRAMBLE 0x2218 +#define HW_REG_BANK_SELECT 0x2048 +#define HW_REG_ACCESS_PATCH0 0x204D +#define HW_REG_EC_SRM_START 0x204F +#define HW_REG_GIO_YS 0x2014 +#define HW_REG_ESD_KEY_EN 0x2318 +#define HW_REG_ESD_KEY_DIS 0x2324 +#define HW_REG_CPU_RUN_FROM 0x4506 /* for nor_L is 0x4006 */ +#define HW_REG_CPU_RUN_FROM_YS 0x4000 +#define HW_REG_ISP_RUN_FLAG 0x6006 +#define HW_REG_ISP_ADDR 0xC000 +#define HW_REG_ISP_BUFFER 0x6100 +#define HW_REG_SUBSYS_TYPE 0x6020 +#define HW_REG_FLASH_FLAG 0x6022 +#define HW_REG_CACHE 0x204B +#define HW_REG_ESD_KEY 0x2318 +#define HW_REG_WTD_TIMER 0x20B0 + +#define FLASH_ADDR_CONFIG_DATA 0x1E000 +#define FLASH_SUBSYS_TYPE_CONFIG 0x03 + +#define CPU_CTRL_PENDING 0x00 +#define CPU_CTRL_RUNNING 0x01 + +#define ISP_STAT_IDLE 0xFF +#define ISP_STAT_READY 0xAA +#define ISP_STAT_WRITING 0xAA +#define ISP_FLASH_SUCCESS 0xBB +#define ISP_FLASH_ERROR 0xCC +#define ISP_FLASH_CHECK_ERROR 0xDD +#define ISP_CMD_PREPARE 0x55 +#define ISP_CMD_FLASH 0xAA + +#define TS_CHECK_ISP_STATE_RETRY_TIMES 200 +#define TS_READ_FLASH_STATE_RETRY_TIMES 200 + +/** + * fw_subsys_info - subsytem firmware infomation + * @type: sybsystem type + * @size: firmware size + * @flash_addr: flash address + * @data: firmware data + */ +struct fw_subsys_info { + u8 type; + u32 size; + u16 flash_addr; + const u8 *data; +}; + +#pragma pack(1) +/** + * firmware_info + * @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 + * @subsys: sybsystem info + */ +struct firmware_info { + u32 size; + u16 checksum; + u8 hw_pid[6]; + u8 hw_vid[3]; + u8 fw_pid[8]; + u8 fw_vid[4]; + u8 subsys_num; + u8 chip_type; + u8 protocol_ver; + u8 reserved[2]; + struct fw_subsys_info subsys[FW_SUBSYS_MAX_NUM]; +}; + +#pragma pack() + +/** + * firmware_data - firmware data structure + * @fw_info: firmware infomation + * @firmware: firmware data structure + */ +struct firmware_data { + struct firmware_info fw_info; + const struct firmware *firmware; +}; + +enum update_status { + UPSTA_NOTWORK = 0, + UPSTA_PREPARING, + UPSTA_UPDATING, + UPSTA_ABORT, + UPSTA_SUCCESS, + UPSTA_FAILED +}; + +/** + * fw_update_ctrl - sturcture 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 + * @ts_dev: touch device + * @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; + int mode; + enum update_status status; + unsigned int progress; + + struct firmware_data fw_data; + struct goodix_ts_device *ts_dev; + struct goodix_ts_core *core_data; + + char fw_name[32]; + struct bin_attribute attr_fwimage; + struct kobject *kobj; +}; +static struct fw_update_ctrl goodix_fw_update_ctrl; +/** + * goodix_parse_firmware - parse firmware header infomation + * and subsystem infomation from firmware data buffer + * + * @fw_data: firmware struct, contains firmware header info + * and firmware data. + * return: 0 - OK, < 0 - error + */ +static int goodix_parse_firmware(struct firmware_data *fw_data) +{ + const struct firmware *firmware; + struct firmware_info *fw_info; + unsigned int i, fw_offset, info_offset; + u16 checksum; + int r = 0; + + if (!fw_data || !fw_data->firmware) { + ts_err("Invalid firmware data"); + return -EINVAL; + } + fw_info = &fw_data->fw_info; + + /* copy firmware head info */ + firmware = fw_data->firmware; + if (firmware->size < FW_SUBSYS_INFO_OFFSET) { + ts_err("Invalid firmware size:%zu", firmware->size); + r = -EINVAL; + goto err_size; + } + memcpy(fw_info, firmware->data, FW_SUBSYS_INFO_OFFSET); + + /* check firmware size */ + fw_info->size = be32_to_cpu(fw_info->size); + if (firmware->size != fw_info->size + 6) { + ts_err("Bad firmware, size not match"); + r = -EINVAL; + goto err_size; + } + + /* calculate checksum, note: sum of bytes, but check + * by u16 checksum + */ + for (i = 6, checksum = 0; i < firmware->size; i++) + checksum += firmware->data[i]; + + /* byte order change, and check */ + fw_info->checksum = be16_to_cpu(fw_info->checksum); + if (checksum != fw_info->checksum) { + ts_err("Bad firmware, cheksum error %x(file) != %x(cal)", + fw_info->checksum, checksum); + r = -EINVAL; + goto err_size; + } + + if (fw_info->subsys_num > FW_SUBSYS_MAX_NUM) { + ts_err("Bad firmware, invalid subsys num: %d", + fw_info->subsys_num); + r = -EINVAL; + goto err_size; + } + + /* parse subsystem info */ + fw_offset = FW_HEADER_SIZE; + for (i = 0; i < fw_info->subsys_num; i++) { + info_offset = FW_SUBSYS_INFO_OFFSET + + i * FW_SUBSYS_INFO_SIZE; + + fw_info->subsys[i].type = firmware->data[info_offset]; + fw_info->subsys[i].size = + be32_to_cpup((__be32 *)&firmware->data[info_offset + 1]); + fw_info->subsys[i].flash_addr = + be16_to_cpup((__be16 *)&firmware->data[info_offset + 5]); + + if (fw_offset > firmware->size) { + ts_err("Sybsys offset exceed Firmware size"); + goto err_size; + } + + fw_info->subsys[i].data = firmware->data + fw_offset; + fw_offset += fw_info->subsys[i].size; + } + + ts_info("Firmware package protocol: V%u", fw_info->protocol_ver); + ts_info("Fimware PID:GT%s", fw_info->fw_pid); + ts_info("Fimware VID:%02X%02X%02X%02x", fw_info->fw_vid[0], + fw_info->fw_vid[1], fw_info->fw_vid[2], fw_info->fw_vid[3]); + ts_info("Firmware chip type:%02X", fw_info->chip_type); + ts_info("Firmware size:%u", fw_info->size); + ts_info("Firmware subsystem num:%u", fw_info->subsys_num); +#ifdef CONFIG_GOODIX_DEBUG + for (i = 0; i < fw_info->subsys_num; i++) { + ts_debug("------------------------------------------"); + ts_debug("Index:%d", i); + ts_debug("Subsystem type:%02X", fw_info->subsys[i].type); + ts_debug("Subsystem size:%u", fw_info->subsys[i].size); + ts_debug("Subsystem flash_addr:%08X", fw_info->subsys[i].flash_addr); + ts_debug("Subsystem Ptr:%p", fw_info->subsys[i].data); + } + ts_debug("------------------------------------------"); +#endif + +err_size: + return r; +} + +/** + * goodix_check_update - compare the version of firmware running in + * touch device with the version getting from the firmware file. + * @fw_info: firmware infomation to be compared + * return: 0 no need do update, + * otherwise need do update + */ +static int goodix_check_update(struct goodix_ts_device *dev, + const struct firmware_info *fw_info) +{ + struct goodix_ts_version fw_ver; + int ret = -EINVAL; + + /* read version from chip, if we got invalid + * firmware version, maybe fimware in flash is + * incorrect, so we need to update firmware + */ + ret = dev->hw_ops->read_version(dev, &fw_ver); + if (ret) { + ts_info("failed get active pid"); + return -EINVAL; + } + + if (fw_ver.valid) { + // should we compare PID before fw update? + // if fw patch demage the PID may unmatch but + // we should de update to recover it. + // TODO skip PID check + /*if (memcmp(fw_ver.pid, fw_info->fw_pid, dev->reg.pid_len)) { + ts_err("Product ID is not match %s != %s", + fw_ver.pid, fw_info->fw_pid); + return -EPERM; + }*/ + + ret = memcmp(fw_ver.vid, fw_info->fw_vid, dev->reg.vid_len); + if (ret == 0) { + ts_info("FW version is equal to the IC's"); + return 0; + } else if (ret > 0) { + ts_info("Warning: fw version is lower the IC's"); + } + } /* else invalid firmware, update firmware */ + + ts_info("Firmware needs to be updated"); + return ret; +} + +/** + * 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(struct goodix_ts_device *dev, + unsigned int addr, unsigned char *data, unsigned int len) +{ + u8 *cfm, cfm_buf[32]; + int r, i; + + if (len > sizeof(cfm_buf)) { + cfm = kzalloc(len, GFP_KERNEL); + if (!cfm) { + ts_err("Mem alloc failed"); + return -ENOMEM; + } + } else { + cfm = &cfm_buf[0]; + } + + for (i = 0; i < GOODIX_BUS_RETRY_TIMES; i++) { + r = dev->hw_ops->write_trans(dev, addr, data, len); + if (r < 0) + goto exit; + + r = dev->hw_ops->read_trans(dev, addr, cfm, len); + if (r < 0) + goto exit; + + if (memcmp(data, cfm, len)) { + ts_info("data[0]:0x%02x, data[1]:0x%02x," + "read cfm[0]:0x%02x, cfm[1]:0x%02x", + data[0], data[1], cfm[0], cfm[1]); + dev->hw_ops->read_trans(dev, 0x6022, cfm, 2); + ts_info("read 0x6022 data[0]:0x%02x, data[1]:0x%02x", + cfm[0], cfm[1]); + r = -EMEMCMP; + continue; + } else { + r = 0; + break; + } + } + +exit: + if (cfm != &cfm_buf[0]) + kfree(cfm); + return r; +} + +static inline int goodix_reg_write(struct goodix_ts_device *dev, + unsigned int addr, unsigned char *data, unsigned int len) +{ + return dev->hw_ops->write_trans(dev, addr, data, len); +} + +static inline int goodix_reg_read(struct goodix_ts_device *dev, + unsigned int addr, unsigned char *data, unsigned int len) +{ + return dev->hw_ops->read_trans(dev, addr, data, len); +} + +/** + * goodix_load_isp - load ISP program to deivce ram + * @dev: pointer to touch device + * @fw_data: firmware data + * return 0 ok, <0 error + */ +static int goodix_load_isp(struct goodix_ts_device *ts_dev, + struct firmware_data *fw_data) +{ + struct fw_subsys_info *fw_isp; + u8 reg_val[8] = {0x00}; + int r; + int i; + + fw_isp = &fw_data->fw_info.subsys[0]; + + ts_info("Loading ISP start"); + reg_val[0] = 0x00; + r = goodix_reg_write(ts_dev, HW_REG_BANK_SELECT, + reg_val, 1); + if (r < 0) { + ts_err("Failed to select bank0"); + return r; + } + ts_debug("Success select bank0, Set 0x%x -->0x00", HW_REG_BANK_SELECT); + + reg_val[0] = 0x01; + r = goodix_reg_write(ts_dev, HW_REG_ACCESS_PATCH0, + reg_val, 1); + if (r < 0) { + ts_err("Failed to enable patch0 access"); + return r; + } + ts_debug("Success select bank0, Set 0x%x -->0x01", HW_REG_ACCESS_PATCH0); + + r = goodix_reg_write_confirm(ts_dev, HW_REG_ISP_ADDR, + (u8 *)fw_isp->data, fw_isp->size); + if (r < 0) { + ts_err("Loading ISP error"); + return r; + } + + ts_debug("Success send ISP data to IC"); + + reg_val[0] = 0x00; + r = goodix_reg_write_confirm(ts_dev, HW_REG_ACCESS_PATCH0, + reg_val, 1); + if (r < 0) { + ts_err("Failed to disable patch0 access"); + return r; + } + ts_debug("Success forbit bank0 accedd, set 0x%x -->0x00", + HW_REG_ACCESS_PATCH0); + + reg_val[0] = 0x00; + reg_val[1] = 0x00; + r = goodix_reg_write(ts_dev, HW_REG_ISP_RUN_FLAG, reg_val, 2); + if (r < 0) { + ts_err("Failed to clear 0x%x", HW_REG_ISP_RUN_FLAG); + return r; + } + ts_debug("Success clear 0x%x", HW_REG_ISP_RUN_FLAG); + + memset(reg_val, 0x55, 8); + if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) + r = goodix_reg_write(ts_dev, HW_REG_CPU_RUN_FROM_YS, + reg_val, 8); + else + r = goodix_reg_write(ts_dev, HW_REG_CPU_RUN_FROM, + reg_val, 8); + if (r < 0) { + ts_err("Failed set HW_REG_CPU_RUN_FROM flag"); + return r; + } + ts_info("Success write [8]0x55 to HW_REG_CPU_RUN_FROM"); + + reg_val[0] = 0x00; + r = goodix_reg_write(ts_dev, HW_REG_CPU_CTRL, + reg_val, 1); + if (r < 0) { + ts_err("Failed to run isp"); + return r; + } + ts_debug("Success run isp, set 0x%x-->0x00", HW_REG_CPU_CTRL); + + /* check isp work state */ + for (i = 0; i < TS_CHECK_ISP_STATE_RETRY_TIMES; i++) { + msleep(10); + r = goodix_reg_read(ts_dev, HW_REG_ISP_RUN_FLAG, + reg_val, 2); + if (r < 0 || (reg_val[0] == 0xAA && reg_val[1] == 0xBB)) + break; + } + if (reg_val[0] == 0xAA && reg_val[1] == 0xBB) { + ts_info("ISP working OK"); + return 0; + } + ts_err("ISP not work,0x%x=0x%x, 0x%x=0x%x", + HW_REG_ISP_RUN_FLAG, reg_val[0], + HW_REG_ISP_RUN_FLAG + 1, reg_val[1]); + return -EFAULT; +} + +/** + * 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) +{ + struct goodix_ts_device *ts_dev = fwu_ctrl->ts_dev; + u8 reg_val[4] = { 0x00 }; + u8 temp_buf[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + int retry = 20; + int r; + + if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) + ts_dev->hw_ops->write(ts_dev, HW_REG_CPU_RUN_FROM_YS, + temp_buf, 8); + else + ts_dev->hw_ops->write(ts_dev, HW_REG_CPU_RUN_FROM, + temp_buf, 8); + + /*reset IC*/ + ts_info("firmware update, reset"); + gpio_direction_output(ts_dev->board_data.reset_gpio, 0); + udelay(2000); + gpio_direction_output(ts_dev->board_data.reset_gpio, 1); + usleep_range(10000, 11000); + + retry = 100; + do { + if (ts_dev->ic_type != IC_TYPE_YELLOWSTONE) + break; + reg_val[0] = 0x00; + r = goodix_reg_write_confirm(ts_dev, HW_REG_GIO_YS, + reg_val, 1); + if (r < 0) + ts_info("Failed to remove GIO hold flag, retry %d", retry); + else + break; + } while (--retry); + if (!retry) { + ts_err("Failed to remove GIO flag"); + return -EINVAL; + } + + retry = 20; + for (retry = 0; retry < 20; retry++) { + if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) { + reg_val[0] = 0x00; + r = goodix_reg_write(ts_dev, HW_REG_ESD_KEY_DIS, + reg_val, 1); + if (r < 0) { + ts_info("failed dis esd key"); + continue; + } + reg_val[0] = 0x95; + r = goodix_reg_write(ts_dev, HW_REG_ESD_KEY_EN, + reg_val, 1); + if (r < 0) { + ts_info("failed open esd key"); + continue; + } + } + reg_val[0] = 0x24; + r = goodix_reg_write_confirm(ts_dev, HW_REG_CPU_CTRL, + reg_val, 1); + if (r < 0) { + ts_info("Failed to hold ss51, retry"); + msleep(20); + } else { + break; + } + } + if (retry >= 20) { + ts_err("Failed hold ss51,return =%d", r); + return -EINVAL; + } + ts_debug("Success hold ss51"); + + /* enable DSP & MCU power */ + reg_val[0] = 0x00; + r = goodix_reg_write_confirm(ts_dev, HW_REG_DSP_MCU_POWER, reg_val, 1); + if (r < 0) { + ts_err("Failed enable DSP&MCU power"); + return r; + } + ts_debug("Success enabled DSP&MCU power,set 0x%x-->0x00", + HW_REG_DSP_MCU_POWER); + + /* disable watchdog timer */ + reg_val[0] = 0x00; + r = goodix_reg_write(ts_dev, HW_REG_CACHE, reg_val, 1); + if (r < 0) { + ts_err("Failed to clear cache"); + return r; + } + ts_debug("Success clear cache"); + + if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) { + reg_val[0] = 0x00; + r = goodix_reg_write(ts_dev, HW_REG_WTD_TIMER, reg_val, 1); + } else { + reg_val[0] = 0x95; + r = goodix_reg_write(ts_dev, HW_REG_ESD_KEY, reg_val, 1); + reg_val[0] = 0x00; + r |= goodix_reg_write(ts_dev, HW_REG_WTD_TIMER, reg_val, 1); + + reg_val[0] = 0x27; + r |= goodix_reg_write(ts_dev, HW_REG_ESD_KEY, reg_val, 1); + } + if (r < 0) { + ts_err("Failed to disable watchdog"); + return r; + } + ts_info("Success disable watchdog"); + + /* set scramble */ + reg_val[0] = 0x00; + r = goodix_reg_write(ts_dev, HW_REG_SCRAMBLE, reg_val, 1); + if (r < 0) { + ts_err("Failed to set scramble"); + return r; + } + ts_debug("Succcess set scramble"); + + /* load ISP code and run form isp */ + r = goodix_load_isp(ts_dev, &fwu_ctrl->fw_data); + if (r < 0) + ts_err("Failed lode and run isp"); + + return r; +} + +/** + * goodix_format_fw_packet - formate one flash packet + * @pkt: target firmware packet + * @flash_addr: flash address + * @size: packet size + * @data: packet data + */ +static int goodix_format_fw_packet(u8 *pkt, u32 flash_addr, + u16 len, const u8 *data) +{ + u16 checksum; + + if (!pkt || !data) + return -EINVAL; + + /* + * checksum rule:sum of data in one format is equal to zero + * data format: byte/le16/be16/le32/be32/le64/be64 + */ + pkt[0] = (len >> 8) & 0xff; + pkt[1] = len & 0xff; + /* u16 >> 16bit seems nosense but really important */ + pkt[2] = (flash_addr >> 16) & 0xff; + pkt[3] = (flash_addr >> 8) & 0xff; + memcpy(&pkt[4], data, len); + checksum = checksum_be16(pkt, len + 4); + checksum = 0 - checksum; + pkt[len + 4] = (checksum >> 8) & 0xff; + pkt[len + 5] = checksum & 0xff; + return 0; +} + +/** + * goodix_send_fw_packet - send one firmware packet to ISP + * @dev: target touch device + * @pkt: firmware packet + * return:0 ok, <0 error + */ +static int goodix_send_fw_packet(struct goodix_ts_device *dev, u8 type, + u8 *pkt, u32 len) +{ + u8 reg_val[4]; + int r, i; + + if (!pkt) + return -EINVAL; + + ts_info("target fw subsys type:0x%x, len %d", type, len); + r = goodix_reg_write_confirm(dev, HW_REG_ISP_BUFFER, pkt, len); + if (r < 0) { + ts_err("Failed to write firmware packet"); + return r; + } + + reg_val[0] = 0; + reg_val[1] = 0; + /* clear flash flag 0X6022 */ + r = goodix_reg_write_confirm(dev, HW_REG_FLASH_FLAG, reg_val, 2); + if (r < 0) { + ts_err("Faile to clear flash flag"); + return r; + } + + /* write subsystem type 0X8020*/ + reg_val[0] = type; + reg_val[1] = type; + r = goodix_reg_write_confirm(dev, HW_REG_SUBSYS_TYPE, reg_val, 2); + if (r < 0) { + ts_err("Failed write subsystem type to IC"); + return r; + } + + for (i = 0; i < TS_READ_FLASH_STATE_RETRY_TIMES; i++) { + r = goodix_reg_read(dev, HW_REG_FLASH_FLAG, reg_val, 2); + if (r < 0) { + ts_err("Failed read flash state"); + return r; + } + + /* flash haven't end */ + if (reg_val[0] == ISP_STAT_WRITING && + reg_val[1] == ISP_STAT_WRITING) { + ts_debug("Flash not ending..."); + usleep_range(55000, 56000); + continue; + } + if (reg_val[0] == ISP_FLASH_SUCCESS && + reg_val[1] == ISP_FLASH_SUCCESS) { + r = goodix_reg_read(dev, HW_REG_FLASH_FLAG, reg_val, 2); + if (!r && reg_val[0] == ISP_FLASH_SUCCESS && + reg_val[1] == ISP_FLASH_SUCCESS) { + ts_info("Flash subsystem ok"); + return 0; + } + } + if (reg_val[0] == ISP_FLASH_ERROR && + reg_val[1] == ISP_FLASH_ERROR) { + ts_err(" Flash subsystem failed"); + return -EAGAIN; + } + if (reg_val[0] == ISP_FLASH_CHECK_ERROR) { + ts_err("Subsystem checksum err"); + return -EAGAIN; + } + + usleep_range(250, 260); + } + + ts_err("Wait for flash end timeout, 0x6022= %x %x", + reg_val[0], reg_val[1]); + return -EAGAIN; +} + +/** + * 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 infomation + * return: 0 ok, < 0 error + */ +static int goodix_flash_subsystem(struct goodix_ts_device *dev, + struct fw_subsys_info *subsys) +{ + u16 data_size, offset; + u32 total_size; + u32 subsys_base_addr = subsys->flash_addr << 8; + u8 *fw_packet; + int r = 0, i; + + /* + * 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 + 6, 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); + + /* format one firmware packet */ + r = goodix_format_fw_packet(fw_packet, subsys_base_addr + offset, + data_size, &subsys->data[offset]); + if (r < 0) { + ts_err("Invalid packet params"); + goto exit; + } + + /* send one firmware packet, retry 3 time if send failed */ + for (i = 0; i < 3; i++) { + r = goodix_send_fw_packet(dev, subsys->type, + fw_packet, data_size + 6); + if (!r) + break; + } + if (r) { + ts_err("Failed flash subsystem"); + goto exit; + } + offset += data_size; + total_size -= data_size; + } /* end while */ + +exit: + 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 goodix_ts_device *dev, + struct firmware_data *fw_data) +{ + struct fw_update_ctrl *fw_ctrl; + struct firmware_info *fw_info; + struct fw_subsys_info *fw_x; + int retry = GOODIX_BUS_RETRY_TIMES; + int i, r = 0, fw_num, prog_step; + u8 *fw_packet = NULL; + u8 *flash_cfg = NULL; + + /* start from subsystem 1, + * subsystem 0 is the ISP program + */ + fw_ctrl = container_of(fw_data, struct fw_update_ctrl, fw_data); + fw_info = &fw_data->fw_info; + fw_num = fw_info->subsys_num; + + /* we have 80% work here */ + prog_step = 80 / (fw_num - 1); + + for (i = 1; i < fw_num && retry;) { + ts_info("--- Start to flash subsystem[%d] ---", i); + fw_x = &fw_info->subsys[i]; + r = goodix_flash_subsystem(dev, fw_x); + if (r == 0) { + ts_info("--- End flash subsystem[%d]: OK ---", i); + fw_ctrl->progress += prog_step; + 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); + break; + } + } + + kfree(fw_packet); + fw_packet = NULL; + kfree(flash_cfg); + flash_cfg = NULL; + 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 goodix_ts_device *ts_dev, + struct fw_update_ctrl *fwu_ctrl) +{ + u8 reg_val[8] = {0}; + int r = 0, i = 0; + + /* hold ss51 */ + reg_val[0] = 0x24; + r = goodix_reg_write(ts_dev, HW_REG_CPU_CTRL, + reg_val, 1); + if (r < 0) + ts_err("Failed to hold ss51"); + + /* clear recovery flag */ + memset(reg_val, 0, sizeof(reg_val)); + if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) + r = goodix_reg_write(ts_dev, HW_REG_CPU_RUN_FROM_YS, + reg_val, 8); + else + r = goodix_reg_write(ts_dev, HW_REG_CPU_RUN_FROM, + reg_val, 8); + if (r) { + ts_err("Failed set CPU run from normal firmware"); + return r; + } + + /* release ss51 */ + reg_val[0] = 0x00; + r = goodix_reg_write(ts_dev, HW_REG_CPU_CTRL, reg_val, 1); + if (r < 0) + ts_err("Failed to run ss51"); + + /*reset*/ + gpio_direction_output(ts_dev->board_data.reset_gpio, 0); + udelay(2000); + gpio_direction_output(ts_dev->board_data.reset_gpio, 1); + msleep(80); + + for (i = 0; ts_dev->ic_type == IC_TYPE_YELLOWSTONE && i < 100; i++) { + reg_val[0] = 0x00; + r = goodix_reg_write_confirm(ts_dev, HW_REG_GIO_YS, reg_val, 1); + if (!r) + break; + ts_info("failed set GIO flag, r %d", r); + } + if (i >= 100) + ts_err("failed set GIO flag, %d", r); + + return r; +} + +static int goodix_flash_config(struct goodix_ts_device *ts_dev) +{ + int ret; + u8 *fw_packet = NULL; + u8 *temp_data = NULL; + + if (!ts_dev || !ts_dev->cfg_bin_state || + !ts_dev->normal_cfg.initialized) { + ts_err("no valid config data for flash"); + return -EINVAL; + } + + temp_data = kzalloc(ISP_MAX_BUFFERSIZE, GFP_KERNEL); + fw_packet = kzalloc(ISP_MAX_BUFFERSIZE + 6, GFP_KERNEL); + if (!temp_data || !fw_packet) { + ts_err("Failed alloc memory"); + ret = -EINVAL; + goto exit; + } + + memset(temp_data, 0xFF, ISP_MAX_BUFFERSIZE); + ts_info("normal config length %d", ts_dev->normal_cfg.length); + memcpy(temp_data, ts_dev->normal_cfg.data, ts_dev->normal_cfg.length); + + /* format one firmware packet */ + ret = goodix_format_fw_packet(fw_packet, FLASH_ADDR_CONFIG_DATA, + ISP_MAX_BUFFERSIZE, temp_data); + if (ret < 0) { + ts_err("Invalid packet params"); + goto exit; + } + ts_debug("fw_pack:%*ph", 10, fw_packet); + ts_info("try flash config"); + ret = goodix_send_fw_packet(ts_dev, FLASH_SUBSYS_TYPE_CONFIG, + fw_packet, ISP_MAX_BUFFERSIZE + 6); + if (ret) + ts_err("failed flash config, ret %d", ret); + else + ts_info("success flash config with isp"); + +exit: + kfree(temp_data); + kfree(fw_packet); + return ret; +} + +/** + * goodix_fw_update_proc - firmware update process, the entry of + * firmware update flow + * @fwu_ctrl: firmware control + * return: 0 ok, < 0 error + */ +static 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 r = 0; + + if (fwu_ctrl->status == UPSTA_PREPARING || + fwu_ctrl->status == UPSTA_UPDATING) { + ts_err("Firmware update already in progress"); + return -EINVAL; + } + + fwu_ctrl->progress = 0; + fwu_ctrl->status = UPSTA_PREPARING; + + r = goodix_parse_firmware(&fwu_ctrl->fw_data); + if (r < 0) { + fwu_ctrl->status = UPSTA_ABORT; + goto err_parse_fw; + } + + fwu_ctrl->progress = 10; + if (!(fwu_ctrl->mode & UPDATE_MODE_FORCE)) { + r = goodix_check_update(fwu_ctrl->ts_dev, + &fwu_ctrl->fw_data.fw_info); + if (!r) { + fwu_ctrl->status = UPSTA_ABORT; + ts_info("fw update skiped"); + goto err_check_update; + } + } else { + ts_info("force update mode"); + } + +start_update: + fwu_ctrl->progress = 20; + fwu_ctrl->status = UPSTA_UPDATING; /* show upgrading status */ + r = goodix_update_prepare(fwu_ctrl); + if ((r == -EBUS || r == -EAGAIN) && --retry0 > 0) { + ts_err("Bus error, retry prepare ISP:%d", + FW_UPDATE_RETRY - retry0); + goto start_update; + } else if (r < 0) { + ts_err("Failed to prepare ISP, exit update:%d", r); + fwu_ctrl->status = UPSTA_FAILED; + goto err_fw_prepare; + } + + if (GOODIX_FLASH_CONFIG_WITH_ISP && + fwu_ctrl->mode & UPDATE_MODE_FLASH_CFG) { + ts_info("need flash config with isp"); + goodix_flash_config(fwu_ctrl->ts_dev); + } + + /* progress: 20%~100% */ + r = goodix_flash_firmware(fwu_ctrl->ts_dev, &fwu_ctrl->fw_data); + if ((r == -EBUS || r == -ETIMEOUT) && --retry1 > 0) { + /* we will retry[twice] if returns bus error[i2c/spi] + * we will do hardware reset and re-prepare ISP and then retry + * flashing + */ + ts_err("Bus error, retry firmware update:%d", + FW_UPDATE_RETRY - retry1); + goto start_update; + } else if (r < 0) { + ts_err("Fatal error, exit update:%d", r); + fwu_ctrl->status = UPSTA_FAILED; + goto err_fw_flash; + } + + fwu_ctrl->status = UPSTA_SUCCESS; + +err_fw_flash: +err_fw_prepare: + goodix_update_finish(fwu_ctrl->ts_dev, fwu_ctrl); +err_check_update: +err_parse_fw: + if (fwu_ctrl->status == UPSTA_SUCCESS) + ts_info("Firmware update successfully"); + else if (fwu_ctrl->status == UPSTA_FAILED) + ts_err("Firmware update failed"); + + fwu_ctrl->progress = 100; /* 100% */ + ts_info("fw update ret %d", r); + return r; +} + +/** + * 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->ts_dev->dev; + int r; + + ts_info("Request firmware image [%s]", name); + r = request_firmware(&fw_data->firmware, name, dev); + if (r < 0) + ts_err("Firmware image [%s] not available,errno:%d", name, r); + else + ts_info("Firmware image [%s] is ready", name); + return r; +} + +/** + * relase firmware resources + * + */ +static inline void goodix_release_firmware(struct firmware_data *fw_data) +{ + if (fw_data->firmware) { + release_firmware(fw_data->firmware); + fw_data->firmware = NULL; + } +} + +static int goodix_fw_update_thread(void *data) +{ + struct fw_update_ctrl *fwu_ctrl = data; + struct firmware *temp_firmware = NULL; + int r = -EINVAL; + + mutex_lock(&fwu_ctrl->mutex); + + if (fwu_ctrl->mode & UPDATE_MODE_SRC_HEAD) { + ts_info("Firmware header update starts"); + temp_firmware = kzalloc(sizeof(struct firmware), GFP_KERNEL); + if (!temp_firmware) { + ts_err("Failed to allocate memory for firmware"); + goto out; + } + temp_firmware->size = sizeof(goodix_default_fw); + temp_firmware->data = goodix_default_fw; + fwu_ctrl->fw_data.firmware = temp_firmware; + } else 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) { + fwu_ctrl->status = UPSTA_ABORT; + fwu_ctrl->progress = 100; + goto out; + } + } else if (fwu_ctrl->mode & UPDATE_MODE_SRC_SYSFS) { + if (!fwu_ctrl->fw_data.firmware) { + ts_err("Invalid firmware from sysfs"); + fwu_ctrl->status = UPSTA_ABORT; + fwu_ctrl->progress = 100; + 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); + } else if (fwu_ctrl->mode & UPDATE_MODE_SRC_SYSFS) { + vfree(fwu_ctrl->fw_data.firmware); + fwu_ctrl->fw_data.firmware = NULL; + } +out: + fwu_ctrl->mode = UPDATE_MODE_DEFAULT; + mutex_unlock(&fwu_ctrl->mutex); + + if (r) { + ts_err("fw update failed, %d", r); + goodix_ts_blocking_notify(NOTIFY_FWUPDATE_FAILED, NULL); + } else { + ts_info("fw update success"); + goodix_ts_blocking_notify(NOTIFY_FWUPDATE_SUCCESS, NULL); + } + return r; +} + +/* + * 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(mode); + if (!ret) { + ts_info("success start update work"); + return count; + } + ts_err("failed start fw update work"); + return -EINVAL; +} + +static ssize_t goodix_sysfs_update_progress_show( + struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl; + return scnprintf(buf, PAGE_SIZE, "%d\n", fw_ctrl->progress); +} + +static ssize_t goodix_sysfs_update_result_show( + struct device *dev, struct device_attribute *attr, + char *buf) +{ + char *result = NULL; + struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl; + + ts_info("result show"); + switch (fw_ctrl->status) { + case UPSTA_NOTWORK: + result = "notwork"; + break; + case UPSTA_PREPARING: + result = "preparing"; + break; + case UPSTA_UPDATING: + result = "upgrading"; + break; + case UPSTA_ABORT: + result = "abort"; + break; + case UPSTA_SUCCESS: + result = "success"; + break; + case UPSTA_FAILED: + result = "failed"; + break; + default: + break; + } + + return scnprintf(buf, PAGE_SIZE, "%s\n", result); +} + +static ssize_t goodix_sysfs_update_fwversion_show( + struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct goodix_ts_version fw_ver; + struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl; + int r = 0; + char str[5]; + + /* read version from chip */ + r = fw_ctrl->ts_dev->hw_ops->read_version(fw_ctrl->ts_dev, + &fw_ver); + if (!r) { + memcpy(str, fw_ver.pid, 4); + str[4] = '\0'; + return scnprintf(buf, PAGE_SIZE, "PID:%s VID:%02x %02x %02x %02x SENSOR_ID:%d\n", + str, fw_ver.vid[0], fw_ver.vid[1], + fw_ver.vid[2], fw_ver.vid[3], fw_ver.sensor_id); + } + return 0; +} + +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 == NULL) { + ts_err("Failed to alloc memory,size:%zu", + size + sizeof(*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; +} + +/* this interface has ben deprecated */ +static ssize_t goodix_sysfs_force_update_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +static DEVICE_ATTR(update_en, 0220, NULL, goodix_sysfs_update_en_store); +static DEVICE_ATTR(progress, 0444, goodix_sysfs_update_progress_show, NULL); +static DEVICE_ATTR(result, 0444, goodix_sysfs_update_result_show, NULL); +static DEVICE_ATTR(fwversion, 0444, + goodix_sysfs_update_fwversion_show, NULL); +static DEVICE_ATTR(fwsize, 0664, goodix_sysfs_fwsize_show, + goodix_sysfs_fwsize_store); +static DEVICE_ATTR(force_update, 0220, NULL, + goodix_sysfs_force_update_store); + +static struct attribute *goodix_fwu_attrs[] = { + &dev_attr_update_en.attr, + &dev_attr_progress.attr, + &dev_attr_result.attr, + &dev_attr_fwversion.attr, + &dev_attr_fwsize.attr, + &dev_attr_force_update.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 = S_IRUGO | S_IWUGO; + 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); +} + +int goodix_do_fw_update(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; + 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; + } else { + /* 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 = 0; + struct goodix_ts_board_data *ts_bdata = board_data(core_data); + + if (goodix_fw_update_ctrl.initialized) { + ts_info("no need reinit"); + return ret; + } + + if (!core_data || !ts_bdata || !core_data->ts_dev) { + ts_err("core_data && ts_dev cann't be null"); + return -ENODEV; + } + + mutex_init(&goodix_fw_update_ctrl.mutex); + + mutex_lock(&goodix_fw_update_ctrl.mutex); + + goodix_fw_update_ctrl.ts_dev = core_data->ts_dev; + goodix_fw_update_ctrl.core_data = core_data; + goodix_fw_update_ctrl.mode = 0; + /* find a valid firmware image name */ + if (ts_bdata && ts_bdata->fw_name) + strlcpy(goodix_fw_update_ctrl.fw_name, ts_bdata->fw_name, + sizeof(goodix_fw_update_ctrl.fw_name)); + else + strlcpy(goodix_fw_update_ctrl.fw_name, TS_DEFAULT_FIRMWARE, + 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"); + goto err_out; + } + + goodix_fw_update_ctrl.initialized = 1; +err_out: + mutex_unlock(&goodix_fw_update_ctrl.mutex); + return ret; +} + +void goodix_fw_update_uninit(void) +{ + mutex_lock(&goodix_fw_update_ctrl.mutex); + if (goodix_fw_update_ctrl.initialized) { + 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/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_core.c b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_core.c new file mode 100644 index 000000000000..8b0276dd4952 --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_core.c @@ -0,0 +1,2405 @@ + /* + * Goodix Touchscreen Driver + * Core layer of touchdriver architecture. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_FB +#include +#include +#endif + +#include "goodix_ts_core.h" + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 38) +#include +#define INPUT_TYPE_B_PROTOCOL +#endif + +#define GOOIDX_INPUT_PHYS "goodix_ts/input0" +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" + +static int goodix_ts_remove(struct platform_device *pdev); +int goodix_start_later_init(struct goodix_ts_core *ts_core); +void goodix_ts_dev_release(void); +int goodix_fw_update_init(struct goodix_ts_core *core_data); +void goodix_fw_update_uninit(void); + +struct goodix_module goodix_modules; + +#define CORE_MODULE_UNPROBED 0 +#define CORE_MODULE_PROB_SUCCESS 1 +#define CORE_MODULE_PROB_FAILED -1 +#define CORE_MODULE_REMOVED -2 +int core_module_prob_sate = CORE_MODULE_UNPROBED; + +/** + * __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; + + ts_info("__do_register_ext_module IN"); + /* 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"); + if (core_module_prob_sate == CORE_MODULE_PROB_FAILED + || core_module_prob_sate == CORE_MODULE_REMOVED) { + ts_err("core layer state error %d", core_module_prob_sate); + return; + } + + if (core_module_prob_sate == CORE_MODULE_UNPROBED) { + /* waitting for core layer */ + if (!wait_for_completion_timeout(&goodix_modules.core_comp, + 25 * HZ)) { + ts_err("Module [%s] timeout", module->name); + return; + } + } + + /* driver probe failed */ + if (core_module_prob_sate != CORE_MODULE_PROB_SUCCESS) { + ts_err("Can't register ext_module core error"); + return; + } + + if (__do_register_ext_module(module)) + ts_err("failed register module: %s", module->name); + else + ts_info("success register module: %s", module->name); +} + +static void goodix_core_module_init(void) +{ + if (goodix_modules.initilized) + return; + goodix_modules.initilized = true; + INIT_LIST_HEAD(&goodix_modules.head); + mutex_init(&goodix_modules.mutex); + init_completion(&goodix_modules.core_comp); +} + +/** + * 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; +} +EXPORT_SYMBOL_GPL(goodix_register_ext_module); + +/** + * 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); +} +EXPORT_SYMBOL_GPL(goodix_register_ext_module_no_wait); + +/* remove all registered ext module + * return 0 on success, otherwise return < 0 + */ +int goodix_unregister_all_module(void) +{ + int ret = 0; + struct goodix_ext_module *ext_module, *next; + + if (!goodix_modules.initilized) + return 0; + + if (!goodix_modules.core_data) + return 0; + + mutex_lock(&goodix_modules.mutex); + if (list_empty(&goodix_modules.head)) { + mutex_unlock(&goodix_modules.mutex); + return 0; + } + + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (ext_module->funcs && ext_module->funcs->exit) { + ret = ext_module->funcs->exit(goodix_modules.core_data, + ext_module); + if (ret) { + ts_err("failed register ext module, %d:%s", + ret, ext_module->name); + break; + } + } + ts_info("remove module: %s", ext_module->name); + list_del(&ext_module->list); + } + mutex_unlock(&goodix_modules.mutex); + return ret; +} + +/** + * goodix_unregister_ext_module - interface for external module + * to unregister external modules + * + * @module: pointer to external module + * return: 0 ok, <0 failed + */ +int goodix_unregister_ext_module(struct goodix_ext_module *module) +{ + struct goodix_ext_module *ext_module, *next; + bool found = false; + + if (!module) + return -EINVAL; + + if (!goodix_modules.initilized) + return -EINVAL; + + if (!goodix_modules.core_data) + return -ENODEV; + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (ext_module == module) { + found = true; + break; + } + } + } else { + mutex_unlock(&goodix_modules.mutex); + return -EFAULT; + } + + if (!found) { + ts_err("Module [%s] never registed", + module->name); + mutex_unlock(&goodix_modules.mutex); + return -EFAULT; + } + + list_del(&module->list); + mutex_unlock(&goodix_modules.mutex); + + if (module->funcs && module->funcs->exit) + module->funcs->exit(goodix_modules.core_data, module); + + ts_info("Moudle [%s] unregistered", + module->name ? module->name : " "); + return 0; +} +EXPORT_SYMBOL_GPL(goodix_unregister_ext_module); + +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; +} +EXPORT_SYMBOL_GPL(goodix_get_default_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; +} +EXPORT_SYMBOL_GPL(goodix_get_default_kobj); + +/* show external module infomation */ +static ssize_t goodix_ts_extmod_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct goodix_ext_module *module, *next; + size_t offset = 0; + int r; + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(module, next, + &goodix_modules.head, list) { + r = snprintf(&buf[offset], PAGE_SIZE, + "priority:%u module:%s\n", + module->priority, module->name); + if (r < 0) { + mutex_unlock(&goodix_modules.mutex); + return -EINVAL; + } + offset += r; + } + } + + mutex_unlock(&goodix_modules.mutex); + return offset; +} + +/* show driver infomation */ +static ssize_t goodix_ts_driver_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "DriverVersion:%s\n", + GOODIX_DRIVER_VERSION); +} + +/* show chip infoamtion */ +static ssize_t goodix_ts_chip_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct goodix_ts_core *core_data = + dev_get_drvdata(dev); + struct goodix_ts_device *ts_dev = core_data->ts_dev; + struct goodix_ts_version chip_ver; + int r, cnt = 0; + + cnt += snprintf(buf, PAGE_SIZE, "TouchDeviceName:%s\n", ts_dev->name); + if (ts_dev->hw_ops->read_version) { + r = ts_dev->hw_ops->read_version(ts_dev, &chip_ver); + if (!r && chip_ver.valid) { + cnt += snprintf(&buf[cnt], PAGE_SIZE, + "PID:%s\nVID:%02x%02x%02x%02x\nSensID:%02x\n", + chip_ver.pid, chip_ver.vid[0], + chip_ver.vid[1], chip_ver.vid[2], + chip_ver.vid[3], chip_ver.sensor_id); + } + } + + 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_device *ts_dev = core_data->ts_dev; + int en; + + if (sscanf(buf, "%d", &en) != 1) + return -EINVAL; + + if (en != 1) + return -EINVAL; + + if (ts_dev->hw_ops->reset) + ts_dev->hw_ops->reset(ts_dev); + return count; + +} + +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_device *ts_dev = core_data->ts_dev; + int ret, i, offset; + char *cfg_buf; + + cfg_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!cfg_buf) + return -ENOMEM; + + if (ts_dev->hw_ops->read_config) + ret = ts_dev->hw_ops->read_config(ts_dev, cfg_buf, PAGE_SIZE); + else + ret = -EINVAL; + + if (ret <= 0) { + kfree(cfg_buf); + return -EINVAL; + } + + offset = 0; + for (i = 0; i < ret && offset < PAGE_SIZE - 5; i++) { + if (i != 0 && i % 20 == 0) + buf[offset++] = '\n'; + if (offset + 3 < PAGE_SIZE - 1) { + offset += snprintf(&buf[offset], PAGE_SIZE - offset, + "%02x,", cfg_buf[i]); + } else { + offset += snprintf(&buf[offset], PAGE_SIZE - offset, "EOF"); + break; + } + } + + kfree(cfg_buf); + 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, + unsigned char *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\n", 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\n", + 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; +} + +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_device *ts_dev = core_data->ts_dev; + int en, r; + const struct firmware *cfg_img; + struct goodix_ts_config *config = NULL; + + if (sscanf(buf, "%d", &en) != 1) + return -EINVAL; + + if (en != 1) + return -EINVAL; + + disable_irq(core_data->irq); + + /*request configuration*/ + r = request_firmware(&cfg_img, GOODIX_DEFAULT_CFG_NAME, dev); + if (r < 0) { + ts_err("cfg file [%s] not available,errno:%d", + GOODIX_DEFAULT_CFG_NAME, r); + goto exit; + } else + ts_info("cfg file [%s] is ready", GOODIX_DEFAULT_CFG_NAME); + + config = kzalloc(sizeof(*config), GFP_KERNEL); + if (config == NULL) + goto exit; + + /*parse cfg data*/ + if (goodix_ts_convert_0x_data(cfg_img->data, cfg_img->size, + config->data, &config->length)) { + ts_err("convert config data FAILED"); + goto exit; + } + + config->reg_base = ts_dev->reg.cfg_addr; + mutex_init(&config->lock); + config->initialized = TS_CFG_STABLE; + + if (ts_dev->hw_ops->send_config) + ts_dev->hw_ops->send_config(ts_dev, config); + +exit: + enable_irq(core_data->irq); + kfree(config); + config = NULL; + if (cfg_img) { + release_firmware(cfg_img); + cfg_img = NULL; + } + + return count; +} + +/* show irq infomation */ +static ssize_t goodix_ts_irq_info_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + struct irq_desc *desc; + size_t offset = 0; + int r; + + r = snprintf(&buf[offset], PAGE_SIZE, "irq:%u\n", core_data->irq); + if (r < 0) + return -EINVAL; + + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, "state:%s\n", + atomic_read(&core_data->irq_enabled) ? + "enabled" : "disabled"); + if (r < 0) + return -EINVAL; + + desc = irq_to_desc(core_data->irq); + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, "disable-depth:%d\n", + desc->depth); + if (r < 0) + return -EINVAL; + + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, "trigger-count:%zu\n", + core_data->irq_trig_cnt); + if (r < 0) + return -EINVAL; + + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, + "echo 0/1 > irq_info to disable/enable irq"); + 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); + int en; + + if (sscanf(buf, "%d", &en) != 1) + return -EINVAL; + + goodix_ts_irq_enable(core_data, en); + return count; +} + +/*reg read/write */ +static u16 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) +{ + int ret; + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + struct goodix_ts_device *ts_dev = core_data->ts_dev; + + if (!rw_addr || !rw_len) { + ts_err("address(0x%x) and length(%d) cann't be null\n", + 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 = ts_dev->hw_ops->read(ts_dev, rw_addr, show_buf, rw_len); + if (ret) { + ts_err("failed read addr(%x) length(%d)\n", 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_device *ts_dev = core_data->ts_dev; + char *pos = NULL, *token = NULL; + long result = 0; + int ret, i; + + if (!buf || !count) { + ts_err("invalid params\n"); + 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'\n"); + goto err_out; + } + + /* get addr */ + pos = (char *)buf; + pos += 2; + token = strsep(&pos, ":"); + if (!token) { + ts_err("invalid address info\n"); + goto err_out; + } else { + if (kstrtol(token, 16, &result)) { + ts_err("failed get addr info\n"); + goto err_out; + } + rw_addr = (u16)result; + ts_info("rw addr is 0x%x\n", rw_addr); + } + + /* get length */ + token = strsep(&pos, ":"); + if (!token) { + ts_err("invalid length info\n"); + goto err_out; + } else { + if (kstrtol(token, 0, &result)) { + ts_err("failed get length info\n"); + goto err_out; + } + rw_len = (u32)result; + ts_info("rw length info is %d\n", rw_len); + if (rw_len > sizeof(store_buf)) { + ts_err("data len > %lu\n", 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\n"); + goto err_out; + } else { + if (kstrtol(token, 16, &result)) { + ts_err("failed get data[%d] info\n", i); + goto err_out; + } + store_buf[i] = (u8)result; + ts_info("get data[%d]=0x%x\n", i, store_buf[i]); + } + } + ret = ts_dev->hw_ops->write(ts_dev, rw_addr, store_buf, rw_len); + if (ret) { + ts_err("failed write addr(%x) data %*ph\n", rw_addr, + rw_len, store_buf); + goto err_out; + } + + ts_info("%s write to addr (%x) with data %*ph\n", + "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; +} + +static DEVICE_ATTR(extmod_info, S_IRUGO, goodix_ts_extmod_show, NULL); +static DEVICE_ATTR(driver_info, S_IRUGO, goodix_ts_driver_info_show, NULL); +static DEVICE_ATTR(chip_info, S_IRUGO, goodix_ts_chip_info_show, NULL); +static DEVICE_ATTR(reset, S_IWUSR | S_IWGRP, NULL, goodix_ts_reset_store); +static DEVICE_ATTR(send_cfg, S_IWUSR | S_IWGRP, NULL, goodix_ts_send_cfg_store); +static DEVICE_ATTR(read_cfg, S_IRUGO, goodix_ts_read_cfg_show, NULL); +static DEVICE_ATTR(irq_info, S_IRUGO | S_IWUSR | S_IWGRP, + goodix_ts_irq_info_show, goodix_ts_irq_info_store); +static DEVICE_ATTR(reg_rw, S_IRUGO | S_IWUSR | S_IWGRP, + goodix_ts_reg_rw_show, goodix_ts_reg_rw_store); + +static struct attribute *sysfs_attrs[] = { + &dev_attr_extmod_info.attr, + &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_irq_info.attr, + &dev_attr_reg_rw.attr, + NULL, +}; + +static const struct attribute_group sysfs_group = { + .attrs = sysfs_attrs, +}; + +static ssize_t goodix_sysfs_config_write(struct file *file, + struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t pos, size_t count) +{ + struct platform_device *pdev = container_of(kobj_to_dev(kobj), + struct platform_device, dev); + struct goodix_ts_core *ts_core = platform_get_drvdata(pdev); + struct goodix_ts_device *ts_dev = ts_core->ts_dev; + struct goodix_ts_config *config = NULL; + int ret; + + if (pos != 0 || count > GOODIX_CFG_MAX_SIZE) { + ts_info("pos(%d) != 0, cfg size %zu", (int)pos, count); + return -EINVAL; + } + + config = kzalloc(sizeof(struct goodix_ts_config), GFP_KERNEL); + if (config == NULL) + return -ENOMEM; + + memcpy(config->data, buf, count); + config->length = count; + config->reg_base = ts_dev->reg.cfg_addr; + mutex_init(&config->lock); + config->initialized = true; + + ret = ts_dev->hw_ops->send_config(ts_dev, config); + if (ret) { + count = -EINVAL; + ts_err("send config failed %d", ret); + } else { + ts_info("send config success"); + } + + kfree(config); + return count; +} + +static ssize_t goodix_sysfs_config_read(struct file *file, + struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t pos, size_t size) +{ + struct platform_device *pdev = container_of(kobj_to_dev(kobj), + struct platform_device, dev); + struct goodix_ts_core *ts_core = platform_get_drvdata(pdev); + struct goodix_ts_device *ts_dev = ts_core->ts_dev; + int ret; + + ts_debug("pos = %d, size = %zu", (int)pos, size); + + if (pos != 0) + return 0; + + if (ts_dev->hw_ops->read_config) + ret = ts_dev->hw_ops->read_config(ts_dev, buf, PAGE_SIZE); + else + ret = -EINVAL; + + ts_debug("read config ret %d", ret); + return ret; +} + +static struct bin_attribute goodix_config_bin_attr = { + .attr = { + .name = "config_bin", + .mode = S_IRUGO | S_IWUSR | S_IWGRP, + }, + .size = GOODIX_CFG_MAX_SIZE, + .read = goodix_sysfs_config_read, + .write = goodix_sysfs_config_write, +}; + +static int goodix_ts_sysfs_init(struct goodix_ts_core *core_data) +{ + int ret; + + ret = sysfs_create_bin_file(&core_data->pdev->dev.kobj, + &goodix_config_bin_attr); + if (ret) { + ts_err("failed create config bin attr"); + return ret; + } + + ret = sysfs_create_group(&core_data->pdev->dev.kobj, &sysfs_group); + if (ret) { + ts_err("failed create core sysfs group"); + sysfs_remove_bin_file(&core_data->pdev->dev.kobj, + &goodix_config_bin_attr); + return ret; + } + + return ret; +} + +static void goodix_ts_sysfs_exit(struct goodix_ts_core *core_data) +{ + sysfs_remove_bin_file(&core_data->pdev->dev.kobj, + &goodix_config_bin_attr); + sysfs_remove_group(&core_data->pdev->dev.kobj, &sysfs_group); +} + +/* proc node */ +static ssize_t goodix_lockdown_info_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct goodix_ts_core *cd = PDE_DATA(file_inode(file)); + int cnt; +#define TP_INFO_MAX_LENGTH 50 + u8 temp_buf[TP_INFO_MAX_LENGTH]; + + if (*pos != 0) + return 0; + + cnt = snprintf(temp_buf, TP_INFO_MAX_LENGTH, + "%02d%02d%02d%02d%02d%02d%02d%02d\n", + cd->lockdown_info[0], cd->lockdown_info[1], + cd->lockdown_info[2], cd->lockdown_info[3], + cd->lockdown_info[4], cd->lockdown_info[5], + cd->lockdown_info[6], cd->lockdown_info[7]); + + return simple_read_from_buffer(buf, count, pos, temp_buf, strlen(temp_buf)); +} + +static const struct file_operations goodix_lockdown_info_ops = { + .read = goodix_lockdown_info_read, + .open = simple_open, + .owner = THIS_MODULE, +}; + +static ssize_t goodix_fw_version_info_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct goodix_ts_core *cd = PDE_DATA(file_inode(file)); + struct goodix_ts_version fw_ver; + u8 temp_buf[100] = {0}; + int ret; + char pid[5] = {0}; + + if (*pos != 0) + return 0; + ret = cd->ts_dev->hw_ops->read_version(cd->ts_dev, &fw_ver); + if (ret) + return 0; + memcpy(pid, fw_ver.pid, 4); + snprintf(temp_buf, 100, "[IC]GT%s\n", pid); + + return simple_read_from_buffer(buf, count, pos, temp_buf, strlen(temp_buf)); +} + +static const struct file_operations goodix_fw_version_info_ops = { + .read = goodix_fw_version_info_read, + .open = simple_open, + .owner = THIS_MODULE, +}; + +static ssize_t gtp_selftest_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct goodix_ts_core *cd = PDE_DATA(file_inode(file)); + struct device *dev = &cd->pdev->dev; + struct ts_rawdata_info *info; + int ret; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + ts_err("alloc memory failed"); + return 0; + } + + ret = gtx8_get_rawdata(dev, info); + if (ret < 0) { + ts_err("goodix_test failed"); + kfree(info); + return 0; + } + strcat(info->result, "\n"); + ret = simple_read_from_buffer(buf, count, pos, info->result, strlen(info->result)); + kfree(info); + + return ret; +} + +static const struct file_operations gtp_selftest_ops = { + .read = gtp_selftest_read, + .open = simple_open, + .owner = THIS_MODULE, +}; + +static ssize_t gtp_gesture_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct goodix_ts_core *cd = PDE_DATA(file_inode(file)); + u8 temp_buf[10] = {0}; + + snprintf(temp_buf, 10, "%d\n", cd->gesture_enable); + + return simple_read_from_buffer(buf, count, pos, temp_buf, strlen(temp_buf)); +} + +static ssize_t gtp_gesture_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct goodix_ts_core *cd = PDE_DATA(file_inode(file)); + char temp_buf[5] = {0}; + long temp_val; + + if (copy_from_user(temp_buf, buf, count)) { + ts_err("read proc input error"); + return count; + } + + if (kstrtol(temp_buf, 10, &temp_val)) { + ts_err("param is invalid"); + return count; + } + if (temp_val == 1) + cd->gesture_enable = 1; + else if (temp_val == 0) + cd->gesture_enable = 0; + else + ts_err("param must 0 or 1"); + + ts_info("gesture_enable:%d", cd->gesture_enable); + return count; +} + +static const struct file_operations gtp_gesture_ops = { + .read = gtp_gesture_read, + .write = gtp_gesture_write, + .open = simple_open, + .owner = THIS_MODULE, +}; + +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, rx, i; + int index; + int ret; + + 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->ts_dev->hw_ops->get_capacitance_data(cd->ts_dev, 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, "%4d,", info->buff[index + i]); + if ((i + 1) % tx == 0) + seq_printf(m, "\n"); + } + +exit: + kfree(info); + return ret; +} + +static int goodix_rawdata_info_open(struct inode *inode, struct file *file) +{ + return single_open(file, rawdata_proc_show, PDE_DATA(inode)); +} + +static const struct file_operations goodix_rawdata_info_ops = { + .open = goodix_rawdata_info_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int goodix_ts_procfs_init(struct goodix_ts_core *core_data) +{ + proc_create_data("tp_data_dump", 0666, NULL, &goodix_rawdata_info_ops, core_data); + proc_create_data("tp_lockdown_info", 0666, NULL, &goodix_lockdown_info_ops, core_data); + proc_create_data("tp_info", 0666, NULL, &goodix_fw_version_info_ops, core_data); + proc_create_data("tp_selftest", 0666, NULL, >p_selftest_ops, core_data); + proc_create_data("tp_gesture", 0666, NULL, >p_gesture_ops, core_data); + + return 0; +} + +/* 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); +} +EXPORT_SYMBOL(goodix_ts_register_notifier); + +/** + * 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); +} +EXPORT_SYMBOL(goodix_ts_unregister_notifier); + +/** + * 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; +} +EXPORT_SYMBOL_GPL(goodix_ts_blocking_notify); + +static void goodix_ts_report_pen(struct input_dev *dev, + struct goodix_pen_data *pen_data) +{ + int i; + + if (pen_data->coords.status == TS_TOUCH) { + input_report_key(dev, BTN_TOUCH, 1); + input_report_key(dev, pen_data->coords.tool_type, 1); + } else if (pen_data->coords.status == TS_RELEASE) { + input_report_key(dev, BTN_TOUCH, 0); + input_report_key(dev, pen_data->coords.tool_type, 0); + } + if (pen_data->coords.status) { + 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); + } + /* report pen button */ + for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) { + if (!pen_data->keys[i].status) + continue; + if (pen_data->keys[i].status == TS_TOUCH) + input_report_key(dev, pen_data->keys[i].code, 1); + else if (pen_data->keys[i].status == TS_RELEASE) + input_report_key(dev, pen_data->keys[i].code, 0); + } + input_sync(dev); +} + +static void goodix_ts_report_finger(struct input_dev *dev, + struct goodix_touch_data *touch_data) +{ + unsigned int touch_num = touch_data->touch_num; + static u32 pre_fin; + int i; + + /*first touch down and last touch up condition*/ + if (touch_num && !pre_fin) + input_report_key(dev, BTN_TOUCH, 1); + else if (!touch_num && pre_fin) + input_report_key(dev, BTN_TOUCH, 0); + + pre_fin = touch_num; + + for (i = 0; i < GOODIX_MAX_TOUCH; i++) { + if (!touch_data->coords[i].status) + continue; + if (touch_data->coords[i].status == TS_RELEASE) { + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, false); + continue; + } + + 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); + } + + /* report panel key */ + for (i = 0; i < GOODIX_MAX_TP_KEY; i++) { + if (!touch_data->keys[i].status) + continue; + if (touch_data->keys[i].status == TS_TOUCH) + input_report_key(dev, touch_data->keys[i].code, 1); + else if (touch_data->keys[i].status == TS_RELEASE) + input_report_key(dev, touch_data->keys[i].code, 0); + } + input_sync(dev); +} + +/** + * 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_device *ts_dev = core_data->ts_dev; + struct goodix_ext_module *ext_module, *next; + struct goodix_ts_event *ts_event = &core_data->ts_event; + u8 irq_flag = 0; + int r; + + 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; + r = ext_module->funcs->irq_event(core_data, ext_module); + if (r == EVT_CANCEL_IRQEVT) { + mutex_unlock(&goodix_modules.mutex); + return IRQ_HANDLED; + } + } + mutex_unlock(&goodix_modules.mutex); + + /* read touch data from touch device */ + r = ts_dev->hw_ops->event_handler(ts_dev, ts_event); + if (likely(r >= 0)) { + if (ts_event->event_type == EVENT_TOUCH) { + /* report touch */ + goodix_ts_report_finger(core_data->input_dev, + &ts_event->touch_data); + } + if (ts_dev->board_data.pen_enable && + ts_event->event_type == EVENT_PEN) { + goodix_ts_report_pen(core_data->pen_dev, + &ts_event->pen_data); + } + } + + /* clean irq flag */ + irq_flag = 0; + ts_dev->hw_ops->write_trans(ts_dev, ts_dev->reg.coor, &irq_flag, 1); + + return IRQ_HANDLED; +} + +/** + * goodix_ts_init_irq - Requset interrput line from system + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +int goodix_ts_irq_setup(struct goodix_ts_core *core_data) +{ + const struct goodix_ts_board_data *ts_bdata = board_data(core_data); + int r; + + /* if ts_bdata-> irq is invalid */ + if (ts_bdata->irq <= 0) + core_data->irq = gpio_to_irq(ts_bdata->irq_gpio); + else + core_data->irq = ts_bdata->irq; + + ts_info("IRQ:%u,flags:%d", core_data->irq, (int)ts_bdata->irq_flags); + r = 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 (r < 0) + ts_err("Failed to requeset threaded irq:%d", r); + else + atomic_set(&core_data->irq_enabled, 1); + + return r; +} + +/** + * goodix_ts_irq_enable - Enable/Disable a irq + * @core_data: pointer to touch core data + * enable: enable or disable irq + * return: 0 ok, <0 failed + */ +int goodix_ts_irq_enable(struct goodix_ts_core *core_data, + bool enable) +{ + if (enable) { + if (!atomic_cmpxchg(&core_data->irq_enabled, 0, 1)) { + enable_irq(core_data->irq); + ts_debug("Irq enabled"); + } + } else { + if (atomic_cmpxchg(&core_data->irq_enabled, 1, 0)) { + disable_irq(core_data->irq); + ts_debug("Irq disabled"); + } + } + + return 0; +} +EXPORT_SYMBOL(goodix_ts_irq_enable); + +/** + * 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; + struct device *dev = NULL; + int r = 0; + + ts_info("Power init"); + /* dev:i2c client device or spi slave device*/ + dev = core_data->ts_dev->dev; + ts_bdata = board_data(core_data); + + 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)) { + r = PTR_ERR(core_data->avdd); + ts_err("Failed to get regulator avdd:%d", r); + core_data->avdd = NULL; + return r; + } + r = regulator_set_voltage(core_data->avdd, 2960000, 2960000); + if (r) { + ts_err("set avdd voltage fail"); + return r; + } + } 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)) { + r = PTR_ERR(core_data->iovdd); + ts_err("Failed to get regulator iovdd:%d", r); + core_data->iovdd = NULL; + } + r = regulator_set_voltage(core_data->iovdd, 1800000, 1800000); + if (r) { + ts_err("set iovdd voltage fail"); + return r; + } + } else { + ts_info("iovdd name is NULL"); + } + + return r; +} + +/** + * 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 *core_data) +{ + int r = 0; + + ts_info("Device power on"); + if (core_data->power_on) + return 0; + + if (!core_data->avdd) { + core_data->power_on = 1; + return 0; + } + + if (core_data->avdd) { + r = regulator_enable(core_data->avdd); + if (!r) { + ts_info("regulator enable SUCCESS"); + } else { + ts_err("Failed to enable avdd:%d", r); + goto err_exit; + } + } + + if (core_data->iovdd) { + r = regulator_enable(core_data->iovdd); + if (r) { + ts_err("Failed to enable iovdd:%d", r); + if (core_data->avdd) + regulator_disable(core_data->avdd); + goto err_exit; + } + } + usleep_range(3000, 3100); + core_data->power_on = 1; +err_exit: + return 0; +} + +/** + * 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 *core_data) +{ + int r; + + ts_info("Device power off"); + if (!core_data->power_on) + return 0; + + if (core_data->avdd) { + r = regulator_disable(core_data->avdd); + if (!r) { + ts_info("regulator disable SUCCESS"); + } else { + ts_err("Failed to disable analog power:%d", r); + goto err_exit; + } + } + + if (core_data->iovdd) { + r = regulator_disable(core_data->iovdd); + if (r) { + ts_err("Failed to disable iovdd:%d", r); + goto err_exit; + } + } + + core_data->power_on = 0; +err_exit: + return r; +} + +#ifdef CONFIG_PINCTRL +/** + * goodix_ts_pinctrl_init - Get pinctrl handler and pinctrl_state + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static int goodix_ts_pinctrl_init(struct goodix_ts_core *core_data) +{ + int r = 0; + + /* get pinctrl handler from of node */ + core_data->pinctrl = devm_pinctrl_get(core_data->ts_dev->dev); + if (IS_ERR_OR_NULL(core_data->pinctrl)) { + ts_info("Failed to get pinctrl handler[need confirm]"); + core_data->pinctrl = NULL; + return -EINVAL; + } + ts_debug("success get pinctrl"); + /* active state */ + core_data->pin_sta_active = pinctrl_lookup_state(core_data->pinctrl, + PINCTRL_STATE_ACTIVE); + if (IS_ERR_OR_NULL(core_data->pin_sta_active)) { + r = PTR_ERR(core_data->pin_sta_active); + ts_err("Failed to get pinctrl state:%s, r:%d", + PINCTRL_STATE_ACTIVE, r); + core_data->pin_sta_active = NULL; + goto exit_pinctrl_put; + } + ts_debug("success get avtive pinctrl state"); + + /* suspend state */ + core_data->pin_sta_suspend = pinctrl_lookup_state(core_data->pinctrl, + PINCTRL_STATE_SUSPEND); + if (IS_ERR_OR_NULL(core_data->pin_sta_suspend)) { + r = PTR_ERR(core_data->pin_sta_suspend); + ts_err("Failed to get pinctrl state:%s, r:%d", + PINCTRL_STATE_SUSPEND, r); + core_data->pin_sta_suspend = NULL; + goto exit_pinctrl_put; + } + ts_debug("success get suspend pinctrl state"); + + return 0; +exit_pinctrl_put: + devm_pinctrl_put(core_data->pinctrl); + core_data->pinctrl = NULL; + return r; +} +#endif + +/** + * goodix_ts_gpio_setup - Request gpio resources from GPIO subsysten + * reset_gpio and irq_gpio number are obtained from goodix_ts_device + * which created in hardware layer driver. e.g.goodix_xx_i2c.c + * A goodix_ts_device should set those two fileds to right value + * before registed to touch core driver. + * @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_HIGH, "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; + } + + return 0; +} + +/** + * goodix_input_set_params - set input parameters + */ +static void goodix_ts_set_input_params(struct input_dev *input_dev, + struct goodix_ts_board_data *ts_bdata) +{ + int i; + + if (ts_bdata->swap_axis) + swap(ts_bdata->panel_max_x, ts_bdata->panel_max_y); + + 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); + + if (ts_bdata->panel_max_key) { + for (i = 0; i < ts_bdata->panel_max_key; i++) + input_set_capability(input_dev, EV_KEY, + ts_bdata->panel_key_map[i]); + } +} + +/** + * goodix_ts_input_dev_config - Requset and config a input device + * then register it to input sybsystem. + * NOTE that some hardware layer may provide a input device + * (ts_dev->input_dev not NULL). + * @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 */ + goodix_ts_set_input_params(input_dev, ts_bdata); + +#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(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, + GOODIX_PEN_MAX_PRESSURE, 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 *core = container_of(ts_esd, + struct goodix_ts_core, ts_esd); + const struct goodix_ts_hw_ops *hw_ops = ts_hw_ops(core); + u8 data = GOODIX_ESD_TICK_WRITE_DATA; + int r = 0; + + if (!atomic_read(&ts_esd->esd_on)) + return; + + if (hw_ops->check_hw) + r = hw_ops->check_hw(core->ts_dev); + if (r < 0) { + goodix_ts_power_off(core); + goodix_ts_power_on(core); + if (hw_ops->reset) + hw_ops->reset(core->ts_dev); + + /*init dynamic esd*/ + r = hw_ops->write_trans(core->ts_dev, core->ts_dev->reg.esd, + &data, 1); + if (r < 0) + ts_err("failed init dynamic esd"); + } else { + /*init dynamic esd*/ + r = hw_ops->write_trans(core->ts_dev, + core->ts_dev->reg.esd, + &data, 1); + if (r < 0) + ts_err("failed init watch dog"); + } + + 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 *core) +{ + struct goodix_ts_esd *ts_esd = &core->ts_esd; + + if (core->ts_dev->reg.esd == 0) + 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 *core) +{ + struct goodix_ts_esd *ts_esd = &core->ts_esd; + int ret; + + 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 *core) +{ + struct goodix_ts_esd *ts_esd = &core->ts_esd; + struct goodix_ts_device *dev = core->ts_dev; + u8 data = GOODIX_ESD_TICK_WRITE_DATA; + int r; + + if (!dev->hw_ops->check_hw || !dev->reg.esd) { + 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 = core; + 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); + + /*init dynamic esd*/ + r = dev->hw_ops->write_trans(core->ts_dev, core->ts_dev->reg.esd, + &data, 1); + if (r < 0) + ts_err("failed init dynamic esd[ignore]"); + + goodix_ts_esd_on(core); + + return 0; +} + +static void goodix_ts_release_connects(struct goodix_ts_core *core_data) +{ + struct input_dev *input_dev = core_data->input_dev; + struct input_mt *mt = input_dev->mt; + int i; + + memset(&core_data->ts_event, 0, sizeof(core_data->ts_event)); + goodix_ts_report_finger(core_data->input_dev, + &core_data->ts_event.touch_data); + + if (mt) { + for (i = 0; i < mt->num_slots; 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); + } +} + +/** + * 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_device *ts_dev = core_data->ts_dev; + int r; + + ts_info("Suspend start"); + + /* + * 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; + + r = ext_module->funcs->before_suspend(core_data, + ext_module); + if (r == EVT_CANCEL_SUSPEND) { + mutex_unlock(&goodix_modules.mutex); + ts_info("Canceled by module:%s", + ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + + /* disable irq */ + goodix_ts_irq_enable(core_data, false); + + /* let touch ic work in sleep mode */ + if (ts_dev && ts_dev->hw_ops->suspend) + ts_dev->hw_ops->suspend(ts_dev); + atomic_set(&core_data->suspended, 1); + +#ifdef CONFIG_PINCTRL + if (core_data->pinctrl) { + r = pinctrl_select_state(core_data->pinctrl, + core_data->pin_sta_suspend); + if (r < 0) + ts_err("Failed to select active pinstate, r:%d", r); + } +#endif + + /* 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; + + r = ext_module->funcs->after_suspend(core_data, + ext_module); + if (r == 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_device *ts_dev = + core_data->ts_dev; + int r; + + ts_info("Resume start"); + + 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; + + r = ext_module->funcs->before_resume(core_data, + ext_module); + if (r == EVT_CANCEL_RESUME) { + mutex_unlock(&goodix_modules.mutex); + ts_info("Canceled by module:%s", + ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + +#ifdef CONFIG_PINCTRL + if (core_data->pinctrl) { + r = pinctrl_select_state(core_data->pinctrl, + core_data->pin_sta_active); + if (r < 0) + ts_err("Failed to select active pinstate, r:%d", r); + } +#endif + + atomic_set(&core_data->suspended, 0); + /* resume device */ + if (ts_dev && ts_dev->hw_ops->resume) + ts_dev->hw_ops->resume(ts_dev); + + 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; + + r = ext_module->funcs->after_resume(core_data, + ext_module); + if (r == EVT_CANCEL_RESUME) { + mutex_unlock(&goodix_modules.mutex); + ts_info("Canceled by module:%s", + ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + + goodix_ts_irq_enable(core_data, true); + disable_irq_wake(core_data->irq); + + /* + * notify resume event, inform the esd protector + * and charger detector to turn on the work + */ + ts_info("try notify resume"); + goodix_ts_blocking_notify(NOTIFY_RESUME, NULL); +out: + ts_debug("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_HAS_EARLYSUSPEND +/** + * goodix_ts_earlysuspend - Early suspend function + * Called by kernel during system suspend phrase + */ +static void goodix_ts_earlysuspend(struct early_suspend *h) +{ + struct goodix_ts_core *core_data = + container_of(h, struct goodix_ts_core, + early_suspend); + + goodix_ts_suspend(core_data); +} +/** + * goodix_ts_lateresume - Late resume function + * Called by kernel during system wakeup + */ +static void goodix_ts_lateresume(struct early_suspend *h) +{ + struct goodix_ts_core *core_data = + container_of(h, struct goodix_ts_core, + early_suspend); + + goodix_ts_resume(core_data); +} +#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 *ts_core = container_of(self, + struct goodix_ts_core, ts_notifier); + struct goodix_ts_device *ts_dev = ts_device(ts_core); + const struct goodix_ts_hw_ops *hw_ops = ts_hw_ops(ts_core); + int r; + + if (ts_core->init_stage < CORE_INIT_STAGE2) + return 0; + + ts_info("notify event type 0x%x", (unsigned int)action); + switch (action) { + case NOTIFY_FWUPDATE_START: + goodix_ts_irq_enable(ts_core, 0); + break; + case NOTIFY_FWUPDATE_SUCCESS: + case NOTIFY_FWUPDATE_FAILED: + r = hw_ops->read_version(ts_dev, &ts_dev->chip_version); + if (r < 0) + ts_info("failed read fw version info[ignore]"); + goodix_ts_irq_enable(ts_core, 1); + break; + default: + break; + } + return 0; +} + +#define TS_LOCKDOWN_REG 0xBDB4 +static int goodix_get_lockdown_info(struct goodix_ts_core *core_data) +{ + int ret = 0; + struct goodix_ts_device *ts_dev = core_data->ts_dev; + + ret = ts_dev->hw_ops->read(ts_dev, TS_LOCKDOWN_REG, + core_data->lockdown_info, GOODIX_LOCKDOWN_SIZE); + if (ret < 0) { + ts_err("get lockdown failed"); + return ret; + } + ts_info("lockdown info:%*ph", GOODIX_LOCKDOWN_SIZE, core_data->lockdown_info); + return 0; +} + +int goodix_ts_stage2_init(struct goodix_ts_core *core_data) +{ + int r; + struct goodix_ts_device *ts_dev = ts_device(core_data); + + /* send normal-cfg to firmware */ + r = ts_dev->hw_ops->send_config(ts_dev, &(ts_dev->normal_cfg)); + if (r < 0) { + ts_info("failed send normal config[ignore]"); + } + + r = ts_dev->hw_ops->read_version(ts_dev, &ts_dev->chip_version); + if (r < 0) + ts_info("failed read fw version info[ignore]"); + + /* get lockdown info */ + goodix_get_lockdown_info(core_data); + + /* alloc/config/register input device */ + r = goodix_ts_input_dev_config(core_data); + if (r < 0) { + ts_err("failed set input device"); + return r; + } + + if (ts_dev->board_data.pen_enable) { + r = goodix_ts_pen_dev_config(core_data); + if (r < 0) { + ts_err("failed set pen device"); + goto err_finger; + } + } + /* request irq line */ + r = goodix_ts_irq_setup(core_data); + if (r < 0) { + ts_info("failed set irq"); + goto exit; + } + ts_info("success register irq"); + +#ifdef CONFIG_FB + core_data->fb_notifier.notifier_call = goodix_ts_fb_notifier_callback; + if (fb_register_client(&core_data->fb_notifier)) + ts_err("Failed to register fb notifier client:%d", r); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + core_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + core_data->early_suspend.resume = goodix_ts_lateresume; + core_data->early_suspend.suspend = goodix_ts_earlysuspend; + register_early_suspend(&core_data->early_suspend); +#endif + /*create sysfs files*/ + goodix_ts_sysfs_init(core_data); + + /* create proc files */ + goodix_ts_procfs_init(core_data); + + /* esd protector */ + goodix_ts_esd_init(core_data); + return 0; +exit: + goodix_ts_pen_dev_remove(core_data); +err_finger: + goodix_ts_input_dev_remove(core_data); + return r; +} + +/** + * 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_ts_device *ts_device; + int r; + + ts_info("goodix_ts_probe IN"); + + ts_device = pdev->dev.platform_data; + if (!ts_device || !ts_device->hw_ops) { + ts_err("Invalid touch device"); + core_module_prob_sate = CORE_MODULE_PROB_FAILED; + return -ENODEV; + } + + core_data = kzalloc(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; + } + + goodix_core_module_init(); + /* touch core layer is a platform driver */ + core_data->pdev = pdev; + core_data->ts_dev = ts_device; + platform_set_drvdata(pdev, core_data); + + r = goodix_ts_power_init(core_data); + if (r < 0) + goto out; + + r = goodix_ts_power_on(core_data); + if (r < 0) + goto out; + +#ifdef CONFIG_PINCTRL + /* Pinctrl handle is optional. */ + r = goodix_ts_pinctrl_init(core_data); + if (!r && core_data->pinctrl) { + r = pinctrl_select_state(core_data->pinctrl, + core_data->pin_sta_active); + if (r < 0) + ts_err("Failed to select active pinstate, r:%d", r); + } +#endif + + /* get GPIO resource */ + r = goodix_ts_gpio_setup(core_data); + if (r < 0) + goto gpio_err; + + /* confirm it's goodix touch dev or not */ + r = ts_device->hw_ops->dev_confirm(ts_device); + if (r) { + ts_err("goodix device confirm failed"); + goto gpio_err; + } + + /* generic notifier callback */ + core_data->ts_notifier.notifier_call = goodix_generic_noti_callback; + goodix_ts_register_notifier(&core_data->ts_notifier); + + core_data->initialized = 1; + goodix_modules.core_data = core_data; + core_data->init_stage = CORE_INIT_STAGE1; + core_module_prob_sate = CORE_MODULE_PROB_SUCCESS; + + r = goodix_fw_update_init(core_data); + if (r) { + ts_err("failed init fwupdate module, %d", r); + goto gpio_err; + } + /* Try start a thread to get config-bin info */ + r = goodix_start_later_init(core_data); + if (r) { + ts_err("Failed start cfg_bin_proc, %d", r); + goto later_thread_err; + } + complete_all(&goodix_modules.core_comp); + ts_info("goodix_ts_core probe success"); + return 0; + +later_thread_err: + goodix_fw_update_uninit(); +gpio_err: + goodix_ts_power_off(core_data); +out: + kfree(core_data); + core_data = NULL; + core_module_prob_sate = CORE_MODULE_PROB_FAILED; + ts_err("goodix_ts_core failed, r:%d", r); + /* wakeup ext module register work */ + complete_all(&goodix_modules.core_comp); + return r; +} + +static int goodix_ts_remove(struct platform_device *pdev) +{ + struct goodix_ts_core *core_data = platform_get_drvdata(pdev); + struct goodix_ts_esd *ts_esd = &core_data->ts_esd; + + if (goodix_unregister_all_module()) + return -EBUSY; + + goodix_fw_update_uninit(); + goodix_ts_unregister_notifier(&core_data->ts_notifier); +#ifdef CONFIG_FB + fb_unregister_client(&core_data->fb_notifier); +#endif + core_data->initialized = 0; + 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_ts_input_dev_remove(core_data); + goodix_ts_pen_dev_remove(core_data); + goodix_ts_irq_enable(core_data, false); + + goodix_ts_power_off(core_data); + goodix_ts_sysfs_exit(core_data); + kfree(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); + ret = goodix_bus_init(); + 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); + goodix_bus_exit(); + return; +} +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/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_core.h b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_core.h new file mode 100644 index 000000000000..63a593dbef7e --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_core.h @@ -0,0 +1,772 @@ +/* + * Goodix Touchscreen Driver + * Core layer of touchdriver architecture. + * + * Copyright (C) 2019 - 2020 Goodix, Inc. + * Authors: Wang Yafei + * + * 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. + * + */ +#ifndef _GOODIX_TS_CORE_H_ +#define _GOODIX_TS_CORE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_OF +#include +#include +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif +#ifdef CONFIG_FB +#include +#include +#endif + +#define GOODIX_FLASH_CONFIG_WITH_ISP 1 +/* macros definition */ +#define GOODIX_CORE_DRIVER_NAME "goodix_ts" +#define GOODIX_PEN_DRIVER_NAME "goodix_ts,pen" +#define GOODIX_DRIVER_VERSION "v1.5.1.0" +#define GOODIX_BUS_RETRY_TIMES 3 +#define GOODIX_MAX_TOUCH 10 +#define GOODIX_CFG_MAX_SIZE 4096 +#define GOODIX_ESD_TICK_WRITE_DATA 0xAA +#define GOODIX_PID_MAX_LEN 8 +#define GOODIX_VID_MAX_LEN 8 + +#define GOODIX_DEFAULT_CFG_NAME "goodix_config.cfg" + +#define IC_TYPE_NONE 0 +#define IC_TYPE_NORMANDY 1 +#define IC_TYPE_NANJING 2 +#define IC_TYPE_YELLOWSTONE 3 + +#define GOODIX_TOUCH_EVENT 0x80 +#define GOODIX_REQUEST_EVENT 0x40 +#define GOODIX_GESTURE_EVENT 0x20 +#define GOODIX_HOTKNOT_EVENT 0x10 + +#define GOODIX_LOCKDOWN_SIZE 8 + +#define GOODIX_PEN_MAX_PRESSURE 4096 +#define GOODIX_MAX_TP_KEY 4 +#define GOODIX_MAX_PEN_KEY 2 + +#define TS_RAWDATA_BUFF_MAX 3000 +#define TS_RAWDATA_RESULT_MAX 100 +struct ts_rawdata_info{ + int used_size; //fill in rawdata size + s16 buff[TS_RAWDATA_BUFF_MAX]; + char result[TS_RAWDATA_RESULT_MAX]; +}; + +/* + * struct goodix_module - external modules container + * @head: external modules list + * @initilized: whether this struct is initilized + * @mutex: mutex lock + * @wq: workqueue to do register work + * @core_data: core_data pointer + */ +struct goodix_module { + struct list_head head; + bool initilized; + struct mutex mutex; + struct workqueue_struct *wq; + struct completion core_comp; + 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 + * @panel_max_key: max supported keys + * @pannel_key_map: key map + * @fw_name: name of the firmware image + */ +struct goodix_ts_board_data { + char avdd_name[24]; + char iovdd_name[24]; + unsigned int reset_gpio; + unsigned int irq_gpio; + int irq; + 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*/ + unsigned int panel_max_key; + unsigned int panel_key_map[GOODIX_MAX_TP_KEY]; + unsigned int x2x; + unsigned int y2y; + bool pen_enable; + unsigned int tp_key_num; + /*add end*/ + + const char *fw_name; + const char *cfg_bin_name; + bool esd_default_on; +}; + +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 */ +}; + +enum goodix_cfg_init_state { + TS_CFG_UNINITALIZED, + TS_CFG_STABLE, + TS_CFG_TEMP, +}; + +enum goodix_core_init_stage { + CORE_UNINIT, + CORE_INIT_FAIL, + CORE_INIT_STAGE1, + CORE_INIT_STAGE2 +}; + +/* + * struct goodix_ts_config - chip config data + * @initialized: cfg init state, 0 uninit, + * 1 init with stable config, 2 init with temp config. + * @name: name of this config + * @lock: mutex + * @reg_base: register base of config data + * @length: bytes of the config + * @delay: delay time after sending config + * @data: config data buffer + */ +struct goodix_ts_config { + int initialized; + char name[24]; + struct mutex lock; + unsigned int reg_base; + unsigned int length; + unsigned int delay; /*ms*/ + unsigned char data[GOODIX_CFG_MAX_SIZE]; +}; + +/* + * struct goodix_ts_cmd - command package + * @initialized: whether initialized + * @cmd_reg: command register + * @length: command length in bytes + * @cmds: command data + */ +#pragma pack(4) +struct goodix_ts_cmd { + u32 initialized; + u32 cmd_reg; + u32 length; + u8 cmds[8]; + }; +#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), +}; + +/* 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; +}; + +struct goodix_ts_key { + int status; + int code; +}; + +/* touch event data */ +struct goodix_touch_data { + int touch_num; + struct goodix_ts_coords coords[GOODIX_MAX_TOUCH]; + struct goodix_ts_key keys[GOODIX_MAX_TP_KEY]; +}; + +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 { + enum ts_event_type event_type; + struct goodix_touch_data touch_data; + struct goodix_pen_data pen_data; +}; + +/* + * struct goodix_ts_version - firmware version + * @valid: whether these infomation is valid + * @pid: product id string + * @vid: firmware version code + * @cid: customer id code + * @sensor_id: sendor id + */ +struct goodix_ts_version { + bool valid; + char pid[GOODIX_PID_MAX_LEN]; + char vid[GOODIX_VID_MAX_LEN]; + u8 cid; + u8 sensor_id; +}; + +struct goodix_ts_regs { + u16 cfg_send_flag; + + u16 version_base; + u8 version_len; + + u16 pid; + u8 pid_len; + + u16 vid; + u8 vid_len; + + u16 sensor_id; + u8 sensor_id_mask; + + u16 fw_mask; + u16 fw_status; + u16 cfg_addr; + u16 esd; + u16 command; + u16 gesture; + u16 coor; + u16 fw_request; + u16 proximity; +}; + +enum goodix_cfg_bin_state { + CFG_BIN_STATE_UNINIT, /* config bin uninit */ + CFG_BIN_STATE_ERROR, /* config bin encounter fatal error */ + CFG_BIN_STATE_INITIALIZED, /* config bin init success */ + CFG_BIN_STATE_TEMP, /* config bin need reparse */ +}; + +/* + * struct goodix_ts_device - ts device data + * @name: device name + * @version: reserved + * @bus_type: i2c or spi + * @ic_type: normandy or yellowstone + * @cfg_bin_state: see enum goodix_cfg_bin_state + * @fw_uptodate: set to 1 after do fw update + * @board_data: board data obtained from dts + * @normal_cfg: normal config data + * @highsense_cfg: high sense config data + * @hw_ops: hardware operations + * @chip_version: firmware version infomation + * @sleep_cmd: sleep commang + * @gesture_cmd: gesture command + * @dev: device pointer,may be a i2c or spi device + * @of_node: device node + */ +struct goodix_ts_device { + char *name; + int version; + int bus_type; + int ic_type; + int cfg_bin_state; + struct goodix_ts_regs reg; + struct goodix_ts_board_data board_data; + struct goodix_ts_config normal_cfg; + struct goodix_ts_config highsense_cfg; + const struct goodix_ts_hw_ops *hw_ops; + + struct goodix_ts_version chip_version; + struct device *dev; +}; + +/* + * struct goodix_ts_hw_ops - hardware opeartions + * @init: hardware initialization + * @reset: hardware reset + * @read: read data from touch device + * @write: write data to touch device + * @send_cmd: send command to touch device + * @send_config: send configuration data + * @read_version: read firmware version + * @event_handler: touch event handler + * @suspend: put touch device into low power mode + * @resume: put touch device into working mode + */ +struct goodix_ts_hw_ops { + int (*init)(struct goodix_ts_device *dev); + int (*dev_confirm)(struct goodix_ts_device *ts_dev); + int (*reset)(struct goodix_ts_device *dev); + int (*read)(struct goodix_ts_device *dev, unsigned int addr, + unsigned char *data, unsigned int len); + int (*write)(struct goodix_ts_device *dev, unsigned int addr, + unsigned char *data, unsigned int len); + int (*read_trans)(struct goodix_ts_device *dev, unsigned int addr, + unsigned char *data, unsigned int len); + int (*write_trans)(struct goodix_ts_device *dev, unsigned int addr, + unsigned char *data, unsigned int len); + int (*send_cmd)(struct goodix_ts_device *dev, struct goodix_ts_cmd *cmd); + int (*send_config)(struct goodix_ts_device *dev, + struct goodix_ts_config *config); + int (*read_config)(struct goodix_ts_device *dev, u8 *config_data, int size); + int (*read_version)(struct goodix_ts_device *dev, + struct goodix_ts_version *version); + int (*event_handler)(struct goodix_ts_device *dev, + struct goodix_ts_event *ts_event); + int (*check_hw)(struct goodix_ts_device *dev); + int (*suspend)(struct goodix_ts_device *dev); + int (*resume)(struct goodix_ts_device *dev); + int (*get_capacitance_data)(struct goodix_ts_device *dev, 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 { + struct delayed_work esd_work; + struct notifier_block esd_notifier; + struct goodix_ts_core *ts_core; + atomic_t esd_on; +}; + +/* + * struct godix_ts_core - core layer data struct + * @initialized: indicate core state, 1 ok, 0 bad + * @pdev: core layer platform device + * @ts_dev: hardware layer touch device + * @input_dev: input device + * @avdd: analog regulator + * @iovdd: analog regulator + * @pinctrl: pinctrl handler + * @pin_sta_active: active/normal pin state + * @pin_sta_suspend: suspend/sleep pin state + * @ts_event: touch event data struct + * @power_on: power on/off flag + * @irq: irq number + * @irq_enabled: irq enabled/disabled flag + * @suspended: suspend/resume flag + * @ts_notifier: generic notifier + * @ts_esd: esd protector structure + * @fb_notifier: framebuffer notifier + * @early_suspend: early suspend + */ +struct goodix_ts_core { + int initialized; + int init_stage; + int gesture_enable; + u8 lockdown_info[GOODIX_LOCKDOWN_SIZE]; + struct platform_device *pdev; + struct goodix_ts_device *ts_dev; + struct input_dev *input_dev; + struct input_dev *pen_dev; + + struct regulator *avdd; + struct regulator *iovdd; +#ifdef CONFIG_PINCTRL + struct pinctrl *pinctrl; + struct pinctrl_state *pin_sta_active; + struct pinctrl_state *pin_sta_suspend; +#endif + struct goodix_ts_event ts_event; + int power_on; + int irq; + size_t irq_trig_cnt; + + atomic_t irq_enabled; + atomic_t suspended; + + u32 drv_num; + u32 sen_num; + + struct notifier_block ts_notifier; + struct goodix_ts_esd ts_esd; + +#ifdef CONFIG_FB + struct notifier_block fb_notifier; +#elif defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#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, +}; + +struct goodix_ext_module; +/* external module's operations callback */ +struct goodix_ext_module_funcs { + int (*init)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*exit)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*before_reset)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*after_reset)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*before_suspend)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*after_suspend)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*before_resume)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*after_resume)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*irq_event)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); +}; + +/* + * struct goodix_ext_module - external module struct + * @list: list used to link into modules manager + * @name: name of external module + * @priority: module priority vlaue, zero is invalid + * @funcs: operations callback + * @priv_data: private data region + * @kobj: kobject + * @work: used to queue one work to do registration + */ +struct goodix_ext_module { + struct list_head list; + char *name; + enum goodix_ext_priority priority; + const struct goodix_ext_module_funcs *funcs; + void *priv_data; + struct kobject kobj; + struct work_struct work; +}; + +/* + * struct goodix_ext_attribute - exteranl attribute struct + * @attr: attribute + * @show: show interface of external attribute + * @store: store interface of external attribute + */ +struct goodix_ext_attribute { + struct attribute attr; + ssize_t (*show)(struct goodix_ext_module *, char *); + ssize_t (*store)(struct goodix_ext_module *, const char *, size_t); +}; + +/* external attrs helper macro */ +#define __EXTMOD_ATTR(_name, _mode, _show, _store) { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +} + +/* external attrs helper macro, used to define external attrs */ +#define DEFINE_EXTMOD_ATTR(_name, _mode, _show, _store) \ +static struct goodix_ext_attribute ext_attr_##_name = \ + __EXTMOD_ATTR(_name, _mode, _show, _store); + +/* + * get board data pointer + */ +static inline struct goodix_ts_board_data *board_data( + struct goodix_ts_core *core) +{ + if (!core || !core->ts_dev) + return NULL; + return &(core->ts_dev->board_data); +} + +/* + * get touch device pointer + */ +static inline struct goodix_ts_device *ts_device( + struct goodix_ts_core *core) +{ + if (!core) + return NULL; + return core->ts_dev; +} + +/* + * get touch hardware operations pointer + */ +static inline const struct goodix_ts_hw_ops *ts_hw_ops( + struct goodix_ts_core *core) +{ + if (!core || !core->ts_dev) + return NULL; + return core->ts_dev->hw_ops; +} + +/* + * checksum helper functions + * checksum can be u8/le16/be16/le32/be32 format + * NOTE: the caller shoule be responsible for the + * legality of @data and @size parameters, so be + * careful when call these functions. + */ +static inline u8 checksum_u8(u8 *data, u32 size) +{ + u8 checksum = 0; + u32 i; + + for (i = 0; i < size; i++) + checksum += data[i]; + return checksum; +} + +/* cal u8 data checksum for yellowston */ +static inline u16 checksum_u8_ys(u8 *data, u32 size) +{ + u16 checksum = 0; + u32 i; + + for (i = 0; i < size - 2; i++) + checksum += data[i]; + return checksum - (data[size - 2] << 8 | data[size - 1]); +} + +static inline u16 checksum_le16(u8 *data, u32 size) +{ + u16 checksum = 0; + u32 i; + + for (i = 0; i < size; i += 2) + checksum += le16_to_cpup((__le16 *)(data + i)); + return checksum; +} + +static inline u16 checksum_be16(u8 *data, u32 size) +{ + u16 checksum = 0; + u32 i; + + for (i = 0; i < size; i += 2) + checksum += be16_to_cpup((__be16 *)(data + i)); + return checksum; +} + +static inline u32 checksum_le32(u8 *data, u32 size) +{ + u32 checksum = 0; + u32 i; + + for (i = 0; i < size; i += 4) + checksum += le32_to_cpup((__le32 *)(data + i)); + return checksum; +} + +static inline u32 checksum_be32(u8 *data, u32 size) +{ + u32 checksum = 0; + u32 i; + + for (i = 0; i < size; i += 4) + checksum += be32_to_cpup((__be32 *)(data + i)); + return checksum; +} + +/* + * define event action + * EVT_xxx macros are used in opeartions callback + * defined in @goodix_ext_module_funcs to control + * the behaviors of event such as suspend/resume/irq_event. + * generally there are two types of behaviors: + * 1. you want the flow of this event be canceled, + * in this condition, you should return EVT_CANCEL_XXX in + * the operations callback. + * e.g. the firmware update module is updating + * the firmware, you want to cancel suspend flow, + * so you need to return EVT_CANCEL_SUSPEND in + * suspend callback function. + * 2. you want the flow of this event continue, in + * this condition, you should return EVT_HANDLED in + * the callback function. + * */ +#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 + +/* + * errno define + * Note: + * 1. bus read/write functions defined in hardware + * layer code(e.g. goodix_xxx_i2c.c) *must* return + * -EBUS if failed to transfer data on bus. + */ +#define EBUS 1000 +#define ETIMEOUT 1001 +#define ECHKSUM 1002 +#define EMEMCMP 1003 + +//#define CONFIG_GOODIX_DEBUG +/* log macro */ +#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 boot_log(fmt, arg...) g_info(fmt, ##arg) +#ifdef CONFIG_GOODIX_DEBUG +#define ts_debug(fmt, arg...) pr_info("[GTP-DBG][%s:%d] "fmt"\n", __func__, __LINE__, ##arg) +#else +#define ts_debug(fmt, arg...) do {} while (0) +#endif + +/** + * 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_unregister_all_module(void); + +/** + * goodix_ts_irq_enable - Enable/Disable a irq + + * @core_data: pointer to touch core data + * enable: enable or disable irq + * return: 0 ok, <0 failed + */ +int goodix_ts_irq_enable(struct goodix_ts_core *core_data, bool enable); + +struct kobj_type *goodix_get_default_ktype(void); + +/** + * 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); + + +/** + * * 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 *core_data); + +/** + * 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 *core_data); + +int goodix_ts_irq_setup(struct goodix_ts_core *core_data); + +int goodix_ts_esd_init(struct goodix_ts_core *core); + +int goodix_ts_register_notifier(struct notifier_block *nb); + +int goodix_ts_fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data); + +int goodix_bus_init(void); +void goodix_bus_exit(void); + +int goodix_do_fw_update(int mode); +int gtx8_get_rawdata(void *tsdev, struct ts_rawdata_info *info); +void goodix_rotate_abcd2cbad(int tx, int rx, s16 *data); +int goodix_get_cfg_value(struct goodix_ts_core *core_data, + u8 *config, u8 *buf, u8 len, u8 sub_bag_num, u8 offset); + +#endif diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_gesture.c b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_gesture.c new file mode 100644 index 000000000000..f912770cd6bf --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_gesture.c @@ -0,0 +1,443 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "goodix_ts_core.h" + +#define GSX_GESTURE_CMD 0x08 + +#define QUERYBIT(longlong, bit) (!!(longlong[bit/8] & (1 << bit%8))) + +#define GSX_MAX_KEY_DATA_LEN 64 +#define GSX_KEY_DATA_LEN 37 +#define GSX_KEY_DATA_LEN_YS 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: store valid gesture type,each bit stand for a gesture + * @gesture_data: gesture data + * @gesture_ts_cmd: gesture command data + */ +struct gesture_module { + atomic_t registered; + unsigned int kobj_initialized; + rwlock_t rwlock; + unsigned char gesture_type[GSX_GESTURE_TYPE_LEN]; + unsigned char gesture_data[GSX_MAX_KEY_DATA_LEN]; + struct goodix_ext_module module; + struct goodix_ts_cmd cmd; +}; + +static int gsx_enter_gesture_mode(struct goodix_ts_device *ts_dev); +static struct gesture_module *gsx_gesture; /*allocated in gesture init module*/ + +/** + * 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; + + if (atomic_read(&gsx_gesture->registered) != 1) { + ts_info("Gesture module not register!"); + return -EPERM; + } + type = kzalloc(256, GFP_KERNEL); + if (!type) + return -ENOMEM; + read_lock(&gsx_gesture->rwlock); + for (i = 0; i < 256; i++) { + if (QUERYBIT(gsx_gesture->gesture_type, i)) { + type[count] = i; + count++; + } + } + type[count] = '\0'; + if (count > 0) + ret = scnprintf(buf, PAGE_SIZE, "%s", 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) +{ + struct goodix_ts_core *cd = module->priv_data; + + return scnprintf(buf, PAGE_SIZE, "%d\n", cd->gesture_enable); +} + +static ssize_t gsx_gesture_enable_store(struct goodix_ext_module *module, + const char *buf, size_t count) +{ + struct goodix_ts_core *cd = module->priv_data; + unsigned int tmp; + + if (sscanf(buf, "%u", &tmp) != 1) { + ts_info("Parameter illegal"); + return -EINVAL; + } + ts_debug("Tmp value =%d", tmp); + + if (tmp == 1) { + cd->gesture_enable = 1; + } else if (tmp == 0) { + cd->gesture_enable = 0; + } else { + ts_err("Parameter error!"); + return -EINVAL; + } + return count; +} + +static ssize_t gsx_gesture_data_show(struct goodix_ext_module *module, + char *buf) +{ + struct goodix_ts_core *cd = module->priv_data; + ssize_t count; + + if (atomic_read(&gsx_gesture->registered) != 1) { + ts_info("Gesture module not register!"); + return -EPERM; + } + if (!buf) { + ts_info("Parameter error!"); + return -EPERM; + } + read_lock(&gsx_gesture->rwlock); + if (cd->ts_dev->ic_type == IC_TYPE_YELLOWSTONE) + count = scnprintf(buf, PAGE_SIZE, "gesture type code:0x%x\n", + gsx_gesture->gesture_data[4]); + else + count = scnprintf(buf, PAGE_SIZE, "gesture type code:0x%x\n", + gsx_gesture->gesture_data[2]); + 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_enter_gesture_mode(struct goodix_ts_device *ts_dev) +{ + if (!gsx_gesture->cmd.initialized) { + if (!ts_dev->reg.command) { + ts_err("command reg can not be null"); + return -EINVAL; + } + if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) { + gsx_gesture->cmd.cmd_reg = ts_dev->reg.command; + gsx_gesture->cmd.length = 5; + gsx_gesture->cmd.cmds[0] = GSX_GESTURE_CMD; + gsx_gesture->cmd.cmds[1] = 0x0; + gsx_gesture->cmd.cmds[2] = 0x0; + gsx_gesture->cmd.cmds[3] = 0x0; + gsx_gesture->cmd.cmds[4] = GSX_GESTURE_CMD; + gsx_gesture->cmd.initialized = 1; + + } else { + gsx_gesture->cmd.cmd_reg = ts_dev->reg.command; + gsx_gesture->cmd.length = 3; + gsx_gesture->cmd.cmds[0] = GSX_GESTURE_CMD; + gsx_gesture->cmd.cmds[1] = 0x01; + gsx_gesture->cmd.cmds[2] = 0 - gsx_gesture->cmd.cmds[0] - gsx_gesture->cmd.cmds[1]; + gsx_gesture->cmd.initialized = 1; + } + } + + return ts_dev->hw_ops->send_cmd(ts_dev, &gsx_gesture->cmd); +} + +static int gsx_gesture_init(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + int i, ret = -EINVAL; + struct goodix_ts_device *ts_dev = core_data->ts_dev; + + if (!core_data || !ts_dev->hw_ops->write || !ts_dev->hw_ops->read) { + ts_err("Register gesture module failed, ts_core unsupported"); + goto exit_gesture_init; + } + + memset(gsx_gesture->gesture_type, 0, GSX_GESTURE_TYPE_LEN); + memset(gsx_gesture->gesture_data, 0xff, + sizeof(gsx_gesture->gesture_data)); + module->priv_data = core_data; + + ts_debug("Set gesture type manually"); + /* set all bit to 1 to enable all gesture wakeup */ + memset(gsx_gesture->gesture_type, 0xff, GSX_GESTURE_TYPE_LEN); + + if (gsx_gesture->kobj_initialized) { + ret = 0; + goto exit_gesture_init; + } + + ret = kobject_init_and_add(&module->kobj, goodix_get_default_ktype(), + &core_data->pdev->dev.kobj, "gesture"); + + if (ret) { + ts_err("Create gesture sysfs node error!"); + goto exit_gesture_init; + } + + ret = 0; + for (i = 0; i < ARRAY_SIZE(gesture_attrs) && !ret; i++) + ret = sysfs_create_file(&module->kobj, &gesture_attrs[i].attr); + if (ret) { + ts_err("failed create gst sysfs files"); + while (--i >= 0) + sysfs_remove_file(&module->kobj, &gesture_attrs[i].attr); + + kobject_put(&module->kobj); + goto exit_gesture_init; + } + + gsx_gesture->kobj_initialized = 1; + core_data->gesture_enable = 1; + +exit_gesture_init: + return ret; +} + +static int gsx_gesture_exit(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + atomic_set(&gsx_gesture->registered, 0); + return 0; +} + +/** + * gsx_gesture_ist - Gesture Irq handle + * This functions is excuted when interrupt happended and + * ic in doze mode. + * + * @core_data: 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 *core_data, + struct goodix_ext_module *module) +{ + int ret; + int key_data_len = 0; + u8 clear_reg = 0, checksum = 0, gsx_type = 0; + u8 temp_data[GSX_MAX_KEY_DATA_LEN]; + struct goodix_ts_device *ts_dev = core_data->ts_dev; + + if (!core_data->gesture_enable || atomic_read(&core_data->suspended) == 0) + return EVT_CONTINUE; + + if (!ts_dev->reg.gesture) { + ts_err("gesture reg can't be null"); + return EVT_CONTINUE; + } + /* get ic gesture state*/ + if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) + key_data_len = GSX_KEY_DATA_LEN_YS; + else + key_data_len = GSX_KEY_DATA_LEN; + + ret = ts_dev->hw_ops->read_trans(ts_dev, ts_dev->reg.gesture, + temp_data, key_data_len); + if (ret < 0 || ((temp_data[0] & GOODIX_GESTURE_EVENT) == 0)) { + ts_debug("invalid gesture event, ret=%d, temp_data[0]=0x%x", + ret, temp_data[0]); + goto re_send_ges_cmd; + } + + if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) + checksum = checksum_u8_ys(temp_data, key_data_len); + else + checksum = checksum_u8(temp_data, key_data_len); + if (checksum) { + ts_err("Gesture data checksum error:0x%x", checksum); + ts_info("Gesture data %*ph", + (int)sizeof(temp_data), temp_data); + goto re_send_ges_cmd; + } + + ts_debug("Gesture data:"); + ts_debug("data[0-4]0x%x, 0x%x, 0x%x, 0x%x", temp_data[0], temp_data[1], + temp_data[2], temp_data[3]); + + write_lock(&gsx_gesture->rwlock); + memcpy(gsx_gesture->gesture_data, temp_data, key_data_len); + write_unlock(&gsx_gesture->rwlock); + + if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) + gsx_type = temp_data[4]; + else + gsx_type = temp_data[2]; + + if (gsx_type == 0xCC) { /* double tap */ + /* do resume routine */ + ts_info("Gesture match success, resume IC"); + input_report_key(core_data->input_dev, KEY_POWER, 1); + input_sync(core_data->input_dev); + input_report_key(core_data->input_dev, KEY_POWER, 0); + input_sync(core_data->input_dev); + goto gesture_ist_exit; + } else { + ts_info("Unsupported gesture:%x", temp_data[2]); + } + +re_send_ges_cmd: + if (gsx_enter_gesture_mode(core_data->ts_dev)) + ts_info("warning: failed re_send gesture cmd\n"); +gesture_ist_exit: + ts_dev->hw_ops->write_trans(ts_dev, ts_dev->reg.gesture, + &clear_reg, 1); + return EVT_CANCEL_IRQEVT; +} + +/** + * gsx_gesture_before_suspend - execute gesture suspend routine + * This functions is excuted to set ic into doze mode + * + * @core_data: 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 *core_data, + struct goodix_ext_module *module) +{ + int ret; + const struct goodix_ts_hw_ops *hw_ops = core_data->ts_dev->hw_ops; + + if (!core_data->gesture_enable || !hw_ops) + return EVT_CONTINUE; + + atomic_set(&core_data->suspended, 1); + ret = gsx_enter_gesture_mode(core_data->ts_dev); + if (ret != 0) + ts_err("failed enter gesture mode"); + else + ts_info("Set IC in gesture mode"); + + enable_irq_wake(core_data->irq); + + return EVT_CANCEL_SUSPEND; +} + +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 +}; + +static int __init goodix_gsx_gesture_init(void) +{ + /* initialize core_data->ts_dev->gesture_cmd */ + int result; + ts_info("gesture module init"); + gsx_gesture = kzalloc(sizeof(struct gesture_module), GFP_KERNEL); + if (!gsx_gesture) + result = -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; + gsx_gesture->kobj_initialized = 0; + atomic_set(&gsx_gesture->registered, 0); + rwlock_init(&gsx_gesture->rwlock); + result = goodix_register_ext_module(&(gsx_gesture->module)); + if (result == 0) + atomic_set(&gsx_gesture->registered, 1); + + return result; +} + +static void __exit goodix_gsx_gesture_exit(void) +{ + int i, ret; + ts_info("gesture module exit"); + if (atomic_read(&gsx_gesture->registered)) { + ret = goodix_unregister_ext_module(&gsx_gesture->module); + atomic_set(&gsx_gesture->registered, 0); + } + if (gsx_gesture->kobj_initialized) { + 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); +} + +late_initcall(goodix_gsx_gesture_init); +module_exit(goodix_gsx_gesture_exit); + +MODULE_DESCRIPTION("Goodix gsx Touchscreen Gesture Module"); +MODULE_AUTHOR("Goodix, Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_i2c.c b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_i2c.c new file mode 100644 index 000000000000..0a247cc080b9 --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_i2c.c @@ -0,0 +1,1997 @@ +/* + * Goodix I2C Module + * Hardware interface layer of touchdriver architecture. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "goodix_ts_core.h" +#include "goodix_cfg_bin.h" + +#define TS_DRIVER_NAME "gtx8" +#define I2C_MAX_TRANSFER_SIZE 60 +#define TS_ADDR_LENGTH 2 +#define TS_DOZE_ENABLE_RETRY_TIMES 3 +#define TS_DOZE_DISABLE_RETRY_TIMES 9 +#define TS_WAIT_CFG_READY_RETRY_TIMES 30 +#define TS_WAIT_CMD_FREE_RETRY_TIMES 10 + +#define TS_REG_COORDS_BASE 0x824E +#define TS_REG_CMD 0x8040 +#define TS_REG_REQUEST 0x8044 +#define TS_REG_VERSION 0x8240 +#define TS_REG_CFG_BASE 0x8050 +#define TS_REG_DOZE_CTRL 0x30F0 +#define TS_REG_DOZE_STAT 0x3100 +#define TS_REG_ESD_TICK_R 0x3103 + +#define CFG_XMAX_OFFSET (0x8052 - 0x8050) +#define CFG_YMAX_OFFSET (0x8054 - 0x8050) + +#define REQUEST_HANDLED 0x00 +#define REQUEST_CONFIG 0x01 +#define REQUEST_BAKREF 0x02 +#define REQUEST_RESET 0x03 +#define REQUEST_RELOADFW 0x05 +#define REQUEST_IDLE 0xff + +#define COMMAND_SLEEP 0x05 +#define COMMAND_CLOSE_HID 0xaa +#define COMMAND_START_SEND_CFG 0x80 +#define COMMAND_END_SEND_CFG 0x83 +#define COMMAND_SEND_SMALL_CFG 0x81 +#define COMMAND_SEND_CFG_PREPARE_OK 0x82 +#define COMMAND_START_READ_CFG 0x86 +#define COMMAND_READ_CFG_PREPARE_OK 0x85 +#define COMMAND_END_SEND_CFG_YS 0x7D + +#define BYTES_PER_COORD 8 +#define TS_MAX_SENSORID 5 +#define TS_CFG_HEAD_LEN 4 +#define TS_CFG_HEAD_LEN_YS 5 +#define TS_CFG_BAG_NUM_INDEX 2 +#define TS_CFG_BAG_START_INDEX 4 + +#define TS_DOZE_DISABLE_DATA 0xAA +#define TS_DOZE_CLOSE_OK_DATA 0xBB +#define TS_DOZE_ENABLE_DATA 0xCC +#define TS_CMD_REG_READY 0xFF +#define TS_CMD_CFG_ERR 0x7E +#define TS_CMD_CFG_OK 0x7F + +#define POINT_TYPE_STYLUS 0x01 +#define POINT_TYPE_STYLUS_HOVER 0x03 + +enum TS_SEND_CFG_REPLY { + TS_CFG_REPLY_PKGS_ERR = 0x01, + TS_CFG_REPLY_CHKSUM_ERR = 0x02, + TS_CFG_REPLY_DATA_ERR = 0x03, + TS_CFG_REPLY_DATA_EQU = 0x07, +}; + +#define IRQ_HEAD_LEN_YS 8 +#define IRQ_HEAD_LEN_NOR 2 + +#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 r, err; + + r = of_property_read_u32(node, "goodix,panel-max-x", + &board_data->panel_max_x); + if (r) + err = -ENOENT; + + r = of_property_read_u32(node, "goodix,panel-max-y", + &board_data->panel_max_y); + if (r) + err = -ENOENT; + + r = of_property_read_u32(node, "goodix,panel-max-w", + &board_data->panel_max_w); + if (r) + err = -ENOENT; + + board_data->swap_axis = of_property_read_bool(node, + "goodix,swap-axis"); + board_data->x2x = of_property_read_bool(node, "goodix,x2x"); + board_data->y2y = of_property_read_bool(node, "goodix,y2y"); + + 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) +{ + struct property *prop; + const char *name_tmp; + int r; + + if (!board_data) { + ts_err("invalid board data"); + return -EINVAL; + } + + 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 form 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 form 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 xyz resolutions */ + r = goodix_parse_dt_resolution(node, board_data); + if (r < 0) { + ts_err("Failed to parse resolutions:%d", r); + return r; + } + + /* key map */ + prop = of_find_property(node, "goodix,panel-key-map", NULL); + if (prop && prop->length) { + if (prop->length / sizeof(u32) > GOODIX_MAX_TP_KEY) { + ts_err("Size of panel-key-map is invalid"); + return r; + } + + board_data->panel_max_key = prop->length / sizeof(u32); + board_data->tp_key_num = prop->length / sizeof(u32); + r = of_property_read_u32_array(node, + "goodix,panel-key-map", + &board_data->panel_key_map[0], + board_data->panel_max_key); + if (r) { + ts_err("failed get key map, %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_info("***key:%d, %d, %d, %d", + board_data->panel_key_map[0], board_data->panel_key_map[1], + board_data->panel_key_map[2], board_data->panel_key_map[3]); + + 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 + +int goodix_i2c_test(struct goodix_ts_device *dev) +{ +#define TEST_ADDR 0x4100 +#define TEST_LEN 1 + struct i2c_client *client = to_i2c_client(dev->dev); + unsigned char test_buf[TEST_LEN + 1], addr_buf[2]; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = !I2C_M_RD, + .buf = &addr_buf[0], + .len = TS_ADDR_LENGTH, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .buf = &test_buf[0], + .len = TEST_LEN, + } + }; + + msgs[0].buf[0] = (TEST_ADDR >> 8) & 0xFF; + msgs[0].buf[1] = TEST_ADDR & 0xFF; + + if (likely(i2c_transfer(client->adapter, msgs, 2) == 2)) + return 0; + + /* test failed */ + return -EINVAL; +} + +/* confirm current device is goodix or not. + * If confirmed 0 will return. + */ +static int goodix_ts_dev_confirm(struct goodix_ts_device *ts_dev) +{ +#define DEV_CONFIRM_RETRY 3 + int retry; + + for (retry = 0; retry < DEV_CONFIRM_RETRY; retry++) { + gpio_direction_output(ts_dev->board_data.reset_gpio, 0); + udelay(2000); + gpio_direction_output(ts_dev->board_data.reset_gpio, 1); + mdelay(5); + if (!goodix_i2c_test(ts_dev)) { + msleep(95); + return 0; + } + } + return -EINVAL; +} + +/** + * goodix_i2c_read_trans - read device register through i2c bus + * @dev: pointer to device data + * @addr: register address + * @data: read buffer + * @len: bytes to read + * return: 0 - read ok, < 0 - i2c transter error + */ +int goodix_i2c_read_trans(struct goodix_ts_device *dev, unsigned int reg, + unsigned char *data, unsigned int len) +{ + struct i2c_client *client = to_i2c_client(dev->dev); + unsigned int transfer_length = 0; + unsigned int pos = 0, address = reg; + unsigned char get_buf[64], addr_buf[2]; + int retry, r = 0; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = !I2C_M_RD, + .buf = &addr_buf[0], + .len = TS_ADDR_LENGTH, + }, { + .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(I2C_MAX_TRANSFER_SIZE < len + ? I2C_MAX_TRANSFER_SIZE : 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 >> 8) & 0xFF; + msgs[0].buf[1] = 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); + msleep(20); + } + if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) { + ts_err("I2c read failed,dev:%02x,reg:%04x,size:%u", + client->addr, reg, len); + r = -EBUS; + goto read_exit; + } + } + +read_exit: + if (unlikely(len >= sizeof(get_buf))) + kfree(msgs[1].buf); + return r; +} + +/** + * goodix_i2c_write_trans - write device register through i2c bus + * @dev: pointer to device data + * @addr: register address + * @data: write buffer + * @len: bytes to write + * return: 0 - write ok; < 0 - i2c transter error. + */ +int goodix_i2c_write_trans(struct goodix_ts_device *dev, unsigned int reg, + unsigned char *data, unsigned int len) +{ + struct i2c_client *client = to_i2c_client(dev->dev); + unsigned int pos = 0, transfer_length = 0; + unsigned int address = reg; + unsigned char put_buf[64]; + int retry, r = 0; + struct i2c_msg msg = { + .addr = client->addr, + .flags = !I2C_M_RD, + }; + + if (likely(len + TS_ADDR_LENGTH < sizeof(put_buf))) { + /* code optimize,use stack memory*/ + msg.buf = &put_buf[0]; + } else { + msg.buf = kmalloc(I2C_MAX_TRANSFER_SIZE < len + TS_ADDR_LENGTH + ? I2C_MAX_TRANSFER_SIZE : len + TS_ADDR_LENGTH, + GFP_KERNEL); + if (msg.buf == NULL) + return -ENOMEM; + } + + while (pos != len) { + if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH)) + transfer_length = I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH; + else + transfer_length = len - pos; + + msg.buf[0] = (unsigned char)((address >> 8) & 0xFF); + msg.buf[1] = (unsigned char)(address & 0xFF); + msg.len = transfer_length + 2; + memcpy(&msg.buf[2], &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 = -EBUS; + goto write_exit; + } + } + +write_exit: + if (likely(len + TS_ADDR_LENGTH >= sizeof(put_buf))) + kfree(msg.buf); + return r; +} + + +/** + * goodix_set_i2c_doze_mode - disable or enable doze mode + * @dev: pointer to device data + * @enable: true/flase + * return: 0 - ok; < 0 - error. + * This func must be used in pairs, when you disable doze + * mode, then you must enable it again. + * Between set_doze_false and set_doze_true, do not reset + * IC! + */ +static int goodix_set_i2c_doze_mode(struct goodix_ts_device *dev, int enable) +{ + static DEFINE_MUTEX(doze_mode_lock); + static int doze_mode_set_count; + int result = -EINVAL; + int i; + u8 w_data, r_data; + + if (dev->ic_type != IC_TYPE_NORMANDY) + return 0; + + mutex_lock(&doze_mode_lock); + + if (enable) { + if (doze_mode_set_count != 0) + doze_mode_set_count--; + + /*when count equal 0, allow ic enter doze mode*/ + if (doze_mode_set_count == 0) { + w_data = TS_DOZE_ENABLE_DATA; + for (i = 0; i < TS_DOZE_ENABLE_RETRY_TIMES; i++) { + result = goodix_i2c_write_trans(dev, + TS_REG_DOZE_CTRL, &w_data, 1); + if (!result) { + result = 0; + goto exit; + } + usleep_range(1000, 1100); + } + if (i >= TS_DOZE_ENABLE_RETRY_TIMES) + ts_err("i2c doze mode enable failed"); + } else { + /*ts_info("doze count not euqal 0, + * so skip doze mode enable"); + */ + result = 0; + goto exit; + } + } else { + doze_mode_set_count++; + + if (doze_mode_set_count == 1) { + w_data = TS_DOZE_DISABLE_DATA; + goodix_i2c_write_trans(dev, TS_REG_DOZE_CTRL, + &w_data, 1); + usleep_range(1000, 1100); + for (i = 0; i < TS_DOZE_DISABLE_RETRY_TIMES; i++) { + goodix_i2c_read_trans(dev, + TS_REG_DOZE_STAT, &r_data, 1); + if (TS_DOZE_CLOSE_OK_DATA == r_data) { + result = 0; + goto exit; + } else if (0xAA != r_data) { + w_data = TS_DOZE_DISABLE_DATA; + goodix_i2c_write_trans(dev, + TS_REG_DOZE_CTRL, &w_data, 1); + } + usleep_range(10000, 10100); + } + ts_err("doze mode disable FAILED"); + } else { + result = 0; + goto exit; + } + } + +exit: + mutex_unlock(&doze_mode_lock); + return result; +} + +/** + * goodix_i2c_write - write device register through i2c bus + * @dev: pointer to device data + * @addr: register address + * @data: write buffer + * @len: bytes to write + * return: 0 - write ok; < 0 - i2c transter error. + */ +int goodix_i2c_write(struct goodix_ts_device *dev, unsigned int reg, + unsigned char *data, unsigned int len) +{ + int r = -EINVAL; + + if (goodix_set_i2c_doze_mode(dev, false)) { + ts_err(" faild disable doze, i2c write:0x%04x", reg); + goto exit; + } + r = goodix_i2c_write_trans(dev, reg, data, len); + +exit: + if (goodix_set_i2c_doze_mode(dev, true)) + ts_err("failed enable doze write:0x%04x", reg); + + return r; +} + +/** + * goodix_i2c_read - read device register through i2c bus + * @dev: pointer to device data + * @addr: register address + * @data: read buffer + * @len: bytes to read + * return: 0 - read ok, < 0 - i2c transter error + */ +int goodix_i2c_read(struct goodix_ts_device *dev, unsigned int reg, + unsigned char *data, unsigned int len) +{ + int r = -EINVAL; + + if (goodix_set_i2c_doze_mode(dev, false)) { + ts_err("failed disable doze:0x%04x", reg); + goto exit; + } + r = goodix_i2c_read_trans(dev, reg, data, len); + +exit: + if (goodix_set_i2c_doze_mode(dev, true)) + ts_err("failed enable doze :0x%04x", reg); + + return r; +} + +/** + * goodix_i2c_write_trans_once + * write device register through i2c bus, no retry + * @dev: pointer to device data + * @addr: register address + * @data: write buffer + * @len: bytes to write + * return: 0 - write ok; < 0 - i2c transter error. + */ +int goodix_i2c_write_trans_once(struct goodix_ts_device *dev, unsigned int reg, + unsigned char *data, unsigned int len) +{ + struct i2c_client *client = to_i2c_client(dev->dev); + unsigned int pos = 0, transfer_length = 0; + unsigned int address = reg; + unsigned char put_buf[64]; + struct i2c_msg msg = { + .addr = client->addr, + .flags = !I2C_M_RD, + }; + + if (likely(len + TS_ADDR_LENGTH < sizeof(put_buf))) { + /* code optimize,use stack memory*/ + msg.buf = &put_buf[0]; + } else { + msg.buf = kmalloc(I2C_MAX_TRANSFER_SIZE < len + TS_ADDR_LENGTH + ? I2C_MAX_TRANSFER_SIZE : len + TS_ADDR_LENGTH, + GFP_KERNEL); + if (msg.buf == NULL) { + ts_err("Malloc failed"); + return -ENOMEM; + } + } + + while (pos != len) { + if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH)) + transfer_length = I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH; + else + transfer_length = len - pos; + + msg.buf[0] = (unsigned char)((address >> 8) & 0xFF); + msg.buf[1] = (unsigned char)(address & 0xFF); + msg.len = transfer_length + 2; + memcpy(&msg.buf[2], &data[pos], transfer_length); + + i2c_transfer(client->adapter, &msg, 1); + pos += transfer_length; + address += transfer_length; + } + + if (likely(len + TS_ADDR_LENGTH >= sizeof(put_buf))) + kfree(msg.buf); + return 0; +} + +static void goodix_cmd_init(struct goodix_ts_device *dev, + struct goodix_ts_cmd *ts_cmd, + u8 cmds, u16 cmd_data, u32 reg_addr) +{ + u16 checksum = 0; + ts_cmd->initialized = false; + if (!reg_addr || !cmds) + return; + + if (dev->ic_type == IC_TYPE_YELLOWSTONE) { + ts_cmd->cmd_reg = reg_addr; + ts_cmd->length = 5; + ts_cmd->cmds[0] = cmds; + ts_cmd->cmds[1] = (cmd_data >> 8) & 0xFF; + ts_cmd->cmds[2] = cmd_data & 0xFF; + checksum = ts_cmd->cmds[0] + ts_cmd->cmds[1] + + ts_cmd->cmds[2]; + ts_cmd->cmds[3] = (checksum >> 8) & 0xFF; + ts_cmd->cmds[4] = checksum & 0xFF; + ts_cmd->initialized = true; + } else if (dev->ic_type == IC_TYPE_NORMANDY) { + ts_cmd->cmd_reg = reg_addr; + ts_cmd->length = 3; + ts_cmd->cmds[0] = cmds; + ts_cmd->cmds[1] = cmd_data & 0xFF; + ts_cmd->cmds[2] = 0 - cmds - cmd_data; + ts_cmd->initialized = true; + } else { + ts_err("unsupported ic type"); + } +} + +/** + * goodix_send_command - seng cmd to firmware + * + * @dev: pointer to device + * @cmd: pointer to command struct which cotain command data + * Returns 0 - succeed,<0 - failed + */ +int goodix_send_command(struct goodix_ts_device *dev, + struct goodix_ts_cmd *cmd) +{ + int ret; + + if (!cmd || !cmd->initialized) + return -EINVAL; + + ret = goodix_i2c_write(dev, cmd->cmd_reg, cmd->cmds, cmd->length); + + return ret; +} + +static int goodix_read_version(struct goodix_ts_device *dev, + struct goodix_ts_version *version) +{ + u8 buffer[GOODIX_PID_MAX_LEN + 1]; + u8 temp_buf[256]; + u16 checksum = 0; + u8 pid_read_len = dev->reg.pid_len; + u8 vid_read_len = dev->reg.vid_len; + u8 sensor_id_mask = dev->reg.sensor_id_mask; + int r; + + if (!version) { + ts_err("pointer of version is NULL"); + return -EINVAL; + } + version->valid = false; + + /*check reg info valid*/ + if (!dev->reg.pid || !dev->reg.sensor_id || !dev->reg.vid) { + ts_err("reg is NULL, pid:0x%04x, vid:0x%04x, sensor_id:0x%04x", + dev->reg.pid, dev->reg.vid, dev->reg.sensor_id); + return -EINVAL; + } + if (!pid_read_len || pid_read_len > GOODIX_PID_MAX_LEN || + !vid_read_len || vid_read_len > GOODIX_VID_MAX_LEN) { + ts_err("invalied pid vid length, pid_len:%d, vid_len:%d", + pid_read_len, vid_read_len); + return -EINVAL; + } + + /*disable doze mode, just valid for normandy + * this func must be used in pairs + */ + if (goodix_set_i2c_doze_mode(dev, false)) { + ts_err("failed disable doze"); + r = -EINVAL; + goto exit; + } + + /*check checksum*/ + if (dev->reg.version_base) { + r = goodix_i2c_read(dev, dev->reg.version_base, + temp_buf, dev->reg.version_len); + if (r < 0) { + ts_err("Read version base failed, reg:0x%02x, len:%d", + dev->reg.version_base, dev->reg.version_len); + if (version) + version->valid = false; + goto exit; + } + + if (dev->ic_type == IC_TYPE_YELLOWSTONE) + checksum = checksum_u8_ys(temp_buf, dev->reg.version_len); + else + checksum = checksum_u8(temp_buf, dev->reg.version_len); + if (checksum) { + ts_err("checksum error:0x%02x, base:0x%02x, len:%d", + checksum, dev->reg.version_base, + dev->reg.version_len); + ts_err("%*ph", (int)(dev->reg.version_len / 2), + temp_buf); + ts_err("%*ph", (int)(dev->reg.version_len - + dev->reg.version_len / 2), + &temp_buf[dev->reg.version_len / 2]); + + if (version) + version->valid = false; + r = -EINVAL; + goto exit; + } + } + + /*read pid*/ + memset(buffer, 0, sizeof(buffer)); + memset(version->pid, 0, sizeof(version->pid)); + r = goodix_i2c_read(dev, dev->reg.pid, buffer, pid_read_len); + if (r < 0) { + ts_err("Read pid failed"); + if (version) + version->valid = false; + goto exit; + } + + /* check pid is digit or not, current we only support digital pid */ + if (!isdigit(buffer[0]) || !isdigit(buffer[1])) { + ts_err("pid not digit: 0x%x,0x%x", buffer[0], buffer[1]); + r = -EINVAL; + goto exit; + } + + memcpy(version->pid, buffer, pid_read_len); + + /*read vid*/ + memset(buffer, 0, sizeof(buffer)); + memset(version->vid, 0, sizeof(version->vid)); + r = goodix_i2c_read(dev, dev->reg.vid, buffer, vid_read_len); + if (r < 0) { + ts_err("Read vid failed"); + if (version) + version->valid = false; + goto exit; + } + memcpy(version->vid, buffer, vid_read_len); + + /*read sensor_id*/ + memset(buffer, 0, sizeof(buffer)); + r = goodix_i2c_read(dev, dev->reg.sensor_id, buffer, 1); + if (r < 0) { + ts_err("Read sensor_id failed"); + if (version) + version->valid = false; + goto exit; + } + if (sensor_id_mask != 0) { + version->sensor_id = buffer[0] & sensor_id_mask; + ts_info("sensor_id_mask:0x%02x, sensor_id:0x%02x", + sensor_id_mask, version->sensor_id); + } else { + version->sensor_id = buffer[0]; + } + + version->valid = true; + + ts_info("PID:%s,SensorID:%d, VID:%*ph", version->pid, + version->sensor_id, (int)sizeof(version->vid), version->vid); +exit: + /*enable doze mode, just valid for normandy + * this func must be used in pairs + */ + goodix_set_i2c_doze_mode(dev, true); + + return r; +} + +static int goodix_wait_cfg_cmd_ready(struct goodix_ts_device *dev, + u8 right_cmd, u8 send_cmd) +{ + int try_times = 0; + u8 cmd_flag = 0; + u8 cmd_buf[3] = {0}; + u16 command_reg = dev->reg.command; + struct goodix_ts_cmd ts_cmd; + + goodix_cmd_init(dev, &ts_cmd, send_cmd, 0, command_reg); + + for (try_times = 0; try_times < TS_WAIT_CFG_READY_RETRY_TIMES; + try_times++) { + if (goodix_i2c_read(dev, command_reg, cmd_buf, 3)) { + ts_err("Read cmd_reg error"); + return -EINVAL; + } + cmd_flag = cmd_buf[0]; + if (cmd_flag == right_cmd) { + return 0; + } else if (cmd_flag != send_cmd) { + ts_err("failed cmd_reg:0x%X, 0x%X, 0x%X", + cmd_buf[0], cmd_buf[1], cmd_buf[2]); + if (goodix_send_command(dev, &ts_cmd)) { + ts_err("Resend cmd 0x%02X FAILED", send_cmd); + return -EINVAL; + } + } + usleep_range(10000, 11000); + } + + return -EINVAL; +} + +static int _do_goodix_send_config(struct goodix_ts_device *dev, + struct goodix_ts_config *config) +{ + int r = 0; + int try_times = 0; + u8 buf[3] = {0}; + u16 command_reg = dev->reg.command; + u16 cfg_reg = dev->reg.cfg_addr; + struct goodix_ts_cmd ts_cmd; + + /*1. Inquire command_reg until it's free*/ + for (try_times = 0; try_times < TS_WAIT_CMD_FREE_RETRY_TIMES; + try_times++) { + if (!goodix_i2c_read(dev, command_reg, buf, 1) && + buf[0] == TS_CMD_REG_READY) + break; + usleep_range(10000, 11000); + } + if (try_times >= TS_WAIT_CMD_FREE_RETRY_TIMES) { + ts_err("failed send cfg, reg:0x%04x is not 0xff", + command_reg); + r = -EINVAL; + goto exit; + } + + /*2. send "start write cfg" command*/ + goodix_cmd_init(dev, &ts_cmd, COMMAND_START_SEND_CFG, + 0, dev->reg.command); + if (goodix_send_command(dev, &ts_cmd)) { + ts_err("failed send cfg, COMMAND_START_SEND_CFG ERROR"); + r = -EINVAL; + goto exit; + } + + /*3. wait ic set command_reg to 0x82*/ + if (goodix_wait_cfg_cmd_ready(dev, COMMAND_SEND_CFG_PREPARE_OK, + COMMAND_START_SEND_CFG)) { + ts_err("failed send cfg, reg:0x%04x is not 0x82", + command_reg); + r = -EINVAL; + goto exit; + } + + /*4. write cfg*/ + if (goodix_i2c_write(dev, cfg_reg, config->data, config->length)) { + ts_err("Send cfg FAILED, write cfg to fw ERROR"); + r = -EINVAL; + goto exit; + } + + /*5. send "end send cfg" command*/ + goodix_cmd_init(dev, &ts_cmd, COMMAND_END_SEND_CFG, + 0, dev->reg.command); + if (goodix_send_command(dev, &ts_cmd)) { + ts_err("failed send cfg, COMMAND_END_SEND_CFG ERROR"); + r = -EINVAL; + goto exit; + } + + if (dev->ic_type == IC_TYPE_YELLOWSTONE) { + /*6. wait 0x7f or 0x7e */ + for (try_times = 0; try_times < TS_WAIT_CMD_FREE_RETRY_TIMES; + try_times++) { + r = goodix_i2c_read(dev, command_reg, buf, 3); + if (!r && (buf[0] == TS_CMD_CFG_ERR || + buf[0] == TS_CMD_CFG_OK)) + break; + usleep_range(10000, 11000); + } + ts_info("send config result: %*ph", 3, buf); + /* set 0x7D to end send config process */ + goodix_cmd_init(dev, &ts_cmd, COMMAND_END_SEND_CFG_YS, + 0, dev->reg.command); + if (goodix_send_command(dev, &ts_cmd)) { + ts_err("failed send cfg end cmd"); + r = -EINVAL; + goto exit; + } + + if (try_times >= TS_WAIT_CMD_FREE_RETRY_TIMES) { + ts_err("failed get result"); + r = -EINVAL; + goto exit; + } + if (buf[0] == TS_CMD_CFG_ERR) { + if (buf[2] == TS_CFG_REPLY_DATA_EQU) { + ts_info("config data equal with flash"); + r = 0; + } else { + ts_err("failed send cfg"); + r = -EINVAL; + } + goto exit; + } + } else { + /*6. wait ic set command_reg to 0xff*/ + for (try_times = 0; try_times < TS_WAIT_CMD_FREE_RETRY_TIMES; + try_times++) { + if (!goodix_i2c_read(dev, command_reg, buf, 1) && + buf[0] == TS_CMD_REG_READY) + break; + usleep_range(10000, 11000); + } + if (try_times >= TS_WAIT_CMD_FREE_RETRY_TIMES) { + ts_err("failed send cfg, reg:0x%04x is 0x%x not 0xff", + command_reg, buf[0]); + r = -EINVAL; + goto exit; + } + } + + ts_info("Send cfg SUCCESS"); + r = 0; + +exit: + return r; +} + +static int goodix_send_config(struct goodix_ts_device *dev, + struct goodix_ts_config *config) +{ + int r = 0; + + if (!config || !config->initialized) { + ts_err("invalid config data"); + return -EINVAL; + } + + ts_info("ver:%02xh,size:%d", config->data[0], config->length); + mutex_lock(&config->lock); + + /*disable doze mode*/ + if (!goodix_set_i2c_doze_mode(dev, false)) { + r = _do_goodix_send_config(dev, config); + } else { + ts_err("failed disable doze[abort]"); + r = -EINVAL; + } + /*enable doze mode*/ + goodix_set_i2c_doze_mode(dev, true); + + mutex_unlock(&config->lock); + return r; +} + +/* success return config length else return -1 */ +static int goodix_read_config_ys(struct goodix_ts_device *dev, + u8 *buf, int size) +{ + u32 cfg_addr = dev->reg.cfg_addr; + int sub_bags = 0; + int offset = 0; + int subbag_len; + u16 checksum; + int i; + int ret; + + ret = goodix_i2c_read(dev, cfg_addr, buf, TS_CFG_HEAD_LEN_YS); + if (ret) + goto err_out; + + offset = TS_CFG_HEAD_LEN_YS; + sub_bags = buf[TS_CFG_BAG_NUM_INDEX]; + checksum = checksum_u8_ys(buf, TS_CFG_HEAD_LEN_YS); + if (checksum) { + ts_err("Config head checksum err:0x%x,data:%*ph", + checksum, TS_CFG_HEAD_LEN_YS, buf); + ret = -EINVAL; + goto err_out; + } + + ts_info("config_version:%u, vub_bags:%u", buf[0], sub_bags); + for (i = 0; i < sub_bags; i++) { + /* read sub head [0]: sub bag num, [1]: sub bag length */ + if (offset + 2 >= size) { + ts_err("config size overflow, %d", offset + 2); + ret = -EINVAL; + goto err_out; + } + ret = goodix_i2c_read(dev, cfg_addr + offset, buf + offset, 2); + if (ret) + goto err_out; + + /* read sub bag data */ + subbag_len = buf[offset + 1]; + + ts_debug("sub bag num:%u,sub bag length:%u", + buf[offset], subbag_len); + if (offset + 2 + subbag_len + 2 > size) { + ts_err("config size overflow, %d", offset + 2); + ret = -EINVAL; + goto err_out; + } + ret = goodix_i2c_read(dev, cfg_addr + offset + 2, + buf + offset + 2, subbag_len + 2); + if (ret) + goto err_out; + checksum = checksum_u8_ys(buf + offset, subbag_len + 4); + if (checksum) { + ts_err("sub bag checksum err:0x%x", checksum); + ret = -EINVAL; + goto err_out; + } + offset += subbag_len + 4; + ts_debug("sub bag %d, data:%*ph", + buf[offset], buf[offset + 1] + 4, buf + offset); + } + ret = offset; + +err_out: + return ret; +} + +/* success return config length else return -1 */ +static int goodix_read_config_nor(struct goodix_ts_device *dev, + u8 *buf, int size) +{ + u32 cfg_addr = dev->reg.cfg_addr; + int sub_bags = 0; + int offset = 0; + int subbag_len; + u8 checksum; + int i; + int ret; + + /*disable doze mode*/ + if (goodix_set_i2c_doze_mode(dev, false)) { + ts_err("failed disable doze mode[abort]"); + ret = -EINVAL; + goto err_out; + } + + ret = goodix_i2c_read(dev, cfg_addr, buf, TS_CFG_HEAD_LEN); + if (ret) + goto err_out; + + offset = TS_CFG_BAG_START_INDEX; + sub_bags = buf[TS_CFG_BAG_NUM_INDEX]; + checksum = checksum_u8(buf, TS_CFG_HEAD_LEN); + if (checksum) { + ts_err("Config head checksum err:0x%x,data:%*ph", + checksum, TS_CFG_HEAD_LEN, buf); + ret = -EINVAL; + goto err_out; + } + + ts_info("config_version:%u, vub_bags:%u", buf[0], sub_bags); + for (i = 0; i < sub_bags; i++) { + /* read sub head [0]: sub bag num, [1]: sub bag length */ + if (offset + 2 >= size) { + ts_err("config size overflow, %d", offset + 2); + ret = -EINVAL; + goto err_out; + } + ret = goodix_i2c_read(dev, cfg_addr + offset, buf + offset, 2); + if (ret) + goto err_out; + + /* read sub bag data */ + subbag_len = buf[offset + 1]; + + ts_debug("sub bag num:%u,sub bag length:%u", + buf[offset], subbag_len); + if (offset + 2 + subbag_len + 1 >= size) { + ts_err("config size overflow, %d", offset + 2 + subbag_len + 1); + ret = -EINVAL; + goto err_out; + } + ret = goodix_i2c_read(dev, cfg_addr + offset + 2, + buf + offset + 2, subbag_len + 1); + if (ret) + goto err_out; + checksum = checksum_u8(buf + offset, subbag_len + 3); + if (checksum) { + ts_err("sub bag checksum err:0x%x", checksum); + ret = -EINVAL; + goto err_out; + } + offset += subbag_len + 3; + ts_debug("sub bag %d, data:%*ph", + buf[offset], buf[offset + 1] + 3, buf + offset); + } + ret = offset; + +err_out: + /*enable doze mode*/ + goodix_set_i2c_doze_mode(dev, true); + + return ret; +} + +/* success return config_len, <= 0 failed */ +static int goodix_read_config(struct goodix_ts_device *dev, + u8 *config_data, int size) +{ + struct goodix_ts_cmd ts_cmd; + u8 cmd_flag; + u32 cmd_reg = dev->reg.command; + int r = 0; + int i; + + if (!config_data) { + ts_err("Illegal params"); + return -EINVAL; + } + if (!dev->reg.command) { + ts_err("command register ERROR:0x%04x", dev->reg.command); + return -EINVAL; + } + + /*disable doze mode*/ + if (goodix_set_i2c_doze_mode(dev, false)) { + ts_err("failed disabled doze[abort]"); + r = -EINVAL; + goto exit; + } + + /* wait for IC in IDLE state */ + for (i = 0; i < TS_WAIT_CMD_FREE_RETRY_TIMES; i++) { + cmd_flag = 0; + r = goodix_i2c_read(dev, cmd_reg, &cmd_flag, 1); + if (r < 0 || cmd_flag == TS_CMD_REG_READY) + break; + usleep_range(10000, 11000); + } + if (cmd_flag != TS_CMD_REG_READY) { + ts_err("Wait for IC ready IDEL state timeout:addr 0x%x\n", + cmd_reg); + r = -EAGAIN; + goto exit; + } + /* 0x86 read config command */ + goodix_cmd_init(dev, &ts_cmd, COMMAND_START_READ_CFG, + 0, cmd_reg); + r = goodix_send_command(dev, &ts_cmd); + if (r) { + ts_err("Failed send read config command"); + goto exit; + } + /* wait for config data ready */ + if (goodix_wait_cfg_cmd_ready(dev, COMMAND_READ_CFG_PREPARE_OK, + COMMAND_START_READ_CFG)) { + ts_err("Wait for config data ready timeout"); + r = -EAGAIN; + goto exit; + } + + if (dev->ic_type == IC_TYPE_YELLOWSTONE) + r = goodix_read_config_ys(dev, config_data, size); + else + r = goodix_read_config_nor(dev, config_data, size); + if (r <= 0) + ts_err("Failed read config data"); + + /* clear command */ + goodix_cmd_init(dev, &ts_cmd, TS_CMD_REG_READY, 0, cmd_reg); + goodix_send_command(dev, &ts_cmd); + +exit: + /*enable doze mode*/ + goodix_set_i2c_doze_mode(dev, true); + + return r; +} + +/** + * goodix_hw_reset - reset device + * + * @dev: pointer to touch device + * Returns 0 - succeed,<0 - failed + */ +int goodix_hw_reset(struct goodix_ts_device *dev) +{ + u8 data[2] = {0x00}; + int r = 0; + + ts_info("HW reset"); + + gpio_direction_output(dev->board_data.reset_gpio, 0); + udelay(2000); + gpio_direction_output(dev->board_data.reset_gpio, 1); + msleep(100); + + /*init dynamic esd*/ + if (dev->reg.esd) { + r = goodix_i2c_write_trans(dev, dev->reg.esd, data, 1); + if (r < 0) + ts_err("IC reset, init dynamic esd FAILED"); + } else { + ts_info("reg.esd is NULL, skip dynamic esd init"); + } + + return 0; +} + +/** + * goodix_request_handler - handle firmware request + * + * @dev: pointer to touch device + * @request_data: requset information + * Returns 0 - succeed,<0 - failed + */ +static int goodix_request_handler(struct goodix_ts_device *dev) +{ + unsigned char buffer[1]; + int r; + + r = goodix_i2c_read_trans(dev, dev->reg.fw_request, buffer, 1); + if (r < 0) + return r; + + switch (buffer[0]) { + case REQUEST_CONFIG: + ts_info("HW request config"); + r = goodix_send_config(dev, &(dev->normal_cfg)); + if (r != 0) + ts_info("request config, send config faild"); + break; + case REQUEST_BAKREF: + ts_info("HW request bakref"); + break; + case REQUEST_RESET: + ts_info("HW requset reset"); + r = goodix_hw_reset(dev); + if (r != 0) + ts_info("request reset, reset faild"); + break; + /*case REQUEST_RELOADFW: + ts_info("HW request reload fw"); + goodix_do_fw_update(UPDATE_MODE_FORCE|UPDATE_MODE_SRC_REQUEST); + break;*/ + case REQUEST_IDLE: + ts_info("HW request idle"); + break; + default: + ts_info("Unknown hw request:%d", buffer[0]); + break; + } + + buffer[0] = 0x00; + r = goodix_i2c_write_trans(dev, dev->reg.fw_request, buffer, 1); + return r; +} + +static void goodix_swap_coords(struct goodix_ts_device *dev, + unsigned int *coor_x, unsigned int *coor_y) +{ + unsigned int temp; + struct goodix_ts_board_data *bdata = &dev->board_data; + + if (bdata->swap_axis) { + temp = *coor_x; + *coor_x = *coor_y; + *coor_y = temp; + } + if (bdata->x2x) + *coor_x = bdata->panel_max_x - *coor_x; + if (bdata->y2y) + *coor_y = bdata->panel_max_y - *coor_y; +} + +#define GOODIX_KEY_STATE 0x10 +static void goodix_parse_finger_nor(struct goodix_ts_device *dev, + struct goodix_touch_data *touch_data, unsigned char *buf, int touch_num) +{ + unsigned int id = 0, x = 0, y = 0, w = 0; + static u8 pre_key_map; + u8 cur_key_map = 0; + static u32 pre_finger_map; + u32 cur_finger_map = 0; + u8 *coor_data; + int i; + + coor_data = &buf[IRQ_HEAD_LEN_NOR]; + for (i = 0; i < touch_num; i++) { + id = coor_data[0]; + if(id >= GOODIX_MAX_TOUCH){ + ts_info("invaild finger id =%d", id); + break; + } + x = le16_to_cpup((__be16 *) (coor_data + 1)); + y = le16_to_cpup((__be16 *) (coor_data + 3)); + w = coor_data[5]; + goodix_swap_coords(dev, &x, &y); + 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; + cur_finger_map |= (1 << id); + coor_data += BYTES_PER_COORD; + } + + /* process finger release */ + for (i = 0; i < GOODIX_MAX_TOUCH; i++) { + if (cur_finger_map & (1 << i)) + continue; + if (pre_finger_map & (1 << i)) + touch_data->coords[i].status = TS_RELEASE; + } + pre_finger_map = cur_finger_map; + touch_data->touch_num = touch_num; + + if (buf[1] & GOODIX_KEY_STATE) { + /* have key */ + cur_key_map = buf[touch_num * BYTES_PER_COORD + 2] & 0x0F; + for (i = 0; i < GOODIX_MAX_TP_KEY; i++) { + if (cur_key_map & (1 << i)) { + touch_data->keys[i].status = TS_TOUCH; + touch_data->keys[i].code = + dev->board_data.panel_key_map[i]; + } + } + } + /* process key release */ + for (i = 0; i < GOODIX_MAX_TP_KEY; i++) { + if (cur_key_map & (1 << i) || !(pre_key_map & (1 << i))) + continue; + touch_data->keys[i].status = TS_RELEASE; + touch_data->keys[i].code = dev->board_data.panel_key_map[i]; + } + pre_key_map = cur_key_map; +} + +static void goodix_parse_finger_ys(struct goodix_ts_device *dev, + struct goodix_touch_data *touch_data, unsigned char *buf, int touch_num) +{ + unsigned int id = 0, x = 0, y = 0, w = 0; + static u32 pre_finger_map; + u32 cur_finger_map = 0; + u8 *coor_data; + int i; + + coor_data = &buf[IRQ_HEAD_LEN_YS]; + for (i = 0; i < touch_num; i++) { + id = (coor_data[0] >> 4) & 0x0F; + if(id >= GOODIX_MAX_TOUCH){ + ts_info("invaild finger id =%d", id); + break; + } + x = be16_to_cpup((__be16 *)(coor_data + 2)); + y = be16_to_cpup((__be16 *)(coor_data + 4)); + w = be16_to_cpup((__be16 *)(coor_data + 6)); + goodix_swap_coords(dev, &x, &y); + 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; + cur_finger_map |= (1 << id); + coor_data += BYTES_PER_COORD; + } + + /* process finger release */ + for (i = 0; i < GOODIX_MAX_TOUCH; i++) { + if (cur_finger_map & (1 << i)) + continue; + if (pre_finger_map & (1 << i)) + touch_data->coords[i].status = TS_RELEASE; + } + pre_finger_map = cur_finger_map; + touch_data->touch_num = touch_num; +} + +static unsigned int goodix_pen_btn_code[] = {BTN_STYLUS, BTN_STYLUS2}; +static void goodix_parse_pen_nor(struct goodix_ts_device *dev, + struct goodix_pen_data *pen_data, unsigned char *buf, int touch_num) +{ + unsigned int id = 0; + static u8 pre_key_map; + u8 cur_key_map = 0; + static u32 pre_pen_status; + u32 cur_pen_status = 0; + u8 *coor_data; + int i; + + coor_data = &buf[2]; + for (i = 0; i < touch_num; i++) { + /* search for pen coordinate */ + id = coor_data[0]; + if (id < 0x80) { + coor_data += BYTES_PER_COORD; + continue; + } + pen_data->coords.x = le16_to_cpup((__be16 *)(coor_data + 1)); + pen_data->coords.y = le16_to_cpup((__be16 *)(coor_data + 3)); + pen_data->coords.p = le16_to_cpup((__be16 *)(coor_data + 5)); + goodix_swap_coords(dev, &pen_data->coords.x, + &pen_data->coords.y); + pen_data->coords.status = TS_TOUCH; + pen_data->coords.tool_type = BTN_TOOL_PEN; + cur_pen_status = 1; + /* currently only support one stylus */ + break; + } + if (!cur_pen_status && pre_pen_status) { + pen_data->coords.status = TS_RELEASE; + } + pre_pen_status = cur_pen_status; + + /* process pen button */ + if (buf[1] & GOODIX_KEY_STATE) { + cur_key_map = (buf[touch_num * BYTES_PER_COORD + 2] >> 4) & 0x0F; + for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) { + if (!(cur_key_map & (1 << i))) + continue; + pen_data->keys[i].status = TS_TOUCH; + pen_data->keys[i].code = goodix_pen_btn_code[i]; + } + } + for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) { + if (cur_key_map & (1 << i) || !(pre_key_map & (1 << i))) + continue; + pen_data->keys[i].status = TS_RELEASE; + pen_data->keys[i].code = goodix_pen_btn_code[i]; + } + pre_key_map = cur_key_map; +} + +static void goodix_parse_pen_ys(struct goodix_ts_device *dev, + struct goodix_pen_data *pen_data, unsigned char *buf, int touch_num) +{ + ts_info("unsupported"); +} + +static int goodix_touch_handler_ys(struct goodix_ts_device *dev, + struct goodix_ts_event *ts_event, + u8 *pre_buf, u32 pre_buf_len) +{ + struct goodix_touch_data *touch_data = &ts_event->touch_data; + struct goodix_pen_data *pen_data = &ts_event->pen_data; + static u8 buffer[IRQ_HEAD_LEN_YS + + BYTES_PER_COORD * GOODIX_MAX_TOUCH + 2]; + int touch_num = 0, r = -EINVAL; + u8 point_type = 0; + u16 chksum = 0; + + static u8 pre_finger_num = 0; + static u8 pre_pen_num = 0; + + /* 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 (unlikely(touch_num > GOODIX_MAX_TOUCH)) { + touch_num = -EINVAL; + goto exit_clean_sta; + } + if (unlikely(touch_num > 1)) { + r = goodix_i2c_read_trans(dev, + dev->reg.coor + pre_buf_len, + &buffer[pre_buf_len], + (touch_num - 1) * BYTES_PER_COORD); + if (unlikely(r < 0)) + goto exit_clean_sta; + } + + if (touch_num > 0) { + chksum = checksum_u8_ys(&buffer[IRQ_HEAD_LEN_YS], + touch_num * BYTES_PER_COORD + 2); + if (unlikely(chksum != 0)) { + ts_debug("checksum error:%x", chksum); + r = -EINVAL; + goto exit_clean_sta; + } + } + if (touch_num > 0) + point_type = buffer[(touch_num - 1) * BYTES_PER_COORD + IRQ_HEAD_LEN_YS] & 0x0F; + if (touch_num >= 1 && (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_ys(dev, touch_data, buffer, 0); + pre_finger_num = 0; + } else { + pre_pen_num = 1; + ts_event->event_type = EVENT_PEN; + goodix_parse_pen_ys(dev, pen_data, buffer, touch_num); + } + } else { + /* finger info */ + if (pre_pen_num) { + ts_event->event_type = EVENT_PEN; + goodix_parse_pen_ys(dev, pen_data, buffer, 0); + pre_pen_num = 0; + } else { + ts_event->event_type = EVENT_TOUCH; + goodix_parse_finger_ys(dev, 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"); + } +exit_clean_sta: + return r; +} + +static int goodix_touch_handler_nor(struct goodix_ts_device *dev, + struct goodix_ts_event *ts_event, + u8 *pre_buf, u32 pre_buf_len) +{ + struct goodix_touch_data *touch_data = &ts_event->touch_data; + struct goodix_pen_data *pen_data = &ts_event->pen_data; + static u8 buffer[IRQ_HEAD_LEN_NOR + + BYTES_PER_COORD * GOODIX_MAX_TOUCH + 2]; + int touch_num = 0, r = -EINVAL; + unsigned char chksum = 0; + + static u8 pre_finger_num = 0; + static u8 pre_pen_num = 0; + + /* 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[1] & 0x0F; + + if (unlikely(touch_num > GOODIX_MAX_TOUCH)) { + touch_num = -EINVAL; + goto exit_clean_sta; + } + if (unlikely(touch_num > 1)) { + r = goodix_i2c_read_trans(dev, + dev->reg.coor + pre_buf_len, + &buffer[pre_buf_len], + (touch_num - 1) * BYTES_PER_COORD); + if (unlikely(r < 0)) + goto exit_clean_sta; + } + + chksum = checksum_u8(&buffer[0], touch_num * BYTES_PER_COORD + 4); + if (unlikely(chksum != 0)) { + ts_debug("checksum error:%X, ic_type:%d", chksum, dev->ic_type); + r = -EINVAL; + goto exit_clean_sta; + } + + if (touch_num >= 1 && + buffer[(touch_num - 1) * BYTES_PER_COORD + 2] >= 0x80) { + if (pre_finger_num) { + ts_event->event_type = EVENT_TOUCH; + goodix_parse_finger_nor(dev, touch_data, buffer, 0); + pre_finger_num = 0; + } else { + pre_pen_num = 1; + ts_event->event_type = EVENT_PEN; + goodix_parse_pen_nor(dev, pen_data, buffer, touch_num); + } + } else { + if (pre_pen_num) { + ts_event->event_type = EVENT_PEN; + goodix_parse_pen_nor(dev, pen_data, buffer, 0); + pre_pen_num = 0; + } else { + ts_event->event_type = EVENT_TOUCH; + goodix_parse_finger_nor(dev, touch_data, + buffer, touch_num); + pre_finger_num = touch_num; + } + } +exit_clean_sta: + return r; +} + +static int goodix_event_handler(struct goodix_ts_device *dev, + struct goodix_ts_event *ts_event) +{ + int pre_read_len; + u8 pre_buf[32]; + u8 event_sta; + int r; + + if (dev->ic_type == IC_TYPE_YELLOWSTONE) + pre_read_len = IRQ_HEAD_LEN_YS + BYTES_PER_COORD + 2; + else + pre_read_len = IRQ_HEAD_LEN_NOR + BYTES_PER_COORD + 2; + r = goodix_i2c_read_trans(dev, dev->reg.coor, + pre_buf, pre_read_len); + if (unlikely(r < 0)) + return r; + + if (dev->ic_type == IC_TYPE_YELLOWSTONE && + checksum_u8_ys(pre_buf, IRQ_HEAD_LEN_YS)) { + ts_debug("irq head checksum error %*ph", + IRQ_HEAD_LEN_YS, pre_buf); + return -EINVAL; + } + /* buffer[0]: event state */ + event_sta = pre_buf[0]; + if (likely((event_sta & GOODIX_TOUCH_EVENT) == GOODIX_TOUCH_EVENT)) { + /* handle touch event */ + if (dev->ic_type == IC_TYPE_YELLOWSTONE) + goodix_touch_handler_ys(dev, ts_event, pre_buf, + pre_read_len); + else + goodix_touch_handler_nor(dev, ts_event, pre_buf, + pre_read_len); + } else if (unlikely((event_sta & GOODIX_REQUEST_EVENT) == + GOODIX_REQUEST_EVENT)) { + /* handle request event */ + ts_event->event_type = EVENT_REQUEST; + goodix_request_handler(dev); + } else if ((event_sta & GOODIX_GESTURE_EVENT) == + GOODIX_GESTURE_EVENT) { + /* handle gesture event */ + ts_debug("Gesture event"); + } else if ((event_sta & GOODIX_HOTKNOT_EVENT) == + GOODIX_HOTKNOT_EVENT) { + /* handle hotknot event */ + ts_debug("Hotknot event"); + } else { + ts_debug("unknow event type:0x%x", event_sta); + r = -EINVAL; + } + + return r; +} + +/** + * goodix_hw_suspend - Let touch deivce stay in lowpower mode. + * @dev: pointer to goodix touch device + * @return: 0 - succeed, < 0 - failed + */ +static int goodix_hw_suspend(struct goodix_ts_device *dev) +{ + struct goodix_ts_cmd sleep_cmd; + int r = 0; + + goodix_cmd_init(dev, &sleep_cmd, COMMAND_SLEEP, + 0, dev->reg.command); + if (sleep_cmd.initialized) { + r = goodix_send_command(dev, &sleep_cmd); + if (!r) + ts_info("Chip in sleep mode"); + } else { + ts_err("Uninitialized sleep command"); + } + return r; +} + +/** + * goodix_hw_resume - Let touch deivce stay in active mode. + * @dev: pointer to goodix touch device + * @return: 0 - succeed, < 0 - failed + */ +static int goodix_hw_resume(struct goodix_ts_device *dev) +{ + goodix_hw_reset(dev); + + return 0; +} + +static int goodix_esd_check(struct goodix_ts_device *dev) +{ + int r; + u8 data = 0; + + if (dev->reg.esd == 0) { + ts_err("esd reg is NULL"); + return 0; + } + + /*check dynamic esd*/ + if (dev->ic_type == IC_TYPE_NORMANDY) + r = dev->hw_ops->read_trans(dev, TS_REG_ESD_TICK_R, &data, 1); + else + r = dev->hw_ops->read_trans(dev, dev->reg.esd, &data, 1); + + if (r < 0 || (data == GOODIX_ESD_TICK_WRITE_DATA)) { + ts_info("dynamic esd occur, r:%d, data:0x%02x", r, data); + r = -EINVAL; + } + + return r; +} + +/* 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 raw/diff data */ +extern struct goodix_module goodix_modules; +static int goodix_capacitance_data(struct goodix_ts_device *dev, + struct ts_rawdata_info *info) +{ + struct goodix_ts_core *cd = goodix_modules.core_data; + int retry = 20; + u16 rawdata_addr = 0x8FA0; + u16 diffdata_addr = 0x9D20; + u32 tx = cd->drv_num; + u32 rx = cd->sen_num; + int size = tx * rx; + u8 irq_flag; + u8 cmds[3]; + int i; + int ret; + + if (!info) { + ts_err("goodix_capacitance_data, input null ptr"); + return -EIO; + } + + /* disable irq & close esd */ + goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL); + goodix_ts_irq_enable(cd, false); + usleep_range(10000, 11000); + + /* switch rawdata mode */ + cmds[0] = 0x01; + cmds[1] = 0x00; + cmds[2] = (u8)(0x00 - 0x01); + ret = goodix_i2c_write(dev, dev->reg.command, cmds, sizeof(cmds)); + if (ret < 0) { + ts_err("switch rawdata mode failed"); + goto exit; + } + msleep(30); //wait command finish + + /* clean touch event flag */ + irq_flag = 0; + ret = goodix_i2c_write_trans(dev, dev->reg.coor, &irq_flag, 1); + if (ret < 0) { + ts_err("clean touch event failed, exit!"); + goto exit; + } + + while (retry--) { + usleep_range(5000, 5100); + ret = goodix_i2c_read_trans(dev, dev->reg.coor, &irq_flag, 1); + if (!ret && (irq_flag & 0x80)) + break; + } + if (retry < 0) { + ts_err("rawdata is not ready irq_flag:0x%02x, exit!", irq_flag); + goto exit; + } + + /* obtain rawdata & diff_rawdata */ + info->buff[0] = rx; + info->buff[1] = tx; + info->used_size = 2; + ret = goodix_i2c_read_trans(dev, rawdata_addr, + (u8 *)&info->buff[info->used_size], size * sizeof(u16)); + if (ret < 0) { + ts_err("obtian raw_data failed, exit!"); + goto exit; + } + for (i = 0; i < size; i++) + info->buff[info->used_size + i] = + be16_to_cpu(info->buff[info->used_size + i]); + goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size]); + info->used_size += size; + + ret = goodix_i2c_read_trans(dev, diffdata_addr, + (u8 *)&info->buff[info->used_size], size * sizeof(u16)); + if (ret < 0) { + ts_err("obtian diff_data failed, exit!"); + goto exit; + } + for (i = 0; i < size; i++) + info->buff[info->used_size + i] = + be16_to_cpu(info->buff[info->used_size + i]); + goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size]); + info->used_size += size; + +exit: + /* switch coor mode */ + cmds[0] = 0; + cmds[1] = 0; + cmds[2] = 0; + goodix_i2c_write_trans(dev, dev->reg.command, cmds, sizeof(cmds)); + /* clean touch event flag */ + irq_flag = 0; + goodix_i2c_write_trans(dev, dev->reg.coor, &irq_flag, 1); + msleep(30); //wait command finish + /* enable irq * esd */ + goodix_ts_irq_enable(cd, true); + goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL); + return ret; +} + +/* hardware opeation funstions */ +static const struct goodix_ts_hw_ops hw_i2c_ops = { + .dev_confirm = goodix_ts_dev_confirm, + .read = goodix_i2c_read, + .write = goodix_i2c_write, + .read_trans = goodix_i2c_read_trans, + .write_trans = goodix_i2c_write_trans, + .reset = goodix_hw_reset, + .event_handler = goodix_event_handler, + .send_config = goodix_send_config, + .read_config = goodix_read_config, + .send_cmd = goodix_send_command, + .read_version = goodix_read_version, + .suspend = goodix_hw_suspend, + .resume = goodix_hw_resume, + .check_hw = goodix_esd_check, + .get_capacitance_data = goodix_capacitance_data, +}; + +static struct platform_device *goodix_pdev; + +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) +{ + struct goodix_ts_device *ts_device = NULL; + int r = 0; + + ts_info("goodix_i2c_probe IN"); + + r = i2c_check_functionality(client->adapter, + I2C_FUNC_I2C); + if (!r) + return -EIO; + + /* ts device data */ + ts_device = devm_kzalloc(&client->dev, + sizeof(struct goodix_ts_device), GFP_KERNEL); + if (!ts_device) + return -ENOMEM; + + if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) { + /* parse devicetree property */ + r = goodix_parse_dt(client->dev.of_node, + &ts_device->board_data); + if (r < 0) { + ts_err("failed parse device info form dts, %d", r); + return -EINVAL; + } + } else { + ts_err("no valid device tree node found"); + return -ENODEV; + } + + ts_device->name = "Goodix TouchDevcie"; + ts_device->dev = &client->dev; + ts_device->hw_ops = &hw_i2c_ops; + + /* ts core device */ + goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL); + if (!goodix_pdev) + return -ENOMEM; + + goodix_pdev->name = GOODIX_CORE_DRIVER_NAME; + goodix_pdev->id = 0; + goodix_pdev->num_resources = 0; + /* + * you can find this platform dev in + * /sys/devices/platfrom/goodix_ts.0 + * goodix_pdev->dev.parent = &client->dev; + */ + goodix_pdev->dev.platform_data = ts_device; + goodix_pdev->dev.release = goodix_pdev_release; + + /* register platform device, then the goodix_ts_core + * module will probe the touch deivce. + */ + r = platform_device_register(goodix_pdev); + if (r) { + ts_err("failed register goodix platform device, %d", r); + goto err_pdev; + } + ts_info("i2c probe out"); + return r; + +err_pdev: + kfree(goodix_pdev); + goodix_pdev = NULL; + ts_info("i2c probe out, %d", r); + return r; +} + +static int goodix_i2c_remove(struct i2c_client *client) +{ + platform_device_unregister(goodix_pdev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id i2c_matchs[] = { + {.compatible = "goodix,gt9885",}, + {.compatible = "goodix,gt9896",}, + {.compatible = "goodix,gt9886",}, + {.compatible = "goodix,gt9889",}, + {.compatible = "goodix,gt8589",}, + {.compatible = "goodix,gt5863",}, + {.compatible = "goodix,gt6861",}, + {.compatible = "goodix,gt6862",}, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_matchs); +#endif + +static const struct i2c_device_id i2c_id_table[] = { + {TS_DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, i2c_id_table); + +static struct i2c_driver goodix_i2c_driver = { + .driver = { + .name = TS_DRIVER_NAME, + // .owner = THIS_MODULE, + .of_match_table = of_match_ptr(i2c_matchs), + }, + .probe = goodix_i2c_probe, + .remove = goodix_i2c_remove, + .id_table = i2c_id_table, +}; + +int goodix_bus_init(void) +{ + ts_info("Goodix driver init"); + return i2c_add_driver(&goodix_i2c_driver); +} + +void goodix_bus_exit(void) +{ + i2c_del_driver(&goodix_i2c_driver); + ts_info("Goodix driver exit"); +} diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_test.c b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_test.c new file mode 100644 index 000000000000..cf7e61844208 --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_test.c @@ -0,0 +1,3216 @@ +/* + * gtx8_ts_test.c - TP test + * + * Copyright (C) 2015 - 2016 gtx8 Technology Incorporated + * Copyright (C) 2015 - 2016 Yulong Cai + * + * 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 gtx8'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 +#include +#include + +#include +#include +#include +#include "goodix_ts_core.h" + +#include +#include +#include +#include +#include + +#define TEST_CODE_VERSION "tp_test_v1.0.4" +/* test switch */ +#define RAWDATA_TEST_16_FRAMES /* test 16 frames of rawdata */ +#define SHORT_TEST +#define NOISE_DATA_TEST +#define SELFNOISE_DATA_TEST +#define SELF_RAWDATA_TEST +// #define SAVE_IN_CSV + +#define MAX_LINE_LEN 1024*3*5 +#define MAX_DATA_BUFFER 15000 +#define PATH_LEN 250 +#define TOTAL_FRAME_NUM 16 +#define GTX8_RETRY_NUM_3 3 +#define GTX8_CONFIG_REFRESH_DATA 0x01 +#define GTX8_TEST_FILE_NAME "gtx8_test_limits.csv" +#define GTX8_RESULT_SAVE_PATH "/etc/firmware/" +#define STRTOL_LEN 10 +#define STRTOL_HEX_LEN 16 + +#define GTX8_TEST_FILE_NAME_LEN 64 +#define MAX_STR_LEN 32 + +#define FLOAT_AMPLIFIER 1000 +#define MAX_U16_VALUE 65535 +#define RAWDATA_TEST_TIMES 10 + +#define STATISTICS_DATA_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 + +/* error code */ +#define NO_ERR 0 +#define RESULT_ERR -1 +#define RAWDATA_SIZE_LIMIT -2 + +/*param key word in .csv */ +#define CSV_TP_UNIFIED_LIMIT "unified_raw_limit" +#define CSV_TP_NOISE_LIMIT "noise_data_limit" +#define CSV_TP_SPECIAL_RAW_MIN "specail_raw_min" +#define CSV_TP_SPECIAL_RAW_MAX "specail_raw_max" +#define CSV_TP_SPECIAL_RAW_DELTA "special_raw_delta" +#define CSV_TP_SHORT_THRESHOLD "shortciurt_threshold" +#define CSV_TP_SELF_UNIFIED_LIMIT "unified_selfraw_limit" +#define CSV_TP_SPECIAL_SELFRAW_MAX "special_selfraw_max" +#define CSV_TP_SPECIAL_SELFRAW_MIN "special_selfraw_min" +#define CSV_TP_SELFNOISE_LIMIT "noise_selfdata_limit" +#define CSV_TP_TEST_CONFIG "test_config" +#define CSV_TP_NOISE_CONFIG "noise_config" + +/*GTX8 CMD*/ +#define GTX8_CMD_NORMAL 0x00 +#define GTX8_CMD_RAWDATA 0x01 +#define GTX8_CMD_SELFDATA 0x14 +/* Regiter for rawdata test*/ +#define GTP_RAWDATA_ADDR_9886 0x8FA0 +#define GTP_NOISEDATA_ADDR_9886 0x9D20 +#define GTP_BASEDATA_ADDR_9886 0xA980 +#define GTP_SELF_RAWDATA_ADDR_9886 0x4C0C +#define GTP_SELF_NOISEDATA_ADDR_9886 0x4CA4 + +#define GTP_RAWDATA_ADDR_6861 0x9078 +#define GTP_NOISEDATA_ADDR_6861 0x9B92 +#define GTP_BASEDATA_ADDR_6861 0xB0DE + +#define GTP_RAWDATA_ADDR_6862 0x9078 +#define GTP_NOISEDATA_ADDR_6862 0x9B92 +#define GTP_BASEDATA_ADDR_6862 0xB0DE + +#define GTP_IC_INFO_ADDR 0x4014 + +/* short test*/ +#define SHORT_TO_GND_RESISTER(sig) (div_s64(5266285, (sig) & (~0x8000)) - 40 * 100) /* (52662.85/code-40) * 100 */ +#define SHORT_TO_VDD_RESISTER(sig, value) (div_s64(36864 * ((value) - 9) * 100, (((sig) & (~0x8000)) * 7)) - 40 * 100) + +#define DRV_CHANNEL_FLAG 0x80 +#define SHORT_STATUS_REG 0x5095 +#define WATCH_DOG_TIMER_REG 0x20B0 + +#define TXRX_THRESHOLD_REG 0x8408 +#define GNDVDD_THRESHOLD_REG 0x840A +#define ADC_DUMP_NUM_REG 0x840C + +#define GNDAVDD_SHORT_VALUE 16 +#define ADC_DUMP_NUM 200 +#define SHORT_CAL_SIZE(a) (4 + (a) * 2 + 2) + +#define SHORT_TESTEND_REG 0x8400 +#define TEST_RESTLT_REG 0x8401 +#define TX_SHORT_NUM 0x8402 + +#define DIFF_CODE_REG 0xA97A +#define DRV_SELF_CODE_REG 0xA8E0 +#define TX_SHORT_NUM_REG 0x8802 +#define YS_IC_MASK_TYPE_REG 0x2222 + +#define MAX_DRV_NUM 40 +#define MAX_SEN_NUM 36 + +#define MAX_DRV_NUM_9886 40 +#define MAX_SEN_NUM_9886 36 + +#define MAX_DRV_NUM_6861 47 +#define MAX_SEN_NUM_6861 36 + +#define MAX_DRV_NUM_6862 47 +#define MAX_SEN_NUM_6862 29 + +#define MAX_DRV_NUM_9896 20 +#define MAX_SEN_NUM_9896 40 +/* end */ + +#define FORMAT_PATH(path,mdir,name,suffix) do{\ + struct timex txc;\ + struct rtc_time tm;\ + do_gettimeofday(&(txc.time));\ + rtc_time_to_tm(txc.time.tv_sec,&tm);\ + sprintf((char*)path,"%s%s_%04d%02d%02d%02d%02d%02d%s",mdir,name,(tm.tm_year+1900),(tm.tm_mon + 1),tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec,suffix);\ +}while(0) + +static int g_save_result = 0; + +static u8 gt9886_drv_map[] = {46, 48, 49, 47, 45, 50, 56, 52, 51, 53, 55, 54, 59, 64, 57, 60, 62, 58, 65, 63, 61, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; +static u8 gt9886_sen_map[] = {32, 34, 35, 30, 31, 33, 27, 28, 29, 10, 25, 26, 23, 13, 24, 12, 9, 11, 8, 7, 5, 6, 4, 3, 2, 1, 0, 73, 75, 74, 39, 72, 40, 36, 37, 38}; + +static u8 gt9885_drv_map[] = {45, 47, 49, 48, 46, 50, 53, 52, 51, 54, 56, 55, 59, 58, 57, 60, 61, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; +static u8 gt9885_sen_map[] = {32, 28, 34, 30, 26, 35, 24, 31, 33, 11, 27, 29, 25, 13, 12, 23, 8, 10, 9, 6, 7, 5, 4, 1, 0, 75, 74, 40, 73, 72, 39, 38, 37, 36,255, 255}; + +static u8 gt6862_drv_map[] = {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, 75, 0, 1, 2, 3, 4, 5, 6}; +static u8 gt6862_sen_map[] = {34, 35, 33, 31, 32, 30, 28, 27, 29, 24, 25, 26, 21, 22, 23, 18, 19, 20, 15, 17, 16, 14, 13, 12, 11, 9, 10, 8, 7}; +static u8 gt6861_drv_map[] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 56, 46, 47, 48, 49, 45, 50, 52, 51, 55, 53, 54, 64, 59, 57, 60, 62, 58, 65, 63, 61, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; +static u8 gt6861_sen_map[] = {72, 37, 38, 40, 36, 74, 39, 75, 0, 73, 2, 1, 3, 4, 5, 6, 7, 8, 10, 11, 9, 12, 13, 24, 23, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; +static u8 gt9896_drv_map[] = {45, 46, 47, 49, 48, 50, 52, 51, 53, 55, 56, 54, 59, 58, 57, 62, 61, 60, 64, 63}; +static u8 gt9896_sen_map[] = {34, 32, 35, 33, 30, 31, 29, 28, 27, 25, 26, 19, 24, 14, 17, 18, 15, 16, 6, 13, 3, 12, 0, 1, 5,75, 4, 2, 44, 74, 41, 42, 43, 38, 40, 36, 39, 37, 66, 65}; + + +/** + * struct ts_test_params - test parameters + * drv_num: touch panel tx(driver) number + * sen_num: touch panel tx(sensor) number + * max_limits: max limits of rawdata + * min_limits: min limits of rawdata + * deviation_limits: channel deviation limits + * short_threshold: short resistance threshold + * r_drv_drv_threshold: resistance threshold between drv and drv + * r_drv_sen_threshold: resistance threshold between drv and sen + * r_sen_sen_threshold: resistance threshold between sen and sen + * r_drv_gnd_threshold: resistance threshold between drv and gnd + * r_sen_gnd_threshold: resistance threshold between sen and gnd + * avdd_value: avdd voltage value + */ +struct ts_test_params { + u16 rawdata_addr; + u16 noisedata_addr; + u16 self_rawdata_addr; + u16 self_noisedata_addr; + + u16 basedata_addr; + u32 max_drv_num; + u32 max_sen_num; + u32 drv_num; + u32 sen_num; + u8 *drv_map; + u8 *sen_map; + + u32 max_limits[MAX_DRV_NUM * MAX_SEN_NUM]; + u32 min_limits[MAX_DRV_NUM * MAX_SEN_NUM]; + u32 deviation_limits[MAX_DRV_NUM * MAX_SEN_NUM]; + u32 self_max_limits[MAX_DRV_NUM + MAX_SEN_NUM]; + u32 self_min_limits[MAX_DRV_NUM + MAX_SEN_NUM]; + + u32 noise_threshold; + u32 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 - rawdata structure + * data: rawdata buffer + * size: rawdata size + */ +struct ts_test_rawdata { + s16 data[MAX_SEN_NUM * MAX_DRV_NUM]; + u32 size; +}; + +struct ts_test_self_rawdata { + s16 data[MAX_DRV_NUM + MAX_SEN_NUM]; + u32 size; +}; + + +/** + * struct gtx8_ts_test - main data structrue + * ts: gtx8 touch screen data + * test_config: test mode config data + * orig_config: original config data + * test_param: test parameters + * rawdata: raw data structure + * test_result: test result string + */ +struct gtx8_ts_test { + //struct gtx8_ts_data *ts; + void *ts; + struct goodix_ts_config test_config; + struct goodix_ts_config orig_config; +#if ((defined NOISE_DATA_TEST) || (defined SELFNOISE_DATA_TEST)) + struct goodix_ts_config noise_config; +#endif + struct goodix_ts_cmd rawdata_cmd; + struct goodix_ts_cmd normal_cmd; +#ifdef SELF_RAWDATA_TEST + struct goodix_ts_cmd selfdata_cmd; +#endif + struct ts_test_params test_params; + struct ts_test_rawdata rawdata; + struct ts_test_rawdata noisedata; + struct ts_test_self_rawdata self_rawdata; + struct ts_test_self_rawdata self_noisedata; + + /*[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]; +}; + +struct short_record { + u32 master; + u32 slave; + u16 short_code; + u8 group1; + u8 group2; +}; + +static int ts_test_reset(struct gtx8_ts_test *ts_test) +{ + struct goodix_ts_core *cd = ts_test->ts; + + return cd->ts_dev->hw_ops->reset(cd->ts_dev); +} + +static int ts_test_read_trans(struct gtx8_ts_test *ts_test, + u32 addr, u8 *data, u32 len) +{ + struct goodix_ts_core *cd = ts_test->ts; + + return cd->ts_dev->hw_ops->read_trans(cd->ts_dev, addr, data, len); +} + +static int ts_test_write(struct gtx8_ts_test *ts_test, + u32 addr, u8 *data, u32 len) +{ + struct goodix_ts_core *cd = ts_test->ts; + + return cd->ts_dev->hw_ops->write(cd->ts_dev, addr, data, len); +} + +static int ts_test_write_trans(struct gtx8_ts_test *ts_test, + u32 addr, u8 *data, u32 len) +{ + struct goodix_ts_core *cd = ts_test->ts; + + return cd->ts_dev->hw_ops->write_trans(cd->ts_dev, addr, data, len); +} + +static int ts_test_send_config(struct gtx8_ts_test *ts_test, + struct goodix_ts_config *config) +{ + struct goodix_ts_core *cd = ts_test->ts; + + return cd->ts_dev->hw_ops->send_config(cd->ts_dev, config); +} + +static void gtx8_data_cal(u16 *data, size_t data_size, u16 *stat_result); + +/*******************************save csv**********************************************/ + +#ifdef SAVE_IN_CSV +static size_t fwrite(const void* buf, size_t size, size_t count, struct file* fp){ + mm_segment_t fs; + loff_t pos; + int len; + fs = get_fs(); + set_fs(KERNEL_DS); + pos = fp->f_pos; + len = vfs_write(fp, buf, size*count, &pos); + fp->f_pos = pos; + set_fs(fs); + return len; +} + +/* save test config in csv file */ +static s32 gtx8_save_test_config(struct gtx8_ts_test *ts_test, struct file *fp) +{ + s32 ret = 0; + u16 i = 0; + s32 bytes = 0; + char *data; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (data == NULL) { + ts_err("alloc memory failed for "); + return -ENOMEM; + } + + bytes += (int) sprintf((char*)&data[bytes], "\n"); + for (i = 0; i < ts_test->test_config.length; i++) { + bytes += (s32) sprintf((char *)&data[bytes], "0x%02x,", ts_test->test_config.data[i]); + if ((i+1)%ts_test->test_params.drv_num == 0) { + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("test config write fail.\n"); + goto save_end; + } + bytes = 0; + } + } + bytes += (int) sprintf((char*)&data[bytes], "\n"); + bytes += (int) sprintf((char*)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("test config write fail.\n"); + goto save_end; + } + bytes = 0; + +save_end: + if (data != NULL) + kfree(data); + return ret; + +} + +/* save header info in csv file */ +static s32 gtx8_save_header(struct gtx8_ts_test *ts_test, struct file *fp) +{ + s32 ret = 0; + u16 i = 0; + s32 bytes = 0; + int result = 0; + char *data; + struct goodix_ts_core* core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (data == NULL) { + ts_err("alloc memory failed for "); + return -ENOMEM; + } + + + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + bytes += (int) sprintf((char*)&data[bytes], "\n"); + bytes += (int) sprintf((char*)&data[bytes], "
\n"); + // save test result + for (i = 0; i < MAX_TEST_ITEMS; i++) { + if (ts_test->test_result[i] == SYS_SOFTWARE_REASON + ||ts_test->test_result[i] == GTP_PANEL_REASON) { + result = 1; + break; + } + } + if (result) + bytes += (int) sprintf((char*)&data[bytes], "NG\n"); + else + bytes += (int) sprintf((char*)&data[bytes], "OK\n"); + + bytes += (int) sprintf((char*)&data[bytes], "GT%s\n", + core_data->ts_dev->chip_version.pid); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("header write fail.\n"); + goto save_end; + } + bytes = 0; + //save test config + ret = gtx8_save_test_config(ts_test, fp); + if (ret < 0) + goto save_end; + + bytes += (int) sprintf((char*)&data[bytes], "
\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("header write fail.\n"); + goto save_end; + } + bytes = 0; + + //item list + bytes += (int) sprintf((char*)&data[bytes], "\n"); + if (ts_test->test_result[GTP_CAP_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_CAP_TEST]) + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + else + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + } + if (ts_test->test_result[GTP_DELTA_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_DELTA_TEST]) + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + else + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + } +#ifdef NOISE_DATA_TEST + if (ts_test->test_result[GTP_NOISE_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_NOISE_TEST]) + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + else + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + } +#endif +#ifdef SELFNOISE_DATA_TEST + if (ts_test->test_result[GTP_SELFNOISE_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_SELFNOISE_TEST]) + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + else + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + } +#endif +#ifdef SELF_RAWDATA_TEST + if (ts_test->test_result[GTP_SELFCAP_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_SELFCAP_TEST]) + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + else + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + } +#endif +#ifdef SHORT_TEST + if (ts_test->test_result[GTP_SHORT_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_SHORT_TEST]) + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + else + bytes += (int) sprintf((char*)&data[bytes], + "\n"); + } +#endif + bytes += (int) sprintf((char*)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("item list write fail.\n"); + goto save_end; + } + bytes = 0; +save_end: + if (data != NULL) + kfree(data); + return ret; + +} + +/* save end tag in csv file */ +static s32 gtx8_save_tail(struct gtx8_ts_test *ts_test, struct file *fp) +{ + s32 ret = 0; + s32 bytes = 0; + char *data; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (data == NULL) { + ts_err("alloc memory failed for "); + return -ENOMEM; + } + + + bytes += (int) sprintf((char*)&data[bytes], "
\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("tail write fail.\n"); + goto save_end; + } + bytes = 0; +save_end: + if (data != NULL) + kfree(data); + return ret; + +} + +/* save data limits in csv file */ +static s32 gtx8_save_limits_data(struct gtx8_ts_test *ts_test, struct file *fp, + int *src_data, int len, u8* title) +{ + s32 ret = 0; + u16 i = 0; + s32 bytes = 0; + char *data; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (data == NULL) { + ts_err("alloc memory failed for "); + return -ENOMEM; + } + + bytes += (int) sprintf((char*)&data[bytes], "<%s>\n",title); + for (i = 0; i < len; i++) { + bytes += (s32) sprintf((char *)&data[bytes], "%d,", src_data[i]); + if ((i+1)%ts_test->test_params.drv_num == 0) { + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("limit write fail.\n"); + goto save_end; + } + bytes = 0; + } + } + if (i%ts_test->test_params.drv_num != 0) + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + bytes += (s32) sprintf((char *)&data[bytes], "\n",title); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("limit write fail.\n"); + goto save_end; + } + bytes = 0; + +save_end: + if (data != NULL) + kfree(data); + return ret; + +} + + +static s32 gtx8_save_limits(struct gtx8_ts_test *ts_test, struct file *fp) +{ + s32 ret = 0; + s32 bytes = 0; + char *data; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (data == NULL) { + ts_err("alloc memory failed for "); + return -ENOMEM; + } + + + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + //rawdata limit + bytes += (int) sprintf((char*)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("limit write fail.\n"); + goto save_end; + } + bytes = 0; + //save max limit + gtx8_save_limits_data(ts_test, fp, &ts_test->test_params.max_limits[0], + ts_test->rawdata.size, "MaxRawLimit"); + gtx8_save_limits_data(ts_test, fp, &ts_test->test_params.min_limits[0], + ts_test->rawdata.size, "MinRawLimit"); + gtx8_save_limits_data(ts_test, fp, &ts_test->test_params.deviation_limits[0], + ts_test->rawdata.size, "MaxAccordLimit"); + + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("Item write fail.\n"); + goto save_end; + } + bytes = 0; + +#ifdef NOISE_DATA_TEST + //save noise limit + bytes += (int) sprintf((char*)&data[bytes], "\n"); + bytes += (int) sprintf((char*)&data[bytes], "%d\n", + ts_test->test_params.noise_threshold); + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("Noise limit write fail.\n"); + goto save_end; + } + bytes = 0; +#endif +#ifdef SELF_RAWDATA_TEST + //self rawdata limit + bytes += (int) sprintf((char*)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("self raw limit write fail.\n"); + goto save_end; + } + bytes = 0; + gtx8_save_limits_data(ts_test, fp, &ts_test->test_params.self_max_limits[0], + ts_test->self_rawdata.size, "MaxRawLimit"); + gtx8_save_limits_data(ts_test, fp, &ts_test->test_params.self_min_limits[0], + ts_test->self_rawdata.size, "MinRawLimit"); + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("self Item write fail.\n"); + goto save_end; + } + bytes = 0; +#endif +#ifdef SELFNOISE_DATA_TEST + //self diffdata limit + bytes += (int) sprintf((char*)&data[bytes], "\n"); + bytes += (int) sprintf((char*)&data[bytes], "%d\n", + ts_test->test_params.self_noise_threshold); + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("Noise limit write fail.\n"); + goto save_end; + } + bytes = 0; +#endif + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("limit write fail.\n"); + goto save_end; + } + bytes = 0; +save_end: + if (data != NULL) + kfree(data); + return ret; + +} + +/* save data in csv file */ +static s32 gtx8_save_sampdata(struct gtx8_ts_test *ts_test, struct file *fp, u16* src_data, + u8 *title, int len) +{ + s32 ret = 0; + u16 i = 0; + s32 bytes = 0; + u16 stat_result[3]; + char *data; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (data == NULL) { + ts_err("alloc memory failed for "); + return -ENOMEM; + } + + bytes += (s32) sprintf((char *)&data[bytes], "<%s>\n",title); + gtx8_data_cal(src_data, len, stat_result); + bytes += (s32) sprintf((char *)&data[bytes], + "\n", + len,stat_result[1],stat_result[2], stat_result[0]); + + for (i = 0; i < len; i++) { + bytes += (s32) sprintf((char *)&data[bytes], "%d,", src_data[i]); + if ((i+1)%ts_test->test_params.drv_num == 0) { + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("rawdata write fail.\n"); + goto save_end; + } + bytes = 0; + } + } + if (i%ts_test->test_params.drv_num != 0) + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + bytes += (s32) sprintf((char *)&data[bytes], "\n",title); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("rawdata write fail.\n"); + goto save_end; + } + bytes = 0; + +save_end: + kfree(data); + return ret; +} + +/* save rawdiff data in csv file */ +static int gtx8_save_rawdiff(struct gtx8_ts_test *ts_test, struct file *fp) +{ + int ret = 0; + s32 bytes = 0; + char *data; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (data == NULL) { + ts_err("alloc memory failed for "); + return -ENOMEM; + } + + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("rawdata data record lable fail.\n"); + goto save_end; + } + bytes = 0; + + ret = gtx8_save_sampdata(ts_test, fp, &ts_test->rawdata.data[0], + "RawDataRecord", ts_test->rawdata.size); + if (ret < 0) + goto save_end; +#ifdef NOISE_DATA_TEST + ret = gtx8_save_sampdata(ts_test, fp, &ts_test->noisedata.data[0], + "DiffDataRecord", ts_test->noisedata.size); + if (ret < 0) + goto save_end; +#endif +#ifdef SELF_RAWDATA_TEST + ret = gtx8_save_sampdata(ts_test, fp, &ts_test->self_rawdata.data[0], + "selfDataRecord", ts_test->self_rawdata.size); + if (ret < 0) + goto save_end; +#endif +#ifdef SELFNOISE_DATA_TEST + ret = gtx8_save_sampdata(ts_test, fp, &ts_test->self_noisedata.data[0], + "selfDiffDataRecord", ts_test->self_noisedata.size); + if (ret < 0) + goto save_end; +#endif + bytes += (s32) sprintf((char *)&data[bytes], "\n"); + ret = fwrite(data, bytes, 1, fp); + if (ret < 0) { + ts_err("rawdata data record lable fail.\n"); + goto save_end; + } + bytes = 0; +save_end: + kfree(data); + return ret; +} + +/* save result and rawdiff data in csv file */ +static int gtx8_save_result_data(struct gtx8_ts_test *ts_test) +{ + int ret = 0; + char save_path[PATH_LEN]; + struct file *fp = NULL; + + //format result file + FORMAT_PATH(save_path, GTX8_RESULT_SAVE_PATH,"Test_Data",".csv"); + + fp = filp_open(save_path, O_CREAT|O_TRUNC|O_WRONLY ,0666); + if (IS_ERR(fp)) { + ts_err("open file:%s fail,fp:%ld", save_path,PTR_ERR(fp)); + return -1; + } + //save header + ret = gtx8_save_header(ts_test,fp); + if (ret < 0) + goto save_end; + + //save limits + ret =gtx8_save_limits(ts_test, fp); + if (ret < 0) + goto save_end; + + //save data + ret = gtx8_save_rawdiff(ts_test, fp); + if (ret < 0) + goto save_end; + //save tail + ret = gtx8_save_tail(ts_test, fp); + if (ret < 0) + goto save_end; + + ts_info("the test result saves in %s\n", save_path); +save_end: + filp_close(fp, NULL); + return ret; +} +#endif + +/*******************************save result to csv end**********************************************/ + +/*******************************csv_parse**********************************************/ +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 void goto_next_line(char **ptr) +{ + do { + *ptr = *ptr + 1; + } while (**ptr != '\n' && **ptr != '\0'); + if (**ptr == '\0') { + return; + } + *ptr = *ptr + 1; +} + +s32 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); + buf = NULL; + + return count; +} + +static int parse_valid_data(char *buf_start, loff_t buf_size, + char *ptr, int32_t* data, int rows) +{ + int i = 0; + int j = 0; + char *token = NULL; + char *tok_ptr = NULL; + char *row_data = NULL; + + if(!ptr) { + ts_err("%s, ptr is NULL\n", __func__); + return -EPERM; + } + if (!data) { + ts_err("%s, data is NULL\n", __func__); + return -EPERM; + } + + row_data = (char *)kzalloc(MAX_LINE_LEN, GFP_KERNEL); + if(NULL == row_data) { + ts_err("%s: kzalloc %d bytes failed.\n", __func__, MAX_LINE_LEN); + return -ESRCH; + } + + for (i = 0; i < rows; i++) { + // copy this line to row_data buffer + 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 ( token[0] == '0' && (token[1] == 'X' || token[1] == 'x')){ + data[j] = (int32_t)simple_strtol(token, NULL, STRTOL_HEX_LEN); + j++; + }else{ + data[j] = (int32_t)simple_strtol(token, NULL, STRTOL_LEN); + j++; + } + } + 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\n"); + kfree(row_data); + row_data = NULL; + return -EPERM; + } + } + kfree(row_data); + row_data = NULL; + return j; +} + +/*parse data from csv + * @return: length of data + */ +int parse_csvfile(struct device *dev, char *target_name, int32_t *data, int rows, int columns) +{ + int ret = 0; + int i = 0; + int32_t read_ret = 0; + char *buf = NULL; + char *ptr = NULL; + const struct firmware *firmware = NULL; + + if(NULL == target_name) { + ts_err("target path pointer is NULL\n"); + ret = -EPERM; + goto exit_free; + } + + ts_info("%s, file name is %s, target is %s.\n", __func__,GTX8_TEST_FILE_NAME,target_name); + for (i = 0; i < 3; i++) { + ret = request_firmware(&firmware, GTX8_TEST_FILE_NAME, dev); + if (ret < 0) { + ts_err("limits file [%s] not available,error:%d, try_times:%d", GTX8_TEST_FILE_NAME, ret, i + 1); + msleep(1000); + } else { + ts_info("limits file [%s] is ready, try_times:%d", GTX8_TEST_FILE_NAME, i + 1); + break; + } + } + if (i >= 3) { + ts_err("get limits file FAILED"); + goto exit_free; + } + + if (firmware->size <= 0) { + ts_err("request_firmware, limits param length ERROR,len:%zu", firmware->size); + ret = -EINVAL; + goto exit_free; + } + + buf = (char *)kzalloc(firmware->size + 1, GFP_KERNEL); + if(NULL == buf) { + ts_err("%s: kzalloc %zu bytes failed.\n", __func__, firmware->size); + ret = -ESRCH; + goto exit_free; + } + memcpy(buf,firmware->data,firmware->size); + read_ret = firmware->size; + if (read_ret > 0) { + buf[firmware->size] = '\0'; + ptr = buf; + ptr = strstr(ptr, target_name); + if (ptr == NULL) { + ts_err("%s: load %s failed 1!\n", __func__,target_name); + ret = -EINTR; + goto exit_free; + } + + // walk thru this line + goto_next_line(&ptr); + if ((NULL == ptr) || (0 == strlen(ptr))) { + ts_err("%s: load %s failed 2!\n", __func__,target_name); + ret = -EIO; + goto exit_free; + } + + //analyze the data + if (data) { + ret = parse_valid_data(buf, firmware->size, ptr, data, rows); + }else{ + ts_err("%s: load %s failed 3!\n", __func__,target_name); + ret = -EINTR; + goto exit_free; + } + } + else { + ts_err("%s: ret=%d,read_ret=%d, buf=%p, firmware->size=%zu\n", __func__, ret, read_ret, buf, firmware->size); + ret = -ENXIO; + goto exit_free; + } +exit_free: + ts_info("%s exit free\n", __func__); + if(buf) { + ts_info("kfree buf\n"); + kfree(buf); + buf = NULL; + } + + if (firmware) { + release_firmware(firmware); + firmware = NULL; + } + return ret; +} +/*******************************csv_parse end**********************************************/ + +/******************************************************************************* +* Function Name : cfg_update_chksum +* Description : update check sum +* Input : u32* config +* Input : u16 cfg_len +* Output : u8* config +* Return : none +*******************************************************************************/ +static void cfg_update_chksum(struct goodix_ts_core *core_data, u8* config, u16 cfg_len) +{ + u16 pack_map_len_arr[255]; + u16 packNum = 0; + u16 pack_len_tmp = 0; + u16 pack_id_tmp = 0; + u16 i = 0,j = 0; + u16 cur_pos = 0; + u8 check_sum = 0; + u16 check_sum_u16 = 0; + u8 chksum_len = 1; + u8 head_len; + + if (core_data->ts_dev->ic_type == IC_TYPE_YELLOWSTONE) + chksum_len = 2; + head_len = 3 + chksum_len; + + if (cfg_len < head_len) return; + /* Head:4 bytes |byte0:version|byte1:config refresh|byte2:package total num|byte3:check sum|*/ + pack_map_len_arr[pack_id_tmp] = head_len; + packNum = config[2]; + for (i = head_len; i < cfg_len;){ + pack_id_tmp++; + pack_len_tmp = config[i + 1] + 2 + chksum_len; + pack_map_len_arr[pack_id_tmp] = pack_len_tmp; + i += pack_len_tmp; + } + + cur_pos = 0; + for (i = 0; i <= pack_id_tmp; i++) + { + check_sum = 0; + check_sum_u16 = 0; + for (j = cur_pos; j < cur_pos + pack_map_len_arr[i] - chksum_len; j++) + { + check_sum += config[j]; + check_sum_u16 += config[j]; + } + if (chksum_len == 1) { + config[cur_pos + pack_map_len_arr[i] - chksum_len] = (u8)(0 - check_sum); + } else if (chksum_len == 2) { + config[cur_pos + pack_map_len_arr[i] - chksum_len] = (check_sum_u16 >> 8) & 0xff; + config[cur_pos + pack_map_len_arr[i] - chksum_len + 1] = check_sum_u16 & 0xff; + + } + cur_pos += pack_map_len_arr[i]; + } +} + +static int disable_hopping(struct gtx8_ts_test *ts_test, struct goodix_ts_config *test_config) +{ + int ret = 0; + u8 value = 0; + u16 offset = 0; + struct goodix_ts_core *core_data; + + core_data = (struct goodix_ts_core*)ts_test->ts; + if (core_data->ts_dev->ic_type == IC_TYPE_YELLOWSTONE) + ret = goodix_get_cfg_value(core_data, test_config->data, &value, 1, 11, 2); /* T11 2 0 0*/ + else + ret = goodix_get_cfg_value(core_data, test_config->data, &value, 1, 10, 2); /* T10 2 0 0*/ + if (ret < 0 ){ + ts_err("Failed parse hopping reg"); + return -EINVAL; + } + offset = ret; + /* disable hopping */ + value = test_config->data[offset]; + value &= 0xfe; + test_config->data[offset] = value; + ts_info("disable_hopping:0x%02x_%d",test_config->data[offset], offset); + cfg_update_chksum(core_data, test_config->data, test_config->length); + return ret; +} + +static int init_test_config(struct gtx8_ts_test *ts_test) { + int ret = 0; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + if (ts_test->test_config.length == 0){ + ts_info("switch to orignal config!"); + memmove(ts_test->test_config.data,ts_test->orig_config.data, + ts_test->orig_config.length); + ts_test->test_config.length = ts_test->orig_config.length; + }else{ + ts_test->test_config.data[0] = ts_test->orig_config.data[0]; + ts_test->test_config.data[1] |= 0x01; + ts_info("switch to test config!"); + } + ts_test->test_config.reg_base = core_data->ts_dev->reg.cfg_addr; + mutex_init(&(ts_test->test_config.lock)); + ts_test->test_config.initialized = true; + strcpy(ts_test->test_config.name, "test_config"); + disable_hopping(ts_test, &ts_test->test_config); + return ret; +} + +static int gtx8_init_testlimits(struct gtx8_ts_test *ts_test) +{ + int ret = 0, i = 0; + u32 data_buf[MAX_DRV_NUM] = {0}; + int *tmp_config = NULL; + struct ts_test_params *test_params = NULL; + struct goodix_ts_config *test_config = NULL; + struct goodix_ts_core *core_data; + + test_params = &ts_test->test_params; + test_config = &ts_test->test_config; + core_data = (struct goodix_ts_core*)ts_test->ts; + tmp_config = (int*)kzalloc(GOODIX_CFG_MAX_SIZE*sizeof(int), GFP_KERNEL); + if(NULL == tmp_config) { + ts_err("%s: config kzalloc bytes failed.\n", __func__); + return -ENOMEM; + } + + /* */ + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_UNIFIED_LIMIT, data_buf, 1, 3); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_UNIFIED_LIMIT); + goto INIT_LIMIT_END; + } + /* store data to test_parms */ + for (i = 0; i < MAX_DRV_NUM * MAX_SEN_NUM; i++) { + test_params->max_limits[i] = data_buf[0]; + test_params->min_limits[i] = data_buf[1]; + test_params->deviation_limits[i] = data_buf[2]; + } +#ifdef NOISE_DATA_TEST + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_NOISE_LIMIT, data_buf, 1, 1); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_NOISE_LIMIT); + goto INIT_LIMIT_END; + } + test_params->noise_threshold = data_buf[0]; +#endif +#ifdef SELFNOISE_DATA_TEST + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_SELFNOISE_LIMIT, data_buf, 1, 1); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_SELFNOISE_LIMIT); + goto INIT_LIMIT_END; + } + test_params->self_noise_threshold = data_buf[0]; +#endif + +#ifdef SELF_RAWDATA_TEST + /* */ + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_SELF_UNIFIED_LIMIT, data_buf, 1, 2); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_SELF_UNIFIED_LIMIT); + goto INIT_LIMIT_END; + } + /* store data to test_parms */ + for (i = 0; i < MAX_DRV_NUM + MAX_SEN_NUM; i++) { + test_params->self_max_limits[i] = data_buf[0]; + test_params->self_min_limits[i] = data_buf[1]; + } + + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_SPECIAL_SELFRAW_MAX, test_params->self_max_limits, + 1, test_params->drv_num + test_params->sen_num); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_SPECIAL_SELFRAW_MAX); + ret = 0; + } + + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_SPECIAL_SELFRAW_MIN, test_params->self_min_limits, + 1, test_params->drv_num + test_params->sen_num); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_SPECIAL_SELFRAW_MIN); + ret = 0; + } +#endif +#ifdef SHORT_TEST + /* shortciurt_threshold */ + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_SHORT_THRESHOLD, data_buf, 1, 7); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_SHORT_THRESHOLD); + goto INIT_LIMIT_END; + } + 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]; +#endif + + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_SPECIAL_RAW_MAX, test_params->max_limits, + test_params->sen_num, test_params->drv_num); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_SPECIAL_RAW_MAX); + ret = 0; /* if does not specialed the node value, we will use unified limits setting */ + } + + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_SPECIAL_RAW_MIN, test_params->min_limits, + test_params->sen_num, test_params->drv_num); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_SPECIAL_RAW_MIN); + ret = 0; + } + + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_SPECIAL_RAW_DELTA, test_params->deviation_limits, + test_params->sen_num, test_params->drv_num); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_SPECIAL_RAW_DELTA); + ret = 0; + } + + ret = parse_csvfile(core_data->ts_dev->dev, CSV_TP_TEST_CONFIG, tmp_config, + 1, GOODIX_CFG_MAX_SIZE); + if (ret < 0) { + ts_info("%s: Failed get %s\n", __func__, CSV_TP_TEST_CONFIG); + test_config->length = 0; + ret = 0; + } else { + for (i = 0; i < ret; i++) + test_config->data[i] = tmp_config[i]; + test_config->length = ret; + } + init_test_config(ts_test); + + ret = 0; +INIT_LIMIT_END: + kfree(tmp_config); + tmp_config = NULL; + return ret; +} + +static int get_ic_info(struct goodix_ts_device *dev, + struct ts_test_params *test_params) +{ + int ret = 0; + u8 buf[10]; + int ic_info_len; + u8 *info_data = NULL; + int offset; + u16 info_addr = GTP_IC_INFO_ADDR; + + ret = dev->hw_ops->read_trans(dev, info_addr, buf, 2); + if (ret < 0) { + ts_err("read ic info len failed,ret:%d\n",ret); + return ret; + } + ic_info_len = (buf[0]<<8) + buf[1]; + + info_data = kzalloc(ic_info_len, GFP_KERNEL); + if (info_data == NULL){ + ts_err("%s: Failed to alloc mem for info_data\n", __func__); + return -ENOMEM; + } + ts_info("ic info len:%d\n",ic_info_len); + + ret = dev->hw_ops->read_trans(dev, info_addr, info_data, ic_info_len); + if (ret < 0){ + ts_err("%s:read ic info failed,ret:%d\n", __func__, ret); + goto err_out; + } + + offset = 36; /* drv num */ + test_params->drv_num = info_data[offset]; + offset++; + test_params->sen_num = info_data[offset]; + + offset = 83; + test_params->rawdata_addr = (info_data[offset]<<8) + info_data[offset+1]; + offset += 2; + test_params->noisedata_addr = (info_data[offset]<<8) + info_data[offset+1]; + offset += 2; + test_params->basedata_addr = (info_data[offset]<<8) + info_data[offset+1]; + offset += 4; /* self data addr */ + test_params->self_rawdata_addr = (info_data[offset]<<8) + info_data[offset+1]; + offset += 2; + test_params->self_noisedata_addr = (info_data[offset]<<8) + info_data[offset+1]; + + if (!strncmp(dev->chip_version.pid, "9896", 4)){ + test_params->max_drv_num = MAX_DRV_NUM_9896; + test_params->max_sen_num = MAX_SEN_NUM_9896; + test_params->drv_map = gt9896_drv_map; + test_params->sen_map = gt9896_sen_map; + }else{ + ts_err("unsupport ic type:%s\n", dev->chip_version.pid); + test_params->max_drv_num = MAX_DRV_NUM_9896; + test_params->max_sen_num = MAX_SEN_NUM_9896; + test_params->drv_map = gt9896_drv_map; + test_params->sen_map = gt9896_sen_map; + ret = NO_ERR; + } + +err_out: + if (info_data) + kfree(info_data); + return ret; +} + +static int gtx8_init_params(struct gtx8_ts_test *ts_test) +{ + int ret = 0; + struct goodix_ts_core *core_data; + struct ts_test_params *test_params = &ts_test->test_params; + core_data = (struct goodix_ts_core*)ts_test->ts; + ts_info("product id:%s\n", core_data->ts_dev->chip_version.pid); + + if (!strncmp(core_data->ts_dev->chip_version.pid, "9886", 4)){ + test_params->rawdata_addr = GTP_RAWDATA_ADDR_9886; + test_params->noisedata_addr = GTP_NOISEDATA_ADDR_9886; + test_params->self_rawdata_addr = GTP_SELF_RAWDATA_ADDR_9886; + test_params->self_noisedata_addr = GTP_SELF_NOISEDATA_ADDR_9886; + test_params->basedata_addr = GTP_BASEDATA_ADDR_9886; + test_params->max_drv_num = MAX_DRV_NUM_9886; + test_params->max_sen_num = MAX_SEN_NUM_9886; + test_params->drv_map = gt9886_drv_map; + test_params->sen_map = gt9886_sen_map; + }else if (!strncmp(core_data->ts_dev->chip_version.pid, "9885", 4)){ + test_params->rawdata_addr = GTP_RAWDATA_ADDR_9886; + test_params->noisedata_addr = GTP_NOISEDATA_ADDR_9886; + test_params->self_rawdata_addr = GTP_SELF_RAWDATA_ADDR_9886; + test_params->self_noisedata_addr = GTP_SELF_NOISEDATA_ADDR_9886; + test_params->basedata_addr = GTP_BASEDATA_ADDR_9886; + test_params->max_drv_num = MAX_DRV_NUM_9886; + test_params->max_sen_num = MAX_SEN_NUM_9886; + test_params->drv_map = gt9885_drv_map; + test_params->sen_map = gt9885_sen_map; + }else if (!strncmp(core_data->ts_dev->chip_version.pid, "6861", 4)){ + test_params->rawdata_addr = GTP_RAWDATA_ADDR_6861; + test_params->noisedata_addr = GTP_NOISEDATA_ADDR_6861; + test_params->basedata_addr = GTP_BASEDATA_ADDR_6861; + test_params->max_drv_num = MAX_DRV_NUM_6861; + test_params->max_sen_num = MAX_SEN_NUM_6861; + test_params->drv_map = gt6861_drv_map; + test_params->sen_map = gt6861_sen_map; + }else if (!strncmp(core_data->ts_dev->chip_version.pid, "6862", 4)){ + test_params->rawdata_addr = GTP_RAWDATA_ADDR_6862; + test_params->noisedata_addr = GTP_NOISEDATA_ADDR_6862; + test_params->basedata_addr = GTP_BASEDATA_ADDR_6862; + test_params->max_drv_num = MAX_DRV_NUM_6862; + test_params->max_sen_num = MAX_SEN_NUM_6862; + test_params->drv_map = gt6862_drv_map; + test_params->sen_map = gt6862_sen_map; + }else if (core_data->ts_dev->ic_type == IC_TYPE_YELLOWSTONE) { + ret = get_ic_info(core_data->ts_dev, test_params); + }else{ + ts_err("unsupport ic type:%s\n", core_data->ts_dev->chip_version.pid); + test_params->rawdata_addr = GTP_RAWDATA_ADDR_9886; + test_params->noisedata_addr = GTP_NOISEDATA_ADDR_9886; + test_params->self_rawdata_addr = GTP_SELF_RAWDATA_ADDR_9886; + test_params->self_noisedata_addr = GTP_SELF_NOISEDATA_ADDR_9886; + test_params->basedata_addr = GTP_BASEDATA_ADDR_9886; + test_params->max_drv_num = MAX_DRV_NUM_9886; + test_params->max_sen_num = MAX_SEN_NUM_9886; + test_params->drv_map = gt9886_drv_map; + test_params->sen_map = gt9886_sen_map; + ret = NO_ERR; + //ret = -1; + } + return ret; +} + +static void goodix_cmd_init(struct goodix_ts_device *dev, + struct goodix_ts_cmd *ts_cmd, + u8 cmds, u16 cmd_data, u32 reg_addr) +{ + u16 checksum = 0; + ts_cmd->initialized = false; + if (!reg_addr || !ts_cmd) + return; + + if (dev->ic_type == IC_TYPE_YELLOWSTONE) { + ts_cmd->cmd_reg = reg_addr; + ts_cmd->length = 5; + ts_cmd->cmds[0] = cmds; + ts_cmd->cmds[1] = (cmd_data >> 8) & 0xFF; + ts_cmd->cmds[2] = cmd_data & 0xFF; + checksum = ts_cmd->cmds[0] + ts_cmd->cmds[1] + + ts_cmd->cmds[2]; + ts_cmd->cmds[3] = (checksum >> 8) & 0xFF; + ts_cmd->cmds[4] = checksum & 0xFF; + ts_cmd->initialized = true; + } else if (dev->ic_type == IC_TYPE_NORMANDY) { + ts_cmd->cmd_reg = reg_addr; + ts_cmd->length = 3; + ts_cmd->cmds[0] = cmds; + ts_cmd->cmds[1] = cmd_data & 0xFF; + ts_cmd->cmds[2] = 0 - cmds - cmd_data; + ts_cmd->initialized = true; + } else { + ts_err("unsupported ic type"); + } +} + +/* gtx8_tptest_prepare + * + * preparation before tp test + */ +static int gtx8_tptest_prepare(struct gtx8_ts_test *ts_test) +{ + int ret = 0; + u8 cmds[3]; + struct goodix_ts_core *core_data; + + core_data = (struct goodix_ts_core*)ts_test->ts; + + ts_info("TP test preparation"); + + /* init reg addr and short cal map */ + ret = gtx8_init_params(ts_test); + if (ret) { + ts_err("Failed init register address"); + return ret; + } + + if (core_data->ts_dev->ic_type != IC_TYPE_YELLOWSTONE) { + /* get sensor and driver num currently in use */ + ts_test->test_params.sen_num = core_data->sen_num; + ts_test->test_params.drv_num = core_data->drv_num; + } + /* parse test limits from csv */ + ret = gtx8_init_testlimits(ts_test); + if (ret) { + ts_err("Failed to init testlimits from csv:%d", ret); + return ret; + } + + if (ts_test->test_config.initialized) { + ret = ts_test_send_config(ts_test, &ts_test->test_config); + if (ret) { + ts_err("Failed to send noise test config config:%d", ret); + return ret; + } + ts_info("send test config success"); + } + + /* disable irq & close esd */ + goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL); + goodix_ts_irq_enable(core_data, false); + usleep_range(10000, 11000); + + /* switch rawdata mode */ + cmds[0] = 0x01; + cmds[1] = 0x00; + cmds[2] = 0 - cmds[0] - cmds[1]; + ret = ts_test_write(ts_test, + core_data->ts_dev->reg.command, cmds, sizeof(cmds)); + if (ret < 0) { + ts_err("switch rawdata mode failed"); + goto err; + } + msleep(30); + return 0; + +err: + goodix_ts_irq_enable(core_data, true); + goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL); + return ret; +} + +/* gtx8_tptest_finish + * + * finish test + */ +static int gtx8_tptest_finish(struct gtx8_ts_test *ts_test) +{ + int ret = RESULT_ERR; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + + ts_info("TP test finish"); + ret = ts_test_reset(ts_test); + if (ret) + ts_err("chip reset failed"); + + /* send normal config */ + ret = ts_test_send_config(ts_test, &core_data->ts_dev->normal_cfg); + if (ret) + ts_err("send normal config failed"); + + goodix_ts_irq_enable(core_data, true); + goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL); + + return ret; +} + +/** + * gtx8_cache_rawdata - cache rawdata + */ +static int gtx8_cache_rawdata(struct gtx8_ts_test *ts_test) +{ + int i = 0; + int ret = -EINVAL; + int retry = 20; + u8 irq_flag; + u32 rawdata_size = 0; + u16 rawdata_addr = 0; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + ts_debug("Cache rawdata"); + ts_test->rawdata.size = 0; + rawdata_size = ts_test->test_params.sen_num *ts_test->test_params.drv_num; + + if (rawdata_size > MAX_DRV_NUM * MAX_SEN_NUM || rawdata_size <= 0) { + ts_err("Invalid rawdata size(%u)", rawdata_size); + return ret; + } + + rawdata_addr = ts_test->test_params.rawdata_addr; + + irq_flag = 0; + ts_test_write_trans(ts_test, core_data->ts_dev->reg.coor, &irq_flag, 1); + while (retry--) { + usleep_range(5000, 5100); + ts_test_read_trans(ts_test, core_data->ts_dev->reg.coor, &irq_flag, 1); + if (irq_flag & 0x80) + break; + } + if (retry < 0) { + ts_err("rawdata is not ready irq_flag:0x%02x, exit", irq_flag); + return -EAGAIN; + } + + ret = ts_test_read_trans(ts_test, rawdata_addr, + (u8 *)&ts_test->rawdata.data[0], rawdata_size * sizeof(u16)); + if (ret < 0) { + ts_err("Failed to read rawdata:%d", ret); + return ret; + } + for (i = 0; i < rawdata_size; i++) + ts_test->rawdata.data[i] = be16_to_cpu(ts_test->rawdata.data[i]); + goodix_rotate_abcd2cbad(ts_test->test_params.drv_num, + ts_test->test_params.sen_num, ts_test->rawdata.data); + ts_test->rawdata.size = rawdata_size; + + return ret; +} + +#ifdef SELF_RAWDATA_TEST +/** + * gtx8_cache_selfrawdata - cache selfrawdata + */ +static int gtx8_cache_self_rawdata(struct gtx8_ts_test *ts_test) +{ + int i = 0; + int ret = -EINVAL; + int retry = 20; + u8 irq_flag; + u16 self_rawdata_size =0; + u16 self_rawdata_addr = 0; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + ts_debug("Cache selfrawdata"); + ts_test->self_rawdata.size = 0; + self_rawdata_size = ts_test->test_params.sen_num +ts_test->test_params.drv_num; + + if (self_rawdata_size > MAX_DRV_NUM + MAX_SEN_NUM || self_rawdata_size <= 0) { + ts_err("Invalid selfrawdata size(%u)", self_rawdata_size); + return ret; + } + + self_rawdata_addr = ts_test->test_params.self_rawdata_addr; + ts_info("Selfraw address=0x%x", self_rawdata_addr); + + irq_flag = 0; + ts_test_write_trans(ts_test, core_data->ts_dev->reg.coor, &irq_flag, 1); + while (retry--) { + usleep_range(5000, 5100); + ts_test_read_trans(ts_test, core_data->ts_dev->reg.coor, &irq_flag, 1); + if (irq_flag & 0x80) + break; + } + if (retry < 0) { + ts_err("self rawdata is not ready irq_flag:0x%02x, exit", irq_flag); + return -EAGAIN; + } + ret = ts_test_read_trans(ts_test, self_rawdata_addr, + (u8 *)&ts_test->self_rawdata.data[0], self_rawdata_size * sizeof(u16)); + if (ret < 0) { + ts_err("Failed to read self_rawdata:%d", ret); + return ret; + } + + for (i = 0; i < self_rawdata_size; i++) + ts_test->self_rawdata.data[i] = be16_to_cpu(ts_test->self_rawdata.data[i]); + ts_test->self_rawdata.size = self_rawdata_size; + + return ret; +} +#endif + +#if ((defined NOISE_DATA_TEST) || (defined SELFNOISE_DATA_TEST)) +/** + * gtx8_noisetest_prepare- noisetest prepare + */ +static int gtx8_noisetest_prepare(struct gtx8_ts_test *ts_test) +{ + int ret = 0; + struct goodix_ts_core *core_data; + u32 noise_data_size = 0; + u32 self_noise_data_size = 0; + + core_data = (struct goodix_ts_core*)ts_test->ts; + noise_data_size = ts_test->test_params.sen_num * ts_test->test_params.drv_num; + self_noise_data_size = ts_test->test_params.sen_num + ts_test->test_params.drv_num; +#ifdef NOISE_DATA_TEST + if (noise_data_size <= 0 || noise_data_size > MAX_DRV_NUM * MAX_SEN_NUM) { + ts_err("Bad noise_data_size[%d]", noise_data_size); + ts_test->test_result[GTP_NOISE_TEST] = SYS_SOFTWARE_REASON; + return -EINVAL; + } + ts_test->noisedata.size = noise_data_size; +#endif + +#ifdef SELFNOISE_DATA_TEST + if (self_noise_data_size <= 0 || self_noise_data_size > MAX_DRV_NUM + MAX_SEN_NUM) { + ts_err("Bad self_noise_data_size[%d]", self_noise_data_size); + ts_test->test_result[GTP_SELFNOISE_TEST] = SYS_SOFTWARE_REASON; + return -EINVAL; + } + ts_test->self_noisedata.size = self_noise_data_size; +#endif + + return ret; +} + +/** + * gtx8_cache_noisedata- cache noisedata + */ +static int gtx8_cache_noisedata(struct gtx8_ts_test *ts_test) +{ + int ret = 0; + int retry = 20; + u8 irq_flag; + int i; + struct goodix_ts_core *core_data; + u16 noisedata_addr = 0; + u16 self_noisedata_addr = 0; + u32 noise_data_size = 0; + u32 self_noise_data_size = 0; + + core_data = (struct goodix_ts_core*)ts_test->ts; + noisedata_addr = ts_test->test_params.noisedata_addr; + self_noisedata_addr = ts_test->test_params.self_noisedata_addr; + noise_data_size = ts_test->noisedata.size; + self_noise_data_size = ts_test->self_noisedata.size; + + /* clear irq event */ + irq_flag = 0; + ts_test_write_trans(ts_test, core_data->ts_dev->reg.coor, &irq_flag, 1); + while (retry--) { + usleep_range(5000, 5100); + ts_test_read_trans(ts_test, core_data->ts_dev->reg.coor, &irq_flag, 1); + if (irq_flag & 0x80) + break; + } + if (retry < 0) { + ts_err("noisedata is not ready irq_flag:0x%02x, exit", irq_flag); + return -EAGAIN; + } + +#ifdef NOISE_DATA_TEST + /* read noise data */ + ret = ts_test_read_trans(ts_test, noisedata_addr, + (u8 *)&ts_test->noisedata.data[0], noise_data_size * sizeof(u16)); + if (ret < 0) { + ts_err("Failed read noise data"); + ts_test->noisedata.size = 0; + ts_test->test_result[GTP_NOISE_TEST] = SYS_SOFTWARE_REASON; + } + for (i = 0; i < noise_data_size; i++) + ts_test->noisedata.data[i] = be16_to_cpu(ts_test->noisedata.data[i]); + goodix_rotate_abcd2cbad(ts_test->test_params.drv_num, + ts_test->test_params.sen_num, ts_test->noisedata.data); +#endif +#ifdef SELFNOISE_DATA_TEST + /* read self noise data */ + ret = ts_test_read_trans(ts_test, self_noisedata_addr, + (u8 *)&ts_test->self_noisedata.data[0], self_noise_data_size * sizeof(u16)); + if (ret < 0) { + ts_err("Failed read self noise data"); + ts_test->self_noisedata.size = 0; + ts_test->test_result[GTP_SELFNOISE_TEST] = SYS_SOFTWARE_REASON; + } + for (i = 0; i < self_noise_data_size; i++) + ts_test->self_noisedata.data[i] = be16_to_cpu(ts_test->self_noisedata.data[i]); +#endif + return ret; +} + +#ifdef NOISE_DATA_TEST +/** + * gtx8_analyse_noisedata- analyse noisedata + */ +static void gtx8_analyse_noisedata(struct gtx8_ts_test *ts_test) +{ + int i = 0; + u32 find_bad_node = 0; + s16 noise_value = 0; + + + for (i = 0; i < ts_test->noisedata.size; i++) + ts_test->noisedata.data[i] = abs(noise_value); + + for (i = 0; i < ts_test->noisedata.size; i++) { + if (ts_test->noisedata.data[i] > ts_test->test_params.noise_threshold) { + find_bad_node++; + ts_err("noise check failed: niose[%d][%d]:%u, > %u\n", + (u32)div_s64(i, ts_test->test_params.drv_num), + i % ts_test->test_params.drv_num, + ts_test->noisedata.data[i], + ts_test->test_params.noise_threshold); + } + } + if (find_bad_node) { + ts_info("%s:noise test find bad node\n", __func__); + ts_test->test_result[GTP_NOISE_TEST] = GTP_PANEL_REASON; + }else{ + ts_test->test_result[GTP_NOISE_TEST] = GTP_TEST_PASS; + } + + return; +} +#endif + +#ifdef SELFNOISE_DATA_TEST +/** + * gtx8_analyse_self_noisedata- analyse self noisedata + */ +static void gtx8_analyse_self_noisedata(struct gtx8_ts_test *ts_test) +{ + int i = 0; + u32 self_find_bad_node = 0; + s16 self_noise_value = 0; + + for (i = 0; i < ts_test->self_noisedata.size; i++) { + ts_test->self_noisedata.data[i] = abs(self_noise_value); + + if (ts_test->self_noisedata.data[i] > ts_test->test_params.self_noise_threshold) { + self_find_bad_node++; + ts_err("self noise check failed: self_noise[%d]:%u, > %u",i, + ts_test->self_noisedata.data[i], + ts_test->test_params.self_noise_threshold); + } + } + + if (self_find_bad_node) { + ts_info("%s:self_noise test find bad node", __func__); + ts_test->test_result[GTP_SELFNOISE_TEST] = GTP_PANEL_REASON; + }else{ + ts_test->test_result[GTP_SELFNOISE_TEST] = GTP_TEST_PASS; + } + + return; +} +#endif + +/* test noise data */ +static void gtx8_test_noisedata(struct gtx8_ts_test *ts_test) +{ + int ret = 0; + int test_cnt = 0; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + + ret = gtx8_noisetest_prepare(ts_test); + if (ret) { + ts_err("Noisetest prepare failed"); + goto soft_err_out; + } + + /* read noisedata and self_noisedata,calculate result */ + for (test_cnt = 0; test_cnt < RAWDATA_TEST_TIMES; test_cnt++) { + ret = gtx8_cache_noisedata(ts_test); + if (ret) { + if (test_cnt == RAWDATA_TEST_TIMES - 1) { + ts_err("Cache noisedata failed"); + goto soft_err_out; + }else{ + continue; + } + } +#ifdef NOISE_DATA_TEST + gtx8_analyse_noisedata(ts_test); +#endif +#ifdef SELFNOISE_DATA_TEST + gtx8_analyse_self_noisedata(ts_test); +#endif + break; + } + + ts_info("Noisedata test end"); + return; + +soft_err_out: +#ifdef NOISE_DATA_TEST + ts_test->noisedata.size = 0; + ts_test->test_result[GTP_NOISE_TEST] = SYS_SOFTWARE_REASON; +#endif +#ifdef SELFNOISE_DATA_TEST + ts_test->self_noisedata.size = 0; + ts_test->test_result[GTP_SELFNOISE_TEST] = SYS_SOFTWARE_REASON; +#endif +} +#endif + + +#ifdef SELF_RAWDATA_TEST +static int gtx8_self_rawcapacitance_test(struct ts_test_self_rawdata *rawdata, + struct ts_test_params *test_params) +{ + int i = 0; + int ret = NO_ERR; + + for (i = 0; i < rawdata->size; i++) { + if (rawdata->data[i] > test_params->self_max_limits[i]) { + ts_err("self_rawdata[%d]:%u >self_max_limit:%u, NG", i, + rawdata->data[i], test_params->self_max_limits[i]); + ret = RESULT_ERR; + } + + if (rawdata->data[i] < test_params->self_min_limits[i]) { + ts_err("self_rawdata[%d]:%u < min_limit:%u, NG", i, + rawdata->data[i], test_params->self_min_limits[i]); + ret = RESULT_ERR; + } + } + + return ret; +} +#endif + +#ifdef RAWDATA_TEST_16_FRAMES +static void gtx8_rawcapacitance_test_spec(struct gtx8_ts_test *ts_test, + u8 *p_beyond_max_cnt, u8 *p_beyond_min_cnt) +{ + int i = 0; + struct ts_test_params *test_params; + struct ts_test_rawdata *rawdata; + test_params = &ts_test->test_params; + rawdata = &ts_test->rawdata; + for (i=0;irawdata.size;i++){ + if (rawdata->data[i] > test_params->max_limits[i]) { + p_beyond_max_cnt[i]++; + } + if (rawdata->data[i] < test_params->min_limits[i]) { + p_beyond_min_cnt[i]++; + } + } +} + +static int gtx8_accord_test_spec(struct gtx8_ts_test *ts_test, u8 *p_beyond_cnt) +{ + int i = 0; + int ret = NO_ERR; + int cols = 0; + u32 max_val = 0; + u32 rawdata_val = 0; + u32 sc_data_num = 0; + u32 up = 0, down = 0, left = 0, right = 0; + struct ts_test_params *test_params; + struct ts_test_rawdata *rawdata; + test_params = &ts_test->test_params; + rawdata = &ts_test->rawdata; + + cols = test_params->drv_num; + sc_data_num = test_params->drv_num * test_params->sen_num; + if (cols <= 0) { + ts_err("%s: parmas invalid", __func__); + return RESULT_ERR; + } + + for (i = 0; i < sc_data_num; i++) { + rawdata_val = rawdata->data[i]; + max_val = 0; + /* calculate deltacpacitance with above node */ + if (i - cols >= 0) { + up = rawdata->data[i - cols]; + up = abs(rawdata_val - up); + if (up > max_val) + max_val = up; + } + + /* calculate deltacpacitance with bellow node */ + if (i + cols < sc_data_num) { + down = rawdata->data[i + cols]; + down = abs(rawdata_val - down); + if (down > max_val) + max_val = down; + } + + /* calculate deltacpacitance with left node */ + if (i % cols) { + left = rawdata->data[i - 1]; + left = abs(rawdata_val - left); + if (left > max_val) + max_val = left; + } + + /* calculate deltacpacitance with right node */ + if ((i + 1) % cols) { + right = rawdata->data[i + 1]; + right = abs(rawdata_val - right); + if (right > max_val) + max_val = right; + } + + /* float to integer */ + if (rawdata_val) { + max_val *= FLOAT_AMPLIFIER; + max_val = (u32)div_s64(max_val, rawdata_val); + if (max_val > test_params->deviation_limits[i]) { + p_beyond_cnt[i]++; + } + } else { + ts_err("Find rawdata=0 when calculate deltacapacitance:[%d][%d]", + (u32)div_s64(i, cols), i % cols); + ret = RESULT_ERR; + } + } + return ret; +} + + +static int gtx8_rawdata_result_analyze(struct gtx8_ts_test *ts_test, u8 *p_beyond_max_cnt, + u8 *p_beyond_min_cnt, u8 *p_beyond_accord_cnt, u8 frame_cnt) +{ + int ret = 0; + int i = 0; + int low_ratio = 1; + int high_ratio = 9; + struct ts_test_params *test_params; + struct ts_test_rawdata *rawdata; + test_params = &ts_test->test_params; + rawdata = &ts_test->rawdata; + ts_test->test_result[GTP_CAP_TEST] = GTP_TEST_PASS; + ts_test->test_result[GTP_DELTA_TEST] = GTP_TEST_PASS; + for (i=0;isize;i++){ + /* analyze max test*/ + if (p_beyond_max_cnt[i]*10/frame_cnt >= high_ratio){ + ts_err("max_cnt:%d, rawdata[%d][%d]:%u > max_limit:%u, NG",p_beyond_max_cnt[i], + (u32)div_s64(i, test_params->drv_num), i % test_params->drv_num, + rawdata->data[i], test_params->max_limits[i]); + ts_test->test_result[GTP_CAP_TEST] = GTP_PANEL_REASON; + }else if (p_beyond_min_cnt[i]*10/frame_cnt > low_ratio){ + ts_test->test_result[GTP_CAP_TEST] = GTP_PANEL_REASON; + ret = -1; + } + /*analyze min test*/ + if (p_beyond_min_cnt[i]*10/frame_cnt >= high_ratio){ + ts_err("min_cnt:%d,rawdata[%d][%d]:%u < min_limit:%u, NG",p_beyond_min_cnt[i], + (u32)div_s64(i, test_params->drv_num), i % test_params->drv_num, + rawdata->data[i], test_params->min_limits[i]); + ts_test->test_result[GTP_CAP_TEST] = GTP_PANEL_REASON; + }else if (p_beyond_min_cnt[i]*10/frame_cnt > low_ratio){ + ts_test->test_result[GTP_CAP_TEST] = GTP_PANEL_REASON; + ret = -1; + } + } + for (i=0;idrv_num * test_params->sen_num;i++){ + + /* analyze accord test*/ + if (p_beyond_accord_cnt[i]*10 >= high_ratio){ + ts_err("beyond accord cnt:%d,rawdata[%d][%d]:%u, deviation_limit:%u, NG", + p_beyond_accord_cnt[i], + (u32)div_s64(i, test_params->drv_num), i % test_params->drv_num, + rawdata->data[i], test_params->deviation_limits[i]); + ts_test->test_result[GTP_DELTA_TEST] = GTP_PANEL_REASON; + }else if (p_beyond_accord_cnt[i]*10 > low_ratio){ + ts_test->test_result[GTP_DELTA_TEST] = GTP_PANEL_REASON; + ret = -1; + } + } + return ret; +} + +/* gtx8_rawdata_test_spec + * test rawdata with 16 frames + */ +static void gtx8_rawdata_test_spec(struct gtx8_ts_test *ts_test) +{ + int ret = NO_ERR; + u16 i = 0; + u8 frame_num = 16; + u16 data_len = 0; + u8* p_beyond_max_limit_cnt = NULL; + u8* p_beyond_min_limit_cnt = NULL; + u8* p_beyond_accord_limit_cnt = NULL; + + data_len = MAX_DRV_NUM*MAX_SEN_NUM; + p_beyond_max_limit_cnt = kzalloc(data_len, GFP_KERNEL); + if (!p_beyond_max_limit_cnt){ + ts_err("%s: Failed to alloc mem", __func__); + goto RAW_TEST_ERR; + } + p_beyond_min_limit_cnt = kzalloc(data_len, GFP_KERNEL); + if (!p_beyond_min_limit_cnt){ + ts_err("%s: Failed to alloc mem", __func__); + goto RAW_TEST_ERR; + } + p_beyond_accord_limit_cnt = kzalloc(data_len, GFP_KERNEL); + if (!p_beyond_accord_limit_cnt){ + ts_err("%s: Failed to alloc mem", __func__); + goto RAW_TEST_ERR; + } + + //init beyond buffer + memset(p_beyond_max_limit_cnt,0,data_len); + memset(p_beyond_min_limit_cnt,0,data_len); + memset(p_beyond_accord_limit_cnt,0,data_len); + ts_info("data len:%d", data_len); + + /* rawdata test */ + for (i = 0; i < TOTAL_FRAME_NUM; i++) { + ret = gtx8_cache_rawdata(ts_test); + if (ret < 0) { + /* Failed read rawdata */ + ts_err("Read rawdata failed"); + goto RAW_TEST_ERR; + } + + gtx8_rawcapacitance_test_spec(ts_test,p_beyond_max_limit_cnt, + p_beyond_min_limit_cnt); + ret = gtx8_accord_test_spec(ts_test,p_beyond_accord_limit_cnt); + if (ret){ + ts_test->test_result[GTP_DELTA_TEST] = GTP_PANEL_REASON; + ts_err("DeltaCap test failed"); + } + if ((i + 1) % frame_num == 0) { + + ret = gtx8_rawdata_result_analyze(ts_test, p_beyond_max_limit_cnt, + p_beyond_min_limit_cnt, p_beyond_accord_limit_cnt,i+1); + if (!ret) { + break; + } + } + } + goto RAW_TEST_END; + +RAW_TEST_ERR: + ts_test->test_result[GTP_CAP_TEST] = SYS_SOFTWARE_REASON; + ts_test->test_result[GTP_DELTA_TEST] = SYS_SOFTWARE_REASON; +RAW_TEST_END: + if(p_beyond_max_limit_cnt){ + kfree(p_beyond_max_limit_cnt); + p_beyond_max_limit_cnt = NULL; + } + if(p_beyond_min_limit_cnt){ + kfree(p_beyond_min_limit_cnt); + p_beyond_min_limit_cnt = NULL; + } + if(p_beyond_accord_limit_cnt){ + kfree(p_beyond_accord_limit_cnt); + p_beyond_accord_limit_cnt = NULL; + } + return; +} + +#else + +static int gtx8_rawcapacitance_test(struct ts_test_rawdata *rawdata, + struct ts_test_params *test_params) +{ + int i = 0; + int ret = NO_ERR; + + for (i = 0; i < rawdata->size; i++) { + if (rawdata->data[i] > test_params->max_limits[i]) { + ts_err("rawdata[%d][%d]:%u > max_limit:%u, NG", + (u32)div_s64(i, test_params->drv_num), i % test_params->drv_num, + rawdata->data[i], test_params->max_limits[i]); + ret = RESULT_ERR; + } + + if (rawdata->data[i] < test_params->min_limits[i]) { + ts_err("rawdata[%d][%d]:%u < min_limit:%u, NG", + (u32)div_s64(i, test_params->drv_num), i % test_params->drv_num, + rawdata->data[i], test_params->min_limits[i]); + ret = RESULT_ERR; + } + } + + return ret; +} + +static int gtx8_deltacapacitance_test(struct ts_test_rawdata *rawdata, + struct ts_test_params *test_params) +{ + int i = 0; + int ret = NO_ERR; + int cols = 0; + u32 max_val = 0; + u32 rawdata_val = 0; + u32 sc_data_num = 0; + u32 up = 0, down = 0, left = 0, right = 0; + + cols = test_params->drv_num; + sc_data_num = test_params->drv_num * test_params->sen_num; + if (cols <= 0) { + ts_err("%s: parmas invalid", __func__); + return RESULT_ERR; + } + + for (i = 0; i < sc_data_num; i++) { + rawdata_val = rawdata->data[i]; + max_val = 0; + /* calculate deltacpacitance with above node */ + if (i - cols >= 0) { + up = rawdata->data[i - cols]; + up = abs(rawdata_val - up); + if (up > max_val) + max_val = up; + } + + /* calculate deltacpacitance with bellow node */ + if (i + cols < sc_data_num) { + down = rawdata->data[i + cols]; + down = abs(rawdata_val - down); + if (down > max_val) + max_val = down; + } + + /* calculate deltacpacitance with left node */ + if (i % cols) { + left = rawdata->data[i - 1]; + left = abs(rawdata_val - left); + if (left > max_val) + max_val = left; + } + + /* calculate deltacpacitance with right node */ + if ((i + 1) % cols) { + right = rawdata->data[i + 1]; + right = abs(rawdata_val - right); + if (right > max_val) + max_val = right; + } + + /* float to integer */ + if (rawdata_val) { + max_val *= FLOAT_AMPLIFIER; + max_val = (u32)div_s64(max_val, rawdata_val); + if (max_val > test_params->deviation_limits[i]) { + ts_err("deviation[%d][%d]:%u > delta_limit:%u, NG", + (u32)div_s64(i, test_params->drv_num;), + i % test_params->drv_num;, max_val, + test_params->deviation_limits[i]); + ret = RESULT_ERR; + } + } else { + ts_err("Find rawdata=0 when calculate deltacapacitance:[%d][%d]", + (u32)div_s64(i, cols), i % cols); + ret = RESULT_ERR; + } + } + + return ret; +} + +/* gtx8_rawdata_test + * test rawdata with one frame + */ +static void gtx8_rawdata_test(struct gtx8_ts_test *ts_test) +{ + int ret = 0; + /* read rawdata and calculate result, statistics fail times */ + ret = gtx8_cache_rawdata(ts_test); + if (ret < 0) { + /* Failed read rawdata */ + ts_err("Read rawdata failed"); + ts_test->test_result[GTP_CAP_TEST] = SYS_SOFTWARE_REASON; + ts_test->test_result[GTP_DELTA_TEST] = SYS_SOFTWARE_REASON; + } else { + ret = gtx8_rawcapacitance_test(&ts_test->rawdata, &ts_test->test_params); + if (!ret) { + ts_test->test_result[GTP_CAP_TEST] = GTP_TEST_PASS; + ts_info("Rawdata test pass"); + } else { + ts_test->test_result[GTP_CAP_TEST] = GTP_PANEL_REASON; + ts_err("RawCap test failed"); + } + + ret = gtx8_deltacapacitance_test(&ts_test->rawdata, &ts_test->test_params); + if (!ret) { + ts_test->test_result[GTP_DELTA_TEST] = GTP_TEST_PASS; + ts_info("DeltaCap test pass"); + } else { + ts_test->test_result[GTP_DELTA_TEST] = GTP_PANEL_REASON; + ts_err("DeltaCap test failed"); + } + } +} +#endif + +static void gtx8_capacitance_test(struct gtx8_ts_test *ts_test) +{ + int ret = 0; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + +#ifdef RAWDATA_TEST_16_FRAMES + gtx8_rawdata_test_spec(ts_test); +#else + gtx8_rawdata_test(ts_test); +#endif + +#ifdef SELF_RAWDATA_TEST + /* read selfrawdata and calculate result, statistics fail times */ + ret = gtx8_cache_self_rawdata(ts_test); + if (ret < 0) { + /* Failed read selfrawdata */ + ts_err("Read selfrawdata failed"); + ts_test->test_result[GTP_SELFCAP_TEST] = SYS_SOFTWARE_REASON; + goto capac_test_exit; + } else { + ret = gtx8_self_rawcapacitance_test(&ts_test->self_rawdata, &ts_test->test_params); + if (!ret) { + ts_test->test_result[GTP_SELFCAP_TEST] = GTP_TEST_PASS; + ts_info("selfrawdata test pass"); + } else { + ts_test->test_result[GTP_SELFCAP_TEST] = GTP_PANEL_REASON; + ts_err("selfrawCap test failed"); + } + } +#endif +capac_test_exit: + ts_test_reset(ts_test); + return; +} +#ifdef SHORT_TEST +static void gtx8_shortcircut_test(struct gtx8_ts_test *ts_test); +static void gtx8_check_setting_group(struct gtx8_ts_test *ts_test, struct short_record *r_data); +#endif +static void gtx8_put_test_result(struct ts_rawdata_info *info, struct gtx8_ts_test *ts_test); + +void set_test_flag(int flag) +{ + g_save_result = flag; +} +int gtx8_get_rawdata(void *tsdev,struct ts_rawdata_info *info) +{ + int ret = 0; + struct gtx8_ts_test *gts_test = NULL; + struct device *dev; + struct goodix_ts_core *core_data; + + ts_info("tp test code version:%s", TEST_CODE_VERSION); + + if (!tsdev || !info) { + ts_err("gtx8_ts is NULL"); + return -ENODEV; + } + dev = (struct device *)tsdev; + core_data = dev_get_drvdata(dev); + + gts_test = kzalloc(sizeof(struct gtx8_ts_test), GFP_KERNEL); + if (!gts_test) { + ts_err("Failed to alloc mem"); + return -ENOMEM; + } + gts_test->ts = core_data; + + ret = gtx8_tptest_prepare(gts_test); + if (ret) { + ts_err("Failed parse test peremeters, exit test"); + strncpy(info->result, "0F-software reason", TS_RAWDATA_RESULT_MAX - 1); + goto exit_finish; + } + ts_info("TP test prepare OK"); +#if ((defined NOISE_DATA_TEST) || (defined SELFNOISE_DATA_TEST)) + gtx8_test_noisedata(gts_test); /*3F 7F test*/ +#endif + gtx8_capacitance_test(gts_test); /* 1F 2F 6F test*/ +#ifdef SHORT_TEST + gtx8_shortcircut_test(gts_test); /* 5F test */ +#endif + gtx8_put_test_result(info, gts_test); + gtx8_tptest_finish(gts_test); + +exit_finish: + if (gts_test) { + kfree(gts_test); + gts_test = NULL; + } + return ret; +} + +char *gtx8_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 *gtx8_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 gtx8_strncat(dest, src_str, dest_size); +} + +static void gtx8_data_cal(u16 *data, size_t data_size, u16 *stat_result) +{ + u16 i = 0; + u16 avg = 0; + u16 min = 0; + u16 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; +} + +/** gtx8_data_statistics + * + * catlculate Avg Min Max value of data + */ +static void gtx8_data_statistics(u16 *data, size_t data_size, char *result, size_t res_size) +{ + u16 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; + } + gtx8_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; +} + +static void gtx8_put_test_result( + struct ts_rawdata_info *info, + struct gtx8_ts_test *ts_test) +{ + int i = 0; + int have_bus_error = 0; + int have_panel_error = 0; + char statistics_data[STATISTICS_DATA_LEN] = {0}; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + + /* save rawdata to info->buff */ + info->used_size = 0; + + if (ts_test->rawdata.size) { + info->buff[0] = ts_test->test_params.sen_num; + info->buff[1] = ts_test->test_params.drv_num; + for (i = 0; i < ts_test->rawdata.size; i++) + info->buff[i + 2] = ts_test->rawdata.data[i]; + + info->used_size += ts_test->rawdata.size + 2; + } +#ifdef NOISE_DATA_TEST + /* save noise data to info->buff */ + if (ts_test->noisedata.size) { + for (i = 0; i < ts_test->noisedata.size; i++) + info->buff[info->used_size + i] = ts_test->noisedata.data[i]; + + info->used_size += ts_test->noisedata.size; + } +#endif +#ifdef SELFNOISE_DATA_TEST + /* 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; + } +#endif + +#ifdef SELF_RAWDATA_TEST + /* 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; + } +#endif + + /* 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 = 1; + else if (ts_test->test_result[i] == GTP_PANEL_REASON) + have_panel_error = 1; + } + ts_info("Have bus error:%d", have_bus_error); + + if (have_bus_error) + gtx8_strncat(ts_test->test_info, "0F-", TS_RAWDATA_RESULT_MAX); + else + gtx8_strncat(ts_test->test_info, "0P-", TS_RAWDATA_RESULT_MAX); + + for (i = 1; 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]) + gtx8_strncatint(ts_test->test_info, i, "%dP-", TS_RAWDATA_RESULT_MAX); + else + gtx8_strncatint(ts_test->test_info, i, "%dF-", TS_RAWDATA_RESULT_MAX); + } + ts_info("test_result_info [%d]%d", i, ts_test->test_result[i]); + } + + if (ts_test->rawdata.size) { + /* calculate rawdata min avg max vale*/ + gtx8_data_statistics( + &ts_test->rawdata.data[0], + ts_test->rawdata.size, + statistics_data, + STATISTICS_DATA_LEN); + gtx8_strncat(ts_test->test_info, statistics_data, TS_RAWDATA_RESULT_MAX); + } else { + ts_info("NO valiable rawdata"); + gtx8_strncat(ts_test->test_info, "[0,0,0]", TS_RAWDATA_RESULT_MAX); + } +#ifdef NOISE_DATA_TEST + if (ts_test->noisedata.size) { + /* calculate noise data min avg max vale*/ + gtx8_data_statistics( + &ts_test->noisedata.data[0], + ts_test->noisedata.size, + statistics_data, + STATISTICS_DATA_LEN); + gtx8_strncat(ts_test->test_info, statistics_data, TS_RAWDATA_RESULT_MAX); + } else { + ts_info("NO valiable noisedata"); + gtx8_strncat(ts_test->test_info, "[0,0,0]", TS_RAWDATA_RESULT_MAX); + } +#endif + +#ifdef SELF_RAWDATA_TEST + if (ts_test->self_rawdata.size) { + /* calculate self_rawdata min avg max vale*/ + gtx8_data_statistics( + &ts_test->self_rawdata.data[0], + ts_test->self_rawdata.size, + statistics_data, + STATISTICS_DATA_LEN); + gtx8_strncat(ts_test->test_info, statistics_data, TS_RAWDATA_RESULT_MAX); + } else { + ts_info("NO valiable self_rawdata"); + gtx8_strncat(ts_test->test_info, "[0,0,0]", TS_RAWDATA_RESULT_MAX); + } +#endif + +#ifdef SELFNOISE_DATA_TEST + if (ts_test->self_noisedata.size) { + /* calculate self_noisedata min avg max vale*/ + gtx8_data_statistics( + &ts_test->self_noisedata.data[0], + ts_test->self_noisedata.size, + statistics_data, + STATISTICS_DATA_LEN); + gtx8_strncat(ts_test->test_info, statistics_data, TS_RAWDATA_RESULT_MAX); + } else { + ts_info("NO valiable self_noisedata"); + gtx8_strncat(ts_test->test_info, "[0,0,0]", TS_RAWDATA_RESULT_MAX); + } +#endif + + gtx8_strncat(ts_test->test_info, "-GT", TS_RAWDATA_RESULT_MAX); + gtx8_strncat(ts_test->test_info, core_data->ts_dev->chip_version.pid, TS_RAWDATA_RESULT_MAX); + gtx8_strncat(ts_test->test_info, "", TS_RAWDATA_RESULT_MAX); + ts_info("ts_test->test_info:%s", ts_test->test_info); + strncpy(info->result, ts_test->test_info, TS_RAWDATA_RESULT_MAX - 1); + +#ifdef SAVE_IN_CSV + if (g_save_result == 1) + gtx8_save_result_data(ts_test); +#endif + return; +} + +#ifdef SHORT_TEST +/* short test */ +static int gtx8_short_test_prepare(struct gtx8_ts_test *ts_test) +{ + int ret = 0, i = 0, retry = GTX8_RETRY_NUM_3; + u8 data[MAX_DRV_NUM + MAX_SEN_NUM] = {0}; + struct goodix_ts_core *core_data; + struct goodix_ts_cmd short_code_cmd; + core_data = (struct goodix_ts_core*)ts_test->ts; + goodix_cmd_init(core_data->ts_dev, &short_code_cmd, + 0x0b, 0, core_data->ts_dev->reg.command); + + ts_info("Short test prepare+"); + + while (--retry) { + /* switch to shrot test system */ + ret = core_data->ts_dev->hw_ops->send_cmd(core_data->ts_dev, + &short_code_cmd); /*bagan test command*/ + if (ret) { + ts_err("Can not switch to short test system"); + return ret; + } + msleep(50); + + /* check firmware running */ + for (i = 0; i < 20; i++) { + ts_info("Check firmware running.."); + ret = ts_test_read_trans(ts_test, + SHORT_STATUS_REG, &data[0], 1); /* SHORT_STATUS_REG is 0x5095 */ + if (ret) { + ts_err("Check firmware running failed"); + return ret; + } else if (data[0] == 0xaa) { + ts_info("Short firmware is running"); + break; + } + msleep(10); + } + if (i < 20) { + break; + } else { + ts_test_reset(ts_test); + } + } + if (retry <= 0) { + ret = -EINVAL; + ts_err("Switch to short test mode timeout"); + return ret; + } + + if (core_data->ts_dev->ic_type == IC_TYPE_NORMANDY) { + data[0] = 0; + /* turn off watch dog timer */ + ret = ts_test_write_trans(ts_test, WATCH_DOG_TIMER_REG, data, 1); + if (ret < 0) { + ts_err("Failed turn off watch dog timer"); + return ret; + } + } + + ts_info("Firmware in short test mode"); + + data[0] = (ts_test->test_params.short_threshold >> 8) & 0xff; + data[1] = ts_test->test_params.short_threshold & 0xff; + + /* write tx/tx, tx/rx, rx/rx short threshold value to 0x8408 */ + ret = ts_test_write_trans(ts_test, TXRX_THRESHOLD_REG, + data, 2); /* SHORT THRESHOLD_REG 0X8808 */ + if (ret < 0) { + ts_err("Failed write tx/tx, tx/rx, rx/rx short threshold value"); + return ret; + } + data[0] = (GNDAVDD_SHORT_VALUE >> 8) & 0xff; + data[1] = GNDAVDD_SHORT_VALUE & 0xff; + /* write default txrx/gndavdd short threshold value 16 to 0x804A*/ + ret = ts_test_write_trans(ts_test, GNDVDD_THRESHOLD_REG, + data, 2); /* SHORT THRESHOLD_REG 0X8808 */ + if (ret < 0) { + ts_err("Failed write txrx/gndavdd short threshold value"); + return ret; + } + + /* Write ADC dump data num to 0x840c */ + data[0] = (ADC_DUMP_NUM >> 8) & 0xff; + data[1] = ADC_DUMP_NUM & 0xff; + ret = ts_test_write_trans(ts_test, ADC_DUMP_NUM_REG, data, 2); + if (ret < 0) { + ts_err("Failed write ADC dump data number"); + return ret; + } + + /* write 0x01 to 0x5095 start short test */ + data[0] = 0x01; + ret = ts_test_write_trans(ts_test, SHORT_STATUS_REG, + data, 1); /* SHORT_STATUS_REG 0X5095 */ + if (ret) { + ts_err("Failed write running dsp reg"); + return ret; + } + + ts_info("Short test prepare-"); + return 0; +} + +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) + MAX_SEN_NUM; + + for (i = 0; i < test_params->max_sen_num; i++) { + if (test_params->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->max_drv_num; i++) { + if (test_params->drv_map[i] == chn_num) { + res = i; + break; + } + } + if (i >= test_params->max_drv_num) + ts_err("Faild found corrresponding channel num:%d", chn_num); + else + res |= DRV_CHANNEL_FLAG; + + return res; +} + +static int gtx8_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; + u32 chn_id_tmp = 0; + u32 pin_num = 0; + + 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 = SHORT_TO_GND_RESISTER(adc_signal); + else /* short to VDD */ + r = SHORT_TO_VDD_RESISTER(adc_signal, avdd_value); + + r = (long)div_s64(r, 100); + r = r > MAX_U16_VALUE ? MAX_U16_VALUE : r; + r = r < 0 ? 0 : r; + + 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 |= DRV_CHANNEL_FLAG; + else + chn_id_tmp -= MAX_DRV_NUM; + + if (r < r_th) { + pin_num = map_die2pin(test_params, chn_id_tmp); + 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 RESULT_ERR; + } + return NO_ERR; +} + +static u32 gtx8_short_resistance_calc(struct gtx8_ts_test *ts_test, + struct short_record *r_data, u16 self_capdata, u8 flag) +{ + u16 lineDrvNum = 0, lineSenNum = 0; + u8 DieNumber1 = 0, DieNumber2 = 0; + long r = 0; + u8 tmp_data; + int ret; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + + lineDrvNum = MAX_DRV_NUM; + lineSenNum = MAX_SEN_NUM; + + if (core_data->ts_dev->ic_type == IC_TYPE_YELLOWSTONE) { + if (r_data->group1 != r_data->group2) { /* different Group */ + ret = ts_test_read_trans(ts_test, + YS_IC_MASK_TYPE_REG, &tmp_data, 1); /* */ + if (ret < 0) { + ts_err("Read IC_MASK_TYPE_ADDR falied"); + return ret; + } + if (tmp_data == 0x12) { + r = div_s64(self_capdata * 77 * FLOAT_AMPLIFIER, r_data->short_code); + r -= (77 * FLOAT_AMPLIFIER); + } else { + r = div_s64(self_capdata * 81 * FLOAT_AMPLIFIER, r_data->short_code); + r -= (81 * FLOAT_AMPLIFIER); + } + } else { + r = div_s64(self_capdata * 64 * FLOAT_AMPLIFIER, r_data->short_code); + r -= (64 * FLOAT_AMPLIFIER); + } + } else { + if (flag == 0) { + if (r_data->group1 != r_data->group2) { /* different Group */ + r = div_s64(self_capdata * 81 * FLOAT_AMPLIFIER, r_data->short_code); + r -= (81 * FLOAT_AMPLIFIER); + } else { + DieNumber1 = ((r_data->master & 0x80) == 0x80) ? + (r_data->master + lineSenNum) : r_data->master; + DieNumber2 = ((r_data->slave & 0x80) == 0x80) ? + (r_data->slave + lineSenNum) : r_data->slave; + DieNumber1 = (DieNumber1 >= DieNumber2) ? + (DieNumber1 - DieNumber2) : (DieNumber2 - DieNumber1); + if ((DieNumber1 > 3) && (r_data->group1 == 0)) { + r = div_s64(self_capdata * 81 * FLOAT_AMPLIFIER, r_data->short_code); + r -= (81 * FLOAT_AMPLIFIER); + } else { + r = div_s64(self_capdata * 64 * FLOAT_AMPLIFIER, r_data->short_code); + r -= (64 * FLOAT_AMPLIFIER); + } + } + } else { + r = div_s64(self_capdata * 81 * FLOAT_AMPLIFIER, r_data->short_code); + r -= (81 * FLOAT_AMPLIFIER); + } + } + + /*if (r < 6553) + r *= 10; + else + r = 65535;*/ + r = (long)div_s64(r, FLOAT_AMPLIFIER); + r = r > MAX_U16_VALUE ? MAX_U16_VALUE : r; + + return r >= 0 ? r : 0; +} + +static int gtx8_shortcircut_analysis(struct gtx8_ts_test *ts_test) +{ + int ret = 0, err = 0; + u32 r_threshold = 0, short_r = 0; + int size = 0, i = 0, j = 0; + u32 master_pin_num, slave_pin_num; + u16 adc_signal = 0, data_addr = 0; + u8 short_flag = 0, *data_buf = NULL, short_status[3] = {0}; + u16 self_capdata[MAX_DRV_NUM + MAX_SEN_NUM] = {0}, short_die_num = 0; + struct short_record temp_short_info; + struct ts_test_params *test_params = &ts_test->test_params; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + + ret = ts_test_read_trans(ts_test, TEST_RESTLT_REG, + &short_flag, 1); /* TEST_RESTLT_REG 0x8401 */ + if (ret < 0) { + ts_err("Read TEST_TESULT_REG falied"); + goto shortcircut_analysis_error; + } else if ((short_flag & 0x0F) == 0x00) { + ts_info("No shortcircut"); + return NO_ERR; + } + + data_buf = kzalloc((MAX_DRV_NUM + MAX_SEN_NUM) * 2, GFP_KERNEL); + if (!data_buf) { + ts_err("Failed to alloc memory"); + goto shortcircut_analysis_error; + } + + /* shortcircut to gnd&vdd */ + if (short_flag & 0x08) { + /* read diff code, diff code will be used to calculate + * resistance between channel and GND */ + size = (MAX_DRV_NUM + MAX_SEN_NUM) * 2; + ret = ts_test_read_trans(ts_test, DIFF_CODE_REG, + data_buf, size); /* DIFF_CODE_REG 0xA97A */ + if (ret < 0) { + ts_err("Failed read to-gnd rawdata"); + goto shortcircut_analysis_error; + } + for (i = 0; i < size; i += 2) { + adc_signal = be16_to_cpup((__be16 *)&data_buf[i]); + ret = gtx8_check_resistance_to_gnd(test_params, + adc_signal, i >> 1); /* i >> 1 = i / 2 */ + if (ret) { + ts_err("Resistance to-gnd test failed"); + err |= ret; + } + } + } + + /* read self-capdata+ */ + size = (MAX_DRV_NUM + MAX_SEN_NUM) * 2; + ret = ts_test_read_trans(ts_test, DRV_SELF_CODE_REG, + data_buf, size); /* DRV_SELF_CODE_REG 0xa8e0 */ + if (ret) { + ts_err("Failed read selfcap rawdata"); + goto shortcircut_analysis_error; + } + for (i = 0; i < MAX_DRV_NUM + MAX_SEN_NUM; i++) + self_capdata[i] = be16_to_cpup((__be16 *)&data_buf[i * 2]) & 0x7fff; + /* read self-capdata- */ + + /* read tx tx short number + ** short_status[0]: tr tx + ** short_status[1]: tr rx + ** short_status[2]: rx rx + */ + ret = ts_test_read_trans(ts_test, TX_SHORT_NUM, + &short_status[0], 3); /* TX_SHORT_NUM 0x8402 */ + if (ret) { + ts_err("Failed read tx-to-tx short rawdata"); + goto shortcircut_analysis_error; + } + ts_info("Tx&Tx:%d,Rx&Rx:%d,Tx&Rx:%d", short_status[0], short_status[1], short_status[2]); + + /* drv&drv shortcircut check */ + data_addr = 0x8460; + for (i = 0; i < short_status[0]; i++) { + size = SHORT_CAL_SIZE(MAX_DRV_NUM); /* 4 + MAX_DRV_NUM * 2 + 2; */ + ret = ts_test_read_trans(ts_test, data_addr, data_buf, size); + if (ret) { + ts_err("Failed read drv-to-drv short rawdata"); + goto shortcircut_analysis_error; + } + + r_threshold = test_params->r_drv_drv_threshold; + short_die_num = be16_to_cpup((__be16 *)&data_buf[0]); + if (short_die_num > MAX_DRV_NUM + MAX_SEN_NUM || + short_die_num < MAX_SEN_NUM) { + ts_info("invalid short pad num:%d", short_die_num); + continue; + } + + /* TODO: j start position need recheck */ + short_die_num -= MAX_SEN_NUM; + for (j = short_die_num + 1; j < MAX_DRV_NUM; j++) { + adc_signal = be16_to_cpup((__be16 *)&data_buf[4 + j * 2]); + + if (adc_signal > test_params->short_threshold) { + temp_short_info.master = short_die_num | DRV_CHANNEL_FLAG; + temp_short_info.slave = j | DRV_CHANNEL_FLAG; + temp_short_info.short_code = adc_signal; + gtx8_check_setting_group(ts_test, &temp_short_info); + + if (self_capdata[short_die_num] == 0xffff || + self_capdata[short_die_num] == 0) { + ts_info("invalid self_capdata:0x%x", self_capdata[short_die_num]); + continue; + } + + short_r = gtx8_short_resistance_calc(ts_test, &temp_short_info, + self_capdata[short_die_num], 0); + if (short_r < 0) + goto shortcircut_analysis_error; + + if (short_r < r_threshold) { + master_pin_num = map_die2pin(test_params, temp_short_info.master); + slave_pin_num = map_die2pin(test_params, temp_short_info.slave); + ts_err("Tx/Tx 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_addr += size; + } + + /* sen&sen shortcircut check */ + data_addr = 0x91d0; + for (i = 0; i < short_status[1]; i++) { + size = SHORT_CAL_SIZE(MAX_SEN_NUM); /* 4 + MAX_SEN_NUM * 2 + 2; */ + ret = ts_test_read_trans(ts_test, data_addr, data_buf, size); + if (ret) { + ts_err("Failed read sen-to-sen short rawdata"); + goto shortcircut_analysis_error; + } + + r_threshold = ts_test->test_params.r_sen_sen_threshold; + short_die_num = be16_to_cpup((__be16 *)&data_buf[0]); + if (short_die_num > MAX_SEN_NUM) + continue; + + for (j = short_die_num + 1; j < MAX_SEN_NUM; j++) { + adc_signal = be16_to_cpup((__be16 *)&data_buf[4 + j * 2]); + if (adc_signal > ts_test->test_params.short_threshold) { + temp_short_info.master = short_die_num; + temp_short_info.slave = j; + temp_short_info.short_code = adc_signal; + gtx8_check_setting_group(ts_test, &temp_short_info); + + if (self_capdata[short_die_num + MAX_DRV_NUM] == 0xffff || + self_capdata[short_die_num + MAX_DRV_NUM] == 0) { + ts_info("invalid self_capdata:0x%x", + self_capdata[short_die_num + MAX_DRV_NUM]); + continue; + } + + short_r = gtx8_short_resistance_calc(ts_test, &temp_short_info, + self_capdata[short_die_num + MAX_DRV_NUM], 0); + if (short_r < 0) + goto shortcircut_analysis_error; + if (short_r < r_threshold) { + master_pin_num = map_die2pin(test_params, temp_short_info.master); + slave_pin_num = map_die2pin(test_params, temp_short_info.slave); + ts_err("Rx/Rx 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_addr += size; + } + + /* sen&drv shortcircut check */ + data_addr = 0x9cc8; + for (i = 0; i < short_status[2]; i++) { + size = SHORT_CAL_SIZE(MAX_DRV_NUM); /* size = 4 + MAX_SEN_NUM * 2 + 2; */ + ret = ts_test_read_trans(ts_test, data_addr, data_buf, size); + if (ret) { + ts_err("Failed read sen-to-drv short rawdata"); + goto shortcircut_analysis_error; + } + + r_threshold = ts_test->test_params.r_drv_sen_threshold; + short_die_num = be16_to_cpup((__be16 *)&data_buf[0]); + if (short_die_num > MAX_SEN_NUM) + continue; + + for (j = 0; j < MAX_DRV_NUM; j++) { + adc_signal = be16_to_cpup((__be16 *)&data_buf[4 + j * 2]); + if (adc_signal > ts_test->test_params.short_threshold) { + temp_short_info.master = short_die_num; + temp_short_info.slave = j | DRV_CHANNEL_FLAG; + temp_short_info.short_code = adc_signal; + gtx8_check_setting_group(ts_test, &temp_short_info); + + if (self_capdata[short_die_num + MAX_DRV_NUM] == 0xffff || + self_capdata[short_die_num + MAX_DRV_NUM] == 0) { + ts_info("invalid self_capdata:0x%x", + self_capdata[short_die_num + MAX_DRV_NUM]); + continue; + } + + short_r = gtx8_short_resistance_calc(ts_test, &temp_short_info, + self_capdata[short_die_num + MAX_DRV_NUM], 0); + if (short_r < 0) + goto shortcircut_analysis_error; + if (short_r < r_threshold) { + master_pin_num = map_die2pin(test_params, temp_short_info.master); + slave_pin_num = map_die2pin(test_params, temp_short_info.slave); + ts_err("Rx/Tx 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_addr += size; + } + + if (data_buf) { + kfree(data_buf); + data_buf = NULL; + } + + return err | ret ? -EFAULT : NO_ERR; +shortcircut_analysis_error: + if (data_buf != NULL) { + kfree(data_buf); + data_buf = NULL; + } + return -EINVAL; +} + +static void gtx8_shortcircut_test(struct gtx8_ts_test *ts_test) +{ + int i = 0; + int ret = 0; + u8 data[2] = {0}; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + + ts_test->test_result[GTP_SHORT_TEST] = GTP_TEST_PASS; + ret = gtx8_short_test_prepare(ts_test); + if (ret < 0) { + ts_err("Failed enter short test mode"); + ts_test->test_result[GTP_SHORT_TEST] = SYS_SOFTWARE_REASON; + return; + } + + msleep(2000); + + for (i = 0; i < 110; i++) { + msleep(50); + ts_info("waitting for short test end...:retry=%d", i); + ret = ts_test_read_trans(ts_test, + SHORT_TESTEND_REG, data, 1); /* SHORT_TESTEND_REG 0x8400 */ + if (ret) + ts_err("Failed get short test result: retry%d", i); + else if (data[0] == 0x88) /* test ok*/ + break; + } + + if (i < 110) { + ret = gtx8_shortcircut_analysis(ts_test); + if (ret) { + ts_test->test_result[GTP_SHORT_TEST] = GTP_PANEL_REASON; + ts_err("Short test failed"); + } else { + ts_info("Short test success"); + } + } else { + ts_err("Wait short test finish timeout:reg_val=0x%x", data[0]); + ts_test->test_result[GTP_SHORT_TEST] = SYS_SOFTWARE_REASON; + } +} + +static u8 get_chn_group_ys(u8 chn) +{ + u8 group = 0; + u8 setting_num = 7; + + //sen + if ((chn >= 32) && (chn < 36)) // (Setting1) C(bit10) + group = 2 * setting_num + 0; + else if ((chn >= 27) && (chn < 32)) // (Setting2) C(bit10) + group = 2 * setting_num + 1; + else if ((chn >= 20) && (chn < 24)) // (Setting3) A(bit00) + group = 0 * setting_num + 2; + else if (chn == 25 || // (Setting3) C(bit10) + chn == 26 || chn == 19 || + chn == 24 || chn == 14 || + chn == 17 || chn == 18 || + chn == 15 || chn == 16 || + chn == 6 || chn == 13) + group = 2 * setting_num + 2; + else if (chn == 3 || chn == 12 || // (Setting4) C(bit10) + chn == 0 || chn == 1) + group = 2 * setting_num + 3; + else if ((chn >= 7) && (chn < 12)) // (Setting5) A(bit00) + group = 0 * setting_num + 4; + else if (chn == 5 || // (Setting5) C(bit10) + chn == 4 || chn == 2) + group = 2 * setting_num + 4; + //drv + else if ((chn >= 67) && (chn < 72)) // (Setting2) B(bit01) + group = 1 * setting_num + 1; + else if (chn == 63 || // (Setting4) B(bit01) + chn ==64 || chn == 60 + || chn == 61) + group = 1 * setting_num + 3; + else if (chn == 62 || // (Setting5) B(bit01) + chn == 57 || chn == 58 || + chn == 59 || chn == 54 ) + group = 1 * setting_num + 4; + else if (chn == 75 || chn == 44) // (Setting5) C(bit10) + group = 2 * setting_num + 4; + else if (chn == 56 || // (Setting6) B(bit01) + chn == 55 || ((chn >= 47) && (chn < 54))) + group = 1 * setting_num + 5; + else if ( chn == 74 || // (Setting6) C(bit10) + ((chn >= 36) && (chn < 44))) + group = 2 * setting_num + 5; + else if (chn == 73 || chn == 72) // (Setting7) A(bit00) + group = 0 * setting_num + 6; + else if (chn == 46 || chn == 45) // (Setting7) B(bit01) + group = 1 * setting_num + 6; + else if (chn == 66 || chn == 65) // (Setting7) C(bit10) + group = 2 * setting_num + 6; + + return group; +} + + +static u8 get_chn_group_nor(u8 chn) +{ + u8 group = 0; + + if ((chn >= 0) && (chn < 9)) /* pad s0~s8 */ + group = 5; + + else if ((chn >= 9) && (chn < 14)) /* pad s9~s13 */ + group = 4; + + else if ((chn >= 14) && (chn < 18)) /* pad s14~s17 */ + group = 3; + + else if ((chn >= 18) && (chn < 27)) /* pad s18~s26 */ + group = 2; + + else if ((chn >= 27) && (chn < 32)) /* pad s27~s31 */ + group = 1; + + else if ((chn >= 32) && (chn < 36)) /* pad s32~s35 */ + group = 0; + + else if ((chn >= 36) && (chn < 45)) /* pad d0~d8 */ + group = 5; + + else if ((chn >= 45) && (chn < 54)) /* pad d9~d17 */ + group = 2; + + else if ((chn >= 54) && (chn < 59)) /* pad d18~d22 */ + group = 1; + + else if ((chn >= 59) && (chn < 63)) /* pad d23~d26 */ + group = 0; + + else if ((chn >= 63) && (chn < 67)) /* pad d27~d30 */ + group = 3; + + else if ((chn >= 67) && (chn < 72)) /* pad d31~d35 */ + group = 4; + + else if ((chn >= 72) && (chn < 76)) /* pad d36~d39 */ + group = 0; + + return group; +} + +static void gtx8_check_setting_group(struct gtx8_ts_test *ts_test, struct short_record *r_data) +{ + u32 master = 0; + u32 slave = 0; + struct goodix_ts_core *core_data; + core_data = (struct goodix_ts_core*)ts_test->ts; + + if (r_data->master & 0x80) + master = MAX_SEN_NUM; + + if (r_data->slave & 0x80) + slave = MAX_SEN_NUM; + + master += (r_data->master & 0x7f); + slave += (r_data->slave & 0x7f); + + if (core_data->ts_dev->ic_type == IC_TYPE_YELLOWSTONE) { + r_data->group1 = get_chn_group_ys(master); + r_data->group2 = get_chn_group_ys(slave); + } else if (core_data->ts_dev->ic_type == IC_TYPE_NORMANDY) { + r_data->group1 = get_chn_group_nor(master); + r_data->group2 = get_chn_group_nor(slave); + } else { + r_data->group1 = get_chn_group_nor(master); + r_data->group2 = get_chn_group_nor(slave); + } +} +#endif diff --git a/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_tools.c b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_tools.c new file mode 100644 index 000000000000..91c8a3bdc5ce --- /dev/null +++ b/drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_tools.c @@ -0,0 +1,652 @@ +/* + * Goodix Tools Dirver 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "goodix_ts_core.h" + +#define GOODIX_TOOLS_NAME "gtp_tools" +#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_DRV_VERSION (_IOR(GOODIX_TS_IOC_MAGIC, 9, u8) & NEGLECT_SIZE_MASK) + +#define GOODIX_TS_IOC_MAXNR 10 +#define MAX_BUF_LENGTH (16*1024) +#define IRQ_FALG (0x01 << 2) + +#define I2C_MSG_HEAD_LEN 20 +#define TS_REG_COORDS_BASE 0x4100 + +/* + * struct goodix_tools_data - goodix tools data message used in sync read + * @data: The buffer into which data is written + * @reg_addr: Slave device register start address to start read data + * @length: Number of data bytes in @data being read from slave device + * @filled: When buffer @data be filled will set this flag with 1, outhrwise 0 + * @list_head:Eonnet every goodix_tools_data struct into a list + */ + +struct goodix_tools_data { + u32 reg_addr; + u32 length; + u8 *data; + bool filled; + struct list_head list; +}; + + +/* + * 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]; + struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev; + const struct goodix_ts_hw_ops *hw_ops = ts_dev->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_trans(ts_dev, 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_device *ts_dev, 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_dev->hw_ops->read_config) + ret = ts_dev->hw_ops->read_config(ts_dev, tmp_buf, length); + else + ret = -EINVAL; + } else { + ret = ts_dev->hw_ops->read_trans(ts_dev, 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; +} + +/* read data from i2c synchronous, + * return data length when success, otherwise return < 0 + */ +static int sync_read(struct goodix_tools_dev *dev, void __user *arg) +{ + int ret = 0; + u8 i2c_msg_head[I2C_MSG_HEAD_LEN]; + struct goodix_tools_data tools_data; + + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN); + if (ret) { + ts_err("Copy data from user failed"); + return -EFAULT; + } + tools_data.reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8) + + (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24); + tools_data.length = i2c_msg_head[4] + (i2c_msg_head[5] << 8) + + (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24); + tools_data.filled = 0; + if (tools_data.length > MAX_BUF_LENGTH) { + ts_err("buffer too long:%d > %d", + tools_data.length, MAX_BUF_LENGTH); + return -EINVAL; + } + tools_data.data = kzalloc(tools_data.length, GFP_KERNEL); + if (!tools_data.data) { + ts_err("Alloc memory failed"); + return -ENOMEM; + } + + mutex_lock(&dev->mutex); + list_add_tail(&tools_data.list, &dev->head); + mutex_unlock(&dev->mutex); + /* wait queue will timeout after 1 seconds */ + wait_event_interruptible_timeout(dev->wq, tools_data.filled == 1, HZ * 3); + + mutex_lock(&dev->mutex); + list_del(&tools_data.list); + mutex_unlock(&dev->mutex); + if (!tools_data.filled) { + ret = -EAGAIN; + ts_err("Wait queue timeout"); + goto out; + } + + ret = copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, + tools_data.data, tools_data.length); + if (ret) { + ret = -EFAULT; + ts_err("Copy_to_user failed"); + goto out; + } + ret = tools_data.length; +out: + kfree(tools_data.data); + 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_device *ts_dev = dev->ts_core->ts_dev; + const struct goodix_ts_hw_ops *hw_ops = ts_dev->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_trans(ts_dev, 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_ts_config *cfg, void __user *arg) +{ + int ret = 0; + u32 reg_addr, length; + u8 i2c_msg_head[I2C_MSG_HEAD_LEN]; + + cfg->initialized = 0; + mutex_init(&cfg->lock); + 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; + } + ret = copy_from_user(cfg->data, (u8 *)arg + I2C_MSG_HEAD_LEN, length); + if (ret) { + ret = -EFAULT; + ts_err("Copy data from user failed"); + goto err_out; + } + cfg->reg_base = reg_addr; + cfg->length = length; + strlcpy(cfg->name, "tools-send-cfg", sizeof(cfg->name)); + cfg->delay = 50; + cfg->initialized = true; + return 0; + +err_out: + return ret; +} + +/** + * 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_device *ts_dev; + const struct goodix_ts_hw_ops *hw_ops; + struct goodix_ts_cmd temp_cmd; + struct goodix_ts_config *temp_cfg = NULL; + + if (dev->ts_core == NULL) { + ts_err("Tools module not register"); + return -EINVAL; + } + ts_dev = dev->ts_core->ts_dev; + hw_ops = ts_dev->hw_ops; + + if (_IOC_TYPE(cmd) != GOODIX_TS_IOC_MAGIC) { + ts_err("Bad magic num:%c", _IOC_TYPE(cmd)); + return -ENOTTY; + } + if (_IOC_NR(cmd) > GOODIX_TS_IOC_MAXNR) { + ts_err("Bad cmd num:%d > %d", + _IOC_NR(cmd), GOODIX_TS_IOC_MAXNR); + return -ENOTTY; + } + + switch (cmd & NEGLECT_SIZE_MASK) { + case GTP_IRQ_ENABLE: + if (arg == 1) { + goodix_ts_irq_enable(dev->ts_core, true); + mutex_lock(&dev->mutex); + dev->ops_mode |= IRQ_FALG; + mutex_unlock(&dev->mutex); + ts_info("IRQ enabled"); + } else if (arg == 0) { + goodix_ts_irq_enable(dev->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_dev); + break; + case GTP_SEND_COMMAND: + ret = copy_from_user(&temp_cmd, (void __user *)arg, + sizeof(struct goodix_ts_cmd)); + if (ret) { + ret = -EINVAL; + goto err_out; + } + + ret = hw_ops->send_cmd(ts_dev, &temp_cmd); + if (ret) { + ts_err("Send command failed"); + ret = -EAGAIN; + } + break; + case GTP_SEND_CONFIG: + temp_cfg = kzalloc(sizeof(struct goodix_ts_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_dev, temp_cfg); + if (ret) { + ts_err("Failed send config"); + ret = -EAGAIN; + } else { + ts_info("Send config success"); + ret = 0; + } + } + break; + case GTP_READ_CONFIG: + ret = read_config_data(ts_dev, (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: + if (filp->f_flags & O_NONBLOCK) { + ts_err("Goodix tools now worked in sync_bus mode"); + ret = -EAGAIN; + goto err_out; + } + ret = sync_read(dev, (void __user *)arg); + if (ret < 0) + ts_err("Sync data read failed"); + break; + case GTP_ASYNC_WRITE: + ret = async_write(dev, (void __user *)arg); + if (ret < 0) + ts_err("Async data write failed"); + break; + case GTP_DRV_VERSION: + ret = copy_to_user((u8 *)arg, GOODIX_DRIVER_VERSION, + sizeof(GOODIX_DRIVER_VERSION)); + if (ret) + ts_err("failed copy driver version info to user"); + break; + default: + ts_info("Invalid cmd"); + ret = -ENOTTY; + break; + } + +err_out: + if (!temp_cfg) { + kfree(temp_cfg); + temp_cfg = NULL; + } + 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"); + 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 */ + atomic_set(&goodix_tools_dev->in_use, 0); + ret = goodix_unregister_ext_module(&goodix_tools_dev->module); + return ret; +} + +/** + * goodix_tools_module_irq - goodix tools Irq handle + * This functions is excuted when interrupt happended + * + * @core_data: pointer to touch core data + * @module: pointer to goodix_ext_module struct + * return: EVT_CONTINUE let other module handle this irq + */ +static int goodix_tools_module_irq(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct goodix_tools_dev *dev = module->priv_data; + struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev; + const struct goodix_ts_hw_ops *hw_ops = ts_dev->hw_ops; + struct goodix_tools_data *tools_data, *next; + int r = 0; + u8 evt_sta = 0; + + mutex_lock(&dev->mutex); + if (!list_empty(&dev->head)) { + r = hw_ops->read_trans(ts_dev, ts_dev->reg.coor/* TS_REG_COORDS_BASE*/, &evt_sta, 1); + if (r < 0 || ((evt_sta & GOODIX_TOUCH_EVENT) == 0)) { + ts_err("data not ready:0x%x, read ret =%d", evt_sta, r); + mutex_unlock(&dev->mutex); + return EVT_CONTINUE; + } + + list_for_each_entry_safe(tools_data, next, &dev->head, list) { + if (!hw_ops->read_trans(ts_dev, tools_data->reg_addr, + tools_data->data, tools_data->length)) { + tools_data->filled = 1; + } + } + wake_up(&dev->wq); + } + mutex_unlock(&dev->mutex); + return EVT_CONTINUE; +} + +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 = { + .irq_event = goodix_tools_module_irq, + .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 + */ +static int __init 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"); + + return ret; +} + +static void __exit goodix_tools_exit(void) +{ + misc_deregister(&goodix_tools_miscdev); + kfree(goodix_tools_dev); + ts_info("Goodix tools miscdev exit"); +} + +late_initcall(goodix_tools_init); +module_exit(goodix_tools_exit); + +MODULE_DESCRIPTION("Goodix tools Module"); +MODULE_AUTHOR("Goodix, Inc."); +MODULE_LICENSE("GPL v2");