diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 8f5ea2ac79247..c8ae5facec5ab 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -447,6 +447,8 @@ config TOUCHSCREEN_GT9XX code includes that in its table of IIC devices. If unsure, say N. +source "drivers/input/touchscreen/gtx8/Kconfig" + config TOUCHSCREEN_HIDEEP tristate "HiDeep Touch IC" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 5b5abdb6e11b2..a629b11f4d1b1 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_TOUCHSCREEN_GSLX680_PAD) += gslx680-pad.o gslx680-pad-y := gslx680_pad.o gsl_point_id.o obj-$(CONFIG_TOUCHSCREEN_GT1X) += gt1x/ obj-$(CONFIG_TOUCHSCREEN_GT9XX) += gt9xx/ +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX8) += gtx8/ obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o obj-$(CONFIG_TOUCHSCREEN_ILITEK) += ilitek_ts_i2c.o diff --git a/drivers/input/touchscreen/gtx8/Kconfig b/drivers/input/touchscreen/gtx8/Kconfig new file mode 100644 index 0000000000000..0b32a33b27d27 --- /dev/null +++ b/drivers/input/touchscreen/gtx8/Kconfig @@ -0,0 +1,46 @@ +# +# Goodix touchscreen driver configuration +# +menuconfig TOUCHSCREEN_GOODIX_GTX8 + bool "Goodix GTx8 touchscreen" + depends on I2C + default y + help + Say Y here if you have a Goodix GTx8 touch controller + to your system. + + If unsure, say N. + +if TOUCHSCREEN_GOODIX_GTX8 + +config TOUCHSCREEN_GOODIX_GTX8_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_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_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/gtx8/Makefile b/drivers/input/touchscreen/gtx8/Makefile new file mode 100644 index 0000000000000..984d4012a5a8b --- /dev/null +++ b/drivers/input/touchscreen/gtx8/Makefile @@ -0,0 +1,9 @@ +# Goodix Touchscreen Makefile +# This Makefile is only for tempory compiling use +# Use xxxproject/Makefile-release when release driver +# +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX8) += goodix_core.o +goodix_core-y := goodix_ts_i2c.o goodix_ts_core.o goodix_cfg_bin.o +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX8_UPDATE) += goodix_gtx8_update.o +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX8_GESTURE) += goodix_ts_gesture.o +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX8_TOOLS) += goodix_ts_tools.o diff --git a/drivers/input/touchscreen/gtx8/README.md b/drivers/input/touchscreen/gtx8/README.md new file mode 100644 index 0000000000000..b64814505772f --- /dev/null +++ b/drivers/input/touchscreen/gtx8/README.md @@ -0,0 +1,2 @@ +# gtx8_driver_linux +Linux driver source code for Goodix gtx8 series touch controller. diff --git a/drivers/input/touchscreen/gtx8/goodix_cfg_bin.c b/drivers/input/touchscreen/gtx8/goodix_cfg_bin.c new file mode 100644 index 0000000000000..f300f679178d1 --- /dev/null +++ b/drivers/input/touchscreen/gtx8/goodix_cfg_bin.c @@ -0,0 +1,569 @@ +#include "goodix_cfg_bin.h" + +extern struct goodix_module goodix_modules; +int goodix_ts_stage2_init(struct goodix_ts_core *core_data); +int goodix_ts_core_release(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); + +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"); + } + + /*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; + + if (!data) { + ts_err("ts core data can't be null"); + return -EINVAL; + } + ts_dev = ts_core->ts_dev; + if (!ts_dev) { + ts_err("ts dev data can't be null"); + return -EINVAL; + } + + 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_err("fw update failed, %d[ignore]", ret); + ret = 0; + } + } + ret = goodix_ts_stage2_init(ts_core); + if (!ret) { + ts_info("stage2 init success"); + return ret; + } + ts_err("stage2 init failed, %d", ret); + +release_core: + goodix_ts_core_release(ts_core); + 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/gtx8/goodix_cfg_bin.h b/drivers/input/touchscreen/gtx8/goodix_cfg_bin.h new file mode 100644 index 0000000000000..c1aa93a82bba8 --- /dev/null +++ b/drivers/input/touchscreen/gtx8/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/gtx8/goodix_default_fw.h b/drivers/input/touchscreen/gtx8/goodix_default_fw.h new file mode 100644 index 0000000000000..1632e6ef3c766 --- /dev/null +++ b/drivers/input/touchscreen/gtx8/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/gtx8/goodix_gtx8_update.c b/drivers/input/touchscreen/gtx8/goodix_gtx8_update.c new file mode 100644 index 0000000000000..a4ec0ab7864ed --- /dev/null +++ b/drivers/input/touchscreen/gtx8/goodix_gtx8_update.c @@ -0,0 +1,1609 @@ +/* + * 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 + * @allow_reset: control the reset callback + * @allow_irq: control the irq callback + * @allow_suspend: control the suspend callback + * @allow_resume: allow resume callback + * @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 + */ +struct fw_update_ctrl { + struct mutex mutex; + int initialized; + int mode; + enum update_status status; + unsigned int progress; + + bool allow_reset; + bool allow_irq; + bool allow_suspend; + bool allow_resume; + + 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; +}; +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*/ + fwu_ctrl->allow_reset = true; + 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); + fwu_ctrl->allow_reset = false; + + 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; +} + +static struct goodix_ext_module goodix_fwu_module; + +/** + * 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; + } + + goodix_register_ext_module(&goodix_fwu_module); + /* DONT allow reset/irq/suspend/resume during update */ + fwu_ctrl->allow_irq = false; + fwu_ctrl->allow_suspend = false; + fwu_ctrl->allow_resume = false; + fwu_ctrl->allow_reset = false; + 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); + + fwu_ctrl->allow_reset = true; + fwu_ctrl->allow_irq = true; + fwu_ctrl->allow_suspend = true; + fwu_ctrl->allow_resume = true; + + /* 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; + } + goodix_unregister_ext_module(&goodix_fwu_module); +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 goodix_ext_module *module, + const char *buf, size_t count) +{ + int ret = 0; + int mode = 0; + struct fw_update_ctrl *fw_ctrl = module->priv_data; + + 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 goodix_ext_module *module, + char *buf) +{ + struct fw_update_ctrl *fw_ctrl = module->priv_data; + return scnprintf(buf, PAGE_SIZE, "%d\n", fw_ctrl->progress); +} + +static ssize_t goodix_sysfs_update_result_show( + struct goodix_ext_module *module, + char *buf) +{ + char *result = NULL; + struct fw_update_ctrl *fw_ctrl = module->priv_data; + + 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 goodix_ext_module *module, + char *buf) +{ + struct goodix_ts_version fw_ver; + struct fw_update_ctrl *fw_ctrl = module->priv_data; + 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 goodix_ext_module *module, + char *buf) +{ + struct fw_update_ctrl *fw_ctrl = module->priv_data; + 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 goodix_ext_module *module, + const char *buf, size_t count) +{ + struct fw_update_ctrl *fw_ctrl = module->priv_data; + 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; + struct firmware_data *fw_data; + + fw_ctrl = container_of(attr, struct fw_update_ctrl, + attr_fwimage); + 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 goodix_ext_module *module, + const char *buf, size_t count) +{ + return count; +} + +static struct goodix_ext_attribute goodix_fwu_attrs[] = { + __EXTMOD_ATTR(update_en, S_IWUGO, NULL, goodix_sysfs_update_en_store), + __EXTMOD_ATTR(progress, S_IRUGO, goodix_sysfs_update_progress_show, NULL), + __EXTMOD_ATTR(result, S_IRUGO, goodix_sysfs_update_result_show, NULL), + __EXTMOD_ATTR(fwversion, S_IRUGO, + goodix_sysfs_update_fwversion_show, NULL), + __EXTMOD_ATTR(fwsize, S_IRUGO | S_IWUGO, goodix_sysfs_fwsize_show, + goodix_sysfs_fwsize_store), + __EXTMOD_ATTR(force_update, S_IWUGO, NULL, + goodix_sysfs_force_update_store), +}; + +static int goodix_fw_sysfs_init(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fw_ctrl = module->priv_data; + struct kobj_type *ktype; + int ret = 0, i; + + ktype = goodix_get_default_ktype(); + ret = kobject_init_and_add(&module->kobj, + ktype, + &core_data->pdev->dev.kobj, + "fwupdate"); + if (ret) { + ts_err("Create fwupdate sysfs node error!"); + goto exit_sysfs_init; + } + + + ret = 0; + for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs) && !ret; i++) + ret = sysfs_create_file(&module->kobj, &goodix_fwu_attrs[i].attr); + if (ret) { + ts_err("failed create fwu sysfs files"); + while (--i >= 0) + sysfs_remove_file(&module->kobj, &goodix_fwu_attrs[i].attr); + + kobject_put(&module->kobj); + ret = -EINVAL; + goto exit_sysfs_init; + } + + 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(&module->kobj, + &fw_ctrl->attr_fwimage); + +exit_sysfs_init: + return ret; +} + +static void goodix_fw_sysfs_remove(struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fw_ctrl = module->priv_data; + int i; + + sysfs_remove_bin_file(&module->kobj, &fw_ctrl->attr_fwimage); + + for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs); i++) + sysfs_remove_file(&module->kobj, + &goodix_fwu_attrs[i].attr); + + kobject_put(&module->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; + } +} + +static int goodix_fw_update_init(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + 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_lock(&goodix_fw_update_ctrl.mutex); + module->priv_data = &goodix_fw_update_ctrl; + + goodix_fw_update_ctrl.ts_dev = core_data->ts_dev; + goodix_fw_update_ctrl.allow_reset = true; + goodix_fw_update_ctrl.allow_irq = true; + goodix_fw_update_ctrl.allow_suspend = true; + goodix_fw_update_ctrl.allow_resume = true; + 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, module); + 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; +} + +static int goodix_fw_update_exit(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + return 0; +} + +static int goodix_fw_before_suspend(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fwu_ctrl = module->priv_data; + + return fwu_ctrl->allow_suspend ? + EVT_HANDLED : EVT_CANCEL_SUSPEND; +} + +static int goodix_fw_before_resume(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fwu_ctrl = module->priv_data; + + return fwu_ctrl->allow_resume ? + EVT_HANDLED : EVT_CANCEL_RESUME; +} + +static int goodix_fw_after_resume(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + return 0; +} + +static int goodix_fw_irq_event(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fwu_ctrl = module->priv_data; + + return fwu_ctrl->allow_irq ? + EVT_HANDLED : EVT_CANCEL_IRQEVT; +} + +static int goodix_fw_before_reset(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fwu_ctrl = module->priv_data; + + return fwu_ctrl->allow_reset ? + EVT_HANDLED : EVT_CANCEL_RESET; +} + +static const struct goodix_ext_module_funcs goodix_ext_funcs = { + .init = goodix_fw_update_init, + .exit = goodix_fw_update_exit, + .before_reset = goodix_fw_before_reset, + .after_reset = NULL, + .before_suspend = goodix_fw_before_suspend, + .after_suspend = NULL, + .before_resume = goodix_fw_before_resume, + .after_resume = goodix_fw_after_resume, + .irq_event = goodix_fw_irq_event, +}; + +static struct goodix_ext_module goodix_fwu_module = { + .name = "goodix-fwu", + .funcs = &goodix_ext_funcs, + .priority = EXTMOD_PRIO_FWUPDATE, +}; + +static int __init goodix_fwu_module_init(void) +{ + ts_info("goodix_fwupdate_module_ini IN"); + mutex_init(&goodix_fw_update_ctrl.mutex); + return goodix_register_ext_module(&goodix_fwu_module); +} + +static void __exit goodix_fwu_module_exit(void) +{ + mutex_lock(&goodix_fw_update_ctrl.mutex); + goodix_unregister_ext_module(&goodix_fwu_module); + if (goodix_fw_update_ctrl.initialized) { + goodix_fw_sysfs_remove(&goodix_fwu_module); + goodix_fw_update_ctrl.initialized = 0; + } + mutex_lock(&goodix_fw_update_ctrl.mutex); +} + +module_init(goodix_fwu_module_init); +module_exit(goodix_fwu_module_exit); + +MODULE_DESCRIPTION("Goodix FWU Module"); +MODULE_AUTHOR("Goodix, Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/gtx8/goodix_ts_core.c b/drivers/input/touchscreen/gtx8/goodix_ts_core.c new file mode 100644 index 0000000000000..6044103ed61f1 --- /dev/null +++ b/drivers/input/touchscreen/gtx8/goodix_ts_core.c @@ -0,0 +1,2103 @@ + /* + * 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 +#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); + +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 + */ +static void __do_register_ext_module(struct work_struct *work) +{ + struct goodix_ext_module *module = + container_of(work, struct goodix_ext_module, work); + struct goodix_ext_module *ext_module, *next; + struct list_head *insert_point = &goodix_modules.head; + + ts_info("__do_register_ext_module 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; + } + + ts_info("start register ext_module"); + + /* prority level *must* be set */ + if (module->priority == EXTMOD_PRIO_RESERVED) { + ts_err("Priority of module [%s] needs to be set", + module->name); + return; + } + + 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) { + ts_info("Module [%s] already exists", + module->name); + mutex_unlock(&goodix_modules.mutex); + return; + } + } + + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + /* small value of priority have + * higher priority level + */ + 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; + } + } + + list_add(&module->list, insert_point->prev); + goodix_modules.count++; + mutex_unlock(&goodix_modules.mutex); + + ts_info("Module [%s] registered,priority:%u", + module->name, + module->priority); +} + +/** + * 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) +{ + if (!module) + return -EINVAL; + + if (!goodix_modules.initilized) { + goodix_modules.initilized = true; + INIT_LIST_HEAD(&goodix_modules.head); + mutex_init(&goodix_modules.mutex); + init_completion(&goodix_modules.core_comp); + } + + ts_info("goodix_register_ext_module IN"); + + INIT_WORK(&module->work, __do_register_ext_module); + schedule_work(&module->work); + + ts_info("goodix_register_ext_module OUT"); + + return 0; +} +EXPORT_SYMBOL_GPL(goodix_register_ext_module); + +/** + * goodix_unregister_ext_module - interface for external module + * to unregister external modules + * + * @module: pointer to external module + * return: 0 ok, <0 failed + */ +int goodix_unregister_ext_module(struct goodix_ext_module *module) +{ + struct goodix_ext_module *ext_module, *next; + bool found = false; + + if (!module) + return -EINVAL; + + if (!goodix_modules.initilized) + return -EINVAL; + + if (!goodix_modules.core_data) + return -ENODEV; + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (ext_module == module) { + found = true; + break; + } + } + } else { + mutex_unlock(&goodix_modules.mutex); + return -EFAULT; + } + + if (!found) { + ts_err("Module [%s] never registed", + module->name); + mutex_unlock(&goodix_modules.mutex); + return -EFAULT; + } + + list_del(&module->list); + goodix_modules.count--; + 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_remove_all_ext_modules(void) +{ + struct goodix_ext_module *ext_module, *next; + + if (!goodix_modules.initilized || !goodix_modules.core_data) + return; + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + list_del(&ext_module->list); + goodix_modules.count--; + if (ext_module->funcs && ext_module->funcs->exit) + ext_module->funcs->exit(goodix_modules.core_data, + ext_module); + } + } + + mutex_unlock(&goodix_modules.mutex); +} + +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); + +/* debug fs */ +struct debugfs_buf { + struct debugfs_blob_wrapper buf; + int pos; + struct dentry *dentry; +} goodix_dbg; + +void goodix_msg_printf(const char *fmt, ...) +{ + va_list args; + int r; + + if (!goodix_dbg.dentry) + return; + if (goodix_dbg.pos < goodix_dbg.buf.size) { + va_start(args, fmt); + r = vscnprintf(goodix_dbg.buf.data + goodix_dbg.pos, + goodix_dbg.buf.size - 1, fmt, args); + goodix_dbg.pos += r; + va_end(args); + } +} +EXPORT_SYMBOL_GPL(goodix_msg_printf); + +static int goodix_debugfs_init(void) +{ + struct dentry *r_b; + + goodix_dbg.buf.size = PAGE_SIZE; + goodix_dbg.pos = 0; + goodix_dbg.buf.data = kzalloc(goodix_dbg.buf.size, GFP_KERNEL); + if (goodix_dbg.buf.data == NULL) { + pr_err("Debugfs init failed\n"); + goto exit; + } + r_b = debugfs_create_blob("goodix_ts", 0644, NULL, &goodix_dbg.buf); + if (!r_b) { + pr_err("Debugfs create failed\n"); + return -ENOENT; + } + goodix_dbg.dentry = r_b; + +exit: + return 0; +} + +static void goodix_debugfs_exit(void) +{ + debugfs_remove(goodix_dbg.dentry); + goodix_dbg.dentry = NULL; + pr_info("Debugfs module exit\n"); +} + +/* 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); + else + ret = -EINVAL; + + if (ret > 0) { + offset = 0; + for (i = 0; i < ret; i++) { + if (i != 0 && i % 20 == 0) + buf[offset++] = '\n'; + offset += snprintf(&buf[offset], PAGE_SIZE - offset, + "%02x,", cfg_buf[i]); + } + } + kfree(cfg_buf); + if (ret <= 0) + return ret; + + return offset; +} + +static u8 ascii2hex(u8 a) +{ + s8 value = 0; + + if (a >= '0' && a <= '9') + value = a - '0'; + else if (a >= 'A' && a <= 'F') + value = a - 'A' + 0x0A; + else if (a >= 'a' && a <= 'f') + value = a - 'a' + 0x0A; + else + value = 0xff; + + return value; +} + +static int goodix_ts_convert_0x_data(const u8 *buf, int buf_size, + 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); + 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); +} + +/* 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. + * + * @core_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; + } + } else { + ts_info("Avdd name is NULL[skip]"); + } + + 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) +{ + struct goodix_ts_board_data *ts_bdata = board_data(core_data); + int r; + + ts_info("Device power on"); + if (core_data->power_on) + return 0; + + if (!core_data->avdd) { + core_data->power_on = 1; + return 0; + } + + r = regulator_enable(core_data->avdd); + if (!r) { + ts_info("regulator enable SUCCESS"); + if (ts_bdata->power_on_delay_us) + usleep_range(ts_bdata->power_on_delay_us, + ts_bdata->power_on_delay_us); + } else { + ts_err("Failed to enable analog power:%d", r); + return r; + } + + core_data->power_on = 1; + 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) +{ + struct goodix_ts_board_data *ts_bdata = board_data(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"); + if (ts_bdata->power_off_delay_us) + usleep_range(ts_bdata->power_off_delay_us, + ts_bdata->power_off_delay_us); + } else { + ts_err("Failed to disable analog power:%d", r); + return r; + } + } + + core_data->power_on = 0; + return 0; +} + +#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) +{ + 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) +{ + 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; + + 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: + 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"); + goodix_ts_release_connects(core_data); + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (!ext_module->funcs->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); + + /* + * 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_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; + + ts_info("notify event type 0x%x", (unsigned int)action); + switch (action) { + 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]"); + break; + default: + break; + } + + 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]"); + + /* 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); + + /* esd protector */ + goodix_ts_esd_init(core_data); + return 0; +exit: + if (ts_dev->board_data.pen_enable) { + 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 a 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; + } + + /* 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 out; + + /* 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[skip]"); + goto out; + } + + /* Try start a thread to get config-bin info */ + r = goodix_start_later_init(core_data); + if (r) { + ts_info("Failed start cfg_bin_proc"); + goto out; + } + + /* 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_module_prob_sate = CORE_MODULE_PROB_SUCCESS; +out: + if (r) { + kfree(core_data); + core_data = NULL; + core_module_prob_sate = CORE_MODULE_PROB_FAILED; + } + ts_info("goodix_ts_probe OUT, 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); + + 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_remove_all_ext_modules(); + goodix_ts_power_off(core_data); + goodix_debugfs_exit(); + goodix_ts_sysfs_exit(core_data); + // can't free the memory for tools or gesture module + //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, +}; + +int goodix_ts_core_init(void) +{ + ts_info("Core layer init"); + if (!goodix_modules.initilized) { + /* this may init by outer modules register event */ + ts_info("init modules struct"); + goodix_modules.initilized = true; + INIT_LIST_HEAD(&goodix_modules.head); + mutex_init(&goodix_modules.mutex); + init_completion(&goodix_modules.core_comp); + } + + goodix_debugfs_init(); + return platform_driver_register(&goodix_ts_driver); +} + +/* uninit module manually */ +int goodix_ts_core_release(struct goodix_ts_core *core_data) +{ + ts_info("goodix core module removed"); + + platform_driver_unregister(&goodix_ts_driver); + goodix_ts_dev_release(); + return 0; +} diff --git a/drivers/input/touchscreen/gtx8/goodix_ts_core.h b/drivers/input/touchscreen/gtx8/goodix_ts_core.h new file mode 100644 index 0000000000000..da4e6fef6ca21 --- /dev/null +++ b/drivers/input/touchscreen/gtx8/goodix_ts_core.h @@ -0,0 +1,746 @@ +/* + * 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.4.4.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_PEN_MAX_PRESSURE 4096 +#define GOODIX_MAX_TP_KEY 4 +#define GOODIX_MAX_PEN_KEY 2 +/* + * struct goodix_module - external modules container + * @head: external modules list + * @initilized: whether this struct is initilized + * @mutex: mutex lock + * @count: current number of registered external module + * @wq: workqueue to do register work + * @core_data: core_data pointer + */ +struct goodix_module { + struct list_head head; + bool initilized; + struct mutex mutex; + unsigned int count; + 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 + * @reset_gpio: reset gpio number + * @irq_gpio: interrupt gpio number + * @irq_flag: irq trigger type + * @power_on_delay_us: power on delay time (us) + * @power_off_delay_us: power off delay time (us) + * @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]; + unsigned int reset_gpio; + unsigned int irq_gpio; + int irq; + unsigned int irq_flags; + + unsigned int power_on_delay_us; + unsigned int power_off_delay_us; + + 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, +}; + +/* + * 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 coor; + u16 gesture; + 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 (*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); +}; + +/* + * 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 + * @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; + struct platform_device *pdev; + struct goodix_ts_device *ts_dev; + struct input_dev *input_dev; + struct input_dev *pen_dev; + + struct regulator *avdd; +#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; + + 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); + +/** + * 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); + +/** + * 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); + +void goodix_msg_printf(const char *fmt, ...); +#ifndef CONFIG_TOUCHSCREEN_GOODIX_GTX8_UPDATE +static inline int goodix_do_fw_update(int mode) +{ + return -1111; +} +#else +int goodix_do_fw_update(int mode); +#endif + +#endif diff --git a/drivers/input/touchscreen/gtx8/goodix_ts_gesture.c b/drivers/input/touchscreen/gtx8/goodix_ts_gesture.c new file mode 100644 index 0000000000000..59203979a888d --- /dev/null +++ b/drivers/input/touchscreen/gtx8/goodix_ts_gesture.c @@ -0,0 +1,460 @@ +/* + * 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 41 +#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) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", + atomic_read(&gsx_gesture->registered)); +} + +static ssize_t gsx_gesture_enable_store(struct goodix_ext_module *module, + const char *buf, size_t count) +{ + unsigned int tmp; + int ret; + + if (sscanf(buf, "%u", &tmp) != 1) { + ts_info("Parameter illegal"); + return -EINVAL; + } + ts_debug("Tmp value =%d", tmp); + + if (tmp == 1) { + if (atomic_read(&gsx_gesture->registered)) { + ts_debug("Gesture module has aready registered"); + return count; + } + ret = goodix_register_ext_module(&gsx_gesture->module); + if (!ret) { + ts_info("Gesture module registered!"); + atomic_set(&gsx_gesture->registered, 1); + } else { + atomic_set(&gsx_gesture->registered, 0); + ts_err("Gesture module register failed"); + } + } else if (tmp == 0) { + if (!atomic_read(&gsx_gesture->registered)) { + ts_debug("Gesture module has aready unregistered"); + return count; + } + ts_debug("Start unregistered gesture module"); + ret = goodix_unregister_ext_module(&gsx_gesture->module); + if (!ret) { + atomic_set(&gsx_gesture->registered, 0); + ts_info("Gesture module unregistered success"); + } else { + atomic_set(&gsx_gesture->registered, 1); + ts_info("Gesture module unregistered failed"); + } + } else { + ts_err("Parameter error!"); + return -EINVAL; + } + return count; +} + +static ssize_t gsx_gesture_data_show(struct goodix_ext_module *module, + char *buf) +{ + ssize_t count; + + if (atomic_read(&gsx_gesture->registered) != 1) { + ts_info("Gesture module not register!"); + return -EPERM; + } + if (!buf || !gsx_gesture->gesture_data) { + ts_info("Parameter error!"); + return -EPERM; + } + read_lock(&gsx_gesture->rwlock); + + count = scnprintf(buf, PAGE_SIZE, "Previous gesture type: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] = 0x0; + gsx_gesture->cmd.cmds[2] = 0 - GSX_GESTURE_CMD; + 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)); + + 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; + +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 (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[3]; + else + gsx_type = temp_data[2]; + + if (QUERYBIT(gsx_gesture->gesture_type, gsx_type)) { + /* 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; + struct goodix_ts_cmd *gesture_cmd = &gsx_gesture->cmd; + + if (!gesture_cmd->initialized || hw_ops == NULL) { + ts_err("Uninitialized doze command or 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"); + + 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); +} + +module_init(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/gtx8/goodix_ts_i2c.c b/drivers/input/touchscreen/gtx8/goodix_ts_i2c.c new file mode 100644 index 0000000000000..549545969c1f6 --- /dev/null +++ b/drivers/input/touchscreen/gtx8/goodix_ts_i2c.c @@ -0,0 +1,1946 @@ +/* + * 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 256 +#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 + +int goodix_ts_core_init(void); +#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)); + } + r = of_property_read_u32(node, "goodix,power-on-delay-us", + &board_data->power_on_delay_us); + if (!r) { + /* 1000ms is too large, maybe you have pass a wrong value */ + if (board_data->power_on_delay_us > 1000 * 1000) { + ts_err("Power on delay time exceed 1s, please check"); + board_data->power_on_delay_us = 0; + } + } + + r = of_property_read_u32(node, "goodix,power-off-delay-us", + &board_data->power_off_delay_us); + if (!r) { + /* 1000ms is too large, maybe you have pass */ + if (board_data->power_off_delay_us > 1000 * 1000) { + ts_err("Power off delay time exceed 1s, please check"); + board_data->power_off_delay_us = 0; + } + } + + /* 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 && dev->reg.version_len < sizeof(temp_buf)) { + 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_err("failed send cfg"); + else + ts_info("config data equal with flash"); + 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_check_cfg_valid(struct goodix_ts_device *dev, u8 *cfg, u32 length) +{ + int ret; + u8 bag_num; + u8 checksum; + int i, j; + int bag_start = 0; + int bag_end = 0; + + if (!cfg || length < TS_CFG_HEAD_LEN) { + ts_err("cfg is INVALID, len:%d", length); + ret = -EINVAL; + goto exit; + } + + checksum = 0; + for (i = 0; i < TS_CFG_HEAD_LEN; i++) + checksum += cfg[i]; + if (checksum != 0) { + ts_err("cfg head checksum ERROR, checksum:0x%02x", + checksum); + ret = -EINVAL; + goto exit; + } + bag_num = cfg[TS_CFG_BAG_NUM_INDEX]; + bag_start = TS_CFG_BAG_START_INDEX; + + ts_info("cfg bag_num:%d, cfg length:%d", bag_num, length); + for (j = 0; j < bag_num; j++) { + if (bag_start >= length - 1) { + ts_err("ERROR, overflow!!bag_start:%d, cfg_len:%d", + bag_start, length); + ret = -EINVAL; + goto exit; + } + + bag_end = bag_start + cfg[bag_start + 1] + 3; + + checksum = 0; + if (bag_end > length) { + ts_err("ERROR, overflow!!bag:%d, bag_start:%d," + "bag_end:%d, cfg length:%d", + j, bag_start, bag_end, length); + ret = -EINVAL; + goto exit; + } + for (i = bag_start; i < bag_end; i++) + checksum += cfg[i]; + if (checksum != 0) { + ts_err("cfg INVALID, bag:%d checksum ERROR:0x%02x", + j, checksum); + ret = -EINVAL; + goto exit; + } + bag_start = bag_end; + } + + ret = 0; + ts_info("configuration check SUCCESS"); + +exit: + return ret; +}*/ + +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; + } + + /*check configuration valid*/ + // TODO remove this + // r = goodix_check_cfg_valid(dev, config->data, config->length); + // if (r != 0) { + // ts_err("cfg check FAILED"); + // 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) +{ + 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 */ + 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); + 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) +{ + 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 */ + 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); + 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) +{ + 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); + else + r = goodix_read_config_nor(dev, config_data); + 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*/ + r = dev->hw_ops->read_trans(dev, TS_REG_ESD_TICK_R, &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; + goto exit; + } + +exit: + return r; +} + +/* 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, +}; + +static struct platform_device *goodix_pdev; + +static void goodix_pdev_release(struct device *dev) +{ + ts_info("goodix pdev released"); +} + +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; + } + r = goodix_ts_core_init(); + if (r) { + ts_err("failed register platform driver, %d", r); + goto err_pdriver; + } + ts_info("i2c probe out"); + return r; + +err_pdriver: + platform_device_unregister(goodix_pdev); +err_pdev: + kfree(goodix_pdev); + goodix_pdev = NULL; + ts_info("i2c probe out, %d", r); + return r; +} + +static void goodix_i2c_remove(struct i2c_client *client) +{ + if (goodix_pdev) { + platform_device_unregister(goodix_pdev); + kfree(goodix_pdev); + goodix_pdev = NULL; + } +} + +#ifdef CONFIG_OF +static const struct of_device_id i2c_matchs[] = { + {.compatible = "goodix,gt9896",}, + {.compatible = "goodix,gt9886",}, + {.compatible = "goodix,gt9889",}, + {.compatible = "goodix,gt5863",}, + {}, +}; +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, +}; + +/* release manully when prob failed */ +void goodix_ts_dev_release(void) +{ + if (goodix_pdev) { + platform_device_unregister(goodix_pdev); + kfree(goodix_pdev); + goodix_pdev = NULL; + } + i2c_del_driver(&goodix_i2c_driver); +} + +static int __init goodix_i2c_init(void) +{ + ts_info("Goodix driver init"); + return i2c_add_driver(&goodix_i2c_driver); +} + +static void __exit goodix_i2c_exit(void) +{ + i2c_del_driver(&goodix_i2c_driver); + ts_info("Goodix driver exit"); +} + +module_init(goodix_i2c_init); +module_exit(goodix_i2c_exit); + +MODULE_DESCRIPTION("Goodix Touchscreen Hardware Module"); +MODULE_AUTHOR("Goodix, Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/gtx8/goodix_ts_tools.c b/drivers/input/touchscreen/gtx8/goodix_ts_tools.c new file mode 100644 index 0000000000000..0028dfea4b367 --- /dev/null +++ b/drivers/input/touchscreen/gtx8/goodix_ts_tools.c @@ -0,0 +1,614 @@ +/* + * 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 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 + */ +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 t_count; + struct goodix_ext_module module; +} *goodix_tools_dev; + + +/* read data from i2c asynchronous, + * success return bytes read, else 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) { + ret = -EFAULT; + goto err_out; + } + 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); + + 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)) { + if (copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, databuf, length)) { + ret = -EFAULT; + ts_err("Copy_to_user failed"); + } else { + ret = length; + } + } else { + ret = -EBUSY; + ts_err("Read i2c failed"); + } +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); + 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); + 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, + * success return bytes read, else 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; + + 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 == 1) { + if (copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, tools_data.data, + tools_data.length)) { + ret = -EFAULT; + ts_err("Copy_to_user failed"); + } else { + ret = tools_data.length; + } + } else { + ret = -EAGAIN; + ts_err("Wait queue timeout"); + } + + 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); + + 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); + + 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; + + filp->private_data = goodix_tools_dev; + ts_info("tools open"); + /* Only the first time open device need to register module */ + ret = goodix_register_ext_module(&goodix_tools_dev->module); + if (ret) { + ts_info("failed register to core module"); + } + + return ret; +} + +static int goodix_tools_release(struct inode *inode, struct file *filp) +{ + int ret = 0; + /* when the last close this dev node unregister the module */ + 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 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, +}; + +/** + * 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->t_count, 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"); +} + +module_init(goodix_tools_init); +module_exit(goodix_tools_exit); + +MODULE_DESCRIPTION("Goodix tools Module"); +MODULE_AUTHOR("Goodix, Inc."); +MODULE_LICENSE("GPL v2");