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