input: goodix_mmi: add goodix i2c 1510 version driver

input/goodix_mmi:
Add goodix i2c version 1510 driver for gt9885.

Change-Id: I8a9ca6c3ba2528363548b1fa218b38b86bb65347
Signed-off-by: wangyq13 <wangyq13@lenovo.com>
Reviewed-on: https://gerrit.mot.com/1923417
SME-Granted: SME Approvals Granted
SLTApproved: Slta Waiver
Tested-by: Jira Key
Reviewed-by: Jun Weng <wengjun1@motorola.com>
Reviewed-by: Wei Deng <dengwei1@motorola.com>
Reviewed-by: Huosheng Liao <liaohs@motorola.com>
Submit-Approved: Jira Key
This commit is contained in:
wangyq13 2021-04-14 15:02:11 +08:00 • committed by Yeqing Wang
commit 2b68f15142
14 changed files with 11811 additions and 0 deletions

View file

@ -0,0 +1,8 @@
DLKM_DIR := motorola/kernel/modules
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := goodix_v1510_mmi.ko
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
include $(DLKM_DIR)/AndroidKernelModule.mk

View file

@ -0,0 +1,8 @@
# add -Wall to try to catch everything we can.
EXTRA_CFLAGS += -Wall
EXTRA_CFLAGS += \
-I$(TOP)/motorola/kernel/modules/include \
-I$(TOP)/motorola/kernel/modules/drivers/input/touchscreen/goodix_gtx8_v1510_mmi
obj-m := goodix_v1510_mmi.o
goodix_v1510_mmi-objs = goodix_ts_i2c.o goodix_ts_core.o goodix_ts_test.o goodix_cfg_bin.o goodix_gtx8_update.o

View file

@ -0,0 +1,47 @@
#
# Goodix touchscreen driver configuration
#
menuconfig TOUCHSCREEN_GOODIX_GTX8_MMI
bool "Goodix GTx8 touchscreen"
depends on I2C
default y
help
Say Y here if you have a Goodix GTx5xx touchscreen connected
to your system.
If unsure, say N.
if TOUCHSCREEN_GOODIX_GTX8_MMI
config TOUCHSCREEN_GOODIX_GTX8_MMI_UPDATE
tristate "Goodix GTx8 firmware update module"
default y
help
Say Y here to enable support for doing firmware update.
If unsure, say N.
To compile this driver as a module, choose M here.
config TOUCHSCREEN_GOODIX_GTX8_MMI_GESTURE
tristate "Goodix GTx8 gesture wakeup feature"
default n
help
Say Y here to enable support for gesture wakeup feature..
If unsure, say N.
To compile this driver as a module, choose M here.
config TOUCHSCREEN_GOODIX_GTX8_MMI_TOOLS
tristate "Goodix touch tools support"
default y
help
Say Y here to enable debug tools.
If unsure, say N.
To compile this driver as a module, choose M here.
endif

View file

