blob: 1e3bc1aceba9297c4b78bdfed7d90417f6bba0f3 [file] [edit]
/*
* Goodix Touchscreen Driver
* Copyright (C) 2020 - 2021 Goodix, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be a reference
* to you, when you are integrating the GOODiX's CTP IC into your system,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
*/
#include "goodix_ts_core.h"
/* berlin_A SPI mode setting */
#define GOODIX_SPI_MODE_REG 0xC900
#define GOODIX_SPI_NORMAL_MODE_0 0x01
/* berlin_A D12 setting */
#define GOODIX_REG_CLK_STA0 0xD807
#define GOODIX_CLK_STA0_ENABLE 0xFF
#define GOODIX_REG_CLK_STA1 0xD806
#define GOODIX_CLK_STA1_ENABLE 0x77
#define GOODIX_REG_TRIM_D12 0xD006
#define GOODIX_TRIM_D12_LEVEL 0x3C
#define GOODIX_REG_RESET 0xD808
#define GOODIX_RESET_EN 0xFA
#define HOLD_CPU_REG_W 0x0002
#define HOLD_CPU_REG_R 0x2000
#define DEV_CONFIRM_VAL 0xAA
#define BOOTOPTION_ADDR 0x10000
#define FW_VERSION_INFO_ADDR_BRA 0x1000C
#define FW_VERSION_INFO_ADDR 0x10014
#define GOODIX_IC_INFO_MAX_LEN 1024
#define GOODIX_IC_INFO_ADDR_BRA 0x10068
#define GOODIX_IC_INFO_ADDR 0x10070
enum brl_request_code {
BRL_REQUEST_CODE_CONFIG = 0x01,
BRL_REQUEST_CODE_REF_ERR = 0x02,
BRL_REQUEST_CODE_RESET = 0x03,
BRL_REQUEST_CODE_CLOCK = 0x04,
BRL_REQUEST_CODE_UPDATE = 0x05,
BRL_REQUEST_PEN_FREQ_HOP = 0x10,
};
static int brl_select_spi_mode(struct goodix_ts_core *cd)
{
int ret;
int i;
u8 w_value = GOODIX_SPI_NORMAL_MODE_0;
u8 r_value;
if (cd->bus->bus_type == GOODIX_BUS_TYPE_I2C ||
cd->bus->ic_type != IC_TYPE_BERLIN_A)
return 0;
for (i = 0; i < GOODIX_RETRY_5; i++) {
cd->hw_ops->write(cd, GOODIX_SPI_MODE_REG, &w_value, 1);
ret = cd->hw_ops->read(cd, GOODIX_SPI_MODE_REG, &r_value, 1);
if (!ret && r_value == w_value)
return 0;
}
ts_err("failed switch SPI mode, ret:%d r_value:%02x", ret, r_value);
return -EINVAL;
}
static int brl_dev_confirm(struct goodix_ts_core *cd)
{
struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
int ret = 0;
int retry = GOODIX_RETRY_3;
u8 tx_buf[8] = { 0 };
u8 rx_buf[8] = { 0 };
memset(tx_buf, DEV_CONFIRM_VAL, sizeof(tx_buf));
while (retry--) {
ret = hw_ops->write(
cd, BOOTOPTION_ADDR, tx_buf, sizeof(tx_buf));
if (ret < 0)
return ret;
ret = hw_ops->read(cd, BOOTOPTION_ADDR, rx_buf, sizeof(rx_buf));
if (ret < 0)
return ret;
if (!memcmp(tx_buf, rx_buf, sizeof(tx_buf)))
break;
usleep_range(5000, 5100);
}
if (retry < 0) {
ts_err("device confirm failed, rx_buf:%*ph", 8, rx_buf);
return -EINVAL;
}
ts_info("device connected");
return ret;
}
static int brl_reset_after(struct goodix_ts_core *cd)
{
u8 reg_val[2] = { 0 };
u8 temp_buf[12] = { 0 };
int ret;
int retry;
if (cd->bus->ic_type != IC_TYPE_BERLIN_A)
return 0;
ts_info("IN");
/* select spi mode */
ret = brl_select_spi_mode(cd);
if (ret < 0)
return ret;
/* hold cpu */
retry = GOODIX_RETRY_10;
while (retry--) {
reg_val[0] = 0x01;
reg_val[1] = 0x00;
ret = cd->hw_ops->write(cd, HOLD_CPU_REG_W, reg_val, 2);
ret |= cd->hw_ops->read(cd, HOLD_CPU_REG_R, &temp_buf[0], 4);
ret |= cd->hw_ops->read(cd, HOLD_CPU_REG_R, &temp_buf[4], 4);
ret |= cd->hw_ops->read(cd, HOLD_CPU_REG_R, &temp_buf[8], 4);
if (!ret && !memcmp(&temp_buf[0], &temp_buf[4], 4) &&
!memcmp(&temp_buf[4], &temp_buf[8], 4) &&
!memcmp(&temp_buf[0], &temp_buf[8], 4)) {
break;
}
}
if (retry < 0) {
ts_err("failed to hold cpu, status:%*ph", 12, temp_buf);
return -EINVAL;
}
/* enable sta0 clk */
retry = GOODIX_RETRY_5;
while (retry--) {
reg_val[0] = GOODIX_CLK_STA0_ENABLE;
ret = cd->hw_ops->write(cd, GOODIX_REG_CLK_STA0, reg_val, 1);
ret |= cd->hw_ops->read(cd, GOODIX_REG_CLK_STA0, temp_buf, 1);
if (!ret && temp_buf[0] == GOODIX_CLK_STA0_ENABLE)
break;
}
if (retry < 0) {
ts_err("failed to enable group0 clock, ret:%d status:%02x", ret,
temp_buf[0]);
return -EINVAL;
}
/* enable sta1 clk */
retry = GOODIX_RETRY_5;
while (retry--) {
reg_val[0] = GOODIX_CLK_STA1_ENABLE;
ret = cd->hw_ops->write(cd, GOODIX_REG_CLK_STA1, reg_val, 1);
ret |= cd->hw_ops->read(cd, GOODIX_REG_CLK_STA1, temp_buf, 1);
if (!ret && temp_buf[0] == GOODIX_CLK_STA1_ENABLE)
break;
}
if (retry < 0) {
ts_err("failed to enable group1 clock, ret:%d status:%02x", ret,
temp_buf[0]);
return -EINVAL;
}
/* set D12 level */
retry = GOODIX_RETRY_5;
while (retry--) {
reg_val[0] = GOODIX_TRIM_D12_LEVEL;
ret = cd->hw_ops->write(cd, GOODIX_REG_TRIM_D12, reg_val, 1);
ret |= cd->hw_ops->read(cd, GOODIX_REG_TRIM_D12, temp_buf, 1);
if (!ret && temp_buf[0] == GOODIX_TRIM_D12_LEVEL)
break;
}
if (retry < 0) {
ts_err("failed to set D12, ret:%d status:%02x", ret,
temp_buf[0]);
return -EINVAL;
}
usleep_range(5000, 5100);
/* soft reset */
reg_val[0] = GOODIX_RESET_EN;
ret = cd->hw_ops->write(cd, GOODIX_REG_RESET, reg_val, 1);
if (ret < 0)
return ret;
/* select spi mode */
ret = brl_select_spi_mode(cd);
if (ret < 0)
return ret;
ts_info("OUT");
return 0;
}
static int brl_power_on(struct goodix_ts_core *cd, bool on)
{
int ret = 0;
int iovdd_gpio = cd->board_data.iovdd_gpio;
int avdd_gpio = cd->board_data.avdd_gpio;
int reset_gpio = cd->board_data.reset_gpio;
if (on) {
if (iovdd_gpio > 0) {
gpio_direction_output(iovdd_gpio, 1);
} else if (cd->iovdd) {
ret = regulator_enable(cd->iovdd);
if (ret < 0) {
ts_err("Failed to enable iovdd:%d", ret);
goto power_off;
}
}
usleep_range(3000, 3100);
if (avdd_gpio > 0) {
gpio_direction_output(avdd_gpio, 1);
} else if (cd->avdd) {
ret = regulator_enable(cd->avdd);
if (ret < 0) {
ts_err("Failed to enable avdd:%d", ret);
goto power_off;
}
}
usleep_range(15000, 15100);
gpio_direction_output(reset_gpio, 1);
usleep_range(4000, 4100);
ret = brl_dev_confirm(cd);
if (ret < 0)
goto power_off;
ret = brl_reset_after(cd);
if (ret < 0)
goto power_off;
msleep(GOODIX_NORMAL_RESET_DELAY_MS);
return 0;
}
power_off:
gpio_direction_output(reset_gpio, 0);
if (iovdd_gpio > 0)
gpio_direction_output(iovdd_gpio, 0);
else if (cd->iovdd)
regulator_disable(cd->iovdd);
if (avdd_gpio > 0)
gpio_direction_output(avdd_gpio, 0);
else if (cd->avdd)
regulator_disable(cd->avdd);
return ret;
}
int brl_suspend(struct goodix_ts_core *cd)
{
u32 cmd_reg = cd->ic_info.misc.cmd_addr;
u8 sleep_cmd[] = { 0x00, 0x00, 0x05, 0xC4, 0x01, 0xCA, 0x00 };
return cd->hw_ops->write(cd, cmd_reg, sleep_cmd, sizeof(sleep_cmd));
}
int brl_resume(struct goodix_ts_core *cd)
{
u32 cmd_reg = cd->ic_info.misc.cmd_addr;
u8 cmd_buf[] = { 0x00, 0x00, 0x04, 0xA7, 0xAB, 0x00 };
int retry = 20;
u8 rcv_buf[2];
cd->hw_ops->write(cd, cmd_reg, cmd_buf, sizeof(cmd_buf));
while (retry--) {
usleep_range(5000, 5100);
cd->hw_ops->read(cd, cmd_reg, rcv_buf, sizeof(rcv_buf));
if (rcv_buf[0] == 0x80 && rcv_buf[1] == 0x80)
break;
}
if (retry < 0) {
ts_err("failed to exit sleep mode, status[%X] ack[%X]",
rcv_buf[0], rcv_buf[1]);
return -EINVAL;
} else {
ts_info("Secceed to exit sleep mode with retry: %d", 19 - retry);
}
return 0;
}
#define GOODIX_GESTURE_CMD_BA 0x12
#define GOODIX_GESTURE_CMD 0xA6
int brl_gesture(struct goodix_ts_core *cd, int gesture_type)
{
struct goodix_ts_cmd cmd;
u8 val;
if ((cd->gesture_type & GESTURE_SINGLE_TAP) &&
(cd->gesture_type & GESTURE_FOD_PRESS)) {
val = 0x0F;
} else if (cd->gesture_type & GESTURE_SINGLE_TAP) {
val = 0x2F;
} else if (cd->gesture_type & GESTURE_FOD_PRESS) {
val = 0x1F;
} else {
val = 0xFF;
}
if (cd->bus->ic_type == IC_TYPE_BERLIN_A)
cmd.cmd = GOODIX_GESTURE_CMD_BA;
else
cmd.cmd = GOODIX_GESTURE_CMD;
cmd.len = 6;
cmd.data[0] = 0xff;
cmd.data[1] = val;
if (cd->hw_ops->send_cmd(cd, &cmd))
ts_err("failed send gesture cmd");
return 0;
}
static int brl_reset(struct goodix_ts_core *cd, int delay)
{
ts_info("chip_reset");
gpio_direction_output(cd->board_data.reset_gpio, 0);
usleep_range(2000, 2100);
gpio_direction_output(cd->board_data.reset_gpio, 1);
if (delay < 20)
usleep_range(delay * 1000, delay * 1000 + 100);
else
msleep(delay);
return brl_select_spi_mode(cd);
}
static int brl_irq_enable(struct goodix_ts_core *cd, bool enable)
{
if (enable && !atomic_cmpxchg(&cd->irq_enabled, 0, 1)) {
enable_irq(cd->irq);
ts_debug("Irq enabled");
return 0;
}
if (!enable && atomic_cmpxchg(&cd->irq_enabled, 1, 0)) {
disable_irq_nosync(cd->irq);
ts_debug("Irq disabled");
return 0;
}
// ts_info("warnning: irq deepth inbalance!");
return 0;
}
static int brl_read(struct goodix_ts_core *cd, unsigned int addr,
unsigned char *data, unsigned int len)
{
struct goodix_bus_interface *bus = cd->bus;
return bus->read(bus->dev, addr, data, len);
}
static int brl_read_fast(struct goodix_ts_core *cd, unsigned int addr,
struct goodix_rx_package *package, unsigned int len)
{
struct goodix_bus_interface *bus = cd->bus;
return bus->read_fast(bus->dev, addr, package, len);
}
static int brl_write(struct goodix_ts_core *cd, unsigned int addr,
unsigned char *data, unsigned int len)
{
struct goodix_bus_interface *bus = cd->bus;
return bus->write(bus->dev, addr, data, len);
}
/* command ack info */
#define CMD_ACK_IDLE 0x01
#define CMD_ACK_BUSY 0x02
#define CMD_ACK_BUFFER_OVERFLOW 0x03
#define CMD_ACK_CHECKSUM_ERROR 0x04
#define CMD_ACK_OK 0x80
#define GOODIX_CMD_RETRY 6
static int brl_send_cmd(struct goodix_ts_core *cd, struct goodix_ts_cmd *cmd)
{
int ret, retry, i;
struct goodix_ts_cmd cmd_ack;
struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
mutex_lock(&cd->cmd_lock);
cmd->state = 0;
cmd->ack = 0;
goodix_append_checksum(
&(cmd->buf[2]), cmd->len - 2, CHECKSUM_MODE_U8_LE);
ts_debug("cmd data %*ph", cmd->len, &(cmd->buf[2]));
retry = 0;
while (retry++ < GOODIX_CMD_RETRY) {
ret = hw_ops->write(cd, misc->cmd_addr, cmd->buf, sizeof(*cmd));
if (ret < 0) {
ts_err("failed write command");
goto exit;
}
for (i = 0; i < GOODIX_CMD_RETRY; i++) {
/* check command result */
ret = hw_ops->read(cd, misc->cmd_addr, cmd_ack.buf,
sizeof(cmd_ack));
if (ret < 0) {
ts_err("failed read command ack, %d", ret);
goto exit;
}
ts_debug("cmd ack data %*ph", (int)sizeof(cmd_ack),
cmd_ack.buf);
if (cmd_ack.ack == CMD_ACK_OK) {
ret = 0;
goto exit;
}
if (cmd_ack.ack == CMD_ACK_BUSY ||
cmd_ack.ack == 0x00) {
usleep_range(1000, 1100);
continue;
}
if (cmd_ack.ack == CMD_ACK_BUFFER_OVERFLOW)
usleep_range(10000, 11000);
usleep_range(1000, 1100);
break;
}
}
ret = -EINVAL;
ts_err("failed get valid cmd ack");
exit:
mutex_unlock(&cd->cmd_lock);
return ret;
}
#pragma pack(1)
struct goodix_config_head {
union {
struct {
u8 panel_name[8];
u8 fw_pid[8];
u8 fw_vid[4];
u8 project_name[8];
u8 file_ver[2];
u32 cfg_id;
u8 cfg_ver;
u8 cfg_time[8];
u8 reserved[15];
u8 flag;
u16 cfg_len;
u8 cfg_num;
u16 checksum;
};
u8 buf[64];
};
};
#pragma pack()
#define CONFIG_CND_LEN 4
#define CONFIG_CMD_START 0x04
#define CONFIG_CMD_WRITE 0x05
#define CONFIG_CMD_EXIT 0x06
#define CONFIG_CMD_READ_START 0x07
#define CONFIG_CMD_READ_EXIT 0x08
#define CONFIG_CMD_STATUS_PASS 0x80
#define CONFIG_CMD_WAIT_RETRY 20
static int wait_cmd_status(
struct goodix_ts_core *cd, u8 target_status, int retry)
{
struct goodix_ts_cmd cmd_ack;
struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
int i, ret;
for (i = 0; i < retry; i++) {
ret = hw_ops->read(
cd, misc->cmd_addr, cmd_ack.buf, sizeof(cmd_ack));
if (!ret && cmd_ack.state == target_status) {
ts_debug("status check pass");
return 0;
}
ts_debug("cmd buf %*ph", (int)sizeof(cmd_ack), cmd_ack.buf);
msleep(20);
}
ts_err("cmd status not ready, retry %d, ack 0x%x, status 0x%x, ret %d",
i, cmd_ack.ack, cmd_ack.state, ret);
return -EINVAL;
}
static int send_cfg_cmd(
struct goodix_ts_core *cd, struct goodix_ts_cmd *cfg_cmd)
{
int ret;
ret = cd->hw_ops->send_cmd(cd, cfg_cmd);
if (ret) {
ts_err("failed write cfg prepare cmd %d", ret);
return ret;
}
ret = wait_cmd_status(
cd, CONFIG_CMD_STATUS_PASS, CONFIG_CMD_WAIT_RETRY);
if (ret) {
ts_err("failed wait for fw ready for config, %d", ret);
return ret;
}
return 0;
}
static int brl_send_config(struct goodix_ts_core *cd, u8 *cfg, int len)
{
int ret;
u8 *tmp_buf;
struct goodix_ts_cmd cfg_cmd;
struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
if (len > misc->fw_buffer_max_len) {
ts_err("config len exceed limit %d > %d", len,
misc->fw_buffer_max_len);
return -EINVAL;
}
tmp_buf = kzalloc(len, GFP_KERNEL);
if (!tmp_buf)
return -ENOMEM;
cfg_cmd.len = CONFIG_CND_LEN;
cfg_cmd.cmd = CONFIG_CMD_START;
ret = send_cfg_cmd(cd, &cfg_cmd);
if (ret) {
ts_err("failed write cfg prepare cmd %d", ret);
goto exit;
}
ts_debug("try send config to 0x%x, len %d", misc->fw_buffer_addr, len);
ret = hw_ops->write(cd, misc->fw_buffer_addr, cfg, len);
if (ret) {
ts_err("failed write config data, %d", ret);
goto exit;
}
ret = hw_ops->read(cd, misc->fw_buffer_addr, tmp_buf, len);
if (ret) {
ts_err("failed read back config data");
goto exit;
}
if (memcmp(cfg, tmp_buf, len)) {
ts_err("config data read back compare file");
ret = -EINVAL;
goto exit;
}
/* notify fw for receiving config */
memset(cfg_cmd.buf, 0, sizeof(cfg_cmd));
cfg_cmd.len = CONFIG_CND_LEN;
cfg_cmd.cmd = CONFIG_CMD_WRITE;
ret = send_cfg_cmd(cd, &cfg_cmd);
if (ret)
ts_err("failed send config data ready cmd %d", ret);
exit:
memset(cfg_cmd.buf, 0, sizeof(cfg_cmd));
cfg_cmd.len = CONFIG_CND_LEN;
cfg_cmd.cmd = CONFIG_CMD_EXIT;
if (send_cfg_cmd(cd, &cfg_cmd)) {
ts_err("failed send config write end command");
ret = -EINVAL;
}
if (!ret) {
ts_info("success send config");
msleep(100);
}
kfree(tmp_buf);
return ret;
}
/*
* return: return config length on success, other wise return < 0
**/
static int brl_read_config(struct goodix_ts_core *cd, u8 *cfg, int size)
{
int ret;
struct goodix_ts_cmd cfg_cmd;
struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
struct goodix_config_head cfg_head;
if (!cfg)
return -EINVAL;
cfg_cmd.len = CONFIG_CND_LEN;
cfg_cmd.cmd = CONFIG_CMD_READ_START;
ret = send_cfg_cmd(cd, &cfg_cmd);
if (ret) {
ts_err("failed send config read prepare command");
return ret;
}
ret = hw_ops->read(
cd, misc->fw_buffer_addr, cfg_head.buf, sizeof(cfg_head));
if (ret) {
ts_err("failed read config head %d", ret);
goto exit;
}
if (checksum_cmp(cfg_head.buf, sizeof(cfg_head), CHECKSUM_MODE_U8_LE)) {
ts_err("config head checksum error");
ret = -EINVAL;
goto exit;
}
cfg_head.cfg_len = le16_to_cpu(cfg_head.cfg_len);
if (cfg_head.cfg_len > misc->fw_buffer_max_len ||
cfg_head.cfg_len > size) {
ts_err("cfg len exceed buffer size %d > %d", cfg_head.cfg_len,
misc->fw_buffer_max_len);
ret = -EINVAL;
goto exit;
}
memcpy(cfg, cfg_head.buf, sizeof(cfg_head));
ret = hw_ops->read(cd, misc->fw_buffer_addr + sizeof(cfg_head),
cfg + sizeof(cfg_head), cfg_head.cfg_len);
if (ret) {
ts_err("failed read cfg pack, %d", ret);
goto exit;
}
ts_info("config len %d", cfg_head.cfg_len);
if (checksum_cmp(cfg + sizeof(cfg_head), cfg_head.cfg_len,
CHECKSUM_MODE_U16_LE)) {
ts_err("config body checksum error");
ret = -EINVAL;
goto exit;
}
ts_info("success read config data: len %zu",
cfg_head.cfg_len + sizeof(cfg_head));
exit:
memset(cfg_cmd.buf, 0, sizeof(cfg_cmd));
cfg_cmd.len = CONFIG_CND_LEN;
cfg_cmd.cmd = CONFIG_CMD_READ_EXIT;
if (send_cfg_cmd(cd, &cfg_cmd)) {
ts_err("failed send config read finish command");
ret = -EINVAL;
}
if (ret)
return -EINVAL;
return cfg_head.cfg_len + sizeof(cfg_head);
}
/*
* return: 0 for no error.
* GOODIX_EBUS when encounter a bus error
* GOODIX_ECHECKSUM version checksum error
* GOODIX_EVERSION patch ID compare failed,
* in this case the sensorID is valid.
*/
static int brl_read_version(
struct goodix_ts_core *cd, struct goodix_fw_version *version)
{
int ret, i;
u32 fw_addr;
struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
u8 buf[sizeof(struct goodix_fw_version)] = { 0 };
u8 temp_pid[8] = { 0 };
if (cd->bus->ic_type == IC_TYPE_BERLIN_A)
fw_addr = FW_VERSION_INFO_ADDR_BRA;
else
fw_addr = FW_VERSION_INFO_ADDR;
for (i = 0; i < GOODIX_RETRY_3; i++) {
ret = hw_ops->read(cd, fw_addr, buf, sizeof(buf));
if (ret) {
ts_info("read fw version: %d, retry %d", ret, i);
ret = -GOODIX_EBUS;
usleep_range(5000, 5100);
continue;
}
if (!checksum_cmp(buf, sizeof(buf), CHECKSUM_MODE_U8_LE))
break;
ts_info("invalid fw version: checksum error!");
ts_info("fw version:%*ph", (int)sizeof(buf), buf);
ret = -GOODIX_ECHECKSUM;
usleep_range(10000, 11000);
}
if (ret) {
ts_err("failed get valied fw version");
return ret;
}
memcpy(version, buf, sizeof(*version));
memcpy(temp_pid, version->rom_pid, sizeof(version->rom_pid));
ts_info("rom_pid:%s", temp_pid);
ts_info("rom_vid:%*ph", (int)sizeof(version->rom_vid),
version->rom_vid);
ts_info("pid:%s", version->patch_pid);
ts_info("vid:%*ph", (int)sizeof(version->patch_vid),
version->patch_vid);
ts_info("sensor_id:%d", version->sensor_id);
return 0;
}
#define LE16_TO_CPU(x) (x = le16_to_cpu(x))
#define LE32_TO_CPU(x) (x = le32_to_cpu(x))
static int convert_ic_info(struct goodix_ic_info *info, const u8 *data)
{
int i;
struct goodix_ic_info_version *version = &info->version;
struct goodix_ic_info_feature *feature = &info->feature;
struct goodix_ic_info_param *parm = &info->parm;
struct goodix_ic_info_misc *misc = &info->misc;
info->length = le16_to_cpup((__le16 *)data);
data += 2;
memcpy(version, data, sizeof(*version));
version->config_id = le32_to_cpu(version->config_id);
data += sizeof(struct goodix_ic_info_version);
memcpy(feature, data, sizeof(*feature));
feature->freqhop_feature = le16_to_cpu(feature->freqhop_feature);
feature->calibration_feature =
le16_to_cpu(feature->calibration_feature);
feature->gesture_feature = le16_to_cpu(feature->gesture_feature);
feature->side_touch_feature = le16_to_cpu(feature->side_touch_feature);
feature->stylus_feature = le16_to_cpu(feature->stylus_feature);
data += sizeof(struct goodix_ic_info_feature);
parm->drv_num = *(data++);
parm->sen_num = *(data++);
parm->button_num = *(data++);
parm->force_num = *(data++);
parm->active_scan_rate_num = *(data++);
if (parm->active_scan_rate_num > MAX_SCAN_RATE_NUM) {
ts_err("invalid scan rate num %d > %d",
parm->active_scan_rate_num, MAX_SCAN_RATE_NUM);
return -EINVAL;
}
for (i = 0; i < parm->active_scan_rate_num; i++)
parm->active_scan_rate[i] =
le16_to_cpup((__le16 *)(data + i * 2));
data += parm->active_scan_rate_num * 2;
parm->mutual_freq_num = *(data++);
if (parm->mutual_freq_num > MAX_SCAN_FREQ_NUM) {
ts_err("invalid mntual freq num %d > %d", parm->mutual_freq_num,
MAX_SCAN_FREQ_NUM);
return -EINVAL;
}
for (i = 0; i < parm->mutual_freq_num; i++)
parm->mutual_freq[i] = le16_to_cpup((__le16 *)(data + i * 2));
data += parm->mutual_freq_num * 2;
parm->self_tx_freq_num = *(data++);
if (parm->self_tx_freq_num > MAX_SCAN_FREQ_NUM) {
ts_err("invalid tx freq num %d > %d", parm->self_tx_freq_num,
MAX_SCAN_FREQ_NUM);
return -EINVAL;
}
for (i = 0; i < parm->self_tx_freq_num; i++)
parm->self_tx_freq[i] = le16_to_cpup((__le16 *)(data + i * 2));
data += parm->self_tx_freq_num * 2;
parm->self_rx_freq_num = *(data++);
if (parm->self_rx_freq_num > MAX_SCAN_FREQ_NUM) {
ts_err("invalid rx freq num %d > %d", parm->self_rx_freq_num,
MAX_SCAN_FREQ_NUM);
return -EINVAL;
}
for (i = 0; i < parm->self_rx_freq_num; i++)
parm->self_rx_freq[i] = le16_to_cpup((__le16 *)(data + i * 2));
data += parm->self_rx_freq_num * 2;
parm->stylus_freq_num = *(data++);
if (parm->stylus_freq_num > MAX_FREQ_NUM_STYLUS) {
ts_err("invalid stylus freq num %d > %d", parm->stylus_freq_num,
MAX_FREQ_NUM_STYLUS);
return -EINVAL;
}
for (i = 0; i < parm->stylus_freq_num; i++)
parm->stylus_freq[i] = le16_to_cpup((__le16 *)(data + i * 2));
data += parm->stylus_freq_num * 2;
memcpy(misc, data, sizeof(*misc));
misc->cmd_addr = le32_to_cpu(misc->cmd_addr);
misc->cmd_max_len = le16_to_cpu(misc->cmd_max_len);
misc->cmd_reply_addr = le32_to_cpu(misc->cmd_reply_addr);
misc->cmd_reply_len = le16_to_cpu(misc->cmd_reply_len);
misc->fw_state_addr = le32_to_cpu(misc->fw_state_addr);
misc->fw_state_len = le16_to_cpu(misc->fw_state_len);
misc->fw_buffer_addr = le32_to_cpu(misc->fw_buffer_addr);
misc->fw_buffer_max_len = le16_to_cpu(misc->fw_buffer_max_len);
misc->frame_data_addr = le32_to_cpu(misc->frame_data_addr);
misc->frame_data_head_len = le16_to_cpu(misc->frame_data_head_len);
misc->fw_attr_len = le16_to_cpu(misc->fw_attr_len);
misc->fw_log_len = le16_to_cpu(misc->fw_log_len);
misc->stylus_struct_len = le16_to_cpu(misc->stylus_struct_len);
misc->mutual_struct_len = le16_to_cpu(misc->mutual_struct_len);
misc->self_struct_len = le16_to_cpu(misc->self_struct_len);
misc->noise_struct_len = le16_to_cpu(misc->noise_struct_len);
misc->touch_data_addr = le32_to_cpu(misc->touch_data_addr);
misc->touch_data_head_len = le16_to_cpu(misc->touch_data_head_len);
misc->point_struct_len = le16_to_cpu(misc->point_struct_len);
LE32_TO_CPU(misc->mutual_rawdata_addr);
LE32_TO_CPU(misc->mutual_diffdata_addr);
LE32_TO_CPU(misc->mutual_refdata_addr);
LE32_TO_CPU(misc->self_rawdata_addr);
LE32_TO_CPU(misc->self_diffdata_addr);
LE32_TO_CPU(misc->self_refdata_addr);
LE32_TO_CPU(misc->iq_rawdata_addr);
LE32_TO_CPU(misc->iq_refdata_addr);
LE32_TO_CPU(misc->im_rawdata_addr);
LE16_TO_CPU(misc->im_readata_len);
LE32_TO_CPU(misc->noise_rawdata_addr);
LE16_TO_CPU(misc->noise_rawdata_len);
LE32_TO_CPU(misc->stylus_rawdata_addr);
LE16_TO_CPU(misc->stylus_rawdata_len);
LE32_TO_CPU(misc->noise_data_addr);
LE32_TO_CPU(misc->esd_addr);
LE32_TO_CPU(misc->auto_scan_cmd_addr);
LE32_TO_CPU(misc->auto_scan_info_addr);
return 0;
}
static void print_ic_info(struct goodix_ic_info *ic_info)
{
struct goodix_ic_info_version *version = &ic_info->version;
struct goodix_ic_info_feature *feature = &ic_info->feature;
struct goodix_ic_info_param *parm = &ic_info->parm;
struct goodix_ic_info_misc *misc = &ic_info->misc;
ts_info("ic_info_length: %d", ic_info->length);
ts_info("info_customer_id: 0x%01X",
version->info_customer_id);
ts_info("info_version_id: 0x%01X",
version->info_version_id);
ts_info("ic_die_id: 0x%01X", version->ic_die_id);
ts_info("ic_version_id: 0x%01X",
version->ic_version_id);
ts_info("config_id: 0x%4X", version->config_id);
ts_info("config_version: 0x%01X",
version->config_version);
ts_info("frame_data_customer_id: 0x%01X",
version->frame_data_customer_id);
ts_info("frame_data_version_id: 0x%01X",
version->frame_data_version_id);
ts_info("touch_data_customer_id: 0x%01X",
version->touch_data_customer_id);
ts_info("touch_data_version_id: 0x%01X",
version->touch_data_version_id);
ts_info("freqhop_feature: 0x%04X",
feature->freqhop_feature);
ts_info("calibration_feature: 0x%04X",
feature->calibration_feature);
ts_info("gesture_feature: 0x%04X",
feature->gesture_feature);
ts_info("side_touch_feature: 0x%04X",
feature->side_touch_feature);
ts_info("stylus_feature: 0x%04X",
feature->stylus_feature);
ts_info("Drv*Sen,Button,Force num: %d x %d, %d, %d", parm->drv_num,
parm->sen_num, parm->button_num, parm->force_num);
ts_info("Cmd: 0x%04X, %d", misc->cmd_addr,
misc->cmd_max_len);
ts_info("Cmd-Reply: 0x%04X, %d",
misc->cmd_reply_addr, misc->cmd_reply_len);
ts_info("FW-State: 0x%04X, %d",
misc->fw_state_addr, misc->fw_state_len);
ts_info("FW-Buffer: 0x%04X, %d",
misc->fw_buffer_addr, misc->fw_buffer_max_len);
ts_info("Touch-Data: 0x%04X, %d",
misc->touch_data_addr, misc->touch_data_head_len);
ts_info("point_struct_len: %d", misc->point_struct_len);
ts_info("mutual_rawdata_addr: 0x%04X",
misc->mutual_rawdata_addr);
ts_info("mutual_diffdata_addr: 0x%04X",
misc->mutual_diffdata_addr);
ts_info("self_rawdata_addr: 0x%04X",
misc->self_rawdata_addr);
ts_info("self_diffdata_addr: 0x%04X",
misc->self_diffdata_addr);
ts_info("stylus_rawdata_addr: 0x%04X, %d",
misc->stylus_rawdata_addr, misc->stylus_rawdata_len);
ts_info("esd_addr: 0x%04X", misc->esd_addr);
ts_info("frame_data_addr: 0x%04X", misc->frame_data_addr);
}
static int brl_get_ic_info(
struct goodix_ts_core *cd, struct goodix_ic_info *ic_info)
{
int ret, i;
u16 length = 0;
u32 ic_addr;
u8 afe_data[GOODIX_IC_INFO_MAX_LEN] = { 0 };
struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
if (cd->bus->ic_type == IC_TYPE_BERLIN_A)
ic_addr = GOODIX_IC_INFO_ADDR_BRA;
else
ic_addr = GOODIX_IC_INFO_ADDR;
for (i = 0; i < GOODIX_RETRY_3; i++) {
ret = hw_ops->read(cd, ic_addr, (u8 *)&length, sizeof(length));
if (ret) {
ts_info("failed get ic info length, %d", ret);
usleep_range(5000, 5100);
continue;
}
length = le16_to_cpu(length);
if (length >= GOODIX_IC_INFO_MAX_LEN) {
ts_info("invalid ic info length %d, retry %d", length,
i);
continue;
}
ret = hw_ops->read(cd, ic_addr, afe_data, length);
if (ret) {
ts_info("failed get ic info data, %d", ret);
usleep_range(5000, 5100);
continue;
}
/* judge whether the data is valid */
if (is_risk_data((const uint8_t *)afe_data, length)) {
ts_info("fw info data invalid");
usleep_range(5000, 5100);
continue;
}
if (checksum_cmp((const uint8_t *)afe_data, length,
CHECKSUM_MODE_U8_LE)) {
ts_info("fw info checksum error!");
usleep_range(5000, 5100);
continue;
}
break;
}
if (i == GOODIX_RETRY_3) {
ts_err("failed get ic info");
return -EINVAL;
}
ret = convert_ic_info(ic_info, afe_data);
if (ret) {
ts_err("convert ic info encounter error");
return ret;
}
print_ic_info(ic_info);
/* check some key info */
if (!ic_info->misc.cmd_addr || !ic_info->misc.fw_buffer_addr ||
!ic_info->misc.touch_data_addr) {
ts_err("cmd_addr fw_buf_addr and touch_data_addr is null");
return -EINVAL;
}
return 0;
}
#define GOODIX_ESD_TICK_WRITE_DATA 0xAA
static int brl_esd_check(struct goodix_ts_core *cd)
{
int ret;
u32 esd_addr;
u8 esd_value;
if (!cd->ic_info.misc.esd_addr)
return 0;
esd_addr = cd->ic_info.misc.esd_addr;
ret = cd->hw_ops->read(cd, esd_addr, &esd_value, 1);
if (ret) {
ts_err("failed get esd value, %d", ret);
return ret;
}
if (esd_value != 0xFF) {
ts_err("esd check failed, 0x%x", esd_value);
return -EINVAL;
}
esd_value = GOODIX_ESD_TICK_WRITE_DATA;
ret = cd->hw_ops->write(cd, esd_addr, &esd_value, 1);
if (ret) {
ts_err("failed refrash esd value");
return ret;
}
return 0;
}
#define IRQ_EVENT_HEAD_LEN 8
#define BYTES_PER_POINT 14
#define COOR_DATA_CHECKSUM_SIZE 2
#define GOODIX_TOUCH_EVENT 0x80
#define GOODIX_REQUEST_EVENT 0x40
#define GOODIX_GESTURE_EVENT 0x20
#define GOODIX_STATUS_EVENT 0x02
#define GOODIX_FP_EVENT 0x08
#define POINT_TYPE_STYLUS_HOVER 0x01
#define POINT_TYPE_STYLUS 0x03
static int point_struct_len;
static void goodix_parse_finger(
struct goodix_touch_data *touch_data, u8 *buf, int touch_num)
{
unsigned int id = 0;
u8 *coor_data;
int i;
struct goodix_ts_coords *coord;
coor_data = &buf[IRQ_EVENT_HEAD_LEN];
for (i = 0; i < touch_num; i++) {
id = (coor_data[0] >> 4) & 0x0F;
if (id >= GOODIX_MAX_TOUCH) {
ts_info("invalid finger id =%d", id);
touch_data->touch_num = 0;
return;
}
coord = &touch_data->coords[id];
coord->status = TS_TOUCH;
coord->x = le16_to_cpup((__le16 *)(coor_data + 2));
coord->y = le16_to_cpup((__le16 *)(coor_data + 4));
coord->w = le16_to_cpup((__le16 *)(coor_data + 6));
if (point_struct_len > 8) {
coord->p = coor_data[8];
coord->major = le16_to_cpup((__le16 *)(coor_data + 9));
coord->minor = le16_to_cpup((__le16 *)(coor_data + 11));
coord->angle = (signed char)coor_data[13];
}
coor_data += point_struct_len;
}
touch_data->touch_num = touch_num;
}
static unsigned int goodix_pen_btn_code[] = { BTN_STYLUS, BTN_STYLUS2 };
static void goodix_parse_pen(
struct goodix_pen_data *pen_data, u8 *buf, int touch_num)
{
unsigned int id = 0;
u8 cur_key_map = 0;
u8 *coor_data;
int16_t x_angle, y_angle;
int i;
pen_data->coords.tool_type = BTN_TOOL_PEN;
if (touch_num) {
pen_data->coords.status = TS_TOUCH;
coor_data = &buf[IRQ_EVENT_HEAD_LEN];
id = (coor_data[0] >> 4) & 0x0F;
pen_data->coords.x = le16_to_cpup((__le16 *)(coor_data + 2));
pen_data->coords.y = le16_to_cpup((__le16 *)(coor_data + 4));
pen_data->coords.p = le16_to_cpup((__le16 *)(coor_data + 6));
x_angle = le16_to_cpup((__le16 *)(coor_data + 8));
y_angle = le16_to_cpup((__le16 *)(coor_data + 10));
pen_data->coords.tilt_x = x_angle / 100;
pen_data->coords.tilt_y = y_angle / 100;
} else {
pen_data->coords.status = TS_RELEASE;
}
cur_key_map = (buf[3] & 0x0F) >> 1;
for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) {
pen_data->keys[i].code = goodix_pen_btn_code[i];
if (!(cur_key_map & (1 << i)))
continue;
pen_data->keys[i].status = TS_TOUCH;
}
}
static int goodix_update_heatmap(struct goodix_ts_core *cd, uint8_t *event_data)
{
struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
int tx = cd->ic_info.parm.drv_num;
int rx = cd->ic_info.parm.sen_num;
int mutual_len = sizeof(struct goodix_mutual_data) + tx * rx * 2;
struct goodix_mutual_data *mutual_data;
struct goodix_self_sensing_data *self_sensing_data;
uint8_t *mutual_head = event_data + misc->frame_data_addr -
misc->touch_data_addr +
misc->frame_data_head_len + misc->fw_attr_len +
misc->fw_log_len;
mutual_data = (struct goodix_mutual_data *)mutual_head;
self_sensing_data =
(struct goodix_self_sensing_data *)(mutual_head + mutual_len);
goodix_rotate_abcd2cbad(tx, rx, mutual_data->data, cd->mutual_data);
memcpy(cd->self_sensing_data, self_sensing_data->data, (tx + rx) * 2);
return 0;
}
static int goodix_touch_handler(struct goodix_ts_core *cd,
struct goodix_ts_event *ts_event,
struct goodix_ts_touch_event_data *event_data)
{
struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
struct goodix_touch_data *touch_data = &ts_event->touch_data;
struct goodix_pen_data *pen_data = &ts_event->pen_data;
int ret = 0;
u8 point_type = 0;
static u8 pre_finger_num;
static u8 pre_pen_num;
if (event_data->touches > GOODIX_MAX_TOUCH) {
ts_debug("invalid touch num %d", event_data->touches);
return -EINVAL;
}
if (event_data->touches > 0) {
point_type = event_data->data[0] & 0x0F;
if (point_type == POINT_TYPE_STYLUS ||
point_type == POINT_TYPE_STYLUS_HOVER) {
ret = checksum_cmp(event_data->data, 16 + 2, CHECKSUM_MODE_U8_LE);
if (ret) {
ts_debug("touch data checksum error");
ts_debug("data:%*ph", 16 + 2, event_data->data);
return -EINVAL;
}
} else {
point_struct_len = misc->point_struct_len;
ret = checksum_cmp(event_data->data,
event_data->touches * point_struct_len + 2,
CHECKSUM_MODE_U8_LE);
if (ret) {
ts_debug("touch data checksum error");
ts_debug("data:%*ph",
event_data->touches * point_struct_len +
2,
event_data->data);
return -EINVAL;
}
}
}
goodix_update_heatmap(cd, (u8 *)event_data);
ts_event->fp_flag = event_data->fp_flag;
if (event_data->touches > 0 &&
(point_type == POINT_TYPE_STYLUS ||
point_type == POINT_TYPE_STYLUS_HOVER)) {
/* stylus info */
if (pre_finger_num) {
ts_event->event_type = EVENT_TOUCH;
goodix_parse_finger(touch_data, (u8 *)event_data, 0);
pre_finger_num = 0;
} else {
pre_pen_num = 1;
ts_event->event_type = EVENT_PEN;
goodix_parse_pen(pen_data, (u8 *)event_data,
event_data->touches);
}
} else {
/* finger info */
if (pre_pen_num) {
ts_event->event_type = EVENT_PEN;
goodix_parse_pen(pen_data, (u8 *)event_data, 0);
pre_pen_num = 0;
} else {
ts_event->event_type = EVENT_TOUCH;
goodix_parse_finger(touch_data, (u8 *)event_data,
event_data->touches);
pre_finger_num = event_data->touches;
}
}
if (event_data->status_changed)
ts_event->event_type |= EVENT_STATUS;
/* process custom info */
if (event_data->custom_coor_info_flag)
ts_debug("TODO add custom info process function");
return 0;
}
static int brl_event_handler(
struct goodix_ts_core *cd, struct goodix_ts_event *ts_event)
{
struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
struct goodix_ts_event_data *event_data;
int ret;
ret = hw_ops->read_fast(cd, misc->touch_data_addr,
cd->touch_frame_package, cd->touch_frame_size);
if (ret) {
ts_debug("failed get event head data");
return ret;
}
event_data =
(struct goodix_ts_event_data *)cd->touch_frame_package->data;
if (event_data->type == 0x00) {
ts_debug("invalid touch head");
return -EINVAL;
}
if (checksum_cmp((u8 *)event_data, IRQ_EVENT_HEAD_LEN,
CHECKSUM_MODE_U8_LE)) {
ts_debug("touch head checksum err[%*ph]", IRQ_EVENT_HEAD_LEN,
event_data);
hw_ops->after_event_handler(cd);
return -EINVAL;
}
/* clean event buffer */
memset(ts_event, 0, sizeof(*ts_event));
ts_event->event_type = EVENT_INVALID;
ts_event->clear_count = event_data->clear_count;
/* read status event */
if (event_data->status_changed)
hw_ops->read(cd, 0x1021C, (u8 *)&ts_event->status_data,
sizeof(ts_event->status_data));
if (event_data->type & (GOODIX_TOUCH_EVENT >> 4))
return goodix_touch_handler(cd, ts_event,
(struct goodix_ts_touch_event_data *)event_data);
if (event_data->type & (GOODIX_REQUEST_EVENT >> 4)) {
struct goodix_ts_request_event_data *request =
(struct goodix_ts_request_event_data *)event_data;
ts_event->event_type = EVENT_REQUEST;
if (request->request_type == BRL_REQUEST_CODE_CONFIG)
ts_event->request_code = REQUEST_TYPE_CONFIG;
else if (request->request_type == BRL_REQUEST_CODE_RESET)
ts_event->request_code = REQUEST_TYPE_RESET;
else if (request->request_type == BRL_REQUEST_CODE_UPDATE)
ts_event->request_code = REQUEST_TYPE_UPDATE;
else if (request->request_type == BRL_REQUEST_PEN_FREQ_HOP)
if (request->data_len && !checksum_cmp(request->data,
request->data_len + 2,
CHECKSUM_MODE_U8_LE)) {
if (request->data_len + 2 <=
GOODIX_REQUEST_DATA_LEN) {
memcpy(ts_event->request_data,
request->data,
request->data_len + 2);
ts_event->request_code =
REQUEST_PEN_FREQ_HOP;
} else {
ts_err("request data len exceed limit %d",
request->data_len + 2);
}
} else {
ts_info("invalid request data %d",
request->data_len);
ts_info("request data:%*ph", request->data_len,
request->data);
}
else
ts_debug("unsupported request code 0x%x",
request->request_type);
}
if (event_data->type & (GOODIX_GESTURE_EVENT >> 4)) {
struct goodix_ts_gesture_event_data *gesture =
(struct goodix_ts_gesture_event_data *)event_data;
ts_event->event_type = EVENT_GESTURE;
if (event_data->status_changed)
ts_event->event_type |= EVENT_STATUS;
ts_event->gesture_data.gesture_type = gesture->gesture_type;
ts_event->gesture_data.touches = gesture->touches;
memcpy(ts_event->gesture_data.data, gesture->data,
GOODIX_GESTURE_DATA_LEN);
}
return 0;
}
static int brl_after_event_handler(struct goodix_ts_core *cd)
{
struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
struct goodix_ic_info_misc *misc = &cd->ic_info.misc;
u8 sync_clean[1] = { 0 };
if (cd->tools_ctrl_sync)
return 0;
return hw_ops->write(cd, misc->touch_data_addr, sync_clean, sizeof(sync_clean));
}
static int brld_get_framedata(
struct goodix_ts_core *cd, struct ts_rawdata_info *info)
{
int ret;
unsigned char val;
int retry = 20;
struct frame_head *frame_head;
u8 *frame_buf;
unsigned char *cur_ptr;
unsigned int flag_addr = cd->ic_info.misc.frame_data_addr;
frame_buf = kzalloc(GOODIX_MAX_FRAMEDATA_LEN, GFP_KERNEL);
if (frame_buf == NULL)
return -ENOMEM;
/* clean touch event flag */
val = 0;
ret = brl_write(cd, flag_addr, &val, 1);
if (ret < 0) {
ts_err("clean touch event failed, exit!");
goto exit;
}
while (retry--) {
usleep_range(2000, 2100);
ret = brl_read(cd, flag_addr, &val, 1);
if (!ret && (val & GOODIX_TOUCH_EVENT))
break;
}
if (retry < 0) {
ts_err("framedata is not ready val:0x%02x, exit!", val);
ret = -EINVAL;
goto exit;
}
ret = brl_read(cd, flag_addr, frame_buf, GOODIX_MAX_FRAMEDATA_LEN);
if (ret < 0) {
ts_err("read frame data failed");
goto exit;
}
if (checksum_cmp(frame_buf, cd->ic_info.misc.frame_data_head_len,
CHECKSUM_MODE_U8_LE)) {
ts_err("frame head checksum error");
ret = -EINVAL;
goto exit;
}
frame_head = (struct frame_head *)frame_buf;
if (checksum_cmp(frame_buf, frame_head->cur_frame_len,
CHECKSUM_MODE_U16_LE)) {
ts_err("frame body checksum error");
ret = -EINVAL;
goto exit;
}
cur_ptr = frame_buf;
cur_ptr += cd->ic_info.misc.frame_data_head_len;
cur_ptr += cd->ic_info.misc.fw_attr_len;
cur_ptr += cd->ic_info.misc.fw_log_len;
memcpy((u8 *)(info->buff + info->used_size), cur_ptr + 8,
cd->ic_info.misc.mutual_struct_len - 8);
exit:
kfree(frame_buf);
return ret;
}
static int brld_get_cap_data(
struct goodix_ts_core *cd, struct ts_rawdata_info *info)
{
struct goodix_ts_cmd temp_cmd;
int tx = cd->ic_info.parm.drv_num;
int rx = cd->ic_info.parm.sen_num;
int size = tx * rx;
int ret;
/* disable irq & close esd */
brl_irq_enable(cd, false);
goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
info->buff[0] = rx;
info->buff[1] = tx;
info->used_size = 2;
temp_cmd.cmd = 0x90;
temp_cmd.data[0] = 0x81;
temp_cmd.len = 5;
ret = brl_send_cmd(cd, &temp_cmd);
if (ret < 0) {
ts_err("report rawdata failed, exit!");
goto exit;
}
ret = brld_get_framedata(cd, info);
if (ret < 0) {
ts_err("brld get rawdata failed");
goto exit;
}
goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size], NULL);
info->used_size += size;
temp_cmd.cmd = 0x90;
temp_cmd.data[0] = 0x82;
temp_cmd.len = 5;
ret = brl_send_cmd(cd, &temp_cmd);
if (ret < 0) {
ts_err("report diffdata failed, exit!");
goto exit;
}
ret = brld_get_framedata(cd, info);
if (ret < 0) {
ts_err("brld get diffdata failed");
goto exit;
}
goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size], NULL);
info->used_size += size;
exit:
temp_cmd.cmd = 0x90;
temp_cmd.data[0] = 0;
temp_cmd.len = 5;
brl_send_cmd(cd, &temp_cmd);
/* enable irq & esd */
brl_irq_enable(cd, true);
goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
return ret;
}
#define GOODIX_CMD_RAWDATA 2
#define GOODIX_CMD_COORD 0
static int brl_get_capacitance_data(
struct goodix_ts_core *cd, struct ts_rawdata_info *info)
{
int ret;
int retry = 20;
struct goodix_ts_cmd temp_cmd;
u32 flag_addr = cd->ic_info.misc.touch_data_addr;
u32 raw_addr = cd->ic_info.misc.mutual_rawdata_addr;
u32 diff_addr = cd->ic_info.misc.mutual_diffdata_addr;
int tx = cd->ic_info.parm.drv_num;
int rx = cd->ic_info.parm.sen_num;
int size = tx * rx;
u8 val;
if (!info) {
ts_err("input null ptr");
return -EIO;
}
if (cd->bus->ic_type == IC_TYPE_BERLIN_D ||
cd->bus->ic_type == IC_TYPE_NOTTINGHAM)
return brld_get_cap_data(cd, info);
/* disable irq & close esd */
brl_irq_enable(cd, false);
goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
/* switch rawdata mode */
temp_cmd.cmd = GOODIX_CMD_RAWDATA;
temp_cmd.len = 4;
ret = brl_send_cmd(cd, &temp_cmd);
if (ret < 0) {
ts_err("switch rawdata mode failed, exit!");
goto exit;
}
/* clean touch event flag */
val = 0;
ret = brl_write(cd, flag_addr, &val, 1);
if (ret < 0) {
ts_err("clean touch event failed, exit!");
goto exit;
}
while (retry--) {
usleep_range(5000, 5100);
ret = brl_read(cd, flag_addr, &val, 1);
if (!ret && (val & GOODIX_TOUCH_EVENT))
break;
}
if (retry < 0) {
ts_err("rawdata is not ready val:0x%02x, exit!", val);
goto exit;
}
/* obtain rawdata & diff_rawdata */
info->buff[0] = rx;
info->buff[1] = tx;
info->used_size = 2;
ret = brl_read(cd, raw_addr, (u8 *)&info->buff[info->used_size],
size * sizeof(s16));
if (ret < 0) {
ts_err("obtian raw_data failed, exit!");
goto exit;
}
goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size], NULL);
info->used_size += size;
ret = brl_read(cd, diff_addr, (u8 *)&info->buff[info->used_size],
size * sizeof(s16));
if (ret < 0) {
ts_err("obtian diff_data failed, exit!");
goto exit;
}
goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size], NULL);
info->used_size += size;
exit:
/* switch coor mode */
temp_cmd.cmd = GOODIX_CMD_COORD;
temp_cmd.len = 4;
brl_send_cmd(cd, &temp_cmd);
/* clean touch event flag */
val = 0;
brl_write(cd, flag_addr, &val, 1);
/* enable irq & esd */
brl_irq_enable(cd, true);
goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
return ret;
}
#define GOODIX_GET_SCAN_MODE_ADDR 0x10219
int brl_get_scan_mode(struct goodix_ts_core *cd, enum raw_scan_mode* mode)
{
int ret = 0;
ret = cd->hw_ops->read(cd, GOODIX_GET_SCAN_MODE_ADDR, mode, 1);
if (ret != 0) {
ts_err("failed to get scan mode, ret: %d", ret);
return ret;
}
return 0;
}
#define GOODIX_CMD_SET_SCAN_MODE 0x9F
int brl_set_scan_mode(struct goodix_ts_core *cd, enum raw_scan_mode mode)
{
struct goodix_ts_cmd cmd;
static const uint8_t raw_scan_mode_cmd_codes[] ={
[RAW_SCAN_MODE_AUTO] = 0x00,
[RAW_SCAN_MODE_NORMAL_ACTIVE] = 0x02,
[RAW_SCAN_MODE_NORMAL_IDLE] = 0x03,
[RAW_SCAN_MODE_LOW_POWER_ACTIVE] = 0x00,
[RAW_SCAN_MODE_LOW_POWER_IDLE] = 0x00,
[RAW_SCAN_MODE_SLEEP] = 0x00,
};
cmd.cmd = GOODIX_CMD_SET_SCAN_MODE;
cmd.len = 5;
cmd.data[0] = raw_scan_mode_cmd_codes[mode];
if (cd->hw_ops->send_cmd(cd, &cmd))
ts_err("failed set scan mode cmd");
return 0;
}
#define GOODIX_CMD_SET_CONTINUOUSLY_REPORT_ENABLED 0xC6
int brl_set_continuously_report_enabled(struct goodix_ts_core *cd, bool enabled)
{
struct goodix_ts_cmd cmd;
cmd.cmd = GOODIX_CMD_SET_CONTINUOUSLY_REPORT_ENABLED;
cmd.len = 5;
cmd.data[0] = enabled ? 0 : 1;
if (cd->hw_ops->send_cmd(cd, &cmd))
ts_err("failed set continuous mode cmd");
return 0;
}
#define GOODIX_CMD_SET_FRAMEDATA_ENABLED 0x90
#define GOODIX_CMD_SET_HEATMAP_ENABLED 0xC9
int brl_set_heatmap_enabled(struct goodix_ts_core *cd, bool enabled)
{
struct goodix_ts_cmd cmd;
int ret = 0;
cmd.cmd = GOODIX_CMD_SET_HEATMAP_ENABLED;
cmd.len = 5;
cmd.data[0] = enabled ? 1 : 0;
ret = cd->hw_ops->send_cmd(cd, &cmd);
if (ret != 0) {
ts_err("failed to set heatmap %s, err: %d",
enabled ? "enabled" : "disabled", ret);
return ret;
}
return ret;
}
#define GOODIX_FEATURE_STATUS_ADDR 0x1021A
#define GOODIX_CMD_SET_CUSTOM_MODE 0xC7
#define CUSTOM_MODE_PALM 0
#define CUSTOM_MODE_GRIP 3
#define CUSTOM_MODE_MASK_PALM 0x02
#define CUSTOM_MODE_MASK_GRIP 0x04
#define CUSTOM_MODE_MASK_SCREEN_PROTECTOR 0x40
int brl_set_palm_enabled(struct goodix_ts_core *cd, bool enabled)
{
struct goodix_ts_cmd cmd = { 0 };
cmd.cmd = GOODIX_CMD_SET_CUSTOM_MODE;
cmd.len = 6;
cmd.data[0] = CUSTOM_MODE_PALM;
cmd.data[1] = enabled ? 1 : 0;
if (cd->hw_ops->send_cmd(cd, &cmd))
ts_err("failed to %s palm mode",
enabled ? "enable" : "disable");
return 0;
}
int brl_get_palm_enabled(struct goodix_ts_core *cd, bool *enabled)
{
int ret = 0;
u8 val;
ret = cd->hw_ops->read(cd, GOODIX_FEATURE_STATUS_ADDR, &val, 1);
if (ret != 0) {
ts_err("failed to get palm enabled, ret: %d", ret);
*enabled = false;
return ret;
}
*enabled = (val & CUSTOM_MODE_MASK_PALM) != 0;
return ret;
}
int brl_set_grip_enabled(struct goodix_ts_core *cd, bool enabled)
{
struct goodix_ts_cmd cmd = { 0 };
cmd.cmd = GOODIX_CMD_SET_CUSTOM_MODE;
cmd.len = 6;
cmd.data[0] = CUSTOM_MODE_GRIP;
cmd.data[1] = enabled ? 1 : 0;
if (cd->hw_ops->send_cmd(cd, &cmd))
ts_err("failed to %s grip mode",
enabled ? "enable" : "disable");
return 0;
}
int brl_get_grip_enabled(struct goodix_ts_core *cd, bool *enabled)
{
int ret = 0;
u8 val;
ret = cd->hw_ops->read(cd, GOODIX_FEATURE_STATUS_ADDR, &val, 1);
if (ret != 0) {
ts_err("failed to get grip enabled, ret: %d", ret);
*enabled = false;
return ret;
}
*enabled = (val & CUSTOM_MODE_MASK_GRIP) != 0;
return ret;
}
#define GOODIX_CMD_SET_SCREEN_PROTECTOR_ENABLED 0x72
int brl_set_screen_protector_mode_enabled(
struct goodix_ts_core *cd, bool enabled)
{
struct goodix_ts_cmd cmd = { 0 };
cmd.cmd = GOODIX_CMD_SET_SCREEN_PROTECTOR_ENABLED;
cmd.len = 5;
cmd.data[0] = enabled ? 1 : 0;
if (cd->hw_ops->send_cmd(cd, &cmd))
ts_err("failed to %s screen protector mode",
enabled ? "enable" : "disable");
return 0;
}
int brl_get_screen_protector_mode_enabled(
struct goodix_ts_core *cd, bool *enabled)
{
int ret = 0;
u8 val;
ret = cd->hw_ops->read(cd, GOODIX_FEATURE_STATUS_ADDR, &val, 1);
if (ret != 0) {
ts_err("failed to get screen protector mode enabled, ret: %d",
ret);
*enabled = false;
return ret;
}
*enabled = (val & CUSTOM_MODE_MASK_SCREEN_PROTECTOR) != 0;
return ret;
}
int brl_get_mutual_data(struct goodix_ts_core *cd, enum frame_data_type type)
{
int ret = 0;
u8 val;
int tx = cd->ic_info.parm.drv_num;
int rx = cd->ic_info.parm.sen_num;
u32 mutual_addr;
u32 flag_addr = cd->ic_info.misc.frame_data_addr;
int retry = 20;
struct goodix_ts_cmd cmd = { 0 };
mutual_addr = cd->ic_info.misc.frame_data_addr +
cd->ic_info.misc.frame_data_head_len +
cd->ic_info.misc.fw_attr_len +
cd->ic_info.misc.fw_log_len + 8;
cmd.cmd = GOODIX_CMD_SET_FRAMEDATA_ENABLED;
cmd.len = 5;
cmd.data[0] = type;
ret = cd->hw_ops->send_cmd(cd, &cmd);
if (ret < 0) {
ts_err("report rawdata failed, exit!");
goto exit;
}
/* clean touch event flag */
val = 0;
ret = cd->hw_ops->write(cd, flag_addr, &val, 1);
if (ret < 0) {
ts_err("clean touch event failed, exit!");
goto exit;
}
while (retry--) {
usleep_range(2000, 2100);
ret = cd->hw_ops->read(cd, flag_addr, &val, 1);
if (!ret && (val & 0x80))
break;
}
if (retry < 0) {
ts_err("framedata is not ready val:0x%02x, exit!", val);
ret = -EINVAL;
goto exit;
}
ret = cd->hw_ops->read(cd, mutual_addr, (u8*)cd->mutual_data, tx * rx * 2);
if (ret < 0) {
ts_err("read frame data failed");
goto exit;
}
goodix_rotate_abcd2cbad(tx, rx, (s16 *)cd->mutual_data,
(s16 *)cd->mutual_data_manual);
exit:
cmd.cmd = GOODIX_CMD_SET_FRAMEDATA_ENABLED;
cmd.data[0] = 0;
cmd.len = 5;
if (ret == 0) {
ret = cd->hw_ops->send_cmd(cd, &cmd);
} else {
cd->hw_ops->send_cmd(cd, &cmd);
}
return ret;
}
int brl_get_self_sensing_data(struct goodix_ts_core *cd, enum frame_data_type type)
{
int ret = 0;
u8 val;
int tx = cd->ic_info.parm.drv_num;
int rx = cd->ic_info.parm.sen_num;
u32 self_addr;
u32 flag_addr = cd->ic_info.misc.frame_data_addr;
int retry = 20;
struct goodix_ts_cmd cmd = { 0 };
self_addr = cd->ic_info.misc.frame_data_addr +
cd->ic_info.misc.frame_data_head_len +
cd->ic_info.misc.fw_attr_len + cd->ic_info.misc.fw_log_len +
cd->ic_info.misc.mutual_struct_len + 10;
cmd.cmd = GOODIX_CMD_SET_FRAMEDATA_ENABLED;
cmd.len = 5;
cmd.data[0] = type;
ret = cd->hw_ops->send_cmd(cd, &cmd);
if (ret < 0) {
ts_err("report rawdata failed, exit!");
goto exit;
}
/* clean touch event flag */
val = 0;
ret = cd->hw_ops->write(cd, flag_addr, &val, 1);
if (ret < 0) {
ts_err("clean touch event failed, exit!");
goto exit;
}
while (retry--) {
usleep_range(2000, 2100);
ret = cd->hw_ops->read(cd, flag_addr, &val, 1);
if (!ret && (val & 0x80))
break;
}
if (retry < 0) {
ts_err("framedata is not ready val:0x%02x, exit!", val);
ret = -EINVAL;
goto exit;
}
ret = cd->hw_ops->read(cd, self_addr, (u8*)cd->self_sensing_data_manual,
(tx + rx) * 2);
if (ret < 0) {
ts_err("read frame data failed");
goto exit;
}
exit:
cmd.cmd = GOODIX_CMD_SET_FRAMEDATA_ENABLED;
cmd.data[0] = 0;
cmd.len = 5;
if (ret == 0) {
ret = cd->hw_ops->send_cmd(cd, &cmd);
} else {
cd->hw_ops->send_cmd(cd, &cmd);
}
return ret;
}
static struct goodix_ts_hw_ops brl_hw_ops = {
.power_on = brl_power_on,
.resume = brl_resume,
.suspend = brl_suspend,
.gesture = brl_gesture,
.reset = brl_reset,
.irq_enable = brl_irq_enable,
.read = brl_read,
.read_fast = brl_read_fast,
.write = brl_write,
.send_cmd = brl_send_cmd,
.send_config = brl_send_config,
.read_config = brl_read_config,
.read_version = brl_read_version,
.get_ic_info = brl_get_ic_info,
.esd_check = brl_esd_check,
.event_handler = brl_event_handler,
.after_event_handler = brl_after_event_handler,
.get_capacitance_data = brl_get_capacitance_data,
.ping = brl_dev_confirm,
.get_scan_mode = brl_get_scan_mode,
.set_scan_mode = brl_set_scan_mode,
.set_continuously_report_enabled = brl_set_continuously_report_enabled,
.set_heatmap_enabled = brl_set_heatmap_enabled,
.set_palm_enabled = brl_set_palm_enabled,
.get_palm_enabled = brl_get_palm_enabled,
.set_grip_enabled = brl_set_grip_enabled,
.get_grip_enabled = brl_get_grip_enabled,
.set_screen_protector_mode_enabled =
brl_set_screen_protector_mode_enabled,
.get_screen_protector_mode_enabled =
brl_get_screen_protector_mode_enabled,
.get_mutual_data = brl_get_mutual_data,
.get_self_sensing_data = brl_get_self_sensing_data,
};
struct goodix_ts_hw_ops *goodix_get_hw_ops(void)
{
return &brl_hw_ops;
}