mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-10 22:40:54 -04:00
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:
parent
5a01001b24
commit
2b68f15142
14 changed files with 11811 additions and 0 deletions
|
|
@ -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
|
||||
8
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Kbuild
Normal file
8
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Kbuild
Normal 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
|
||||
47
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Kconfig
Normal file
47
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Kconfig
Normal 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
|
||||
10
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Makefile
Normal file
10
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/Makefile
Normal 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
|
||||
650
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_cfg_bin.c
Normal file
650
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_cfg_bin.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef _GOODIX_FIRMWARE_FW_H_
|
||||
#define _GOODIX_FIRMWARE_FW_H_
|
||||
|
||||
u8 goodix_default_fw[] = { 0x00};
|
||||
|
||||
#endif
|
||||
1505
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_gtx8_update.c
Normal file
1505
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_gtx8_update.c
Normal file
File diff suppressed because it is too large
Load diff
2405
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_core.c
Normal file
2405
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_core.c
Normal file
File diff suppressed because it is too large
Load diff
772
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_core.h
Normal file
772
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_core.h
Normal 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
|
||||
|
|
@ -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");
|
||||
1997
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_i2c.c
Normal file
1997
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_i2c.c
Normal file
File diff suppressed because it is too large
Load diff
3216
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_test.c
Normal file
3216
drivers/input/touchscreen/goodix_gtx8_v1510_mmi/goodix_ts_test.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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");
|
||||
Loading…
Reference in a new issue