@ -0,0 +1,10 @@
KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) modules $(KBUILD_OPTIONS)
modules_install:
$(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(shell pwd) modules_install
clean:
$(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean

View file

@ -0,0 +1,650 @@
#include "goodix_cfg_bin.h"
extern struct goodix_module goodix_modules;
int goodix_ts_stage2_init(struct goodix_ts_core *core_data);
static int goodix_parse_cfg_bin(struct goodix_cfg_bin *cfg_bin);
static int goodix_get_reg_and_cfg(struct goodix_ts_device *ts_dev,
struct goodix_cfg_bin *cfg_bin);
static int goodix_read_cfg_bin(struct device *dev,
struct goodix_cfg_bin *cfg_bin);
static void goodix_cfg_pkg_leToCpu(struct goodix_cfg_package *pkg);
int goodix_get_cfg_value(struct goodix_ts_core *core_data,
u8 *config, u8 *buf, u8 len, u8 sub_bag_num, u8 offset)
{
u8 *sub_bag_ptr = NULL;
u8 i = 0;
u8 chksum_len = 1;
u8 head_len = 0;
if (core_data->ts_dev->ic_type == IC_TYPE_YELLOWSTONE)
chksum_len = 2;
head_len = 3 + chksum_len;
sub_bag_ptr = &config[head_len];
for (i = 0; i < config[2]; i++) {
if (sub_bag_ptr[0] == sub_bag_num)
break;
sub_bag_ptr += sub_bag_ptr[1] + 2 + chksum_len;
}
if (i >= config[2]) {
ts_err("Cann't find the specifiled bag num %d", sub_bag_num);
return -EINVAL;
}
if (sub_bag_ptr[1]+2+chksum_len < offset + len) {
ts_err("Sub bag len less then you want to read: %d < %d", sub_bag_ptr[1]+3, offset + len);
return -EINVAL;
}
if (len)
memcpy(buf, sub_bag_ptr + offset, len);
else
memcpy(buf, sub_bag_ptr, sub_bag_ptr[1] + 2 + chksum_len);
return (sub_bag_ptr + offset - config);
}
/*
* parse driver and sensor num only on screen
*/
#define MAX_DRV_NUM 40
#define MAX_SEN_NUM 36
static int goodix_get_channel_num(struct goodix_ts_core *core_data,
u32 *sen_num, u32 *drv_num, u8 *cfg_data)
{
int ret = 0;
u8 buf[3] = {0};
u8 *temp_cfg_data = cfg_data;
if (!sen_num || !drv_num || !temp_cfg_data) {
ts_err("%s: invalid param", __func__);
return -EINVAL;
}
ret = goodix_get_cfg_value(core_data, temp_cfg_data, buf, 2, 1, 14);
if (ret > 0) {
*drv_num = buf[0] + buf[1];
} else {
ts_err("Failed read drv_num reg");
ret = -EINVAL;
goto err_out;
}
ret = goodix_get_cfg_value(core_data, temp_cfg_data, buf, 1, 1, 10);
if (ret > 0) {
*sen_num = buf[0];
} else {
ts_err("Failed read sen_num reg");
ret = -EINVAL;
goto err_out;
}
ts_info("drv_num:%d,sen_num:%d", *drv_num, *sen_num);
if (*drv_num > MAX_DRV_NUM || *drv_num <= 0
|| *sen_num > MAX_SEN_NUM || *sen_num <= 0) {
ts_err("invalid sensor or driver num");
ret = -EINVAL;
}
err_out:
return ret < 0 ? ret : 0;
}
static int goodix_parse_cfg_bin(struct goodix_cfg_bin *cfg_bin)
{
u8 checksum;
int i, r;
u16 offset1, offset2;
if (!cfg_bin->bin_data || cfg_bin->bin_data_len == 0) {
ts_err("NO cfg_bin data, cfg_bin data length:%d",
cfg_bin->bin_data_len);
r = -EINVAL;
goto exit;
}
/* copy cfg_bin head info */
if (cfg_bin->bin_data_len < sizeof(struct goodix_cfg_bin_head)) {
ts_err("Invalid cfg_bin size:%d", cfg_bin->bin_data_len);
r = -EINVAL;
goto exit;
}
memcpy(&cfg_bin->head, cfg_bin->bin_data,
sizeof(struct goodix_cfg_bin_head));
cfg_bin->head.bin_len = le32_to_cpu(cfg_bin->head.bin_len);
/*check length*/
if (cfg_bin->bin_data_len != cfg_bin->head.bin_len) {
ts_err("cfg_bin len check failed,%d != %d",
cfg_bin->head.bin_len, cfg_bin->bin_data_len);
r = -EINVAL;
goto exit;
}
/*check cfg_bin valid*/
checksum = 0;
for (i = TS_BIN_VERSION_START_INDEX; i < cfg_bin->bin_data_len; i++) {
checksum += cfg_bin->bin_data[i];
}
if (checksum != cfg_bin->head.checksum) {
ts_err("cfg_bin checksum check filed 0x%02x != 0x%02x",
cfg_bin->head.checksum, checksum);
r = -EINVAL;
goto exit;
}
/*allocate memory for cfg packages*/
cfg_bin->cfg_pkgs = kzalloc(sizeof(struct goodix_cfg_package) *
cfg_bin->head.pkg_num, GFP_KERNEL);
if (!cfg_bin->cfg_pkgs) {
ts_err("cfg_pkgs, allocate memory ERROR");
r = -ENOMEM;
goto exit;
}
/*get cfg_pkg's info*/
for (i = 0; i < cfg_bin->head.pkg_num; i++) {
/*get cfg pkg length*/
if (i == cfg_bin->head.pkg_num - 1) {
offset1 = cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN] +
(cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN + 1] << 8);
cfg_bin->cfg_pkgs[i].pkg_len = cfg_bin->bin_data_len - offset1;
} else {
offset1 = cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN] +
(cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN + 1] << 8);
offset2 = cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN + 2] +
(cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + i * TS_CFG_OFFSET_LEN + 3] << 8);
if (offset2 <= offset1) {
ts_err("offset error,pkg:%d, offset1:%d, offset2:%d", i, offset1, offset2);
r = -EINVAL;
goto exit;
}
cfg_bin->cfg_pkgs[i].pkg_len = offset2 - offset1;
}
/*get cfg pkg head*/
memcpy(&cfg_bin->cfg_pkgs[i].cnst_info,
&cfg_bin->bin_data[offset1], TS_PKG_CONST_INFO_LEN);
memcpy(&cfg_bin->cfg_pkgs[i].reg_info,
&cfg_bin->bin_data[offset1 + TS_PKG_CONST_INFO_LEN],
TS_PKG_REG_INFO_LEN);
/*compatible little edition and big edition*/
goodix_cfg_pkg_leToCpu(&cfg_bin->cfg_pkgs[i]);
/*get configuration data*/
cfg_bin->cfg_pkgs[i].cfg = &cfg_bin->bin_data[offset1 + TS_PKG_HEAD_LEN];
}
/*debug, print pkg information*/
ts_info("Driver bin info: ver %s, len %d, pkgs %d", cfg_bin->head.bin_version,
cfg_bin->head.bin_len, cfg_bin->head.pkg_num);
for (i = 0; i < cfg_bin->head.pkg_num; i++) {
ts_debug("---------------------------------------------");
ts_debug("------package:%d------", i + 1);
ts_debug("package len:%04x", cfg_bin->cfg_pkgs[i].cnst_info.pkg_len);
ts_debug("package ic_type:%s", cfg_bin->cfg_pkgs[i].cnst_info.ic_type);
ts_debug("package cfg_type:%01x", cfg_bin->cfg_pkgs[i].cnst_info.cfg_type);
ts_debug("package sensor_id:%01x", cfg_bin->cfg_pkgs[i].cnst_info.sensor_id);
ts_debug("package hw_pid:%s", cfg_bin->cfg_pkgs[i].cnst_info.hw_pid);
ts_debug("package hw_vid:%s", cfg_bin->cfg_pkgs[i].cnst_info.hw_vid);
ts_debug("package fw_mask_version:%s", cfg_bin->cfg_pkgs[i].cnst_info.fw_mask);
ts_debug("package fw_patch_version:%s", cfg_bin->cfg_pkgs[i].cnst_info.fw_patch);
ts_debug("package x_res_offset:%02x", cfg_bin->cfg_pkgs[i].cnst_info.x_res_offset);
ts_debug("package y_res_offset:%02x", cfg_bin->cfg_pkgs[i].cnst_info.y_res_offset);
ts_debug("package trigger_offset:%02x", cfg_bin->cfg_pkgs[i].cnst_info.trigger_offset);
ts_debug("reg info");
ts_debug("send_cfg_flag reg:%02x", cfg_bin->cfg_pkgs[i].reg_info.cfg_send_flag.addr);
ts_debug("version base reg:%02x, len:%d",
cfg_bin->cfg_pkgs[i].reg_info.version_base.addr,
cfg_bin->cfg_pkgs[i].reg_info.version_base.reserved1);
ts_debug("pid reg:%02x:%d", cfg_bin->cfg_pkgs[i].reg_info.pid.addr,
cfg_bin->cfg_pkgs[i].reg_info.pid.reserved1);
ts_debug("vid reg:%02x:%d", cfg_bin->cfg_pkgs[i].reg_info.vid.addr,
cfg_bin->cfg_pkgs[i].reg_info.vid.reserved1);
ts_debug("sensor_id reg:%02x,mask:%x", cfg_bin->cfg_pkgs[i].reg_info.sensor_id.addr,
cfg_bin->cfg_pkgs[i].reg_info.sensor_id.addr);
ts_debug("fw_status reg:%02x", cfg_bin->cfg_pkgs[i].reg_info.fw_status.addr);
ts_debug("cfg_addr reg:%02x", cfg_bin->cfg_pkgs[i].reg_info.cfg_addr.addr);
ts_debug("esd reg:%02x", cfg_bin->cfg_pkgs[i].reg_info.esd.addr);
ts_debug("command reg:%02x", cfg_bin->cfg_pkgs[i].reg_info.command.addr);
ts_debug("coor:%02x", cfg_bin->cfg_pkgs[i].reg_info.coor.addr);
ts_debug("gesture:%02x", cfg_bin->cfg_pkgs[i].reg_info.gesture.addr);
ts_debug("fw_request:%02x", cfg_bin->cfg_pkgs[i].reg_info.fw_request.addr);
ts_debug("proximity:%02x", cfg_bin->cfg_pkgs[i].reg_info.proximity.addr);
ts_debug("--------------------------------------------");
}
r = 0;
exit:
return r;
}
static int goodix_cfg_bin_proc(struct goodix_ts_core *core_data)
{
int r;
struct goodix_ts_device *ts_dev = core_data->ts_dev;
struct goodix_cfg_bin *cfg_bin = kzalloc(sizeof(struct goodix_cfg_bin),
GFP_KERNEL);
if (!cfg_bin) {
return -ENOMEM;
}
if (!ts_dev) {
ts_err("ts device can't be null");
return -EINVAL;
}
/* when start init config bin with error state */
ts_dev->cfg_bin_state = CFG_BIN_STATE_ERROR;
/*get cfg_bin from file system*/
r = goodix_read_cfg_bin(ts_dev->dev, cfg_bin);
if (r < 0) {
ts_err("Failed get valid config bin data");
goto exit;
}
/*parse cfg bin*/
r = goodix_parse_cfg_bin(cfg_bin);
if (!r) {
ts_info("parse cfg bin SUCCESS");
} else {
ts_err("parse cfg bin FAILED");
goto exit;
}
/*get register address and configuration from cfg bin*/
r = goodix_get_reg_and_cfg(ts_dev, cfg_bin);
if (!r) {
ts_info("success get reg and cfg info from cfg bin");
} else {
ts_err("failed get cfg and reg info, update fw then retry");
}
/* get sen_num/drv_num */
if (ts_dev->normal_cfg.initialized) {
r = goodix_get_channel_num(core_data, &core_data->sen_num,
&core_data->drv_num, ts_dev->normal_cfg.data);
if (r)
ts_err("failed get channel_num");
}
/*debug*/
ts_info("cfg_send_flag:0x%04x", ts_dev->reg.cfg_send_flag);
ts_info("pid:0x%04x", ts_dev->reg.pid);
ts_info("vid:0x%04x", ts_dev->reg.vid);
ts_info("sensor_id:0x%04x", ts_dev->reg.sensor_id);
ts_info("fw_mask:0x%04x", ts_dev->reg.fw_mask);
ts_info("fw_status:0x%04x", ts_dev->reg.fw_status);
ts_info("cfg_addr:0x%04x", ts_dev->reg.cfg_addr);
ts_info("esd:0x%04x", ts_dev->reg.esd);
ts_info("command:0x%04x", ts_dev->reg.command);
ts_info("coor:0x%04x", ts_dev->reg.coor);
ts_info("gesture:0x%04x", ts_dev->reg.gesture);
ts_info("fw_request:0x%04x", ts_dev->reg.fw_request);
ts_info("proximity:0x%04x", ts_dev->reg.proximity);
exit:
kfree(cfg_bin->cfg_pkgs);
kfree(cfg_bin->bin_data);
kfree(cfg_bin);
if (r)
goodix_ts_blocking_notify(NOTIFY_CFG_BIN_FAILED, &r);
else
goodix_ts_blocking_notify(NOTIFY_CFG_BIN_SUCCESS, &r);
ts_info("cfg bin state %d, ret %d", ts_dev->cfg_bin_state, r);
return r;
}
static int goodix_extract_cfg_pkg(struct goodix_ts_device *ts_dev,
struct goodix_cfg_package *cfg_pkg)
{
struct goodix_ts_config *ts_cfg;
if (cfg_pkg->cnst_info.cfg_type == TS_NORMAL_CFG) {
ts_cfg = &(ts_dev->normal_cfg);
} else if (cfg_pkg->cnst_info.cfg_type == TS_HIGH_SENSE_CFG) {
ts_cfg = &(ts_dev->highsense_cfg);
} else {
ts_err("unknown cfg type %d", cfg_pkg->cnst_info.cfg_type);
return -EINVAL;
}
ts_cfg->length = cfg_pkg->pkg_len -
TS_PKG_CONST_INFO_LEN - TS_PKG_REG_INFO_LEN;
if (ts_cfg->length > sizeof(ts_cfg->data)) {
ts_err("illegal cfg length %d", ts_cfg->length);
return -EINVAL;
}
if (ts_cfg->length) {
ts_info("get config type %d, len %d",
cfg_pkg->cnst_info.cfg_type, ts_cfg->length);
memcpy(ts_cfg->data, cfg_pkg->cfg, ts_cfg->length);
ts_cfg->initialized = TS_CFG_STABLE;
mutex_init(&ts_cfg->lock);
} else {
ts_info("no config data");
}
/*get register info*/
ts_dev->reg.cfg_send_flag = cfg_pkg->reg_info.cfg_send_flag.addr;
ts_dev->reg.version_base = cfg_pkg->reg_info.version_base.addr;
ts_dev->reg.version_len = cfg_pkg->reg_info.version_base.reserved1;
ts_dev->reg.pid = cfg_pkg->reg_info.pid.addr;
ts_dev->reg.pid_len = cfg_pkg->reg_info.pid.reserved1;
ts_dev->reg.vid = cfg_pkg->reg_info.vid.addr;
ts_dev->reg.vid_len = cfg_pkg->reg_info.vid.reserved1;
ts_dev->reg.sensor_id = cfg_pkg->reg_info.sensor_id.addr;
ts_dev->reg.sensor_id_mask = cfg_pkg->reg_info.sensor_id.reserved1;
ts_dev->reg.fw_mask = cfg_pkg->reg_info.fw_mask.addr;
ts_dev->reg.fw_status = cfg_pkg->reg_info.fw_status.addr;
ts_dev->reg.cfg_addr = cfg_pkg->reg_info.cfg_addr.addr;
ts_dev->reg.esd = cfg_pkg->reg_info.esd.addr;
ts_dev->reg.command = cfg_pkg->reg_info.command.addr;
ts_dev->reg.coor = cfg_pkg->reg_info.coor.addr;
ts_dev->reg.gesture = cfg_pkg->reg_info.gesture.addr;
ts_dev->reg.fw_request = cfg_pkg->reg_info.fw_request.addr;
ts_dev->reg.proximity = cfg_pkg->reg_info.proximity.addr;
return 0;
}
static int goodix_get_reg_and_cfg(struct goodix_ts_device *ts_dev,
struct goodix_cfg_bin *cfg_bin)
{
int i;
u16 addr;
u8 read_len;
char temp_sensor_id = -1;
u8 temp_fw_mask[TS_CFG_BLOCK_FW_MASK_LEN] = {0x00};
u8 temp_pid[TS_CFG_BLOCK_PID_LEN] = {0x00};
int r = -EINVAL;
if (!cfg_bin->head.pkg_num || !cfg_bin->cfg_pkgs) {
ts_err("there is none cfg package, pkg_num:%d",
cfg_bin->head.pkg_num);
return -EINVAL;
}
memset(&ts_dev->normal_cfg, 0, sizeof(ts_dev->normal_cfg));
memset(&ts_dev->highsense_cfg, 0, sizeof(ts_dev->highsense_cfg));
/* find suitable cfg packages */
for (i = 0; i < cfg_bin->head.pkg_num; i++) {
/*get ic type*/
if (!strncmp(cfg_bin->cfg_pkgs[i].cnst_info.ic_type,
"normandy", strlen("normandy"))) {
ts_dev->ic_type = IC_TYPE_NORMANDY;
} else if (!strncmp(cfg_bin->cfg_pkgs[i].cnst_info.ic_type,
"yellowstone", strlen("yellowstone"))) {
ts_dev->ic_type = IC_TYPE_YELLOWSTONE;
} else {
ts_err("unknow ic type of cfg_bin:%s",
cfg_bin->cfg_pkgs[i].cnst_info.ic_type);
continue;
}
ts_info("ic_type:%d", ts_dev->ic_type);
/* contrast sensor id */
addr = cfg_bin->cfg_pkgs[i].reg_info.sensor_id.addr;
if (!addr) {
ts_info("pkg:%d, sensor_id reg is NULL", i);
continue;
}
r = ts_dev->hw_ops->read(ts_dev, addr, &temp_sensor_id, 1);
if (r < 0) {
ts_err("failed get sensor of pkg:%d, reg:0x%02x", i, addr);
goto get_default_pkg;
}
ts_info("sensor id is %d", temp_sensor_id);
/*sensor.reserved1 is a mask, if it's not ZERO, use it*/
if (cfg_bin->cfg_pkgs[i].reg_info.sensor_id.reserved1 != 0)
temp_sensor_id &= cfg_bin->cfg_pkgs[i].reg_info.sensor_id.reserved1;
if (temp_sensor_id != cfg_bin->cfg_pkgs[i].cnst_info.sensor_id) {
ts_err("pkg:%d, sensor id contrast FAILED, reg:0x%02x",
i, addr);
ts_err("sensor_id from i2c:%d, sensor_id of cfg bin:%d",
temp_sensor_id,
cfg_bin->cfg_pkgs[i].cnst_info.sensor_id);
continue;
}
/*contrast fw_mask, if this reg is null, skip this step*/
addr = cfg_bin->cfg_pkgs[i].reg_info.fw_mask.addr;
if (addr && cfg_bin->cfg_pkgs[i].cnst_info.fw_mask[0]) {
r = ts_dev->hw_ops->read(ts_dev, addr, temp_fw_mask,
sizeof(temp_fw_mask));
if (r < 0) {
ts_err("failed read fw_mask pkg:%d, reg:0x%02x",
i, addr);
goto get_default_pkg;
}
if (strncmp(temp_fw_mask, cfg_bin->cfg_pkgs[i].cnst_info.fw_mask,
sizeof(temp_fw_mask))) {
ts_err("pkg:%d, fw_mask contrast FAILED, reg:0x%02x,", i, addr);
ts_err("mask from i2c:%s, mask of cfg bin:%s",
temp_fw_mask,
cfg_bin->cfg_pkgs[i].cnst_info.fw_mask);
continue;
}
}
/*contrast pid*/
addr = cfg_bin->cfg_pkgs[i].reg_info.pid.addr;
read_len = cfg_bin->cfg_pkgs[i].reg_info.pid.reserved1;
if (!addr) {
ts_err("pkg:%d, pid reg is NULL", i);
continue;
}
if (read_len <= 0 || read_len > TS_CFG_BLOCK_PID_LEN) {
ts_err("pkg:%d, hw_pid length ERROR, len:%d",
i, read_len);
continue;
}
r = ts_dev->hw_ops->read(ts_dev, addr, temp_pid, read_len);
if (r < 0) {
ts_err("failed read pid pkg:%d, pid reg:0x%02x", i, addr);
goto get_default_pkg;
}
if (strncmp(temp_pid, cfg_bin->cfg_pkgs[i].cnst_info.hw_pid, read_len)) {
ts_err("pkg:%d, pid contrast FAILED, reg:0x%02x", i, addr);
ts_err("pid from i2c:%s, pid of cfg bin:%s",
temp_pid, cfg_bin->cfg_pkgs[i].cnst_info.hw_pid);
continue;
}
ts_info("try get package info: ic type %s, cfg type %d",
cfg_bin->cfg_pkgs[i].cnst_info.ic_type,
cfg_bin->cfg_pkgs[i].cnst_info.cfg_type);
/* currently only support normal and high_sense config */
if (cfg_bin->cfg_pkgs[i].cnst_info.cfg_type == TS_NORMAL_CFG ||
cfg_bin->cfg_pkgs[i].cnst_info.cfg_type == TS_HIGH_SENSE_CFG) {
r = goodix_extract_cfg_pkg(ts_dev, &cfg_bin->cfg_pkgs[i]);
if (!r) {
ts_dev->cfg_bin_state = CFG_BIN_STATE_INITIALIZED;
ts_info("success parse cfg bin");
} else {
ts_err("failed parse cfg bin");
break;
}
}
}
get_default_pkg:
if (ts_dev->cfg_bin_state != CFG_BIN_STATE_INITIALIZED) {
ts_err("no valid normal cfg, use cfg_pkg 0 as default");
/* Foo code for recover dead IC.
* force set package 0 config type to normal config, this will
* config will use to recover IC.
*/
cfg_bin->cfg_pkgs[0].cnst_info.cfg_type = TS_NORMAL_CFG;
if (goodix_extract_cfg_pkg(ts_dev, &cfg_bin->cfg_pkgs[0])) {
ts_err("failed get valid config for IC recover");
ts_dev->cfg_bin_state = CFG_BIN_STATE_ERROR;
} else {
ts_dev->cfg_bin_state = CFG_BIN_STATE_TEMP;
ts_info("get temp config data");
}
r = -EINVAL;
}
return r;
}
static int goodix_read_cfg_bin(struct device *dev, struct goodix_cfg_bin *cfg_bin)
{
const struct firmware *firmware = NULL;
char cfg_bin_name[32] = {0x00};
int i = 0, r;
/*get cfg_bin_name*/
strlcpy(cfg_bin_name, TS_DEFAULT_CFG_BIN, sizeof(cfg_bin_name));
ts_info("cfg_bin_name:%s", cfg_bin_name);
for (i = 0; i < TS_RQST_FW_RETRY_TIMES; i++) {
r = request_firmware(&firmware, cfg_bin_name, dev);
if (r < 0) {
ts_err("failed get cfg bin[%s] error:%d, try_times:%d",
cfg_bin_name, r, i + 1);
msleep(1000);
} else {
ts_info("Cfg_bin image [%s] is ready, try_times:%d",
cfg_bin_name, i + 1);
break;
}
}
if (i >= TS_RQST_FW_RETRY_TIMES) {
ts_err("get cfg_bin FAILED");
goto exit;
}
if (firmware->size <= 0) {
ts_err("request_firmware, cfg_bin length ERROR,len:%zu",
firmware->size);
r = -EINVAL;
goto exit;
}
cfg_bin->bin_data_len = firmware->size;
/*allocate memory for cfg_bin->bin_data*/
cfg_bin->bin_data = kzalloc(cfg_bin->bin_data_len, GFP_KERNEL);
if (!cfg_bin->bin_data)
r = -ENOMEM;
memcpy(cfg_bin->bin_data, firmware->data, cfg_bin->bin_data_len);
r = 0;
exit:
if (firmware) {
release_firmware(firmware);
firmware = NULL;
}
return r;
}
static void goodix_cfg_pkg_leToCpu(struct goodix_cfg_package *pkg)
{
if (!pkg) {
ts_err("cfg package is NULL");
return;
}
/*package const_info*/
pkg->cnst_info.pkg_len = le32_to_cpu(pkg->cnst_info.pkg_len);
pkg->cnst_info.x_res_offset = le16_to_cpu(pkg->cnst_info.x_res_offset);
pkg->cnst_info.y_res_offset = le16_to_cpu(pkg->cnst_info.y_res_offset);
pkg->cnst_info.trigger_offset = le16_to_cpu(pkg->cnst_info.trigger_offset);
/*package reg_info*/
pkg->reg_info.cfg_send_flag.addr = le16_to_cpu(pkg->reg_info.cfg_send_flag.addr);
pkg->reg_info.pid.addr = le16_to_cpu(pkg->reg_info.pid.addr);
pkg->reg_info.vid.addr = le16_to_cpu(pkg->reg_info.vid.addr);
pkg->reg_info.sensor_id.addr = le16_to_cpu(pkg->reg_info.sensor_id.addr);
pkg->reg_info.fw_status.addr = le16_to_cpu(pkg->reg_info.fw_status.addr);
pkg->reg_info.cfg_addr.addr = le16_to_cpu(pkg->reg_info.cfg_addr.addr);
pkg->reg_info.esd.addr = le16_to_cpu(pkg->reg_info.esd.addr);
pkg->reg_info.command.addr = le16_to_cpu(pkg->reg_info.command.addr);
pkg->reg_info.coor.addr = le16_to_cpu(pkg->reg_info.coor.addr);
pkg->reg_info.gesture.addr = le16_to_cpu(pkg->reg_info.gesture.addr);
pkg->reg_info.fw_request.addr = le16_to_cpu(pkg->reg_info.fw_request.addr);
pkg->reg_info.proximity.addr = le16_to_cpu(pkg->reg_info.proximity.addr);
}
static int goodix_later_init_thread(void *data)
{
int ret;
struct goodix_ts_core *ts_core = data;
struct goodix_ts_device *ts_dev = ts_core->ts_dev;
ret = goodix_cfg_bin_proc(ts_core);
if (ret)
ts_err("parse cfg bin encounter error, %d", ret);
else
ts_info("success get cfg bin");
if (ts_dev->cfg_bin_state == CFG_BIN_STATE_ERROR) {
ts_err("parse cfg bin encounter fatal err");
goto release_core;
}
if (ts_dev->cfg_bin_state == CFG_BIN_STATE_TEMP) {
ts_err("failed get valid config data, retry after fwupdate");
ret = goodix_do_fw_update(UPDATE_MODE_BLOCK|UPDATE_MODE_FORCE|
UPDATE_MODE_FLASH_CFG|
UPDATE_MODE_SRC_REQUEST);
if (ret) {
ts_err("fw update failed, %d", ret);
goto release_core;
}
ts_info("fw update success retry parse cfg bin");
ret = goodix_cfg_bin_proc(ts_core);
if (ret) {
ts_err("failed parse cfg bin after fw update");
goto release_core;
}
} else {
ts_info("success parse config bin");
ret = goodix_do_fw_update(UPDATE_MODE_BLOCK|
UPDATE_MODE_FLASH_CFG|
UPDATE_MODE_SRC_REQUEST);
if (ret) {
ts_info("fw update failed, %d[ignore]", ret);
ret = 0;
}
}
ret = goodix_ts_stage2_init(ts_core);
if (!ret) {
ts_info("stage2 init success");
ts_core->init_stage = CORE_INIT_STAGE2;
return ret;
}
ts_err("stage2 init failed, %d", ret);
release_core:
ts_err("stage2 init failed");
return ret;
}
int goodix_start_later_init(struct goodix_ts_core *ts_core)
{
struct task_struct *init_thrd;
/* create and run update thread */
init_thrd = kthread_run(goodix_later_init_thread,
ts_core, "goodix_init_thread");
if (IS_ERR_OR_NULL(init_thrd)) {
ts_err("Failed to create update thread:%ld",
PTR_ERR(init_thrd));
return -EFAULT;
}
return 0;
}

View file

@ -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

View file

@ -0,0 +1,6 @@
#ifndef _GOODIX_FIRMWARE_FW_H_
#define _GOODIX_FIRMWARE_FW_H_
u8 goodix_default_fw[] = { 0x00};
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,772 @@
/*
* Goodix Touchscreen Driver
* Core layer of touchdriver architecture.
*
* Copyright (C) 2019 - 2020 Goodix, Inc.
* Authors: Wang Yafei <wangyafei@goodix.com>
*
* 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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/firmware.h>
#include <linux/slab.h>
#include <asm/unaligned.h>
#include <linux/vmalloc.h>
#include <linux/kthread.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/of_irq.h>
#ifdef CONFIG_OF
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#endif
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif
#ifdef CONFIG_FB
#include <linux/notifier.h>
#include <linux/fb.h>
#endif
#define GOODIX_FLASH_CONFIG_WITH_ISP 1
/* macros definition */
#define GOODIX_CORE_DRIVER_NAME "goodix_ts"
#define GOODIX_PEN_DRIVER_NAME "goodix_ts,pen"
#define GOODIX_DRIVER_VERSION "v1.5.1.0"
#define GOODIX_BUS_RETRY_TIMES 3
#define GOODIX_MAX_TOUCH 10
#define GOODIX_CFG_MAX_SIZE 4096
#define GOODIX_ESD_TICK_WRITE_DATA 0xAA
#define GOODIX_PID_MAX_LEN 8
#define GOODIX_VID_MAX_LEN 8
#define GOODIX_DEFAULT_CFG_NAME "goodix_config.cfg"
#define IC_TYPE_NONE 0
#define IC_TYPE_NORMANDY 1
#define IC_TYPE_NANJING 2
#define IC_TYPE_YELLOWSTONE 3
#define GOODIX_TOUCH_EVENT 0x80
#define GOODIX_REQUEST_EVENT 0x40
#define GOODIX_GESTURE_EVENT 0x20
#define GOODIX_HOTKNOT_EVENT 0x10
#define GOODIX_LOCKDOWN_SIZE 8
#define GOODIX_PEN_MAX_PRESSURE 4096
#define GOODIX_MAX_TP_KEY 4
#define GOODIX_MAX_PEN_KEY 2
#define TS_RAWDATA_BUFF_MAX 3000
#define TS_RAWDATA_RESULT_MAX 100
struct ts_rawdata_info{
int used_size; //fill in rawdata size
s16 buff[TS_RAWDATA_BUFF_MAX];
char result[TS_RAWDATA_RESULT_MAX];
};
/*
* struct goodix_module - external modules container
* @head: external modules list
* @initilized: whether this struct is initilized
* @mutex: mutex lock
* @wq: workqueue to do register work
* @core_data: core_data pointer
*/
struct goodix_module {
struct list_head head;
bool initilized;
struct mutex mutex;
struct workqueue_struct *wq;
struct completion core_comp;
struct goodix_ts_core *core_data;
};
/*
* struct goodix_ts_board_data - board data
* @avdd_name: name of analoy regulator
* @iovdd_name: name of analoy regulator
* @reset_gpio: reset gpio number
* @irq_gpio: interrupt gpio number
* @irq_flag: irq trigger type
* @swap_axis: whether swaw x y axis
* @panel_max_x/y/w/p: resolution and size
* @panel_max_key: max supported keys
* @pannel_key_map: key map
* @fw_name: name of the firmware image
*/
struct goodix_ts_board_data {
char avdd_name[24];
char iovdd_name[24];
unsigned int reset_gpio;
unsigned int irq_gpio;
int irq;
unsigned int irq_flags;
unsigned int swap_axis;
unsigned int panel_max_x;
unsigned int panel_max_y;
unsigned int panel_max_w; /*major and minor*/
unsigned int panel_max_p; /*pressure*/
unsigned int panel_max_key;
unsigned int panel_key_map[GOODIX_MAX_TP_KEY];
unsigned int x2x;
unsigned int y2y;
bool pen_enable;
unsigned int tp_key_num;
/*add end*/
const char *fw_name;
const char *cfg_bin_name;
bool esd_default_on;
};
enum goodix_fw_update_mode {
UPDATE_MODE_DEFAULT = 0,
UPDATE_MODE_FORCE = (1<<0), /* force update mode */
UPDATE_MODE_BLOCK = (1<<1), /* update in block mode */
UPDATE_MODE_FLASH_CFG = (1<<2), /* reflash config */
UPDATE_MODE_SRC_SYSFS = (1<<4), /* firmware file from sysfs */
UPDATE_MODE_SRC_HEAD = (1<<5), /* firmware file from head file */
UPDATE_MODE_SRC_REQUEST = (1<<6), /* request firmware */
UPDATE_MODE_SRC_ARGS = (1<<7), /* firmware data from function args */
};
enum goodix_cfg_init_state {
TS_CFG_UNINITALIZED,
TS_CFG_STABLE,
TS_CFG_TEMP,
};
enum goodix_core_init_stage {
CORE_UNINIT,
CORE_INIT_FAIL,
CORE_INIT_STAGE1,
CORE_INIT_STAGE2
};
/*
* struct goodix_ts_config - chip config data
* @initialized: cfg init state, 0 uninit,
* 1 init with stable config, 2 init with temp config.
* @name: name of this config
* @lock: mutex
* @reg_base: register base of config data
* @length: bytes of the config
* @delay: delay time after sending config
* @data: config data buffer
*/
struct goodix_ts_config {
int initialized;
char name[24];
struct mutex lock;
unsigned int reg_base;
unsigned int length;
unsigned int delay; /*ms*/
unsigned char data[GOODIX_CFG_MAX_SIZE];
};
/*
* struct goodix_ts_cmd - command package
* @initialized: whether initialized
* @cmd_reg: command register
* @length: command length in bytes
* @cmds: command data
*/
#pragma pack(4)
struct goodix_ts_cmd {
u32 initialized;
u32 cmd_reg;
u32 length;
u8 cmds[8];
};
#pragma pack()
/* interrupt event type */
enum ts_event_type {
EVENT_INVALID = 0,
EVENT_TOUCH = (1 << 0), /* finger touch event */
EVENT_PEN = (1 << 1), /* pen event */
EVENT_REQUEST = (1 << 2),
};
/* notifier event */
enum ts_notify_event {
NOTIFY_FWUPDATE_START,
NOTIFY_FWUPDATE_FAILED,
NOTIFY_FWUPDATE_SUCCESS,
NOTIFY_SUSPEND,
NOTIFY_RESUME,
NOTIFY_ESD_OFF,
NOTIFY_ESD_ON,
NOTIFY_CFG_BIN_FAILED,
NOTIFY_CFG_BIN_SUCCESS,
};
enum touch_point_status {
TS_NONE,
TS_RELEASE,
TS_TOUCH,
};
/* coordinate package */
struct goodix_ts_coords {
int status; /* NONE, RELEASE, TOUCH */
unsigned int x, y, w, p;
};
struct goodix_pen_coords {
int status; /* NONE, RELEASE, TOUCH */
int tool_type; /* BTN_TOOL_RUBBER BTN_TOOL_PEN */
unsigned int x, y, p;
signed char tilt_x;
signed char tilt_y;
};
struct goodix_ts_key {
int status;
int code;
};
/* touch event data */
struct goodix_touch_data {
int touch_num;
struct goodix_ts_coords coords[GOODIX_MAX_TOUCH];
struct goodix_ts_key keys[GOODIX_MAX_TP_KEY];
};
struct goodix_pen_data {
struct goodix_pen_coords coords;
struct goodix_ts_key keys[GOODIX_MAX_PEN_KEY];
};
/*
* struct goodix_ts_event - touch event struct
* @event_type: touch event type, touch data or
* request event
* @event_data: event data
*/
struct goodix_ts_event {
enum ts_event_type event_type;
struct goodix_touch_data touch_data;
struct goodix_pen_data pen_data;
};
/*
* struct goodix_ts_version - firmware version
* @valid: whether these infomation is valid
* @pid: product id string
* @vid: firmware version code
* @cid: customer id code
* @sensor_id: sendor id
*/
struct goodix_ts_version {
bool valid;
char pid[GOODIX_PID_MAX_LEN];
char vid[GOODIX_VID_MAX_LEN];
u8 cid;
u8 sensor_id;
};
struct goodix_ts_regs {
u16 cfg_send_flag;
u16 version_base;
u8 version_len;
u16 pid;
u8 pid_len;
u16 vid;
u8 vid_len;
u16 sensor_id;
u8 sensor_id_mask;
u16 fw_mask;
u16 fw_status;
u16 cfg_addr;
u16 esd;
u16 command;
u16 gesture;
u16 coor;
u16 fw_request;
u16 proximity;
};
enum goodix_cfg_bin_state {
CFG_BIN_STATE_UNINIT, /* config bin uninit */
CFG_BIN_STATE_ERROR, /* config bin encounter fatal error */
CFG_BIN_STATE_INITIALIZED, /* config bin init success */
CFG_BIN_STATE_TEMP, /* config bin need reparse */
};
/*
* struct goodix_ts_device - ts device data
* @name: device name
* @version: reserved
* @bus_type: i2c or spi
* @ic_type: normandy or yellowstone
* @cfg_bin_state: see enum goodix_cfg_bin_state
* @fw_uptodate: set to 1 after do fw update
* @board_data: board data obtained from dts
* @normal_cfg: normal config data
* @highsense_cfg: high sense config data
* @hw_ops: hardware operations
* @chip_version: firmware version infomation
* @sleep_cmd: sleep commang
* @gesture_cmd: gesture command
* @dev: device pointer,may be a i2c or spi device
* @of_node: device node
*/
struct goodix_ts_device {
char *name;
int version;
int bus_type;
int ic_type;
int cfg_bin_state;
struct goodix_ts_regs reg;
struct goodix_ts_board_data board_data;
struct goodix_ts_config normal_cfg;
struct goodix_ts_config highsense_cfg;
const struct goodix_ts_hw_ops *hw_ops;
struct goodix_ts_version chip_version;
struct device *dev;
};
/*
* struct goodix_ts_hw_ops - hardware opeartions
* @init: hardware initialization
* @reset: hardware reset
* @read: read data from touch device
* @write: write data to touch device
* @send_cmd: send command to touch device
* @send_config: send configuration data
* @read_version: read firmware version
* @event_handler: touch event handler
* @suspend: put touch device into low power mode
* @resume: put touch device into working mode
*/
struct goodix_ts_hw_ops {
int (*init)(struct goodix_ts_device *dev);
int (*dev_confirm)(struct goodix_ts_device *ts_dev);
int (*reset)(struct goodix_ts_device *dev);
int (*read)(struct goodix_ts_device *dev, unsigned int addr,
unsigned char *data, unsigned int len);
int (*write)(struct goodix_ts_device *dev, unsigned int addr,
unsigned char *data, unsigned int len);
int (*read_trans)(struct goodix_ts_device *dev, unsigned int addr,
unsigned char *data, unsigned int len);
int (*write_trans)(struct goodix_ts_device *dev, unsigned int addr,
unsigned char *data, unsigned int len);
int (*send_cmd)(struct goodix_ts_device *dev, struct goodix_ts_cmd *cmd);
int (*send_config)(struct goodix_ts_device *dev,
struct goodix_ts_config *config);
int (*read_config)(struct goodix_ts_device *dev, u8 *config_data, int size);
int (*read_version)(struct goodix_ts_device *dev,
struct goodix_ts_version *version);
int (*event_handler)(struct goodix_ts_device *dev,
struct goodix_ts_event *ts_event);
int (*check_hw)(struct goodix_ts_device *dev);
int (*suspend)(struct goodix_ts_device *dev);
int (*resume)(struct goodix_ts_device *dev);
int (*get_capacitance_data)(struct goodix_ts_device *dev, struct ts_rawdata_info *info);
};
/*
* struct goodix_ts_esd - esd protector structure
* @esd_work: esd delayed work
* @esd_on: 1 - turn on esd protection, 0 - turn
* off esd protection
*/
struct goodix_ts_esd {
struct delayed_work esd_work;
struct notifier_block esd_notifier;
struct goodix_ts_core *ts_core;
atomic_t esd_on;
};
/*
* struct godix_ts_core - core layer data struct
* @initialized: indicate core state, 1 ok, 0 bad
* @pdev: core layer platform device
* @ts_dev: hardware layer touch device
* @input_dev: input device
* @avdd: analog regulator
* @iovdd: analog regulator
* @pinctrl: pinctrl handler
* @pin_sta_active: active/normal pin state
* @pin_sta_suspend: suspend/sleep pin state
* @ts_event: touch event data struct
* @power_on: power on/off flag
* @irq: irq number
* @irq_enabled: irq enabled/disabled flag
* @suspended: suspend/resume flag
* @ts_notifier: generic notifier
* @ts_esd: esd protector structure
* @fb_notifier: framebuffer notifier
* @early_suspend: early suspend
*/
struct goodix_ts_core {
int initialized;
int init_stage;
int gesture_enable;
u8 lockdown_info[GOODIX_LOCKDOWN_SIZE];
struct platform_device *pdev;
struct goodix_ts_device *ts_dev;
struct input_dev *input_dev;
struct input_dev *pen_dev;
struct regulator *avdd;
struct regulator *iovdd;
#ifdef CONFIG_PINCTRL
struct pinctrl *pinctrl;
struct pinctrl_state *pin_sta_active;
struct pinctrl_state *pin_sta_suspend;
#endif
struct goodix_ts_event ts_event;
int power_on;
int irq;
size_t irq_trig_cnt;
atomic_t irq_enabled;
atomic_t suspended;
u32 drv_num;
u32 sen_num;
struct notifier_block ts_notifier;
struct goodix_ts_esd ts_esd;
#ifdef CONFIG_FB
struct notifier_block fb_notifier;
#elif defined(CONFIG_HAS_EARLYSUSPEND)
struct early_suspend early_suspend;
#endif
};
/* external module structures */
enum goodix_ext_priority {
EXTMOD_PRIO_RESERVED = 0,
EXTMOD_PRIO_FWUPDATE,
EXTMOD_PRIO_GESTURE,
EXTMOD_PRIO_HOTKNOT,
EXTMOD_PRIO_DBGTOOL,
EXTMOD_PRIO_DEFAULT,
};
struct goodix_ext_module;
/* external module's operations callback */
struct goodix_ext_module_funcs {
int (*init)(struct goodix_ts_core *core_data,
struct goodix_ext_module *module);
int (*exit)(struct goodix_ts_core *core_data,
struct goodix_ext_module *module);
int (*before_reset)(struct goodix_ts_core *core_data,
struct goodix_ext_module *module);
int (*after_reset)(struct goodix_ts_core *core_data,
struct goodix_ext_module *module);
int (*before_suspend)(struct goodix_ts_core *core_data,
struct goodix_ext_module *module);
int (*after_suspend)(struct goodix_ts_core *core_data,
struct goodix_ext_module *module);
int (*before_resume)(struct goodix_ts_core *core_data,
struct goodix_ext_module *module);
int (*after_resume)(struct goodix_ts_core *core_data,
struct goodix_ext_module *module);
int (*irq_event)(struct goodix_ts_core *core_data,
struct goodix_ext_module *module);
};
/*
* struct goodix_ext_module - external module struct
* @list: list used to link into modules manager
* @name: name of external module
* @priority: module priority vlaue, zero is invalid
* @funcs: operations callback
* @priv_data: private data region
* @kobj: kobject
* @work: used to queue one work to do registration
*/
struct goodix_ext_module {
struct list_head list;
char *name;
enum goodix_ext_priority priority;
const struct goodix_ext_module_funcs *funcs;
void *priv_data;
struct kobject kobj;
struct work_struct work;
};
/*
* struct goodix_ext_attribute - exteranl attribute struct
* @attr: attribute
* @show: show interface of external attribute
* @store: store interface of external attribute
*/
struct goodix_ext_attribute {
struct attribute attr;
ssize_t (*show)(struct goodix_ext_module *, char *);
ssize_t (*store)(struct goodix_ext_module *, const char *, size_t);
};
/* external attrs helper macro */
#define __EXTMOD_ATTR(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
}
/* external attrs helper macro, used to define external attrs */
#define DEFINE_EXTMOD_ATTR(_name, _mode, _show, _store) \
static struct goodix_ext_attribute ext_attr_##_name = \
__EXTMOD_ATTR(_name, _mode, _show, _store);
/*
* get board data pointer
*/
static inline struct goodix_ts_board_data *board_data(
struct goodix_ts_core *core)
{
if (!core || !core->ts_dev)
return NULL;
return &(core->ts_dev->board_data);
}
/*
* get touch device pointer
*/
static inline struct goodix_ts_device *ts_device(
struct goodix_ts_core *core)
{
if (!core)
return NULL;
return core->ts_dev;
}
/*
* get touch hardware operations pointer
*/
static inline const struct goodix_ts_hw_ops *ts_hw_ops(
struct goodix_ts_core *core)
{
if (!core || !core->ts_dev)
return NULL;
return core->ts_dev->hw_ops;
}
/*
* checksum helper functions
* checksum can be u8/le16/be16/le32/be32 format
* NOTE: the caller shoule be responsible for the
* legality of @data and @size parameters, so be
* careful when call these functions.
*/
static inline u8 checksum_u8(u8 *data, u32 size)
{
u8 checksum = 0;
u32 i;
for (i = 0; i < size; i++)
checksum += data[i];
return checksum;
}
/* cal u8 data checksum for yellowston */
static inline u16 checksum_u8_ys(u8 *data, u32 size)
{
u16 checksum = 0;
u32 i;
for (i = 0; i < size - 2; i++)
checksum += data[i];
return checksum - (data[size - 2] << 8 | data[size - 1]);
}
static inline u16 checksum_le16(u8 *data, u32 size)
{
u16 checksum = 0;
u32 i;
for (i = 0; i < size; i += 2)
checksum += le16_to_cpup((__le16 *)(data + i));
return checksum;
}
static inline u16 checksum_be16(u8 *data, u32 size)
{
u16 checksum = 0;
u32 i;
for (i = 0; i < size; i += 2)
checksum += be16_to_cpup((__be16 *)(data + i));
return checksum;
}
static inline u32 checksum_le32(u8 *data, u32 size)
{
u32 checksum = 0;
u32 i;
for (i = 0; i < size; i += 4)
checksum += le32_to_cpup((__le32 *)(data + i));
return checksum;
}
static inline u32 checksum_be32(u8 *data, u32 size)
{
u32 checksum = 0;
u32 i;
for (i = 0; i < size; i += 4)
checksum += be32_to_cpup((__be32 *)(data + i));
return checksum;
}
/*
* define event action
* EVT_xxx macros are used in opeartions callback
* defined in @goodix_ext_module_funcs to control
* the behaviors of event such as suspend/resume/irq_event.
* generally there are two types of behaviors:
* 1. you want the flow of this event be canceled,
* in this condition, you should return EVT_CANCEL_XXX in
* the operations callback.
* e.g. the firmware update module is updating
* the firmware, you want to cancel suspend flow,
* so you need to return EVT_CANCEL_SUSPEND in
* suspend callback function.
* 2. you want the flow of this event continue, in
* this condition, you should return EVT_HANDLED in
* the callback function.
* */
#define EVT_HANDLED 0
#define EVT_CONTINUE 0
#define EVT_CANCEL 1
#define EVT_CANCEL_IRQEVT 1
#define EVT_CANCEL_SUSPEND 1
#define EVT_CANCEL_RESUME 1
#define EVT_CANCEL_RESET 1
/*
* errno define
* Note:
* 1. bus read/write functions defined in hardware
* layer code(e.g. goodix_xxx_i2c.c) *must* return
* -EBUS if failed to transfer data on bus.
*/
#define EBUS 1000
#define ETIMEOUT 1001
#define ECHKSUM 1002
#define EMEMCMP 1003
//#define CONFIG_GOODIX_DEBUG
/* log macro */
#define ts_info(fmt, arg...) pr_info("[GTP-INF][%s:%d] "fmt"\n", __func__, __LINE__, ##arg)
#define ts_err(fmt, arg...) pr_err("[GTP-ERR][%s:%d] "fmt"\n", __func__, __LINE__, ##arg)
#define boot_log(fmt, arg...) g_info(fmt, ##arg)
#ifdef CONFIG_GOODIX_DEBUG
#define ts_debug(fmt, arg...) pr_info("[GTP-DBG][%s:%d] "fmt"\n", __func__, __LINE__, ##arg)
#else
#define ts_debug(fmt, arg...) do {} while (0)
#endif
/**
* goodix_register_ext_module - interface for external module
* to register into touch core modules structure
*
* @module: pointer to external module to be register
* return: 0 ok, <0 failed
*/
int goodix_register_ext_module(struct goodix_ext_module *module);
/* register module no wait */
int goodix_register_ext_module_no_wait(struct goodix_ext_module *module);
/**
* goodix_unregister_ext_module - interface for external module
* to unregister external modules
*
* @module: pointer to external module
* return: 0 ok, <0 failed
*/
int goodix_unregister_ext_module(struct goodix_ext_module *module);
/* remove all registered ext module
* return 0 on success, otherwise return < 0
*/
int goodix_unregister_all_module(void);
/**
* goodix_ts_irq_enable - Enable/Disable a irq
* @core_data: pointer to touch core data
* enable: enable or disable irq
* return: 0 ok, <0 failed
*/
int goodix_ts_irq_enable(struct goodix_ts_core *core_data, bool enable);
struct kobj_type *goodix_get_default_ktype(void);
/**
* fb_notifier_call_chain - notify clients of fb_events
* see enum ts_notify_event in goodix_ts_core.h
*/
int goodix_ts_blocking_notify(enum ts_notify_event evt, void *v);
/**
* * goodix_ts_power_on - Turn on power to the touch device
* * @core_data: pointer to touch core data
* * return: 0 ok, <0 failed
* */
int goodix_ts_power_on(struct goodix_ts_core *core_data);
/**
* goodix_ts_power_off - Turn off power to the touch device
* @core_data: pointer to touch core data
* return: 0 ok, <0 failed
*/
int goodix_ts_power_off(struct goodix_ts_core *core_data);
int goodix_ts_irq_setup(struct goodix_ts_core *core_data);
int goodix_ts_esd_init(struct goodix_ts_core *core);
int goodix_ts_register_notifier(struct notifier_block *nb);
int goodix_ts_fb_notifier_callback(struct notifier_block *self,
unsigned long event, void *data);
int goodix_bus_init(void);
void goodix_bus_exit(void);
int goodix_do_fw_update(int mode);
int gtx8_get_rawdata(void *tsdev, struct ts_rawdata_info *info);
void goodix_rotate_abcd2cbad(int tx, int rx, s16 *data);
int goodix_get_cfg_value(struct goodix_ts_core *core_data,
u8 *config, u8 *buf, u8 len, u8 sub_bag_num, u8 offset);
#endif

View file

@ -0,0 +1,443 @@
/*
* Goodix Gesture Module
*
* Copyright (C) 2019 - 2020 Goodix, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be a reference
* to you, when you are integrating the GOODiX's CTP IC into your system,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include "goodix_ts_core.h"
#define GSX_GESTURE_CMD 0x08
#define QUERYBIT(longlong, bit) (!!(longlong[bit/8] & (1 << bit%8)))
#define GSX_MAX_KEY_DATA_LEN 64
#define GSX_KEY_DATA_LEN 37
#define GSX_KEY_DATA_LEN_YS 8
#define GSX_GESTURE_TYPE_LEN 32
/*
* struct gesture_module - gesture module data
* @registered: module register state
* @sysfs_node_created: sysfs node state
* @gesture_type: store valid gesture type,each bit stand for a gesture
* @gesture_data: gesture data
* @gesture_ts_cmd: gesture command data
*/
struct gesture_module {
atomic_t registered;
unsigned int kobj_initialized;
rwlock_t rwlock;
unsigned char gesture_type[GSX_GESTURE_TYPE_LEN];
unsigned char gesture_data[GSX_MAX_KEY_DATA_LEN];
struct goodix_ext_module module;
struct goodix_ts_cmd cmd;
};
static int gsx_enter_gesture_mode(struct goodix_ts_device *ts_dev);
static struct gesture_module *gsx_gesture; /*allocated in gesture init module*/
/**
* gsx_gesture_type_show - show valid gesture type
*
* @module: pointer to goodix_ext_module struct
* @buf: pointer to output buffer
* Returns >=0 - succeed,< 0 - failed
*/
static ssize_t gsx_gesture_type_show(struct goodix_ext_module *module,
char *buf)
{
int count = 0, i, ret = 0;
unsigned char *type;
if (atomic_read(&gsx_gesture->registered) != 1) {
ts_info("Gesture module not register!");
return -EPERM;
}
type = kzalloc(256, GFP_KERNEL);
if (!type)
return -ENOMEM;
read_lock(&gsx_gesture->rwlock);
for (i = 0; i < 256; i++) {
if (QUERYBIT(gsx_gesture->gesture_type, i)) {
type[count] = i;
count++;
}
}
type[count] = '\0';
if (count > 0)
ret = scnprintf(buf, PAGE_SIZE, "%s", type);
read_unlock(&gsx_gesture->rwlock);
kfree(type);
return ret;
}
/**
* gsx_gesture_type_store - set vailed gesture
*
* @module: pointer to goodix_ext_module struct
* @buf: pointer to valid gesture type
* @count: length of buf
* Returns >0 - valid gestures, < 0 - failed
*/
static ssize_t gsx_gesture_type_store(struct goodix_ext_module *module,
const char *buf, size_t count)
{
int i;
if (count <= 0 || count > 256 || buf == NULL) {
ts_err("Parameter error");
return -EINVAL;
}
write_lock(&gsx_gesture->rwlock);
memset(gsx_gesture->gesture_type, 0, GSX_GESTURE_TYPE_LEN);
for (i = 0; i < count; i++)
gsx_gesture->gesture_type[buf[i]/8] |= (0x1 << buf[i]%8);
write_unlock(&gsx_gesture->rwlock);
return count;
}
static ssize_t gsx_gesture_enable_show(struct goodix_ext_module *module,
char *buf)
{
struct goodix_ts_core *cd = module->priv_data;
return scnprintf(buf, PAGE_SIZE, "%d\n", cd->gesture_enable);
}
static ssize_t gsx_gesture_enable_store(struct goodix_ext_module *module,
const char *buf, size_t count)
{
struct goodix_ts_core *cd = module->priv_data;
unsigned int tmp;
if (sscanf(buf, "%u", &tmp) != 1) {
ts_info("Parameter illegal");
return -EINVAL;
}
ts_debug("Tmp value =%d", tmp);
if (tmp == 1) {
cd->gesture_enable = 1;
} else if (tmp == 0) {
cd->gesture_enable = 0;
} else {
ts_err("Parameter error!");
return -EINVAL;
}
return count;
}
static ssize_t gsx_gesture_data_show(struct goodix_ext_module *module,
char *buf)
{
struct goodix_ts_core *cd = module->priv_data;
ssize_t count;
if (atomic_read(&gsx_gesture->registered) != 1) {
ts_info("Gesture module not register!");
return -EPERM;
}
if (!buf) {
ts_info("Parameter error!");
return -EPERM;
}
read_lock(&gsx_gesture->rwlock);
if (cd->ts_dev->ic_type == IC_TYPE_YELLOWSTONE)
count = scnprintf(buf, PAGE_SIZE, "gesture type code:0x%x\n",
gsx_gesture->gesture_data[4]);
else
count = scnprintf(buf, PAGE_SIZE, "gesture type code:0x%x\n",
gsx_gesture->gesture_data[2]);
read_unlock(&gsx_gesture->rwlock);
return count;
}
const struct goodix_ext_attribute gesture_attrs[] = {
__EXTMOD_ATTR(type, 0666, gsx_gesture_type_show,
gsx_gesture_type_store),
__EXTMOD_ATTR(enable, 0666, gsx_gesture_enable_show,
gsx_gesture_enable_store),
__EXTMOD_ATTR(data, 0444, gsx_gesture_data_show, NULL)
};
static int gsx_enter_gesture_mode(struct goodix_ts_device *ts_dev)
{
if (!gsx_gesture->cmd.initialized) {
if (!ts_dev->reg.command) {
ts_err("command reg can not be null");
return -EINVAL;
}
if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) {
gsx_gesture->cmd.cmd_reg = ts_dev->reg.command;
gsx_gesture->cmd.length = 5;
gsx_gesture->cmd.cmds[0] = GSX_GESTURE_CMD;
gsx_gesture->cmd.cmds[1] = 0x0;
gsx_gesture->cmd.cmds[2] = 0x0;
gsx_gesture->cmd.cmds[3] = 0x0;
gsx_gesture->cmd.cmds[4] = GSX_GESTURE_CMD;
gsx_gesture->cmd.initialized = 1;
} else {
gsx_gesture->cmd.cmd_reg = ts_dev->reg.command;
gsx_gesture->cmd.length = 3;
gsx_gesture->cmd.cmds[0] = GSX_GESTURE_CMD;
gsx_gesture->cmd.cmds[1] = 0x01;
gsx_gesture->cmd.cmds[2] = 0 - gsx_gesture->cmd.cmds[0] - gsx_gesture->cmd.cmds[1];
gsx_gesture->cmd.initialized = 1;
}
}
return ts_dev->hw_ops->send_cmd(ts_dev, &gsx_gesture->cmd);
}
static int gsx_gesture_init(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
int i, ret = -EINVAL;
struct goodix_ts_device *ts_dev = core_data->ts_dev;
if (!core_data || !ts_dev->hw_ops->write || !ts_dev->hw_ops->read) {
ts_err("Register gesture module failed, ts_core unsupported");
goto exit_gesture_init;
}
memset(gsx_gesture->gesture_type, 0, GSX_GESTURE_TYPE_LEN);
memset(gsx_gesture->gesture_data, 0xff,
sizeof(gsx_gesture->gesture_data));
module->priv_data = core_data;
ts_debug("Set gesture type manually");
/* set all bit to 1 to enable all gesture wakeup */
memset(gsx_gesture->gesture_type, 0xff, GSX_GESTURE_TYPE_LEN);
if (gsx_gesture->kobj_initialized) {
ret = 0;
goto exit_gesture_init;
}
ret = kobject_init_and_add(&module->kobj, goodix_get_default_ktype(),
&core_data->pdev->dev.kobj, "gesture");
if (ret) {
ts_err("Create gesture sysfs node error!");
goto exit_gesture_init;
}
ret = 0;
for (i = 0; i < ARRAY_SIZE(gesture_attrs) && !ret; i++)
ret = sysfs_create_file(&module->kobj, &gesture_attrs[i].attr);
if (ret) {
ts_err("failed create gst sysfs files");
while (--i >= 0)
sysfs_remove_file(&module->kobj, &gesture_attrs[i].attr);
kobject_put(&module->kobj);
goto exit_gesture_init;
}
gsx_gesture->kobj_initialized = 1;
core_data->gesture_enable = 1;
exit_gesture_init:
return ret;
}
static int gsx_gesture_exit(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
atomic_set(&gsx_gesture->registered, 0);
return 0;
}
/**
* gsx_gesture_ist - Gesture Irq handle
* This functions is excuted when interrupt happended and
* ic in doze mode.
*
* @core_data: pointer to touch core data
* @module: pointer to goodix_ext_module struct
* return: 0 goon execute, EVT_CANCEL_IRQEVT stop execute
*/
static int gsx_gesture_ist(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
int ret;
int key_data_len = 0;
u8 clear_reg = 0, checksum = 0, gsx_type = 0;
u8 temp_data[GSX_MAX_KEY_DATA_LEN];
struct goodix_ts_device *ts_dev = core_data->ts_dev;
if (!core_data->gesture_enable || atomic_read(&core_data->suspended) == 0)
return EVT_CONTINUE;
if (!ts_dev->reg.gesture) {
ts_err("gesture reg can't be null");
return EVT_CONTINUE;
}
/* get ic gesture state*/
if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE)
key_data_len = GSX_KEY_DATA_LEN_YS;
else
key_data_len = GSX_KEY_DATA_LEN;
ret = ts_dev->hw_ops->read_trans(ts_dev, ts_dev->reg.gesture,
temp_data, key_data_len);
if (ret < 0 || ((temp_data[0] & GOODIX_GESTURE_EVENT) == 0)) {
ts_debug("invalid gesture event, ret=%d, temp_data[0]=0x%x",
ret, temp_data[0]);
goto re_send_ges_cmd;
}
if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE)
checksum = checksum_u8_ys(temp_data, key_data_len);
else
checksum = checksum_u8(temp_data, key_data_len);
if (checksum) {
ts_err("Gesture data checksum error:0x%x", checksum);
ts_info("Gesture data %*ph",
(int)sizeof(temp_data), temp_data);
goto re_send_ges_cmd;
}
ts_debug("Gesture data:");
ts_debug("data[0-4]0x%x, 0x%x, 0x%x, 0x%x", temp_data[0], temp_data[1],
temp_data[2], temp_data[3]);
write_lock(&gsx_gesture->rwlock);
memcpy(gsx_gesture->gesture_data, temp_data, key_data_len);
write_unlock(&gsx_gesture->rwlock);
if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE)
gsx_type = temp_data[4];
else
gsx_type = temp_data[2];
if (gsx_type == 0xCC) { /* double tap */
/* do resume routine */
ts_info("Gesture match success, resume IC");
input_report_key(core_data->input_dev, KEY_POWER, 1);
input_sync(core_data->input_dev);
input_report_key(core_data->input_dev, KEY_POWER, 0);
input_sync(core_data->input_dev);
goto gesture_ist_exit;
} else {
ts_info("Unsupported gesture:%x", temp_data[2]);
}
re_send_ges_cmd:
if (gsx_enter_gesture_mode(core_data->ts_dev))
ts_info("warning: failed re_send gesture cmd\n");
gesture_ist_exit:
ts_dev->hw_ops->write_trans(ts_dev, ts_dev->reg.gesture,
&clear_reg, 1);
return EVT_CANCEL_IRQEVT;
}
/**
* gsx_gesture_before_suspend - execute gesture suspend routine
* This functions is excuted to set ic into doze mode
*
* @core_data: pointer to touch core data
* @module: pointer to goodix_ext_module struct
* return: 0 goon execute, EVT_IRQCANCLED stop execute
*/
static int gsx_gesture_before_suspend(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
int ret;
const struct goodix_ts_hw_ops *hw_ops = core_data->ts_dev->hw_ops;
if (!core_data->gesture_enable || !hw_ops)
return EVT_CONTINUE;
atomic_set(&core_data->suspended, 1);
ret = gsx_enter_gesture_mode(core_data->ts_dev);
if (ret != 0)
ts_err("failed enter gesture mode");
else
ts_info("Set IC in gesture mode");
enable_irq_wake(core_data->irq);
return EVT_CANCEL_SUSPEND;
}
static struct goodix_ext_module_funcs gsx_gesture_funcs = {
.irq_event = gsx_gesture_ist,
.init = gsx_gesture_init,
.exit = gsx_gesture_exit,
.before_suspend = gsx_gesture_before_suspend
};
static int __init goodix_gsx_gesture_init(void)
{
/* initialize core_data->ts_dev->gesture_cmd */
int result;
ts_info("gesture module init");
gsx_gesture = kzalloc(sizeof(struct gesture_module), GFP_KERNEL);
if (!gsx_gesture)
result = -ENOMEM;
gsx_gesture->module.funcs = &gsx_gesture_funcs;
gsx_gesture->module.priority = EXTMOD_PRIO_GESTURE;
gsx_gesture->module.name = "Goodix_gsx_gesture";
gsx_gesture->module.priv_data = gsx_gesture;
gsx_gesture->kobj_initialized = 0;
atomic_set(&gsx_gesture->registered, 0);
rwlock_init(&gsx_gesture->rwlock);
result = goodix_register_ext_module(&(gsx_gesture->module));
if (result == 0)
atomic_set(&gsx_gesture->registered, 1);
return result;
}
static void __exit goodix_gsx_gesture_exit(void)
{
int i, ret;
ts_info("gesture module exit");
if (atomic_read(&gsx_gesture->registered)) {
ret = goodix_unregister_ext_module(&gsx_gesture->module);
atomic_set(&gsx_gesture->registered, 0);
}
if (gsx_gesture->kobj_initialized) {
for (i = 0; i < ARRAY_SIZE(gesture_attrs); i++)
sysfs_remove_file(&gsx_gesture->module.kobj,
&gesture_attrs[i].attr);
kobject_put(&gsx_gesture->module.kobj);
}
kfree(gsx_gesture);
}
late_initcall(goodix_gsx_gesture_init);
module_exit(goodix_gsx_gesture_exit);
MODULE_DESCRIPTION("Goodix gsx Touchscreen Gesture Module");
MODULE_AUTHOR("Goodix, Inc.");
MODULE_LICENSE("GPL v2");

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,652 @@
/*
* Goodix Tools Dirver Module
*
* Copyright (C) 2019 - 2020 Goodix, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be a reference
* to you, when you are integrating the GOODiX's CTP IC into your system,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/atomic.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/ioctl.h>
#include <linux/wait.h>
#include "goodix_ts_core.h"
#define GOODIX_TOOLS_NAME "gtp_tools"
#define GOODIX_TS_IOC_MAGIC 'G'
#define NEGLECT_SIZE_MASK (~(_IOC_SIZEMASK << _IOC_SIZESHIFT))
#define GTP_IRQ_ENABLE _IO(GOODIX_TS_IOC_MAGIC, 0)
#define GTP_DEV_RESET _IO(GOODIX_TS_IOC_MAGIC, 1)
#define GTP_SEND_COMMAND (_IOW(GOODIX_TS_IOC_MAGIC, 2, u8) & NEGLECT_SIZE_MASK)
#define GTP_SEND_CONFIG (_IOW(GOODIX_TS_IOC_MAGIC, 3, u8) & NEGLECT_SIZE_MASK)
#define GTP_ASYNC_READ (_IOR(GOODIX_TS_IOC_MAGIC, 4, u8) & NEGLECT_SIZE_MASK)
#define GTP_SYNC_READ (_IOR(GOODIX_TS_IOC_MAGIC, 5, u8) & NEGLECT_SIZE_MASK)
#define GTP_ASYNC_WRITE (_IOW(GOODIX_TS_IOC_MAGIC, 6, u8) & NEGLECT_SIZE_MASK)
#define GTP_READ_CONFIG (_IOW(GOODIX_TS_IOC_MAGIC, 7, u8) & NEGLECT_SIZE_MASK)
#define GTP_ESD_ENABLE _IO(GOODIX_TS_IOC_MAGIC, 8)
#define GTP_DRV_VERSION (_IOR(GOODIX_TS_IOC_MAGIC, 9, u8) & NEGLECT_SIZE_MASK)
#define GOODIX_TS_IOC_MAXNR 10
#define MAX_BUF_LENGTH (16*1024)
#define IRQ_FALG (0x01 << 2)
#define I2C_MSG_HEAD_LEN 20
#define TS_REG_COORDS_BASE 0x4100
/*
* struct goodix_tools_data - goodix tools data message used in sync read
* @data: The buffer into which data is written
* @reg_addr: Slave device register start address to start read data
* @length: Number of data bytes in @data being read from slave device
* @filled: When buffer @data be filled will set this flag with 1, outhrwise 0
* @list_head:Eonnet every goodix_tools_data struct into a list
*/
struct goodix_tools_data {
u32 reg_addr;
u32 length;
u8 *data;
bool filled;
struct list_head list;
};
/*
* struct goodix_tools_dev - goodix tools device struct
* @ts_core: The core data struct of ts driver
* @ops_mode: represent device work mode
* @rawdiffcmd: Set slave device into rawdata mode
* @normalcmd: Set slave device into normal mode
* @wq: Wait queue struct use in synchronous data read
* @mutex: Protect goodix_tools_dev
* @in_use: device in use
*/
struct goodix_tools_dev {
struct goodix_ts_core *ts_core;
struct list_head head;
unsigned int ops_mode;
struct goodix_ts_cmd rawdiffcmd, normalcmd;
wait_queue_head_t wq;
struct mutex mutex;
atomic_t in_use;
struct goodix_ext_module module;
} *goodix_tools_dev;
/* read data asynchronous,
* success return data length, otherwise return < 0
*/
static int async_read(struct goodix_tools_dev *dev, void __user *arg)
{
u8 *databuf = NULL;
int ret = 0;
u32 reg_addr, length;
u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev;
const struct goodix_ts_hw_ops *hw_ops = ts_dev->hw_ops;
ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
if (ret)
return -EFAULT;
reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8)
+ (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24);
length = i2c_msg_head[4] + (i2c_msg_head[5] << 8)
+ (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24);
if (length > MAX_BUF_LENGTH) {
ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH);
return -EINVAL;
}
databuf = kzalloc(length, GFP_KERNEL);
if (!databuf) {
ts_err("Alloc memory failed");
return -ENOMEM;
}
if (hw_ops->read_trans(ts_dev, reg_addr, databuf, length)) {
ret = -EBUSY;
ts_err("Read i2c failed");
goto err_out;
}
ret = copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, databuf, length);
if (ret) {
ret = -EFAULT;
ts_err("Copy_to_user failed");
goto err_out;
}
ret = length;
err_out:
kfree(databuf);
return ret;
}
/* if success return config data length */
static int read_config_data(struct goodix_ts_device *ts_dev, void __user *arg)
{
int ret = 0;
u32 reg_addr, length;
u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
u8 *tmp_buf;
ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
if (ret) {
ts_err("Copy data from user failed");
return -EFAULT;
}
reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8)
+ (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24);
length = i2c_msg_head[4] + (i2c_msg_head[5] << 8)
+ (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24);
ts_info("read config,reg_addr=0x%x, length=%d", reg_addr, length);
if (length > MAX_BUF_LENGTH) {
ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH);
return -EINVAL;
}
tmp_buf = kzalloc(length, GFP_KERNEL);
if (!tmp_buf) {
ts_err("failed alloc memory");
return -ENOMEM;
}
/* if reg_addr == 0, read config data with specific flow */
if (!reg_addr) {
if (ts_dev->hw_ops->read_config)
ret = ts_dev->hw_ops->read_config(ts_dev, tmp_buf, length);
else
ret = -EINVAL;
} else {
ret = ts_dev->hw_ops->read_trans(ts_dev, reg_addr, tmp_buf, length);
if (!ret)
ret = length;
}
if (ret <= 0)
goto err_out;
if (copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, tmp_buf, ret)) {
ret = -EFAULT;
ts_err("Copy_to_user failed");
}
err_out:
kfree(tmp_buf);
return ret;
}
/* read data from i2c synchronous,
* return data length when success, otherwise return < 0
*/
static int sync_read(struct goodix_tools_dev *dev, void __user *arg)
{
int ret = 0;
u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
struct goodix_tools_data tools_data;
ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
if (ret) {
ts_err("Copy data from user failed");
return -EFAULT;
}
tools_data.reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8)
+ (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24);
tools_data.length = i2c_msg_head[4] + (i2c_msg_head[5] << 8)
+ (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24);
tools_data.filled = 0;
if (tools_data.length > MAX_BUF_LENGTH) {
ts_err("buffer too long:%d > %d",
tools_data.length, MAX_BUF_LENGTH);
return -EINVAL;
}
tools_data.data = kzalloc(tools_data.length, GFP_KERNEL);
if (!tools_data.data) {
ts_err("Alloc memory failed");
return -ENOMEM;
}
mutex_lock(&dev->mutex);
list_add_tail(&tools_data.list, &dev->head);
mutex_unlock(&dev->mutex);
/* wait queue will timeout after 1 seconds */
wait_event_interruptible_timeout(dev->wq, tools_data.filled == 1, HZ * 3);
mutex_lock(&dev->mutex);
list_del(&tools_data.list);
mutex_unlock(&dev->mutex);
if (!tools_data.filled) {
ret = -EAGAIN;
ts_err("Wait queue timeout");
goto out;
}
ret = copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN,
tools_data.data, tools_data.length);
if (ret) {
ret = -EFAULT;
ts_err("Copy_to_user failed");
goto out;
}
ret = tools_data.length;
out:
kfree(tools_data.data);
return ret;
}
/* write data to i2c asynchronous,
* success return bytes write, else return <= 0
*/
static int async_write(struct goodix_tools_dev *dev, void __user *arg)
{
u8 *databuf;
int ret = 0;
u32 reg_addr, length;
u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev;
const struct goodix_ts_hw_ops *hw_ops = ts_dev->hw_ops;
ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
if (ret) {
ts_err("Copy data from user failed");
return -EFAULT;
}
reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8)
+ (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24);
length = i2c_msg_head[4] + (i2c_msg_head[5] << 8)
+ (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24);
if (length > MAX_BUF_LENGTH) {
ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH);
return -EINVAL;
}
databuf = kzalloc(length, GFP_KERNEL);
if (!databuf) {
ts_err("Alloc memory failed");
return -ENOMEM;
}
ret = copy_from_user(databuf, (u8 *)arg + I2C_MSG_HEAD_LEN, length);
if (ret) {
ret = -EFAULT;
ts_err("Copy data from user failed");
goto err_out;
}
if (hw_ops->write_trans(ts_dev, reg_addr, databuf, length)) {
ret = -EBUSY;
ts_err("Write data to device failed");
} else {
ret = length;
}
err_out:
kfree(databuf);
return ret;
}
static int init_cfg_data(struct goodix_ts_config *cfg, void __user *arg)
{
int ret = 0;
u32 reg_addr, length;
u8 i2c_msg_head[I2C_MSG_HEAD_LEN];
cfg->initialized = 0;
mutex_init(&cfg->lock);
ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN);
if (ret) {
ts_err("Copy data from user failed");
return -EFAULT;
}
reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8)
+ (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24);
length = i2c_msg_head[4] + (i2c_msg_head[5] << 8)
+ (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24);
if (length > MAX_BUF_LENGTH) {
ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH);
return -EINVAL;
}
ret = copy_from_user(cfg->data, (u8 *)arg + I2C_MSG_HEAD_LEN, length);
if (ret) {
ret = -EFAULT;
ts_err("Copy data from user failed");
goto err_out;
}
cfg->reg_base = reg_addr;
cfg->length = length;
strlcpy(cfg->name, "tools-send-cfg", sizeof(cfg->name));
cfg->delay = 50;
cfg->initialized = true;
return 0;
err_out:
return ret;
}
/**
* goodix_tools_ioctl - ioctl implementation
*
* @filp: Pointer to file opened
* @cmd: Ioctl opertion command
* @arg: Command data
* Returns >=0 - succeed, else failed
*/
static long goodix_tools_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
int ret = 0;
struct goodix_tools_dev *dev = filp->private_data;
struct goodix_ts_device *ts_dev;
const struct goodix_ts_hw_ops *hw_ops;
struct goodix_ts_cmd temp_cmd;
struct goodix_ts_config *temp_cfg = NULL;
if (dev->ts_core == NULL) {
ts_err("Tools module not register");
return -EINVAL;
}
ts_dev = dev->ts_core->ts_dev;
hw_ops = ts_dev->hw_ops;
if (_IOC_TYPE(cmd) != GOODIX_TS_IOC_MAGIC) {
ts_err("Bad magic num:%c", _IOC_TYPE(cmd));
return -ENOTTY;
}
if (_IOC_NR(cmd) > GOODIX_TS_IOC_MAXNR) {
ts_err("Bad cmd num:%d > %d",
_IOC_NR(cmd), GOODIX_TS_IOC_MAXNR);
return -ENOTTY;
}
switch (cmd & NEGLECT_SIZE_MASK) {
case GTP_IRQ_ENABLE:
if (arg == 1) {
goodix_ts_irq_enable(dev->ts_core, true);
mutex_lock(&dev->mutex);
dev->ops_mode |= IRQ_FALG;
mutex_unlock(&dev->mutex);
ts_info("IRQ enabled");
} else if (arg == 0) {
goodix_ts_irq_enable(dev->ts_core, false);
mutex_lock(&dev->mutex);
dev->ops_mode &= ~IRQ_FALG;
mutex_unlock(&dev->mutex);
ts_info("IRQ disabled");
} else {
ts_info("Irq aready set with, arg = %ld", arg);
}
ret = 0;
break;
case GTP_ESD_ENABLE:
if (arg == 0)
goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
else
goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
break;
case GTP_DEV_RESET:
hw_ops->reset(ts_dev);
break;
case GTP_SEND_COMMAND:
ret = copy_from_user(&temp_cmd, (void __user *)arg,
sizeof(struct goodix_ts_cmd));
if (ret) {
ret = -EINVAL;
goto err_out;
}
ret = hw_ops->send_cmd(ts_dev, &temp_cmd);
if (ret) {
ts_err("Send command failed");
ret = -EAGAIN;
}
break;
case GTP_SEND_CONFIG:
temp_cfg = kzalloc(sizeof(struct goodix_ts_config), GFP_KERNEL);
if (temp_cfg == NULL) {
ts_err("Memory allco err");
ret = -ENOMEM;
goto err_out;
}
ret = init_cfg_data(temp_cfg, (void __user *)arg);
if (!ret && hw_ops->send_config) {
ret = hw_ops->send_config(ts_dev, temp_cfg);
if (ret) {
ts_err("Failed send config");
ret = -EAGAIN;
} else {
ts_info("Send config success");
ret = 0;
}
}
break;
case GTP_READ_CONFIG:
ret = read_config_data(ts_dev, (void __user *)arg);
if (ret > 0)
ts_info("success read config:len=%d", ret);
else
ts_err("failed read config:ret=0x%x", ret);
break;
case GTP_ASYNC_READ:
ret = async_read(dev, (void __user *)arg);
if (ret < 0)
ts_err("Async data read failed");
break;
case GTP_SYNC_READ:
if (filp->f_flags & O_NONBLOCK) {
ts_err("Goodix tools now worked in sync_bus mode");
ret = -EAGAIN;
goto err_out;
}
ret = sync_read(dev, (void __user *)arg);
if (ret < 0)
ts_err("Sync data read failed");
break;
case GTP_ASYNC_WRITE:
ret = async_write(dev, (void __user *)arg);
if (ret < 0)
ts_err("Async data write failed");
break;
case GTP_DRV_VERSION:
ret = copy_to_user((u8 *)arg, GOODIX_DRIVER_VERSION,
sizeof(GOODIX_DRIVER_VERSION));
if (ret)
ts_err("failed copy driver version info to user");
break;
default:
ts_info("Invalid cmd");
ret = -ENOTTY;
break;
}
err_out:
if (!temp_cfg) {
kfree(temp_cfg);
temp_cfg = NULL;
}
return ret;
}
#ifdef CONFIG_COMPAT
static long goodix_tools_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *arg32 = compat_ptr(arg);
if (!file->f_op || !file->f_op->unlocked_ioctl)
return -ENOTTY;
return file->f_op->unlocked_ioctl(file, cmd, (unsigned long)arg32);
}
#endif
static int goodix_tools_open(struct inode *inode, struct file *filp)
{
int ret = 0;
ts_info("try open tool");
/* Only the first time open device need to register module */
ret = goodix_register_ext_module_no_wait(&goodix_tools_dev->module);
if (ret) {
ts_info("failed register to core module");
return -EFAULT;
}
ts_info("success open tools");
filp->private_data = goodix_tools_dev;
atomic_set(&goodix_tools_dev->in_use, 1);
return 0;
}
static int goodix_tools_release(struct inode *inode, struct file *filp)
{
int ret = 0;
/* when the last close this dev node unregister the module */
atomic_set(&goodix_tools_dev->in_use, 0);
ret = goodix_unregister_ext_module(&goodix_tools_dev->module);
return ret;
}
/**
* goodix_tools_module_irq - goodix tools Irq handle
* This functions is excuted when interrupt happended
*
* @core_data: pointer to touch core data
* @module: pointer to goodix_ext_module struct
* return: EVT_CONTINUE let other module handle this irq
*/
static int goodix_tools_module_irq(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
struct goodix_tools_dev *dev = module->priv_data;
struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev;
const struct goodix_ts_hw_ops *hw_ops = ts_dev->hw_ops;
struct goodix_tools_data *tools_data, *next;
int r = 0;
u8 evt_sta = 0;
mutex_lock(&dev->mutex);
if (!list_empty(&dev->head)) {
r = hw_ops->read_trans(ts_dev, ts_dev->reg.coor/* TS_REG_COORDS_BASE*/, &evt_sta, 1);
if (r < 0 || ((evt_sta & GOODIX_TOUCH_EVENT) == 0)) {
ts_err("data not ready:0x%x, read ret =%d", evt_sta, r);
mutex_unlock(&dev->mutex);
return EVT_CONTINUE;
}
list_for_each_entry_safe(tools_data, next, &dev->head, list) {
if (!hw_ops->read_trans(ts_dev, tools_data->reg_addr,
tools_data->data, tools_data->length)) {
tools_data->filled = 1;
}
}
wake_up(&dev->wq);
}
mutex_unlock(&dev->mutex);
return EVT_CONTINUE;
}
static int goodix_tools_module_init(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
struct goodix_tools_dev *tools_dev = module->priv_data;
if (core_data)
tools_dev->ts_core = core_data;
else
return -ENODEV;
return 0;
}
static int goodix_tools_module_exit(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
struct goodix_tools_dev *tools_dev = module->priv_data;
ts_debug("tools module unregister");
if (atomic_read(&tools_dev->in_use)) {
ts_err("tools module busy, please close it then retry");
return -EBUSY;
}
return 0;
}
static const struct file_operations goodix_tools_fops = {
.owner = THIS_MODULE,
.open = goodix_tools_open,
.release = goodix_tools_release,
.unlocked_ioctl = goodix_tools_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = goodix_tools_compat_ioctl,
#endif
};
static struct miscdevice goodix_tools_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = GOODIX_TOOLS_NAME,
.fops = &goodix_tools_fops,
};
static struct goodix_ext_module_funcs goodix_tools_module_funcs = {
.irq_event = goodix_tools_module_irq,
.init = goodix_tools_module_init,
.exit = goodix_tools_module_exit,
};
/**
* goodix_tools_init - init goodix tools device and register a miscdevice
*
* return: 0 success, else failed
*/
static int __init goodix_tools_init(void)
{
int ret;
goodix_tools_dev = kzalloc(sizeof(struct goodix_tools_dev), GFP_KERNEL);
if (goodix_tools_dev == NULL) {
ts_err("Memory allco err");
return -ENOMEM;
}
INIT_LIST_HEAD(&goodix_tools_dev->head);
goodix_tools_dev->ops_mode = 0;
goodix_tools_dev->ops_mode |= IRQ_FALG;
init_waitqueue_head(&goodix_tools_dev->wq);
mutex_init(&goodix_tools_dev->mutex);
atomic_set(&goodix_tools_dev->in_use, 0);
goodix_tools_dev->module.funcs = &goodix_tools_module_funcs;
goodix_tools_dev->module.name = GOODIX_TOOLS_NAME;
goodix_tools_dev->module.priv_data = goodix_tools_dev;
goodix_tools_dev->module.priority = EXTMOD_PRIO_DBGTOOL;
ret = misc_register(&goodix_tools_miscdev);
if (ret)
ts_err("Debug tools miscdev register failed");
return ret;
}
static void __exit goodix_tools_exit(void)
{
misc_deregister(&goodix_tools_miscdev);
kfree(goodix_tools_dev);
ts_info("Goodix tools miscdev exit");
}
late_initcall(goodix_tools_init);
module_exit(goodix_tools_exit);
MODULE_DESCRIPTION("Goodix tools Module");
MODULE_AUTHOR("Goodix, Inc.");
MODULE_LICENSE("GPL v2");