6db4831e98
Android 14
2419 lines
58 KiB
C
2419 lines
58 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/interrupt.h>
|
|
#include "goodix_ts_core.h"
|
|
#include "goodix_cfg_bin.h"
|
|
#include <linux/uaccess.h>
|
|
#include <linux/miscdevice.h>
|
|
|
|
#define TS_DT_COMPATIBLE "goodix,gt9886"
|
|
#define TS_DRIVER_NAME "GT9886"
|
|
#define I2C_MAX_TRANSFER_SIZE 256
|
|
#define TS_ADDR_LENGTH 2
|
|
#define TS_DOZE_ENABLE_RETRY_TIMES 3
|
|
#define TS_DOZE_DISABLE_RETRY_TIMES 9
|
|
#define TS_WAIT_CFG_READY_RETRY_TIMES 30
|
|
#define TS_WAIT_CMD_FREE_RETRY_TIMES 10
|
|
|
|
#define TS_REG_COORDS_BASE 0x824E
|
|
#define TS_REG_CMD 0x8040
|
|
#define TS_REG_REQUEST 0x8044
|
|
#define TS_REG_VERSION 0x8240
|
|
#define TS_REG_CFG_BASE 0x8050
|
|
#define TS_REG_DOZE_CTRL 0x30F0
|
|
#define TS_REG_DOZE_STAT 0x3100
|
|
#define TS_REG_ESD_TICK_R 0x3103
|
|
#define TS_REG_PID 0x4535
|
|
|
|
#define CFG_XMAX_OFFSET (0x8052 - 0x8050)
|
|
#define CFG_YMAX_OFFSET (0x8054 - 0x8050)
|
|
|
|
#define REQUEST_HANDLED 0x00
|
|
#define REQUEST_CONFIG 0x01
|
|
#define REQUEST_BAKREF 0x02
|
|
#define REQUEST_RESET 0x03
|
|
#define REQUEST_MAINCLK 0x04
|
|
#define REQUEST_IDLE 0x05
|
|
|
|
#define COMMAND_SLEEP 0x05
|
|
#define COMMAND_CLOSE_HID 0xaa
|
|
#define COMMAND_START_SEND_CFG 0x80
|
|
#define COMMAND_END_SEND_CFG 0x83
|
|
#define COMMAND_SEND_SMALL_CFG 0x81
|
|
#define COMMAND_SEND_CFG_PREPARE_OK 0x82
|
|
#define COMMAND_START_READ_CFG 0x86
|
|
#define COMMAND_READ_CFG_PREPARE_OK 0x85
|
|
|
|
#define BYTES_PER_COORD 8
|
|
#define TS_MAX_SENSORID 5
|
|
#define TS_CFG_MAX_LEN 1024
|
|
#define TS_CFG_HEAD_LEN 4
|
|
#define TS_CFG_BAG_NUM_INDEX 2
|
|
#define TS_CFG_BAG_START_INDEX 4
|
|
#if TS_CFG_MAX_LEN > GOODIX_CFG_MAX_SIZE
|
|
#error GOODIX_CFG_MAX_SIZE too small, please fix.
|
|
#endif
|
|
|
|
#define TAG_I2C ""
|
|
#define TS_DOZE_DISABLE_DATA 0xAA
|
|
#define TS_DOZE_CLOSE_OK_DATA 0xBB
|
|
#define TS_DOZE_ENABLE_DATA 0xCC
|
|
#define TS_CMD_REG_READY 0xFF
|
|
|
|
/***********for config & firmware*************/
|
|
const char *gt9886_firmware_buf;
|
|
const char *gt9886_config_buf;
|
|
|
|
static struct goodix_ts_board_data *touch_filter_bdata;
|
|
static int tpd_misc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return nonseekable_open(inode, file);
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
static long tpd_compat_ioctl(
|
|
struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
long ret;
|
|
void __user *arg32 = compat_ptr(arg);
|
|
|
|
if (!file->f_op || !file->f_op->unlocked_ioctl)
|
|
return -ENOTTY;
|
|
switch (cmd) {
|
|
case COMPAT_TPD_GET_FILTER_PARA:
|
|
if (arg32 == NULL) {
|
|
ts_err("invalid argument.");
|
|
return -EINVAL;
|
|
}
|
|
ret = file->f_op->unlocked_ioctl(file, TPD_GET_FILTER_PARA,
|
|
(unsigned long)arg32);
|
|
if (ret) {
|
|
ts_err("TPD_GET_FILTER_PARA unlocked_ioctl failed.");
|
|
return ret;
|
|
}
|
|
break;
|
|
default:
|
|
ts_err("tpd: unknown IOCTL: 0x%08x\n", cmd);
|
|
ret = -ENOIOCTLCMD;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static long tpd_unlocked_ioctl(struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
/* char strbuf[256]; */
|
|
void __user *data;
|
|
|
|
long err = 0;
|
|
|
|
if (_IOC_DIR(cmd) & _IOC_READ)
|
|
err = !access_ok(VERIFY_WRITE,
|
|
(void __user *)arg, _IOC_SIZE(cmd));
|
|
else if (_IOC_DIR(cmd) & _IOC_WRITE)
|
|
err = !access_ok(VERIFY_READ,
|
|
(void __user *)arg, _IOC_SIZE(cmd));
|
|
if (err) {
|
|
pr_info("tpd: access error: %08X, (%2d, %2d)\n",
|
|
cmd, _IOC_DIR(cmd), _IOC_SIZE(cmd));
|
|
return -EFAULT;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case TPD_GET_FILTER_PARA:
|
|
data = (void __user *) arg;
|
|
|
|
if (data == NULL) {
|
|
err = -EINVAL;
|
|
ts_err("GET_FILTER_PARA: data is null\n");
|
|
break;
|
|
}
|
|
|
|
if (copy_to_user(data,
|
|
&(touch_filter_bdata->tpd_filter),
|
|
sizeof(struct tpd_filter_t))) {
|
|
ts_err("GET_FILTER_PARA: copy data error\n");
|
|
err = -EFAULT;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
ts_info("tpd: unknown IOCTL: 0x%08x\n", cmd);
|
|
err = -ENOIOCTLCMD;
|
|
break;
|
|
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static const struct file_operations gt9886_fops = {
|
|
/* .owner = THIS_MODULE, */
|
|
.open = tpd_misc_open,
|
|
.release = NULL,
|
|
.unlocked_ioctl = tpd_unlocked_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = tpd_compat_ioctl,
|
|
#endif
|
|
};
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
static struct miscdevice tpd_misc_device = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "touch",
|
|
.fops = >9886_fops,
|
|
};
|
|
|
|
int gt9886_touch_filter_register(void)
|
|
{
|
|
return misc_register(&tpd_misc_device);
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
/**
|
|
* goodix_parse_dt_resolution - parse resolution from dt
|
|
* @node: devicetree node
|
|
* @board_data: pointer to board data structure
|
|
* return: 0 - no error, <0 error
|
|
*/
|
|
static int goodix_parse_dt_resolution(struct device_node *node,
|
|
struct goodix_ts_board_data *board_data)
|
|
{
|
|
int r, err;
|
|
|
|
r = of_property_read_u32(node, "goodix,panel-max-id",
|
|
&board_data->panel_max_id);
|
|
if (r) {
|
|
err = -ENOENT;
|
|
} else {
|
|
if (board_data->panel_max_id > GOODIX_MAX_TOUCH)
|
|
board_data->panel_max_id = GOODIX_MAX_TOUCH;
|
|
}
|
|
|
|
r = of_property_read_u32(node, "goodix,panel-max-x",
|
|
&board_data->panel_max_x);
|
|
if (r)
|
|
err = -ENOENT;
|
|
|
|
r = of_property_read_u32(node, "goodix,panel-max-y",
|
|
&board_data->panel_max_y);
|
|
if (r)
|
|
err = -ENOENT;
|
|
|
|
ts_info("Set Default lcm-resolution!");
|
|
r = of_property_read_u32(node, "goodix,input-max-x",
|
|
&board_data->input_max_x);
|
|
if (r)
|
|
err = -ENOENT;
|
|
|
|
r = of_property_read_u32(node, "goodix,input-max-y",
|
|
&board_data->input_max_y);
|
|
if (r)
|
|
err = -ENOENT;
|
|
|
|
r = of_property_read_u32(node, "goodix,panel-max-w",
|
|
&board_data->panel_max_w);
|
|
if (r)
|
|
err = -ENOENT;
|
|
|
|
r = of_property_read_u32(node, "goodix,panel-max-p",
|
|
&board_data->panel_max_p);
|
|
if (r)
|
|
err = -ENOENT;
|
|
|
|
board_data->swap_axis = of_property_read_bool(node,
|
|
"goodix,swap-axis");
|
|
|
|
board_data->x2x = of_property_read_bool(node,
|
|
"goodix,x2x");
|
|
|
|
board_data->y2y = of_property_read_bool(node,
|
|
"goodix,y2y");
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* goodix_parse_dt - parse board data from dt
|
|
* @dev: pointer to device
|
|
* @board_data: pointer to board data structure
|
|
* return: 0 - no error, <0 error
|
|
*/
|
|
static int goodix_parse_dt(struct device_node *node,
|
|
struct goodix_ts_board_data *board_data)
|
|
{
|
|
struct property *prop;
|
|
int r;
|
|
|
|
if (!board_data) {
|
|
ts_err("Invalid board data");
|
|
return -EINVAL;
|
|
}
|
|
|
|
r = of_get_named_gpio(node, "goodix,reset-gpio", 0);
|
|
if (r < 0) {
|
|
ts_err("Invalid reset-gpio in dt: %d", r);
|
|
return -EINVAL;
|
|
} else {
|
|
ts_info("Parse reset-gpio[%d] from dt", r);
|
|
board_data->reset_gpio = r;
|
|
}
|
|
|
|
r = of_get_named_gpio(node, "goodix,irq-gpio", 0);
|
|
if (r < 0) {
|
|
ts_err("Invalid irq-gpio in dt: %d", r);
|
|
return -EINVAL;
|
|
} else {
|
|
ts_info("Parse irq-gpio[%d] from dt", r);
|
|
board_data->irq_gpio = r;
|
|
}
|
|
|
|
r = of_property_read_u32(node, "goodix,irq-flags",
|
|
&board_data->irq_flags);
|
|
if (r) {
|
|
ts_err("Invalid irq-flags");
|
|
return -EINVAL;
|
|
}
|
|
|
|
r = of_property_read_string(node,
|
|
"goodix,firmware-version",
|
|
>9886_firmware_buf);
|
|
if (r < 0)
|
|
ts_err("Invalid firmware version in dts : %d", r);
|
|
|
|
r = of_property_read_string(node,
|
|
"goodix,config-version",
|
|
>9886_config_buf);
|
|
if (r < 0) {
|
|
ts_err("Invalid config version in dts : %d", r);
|
|
return -EINVAL;
|
|
}
|
|
|
|
board_data->avdd_name = "vtouch";
|
|
r = of_property_read_u32(node, "goodix,power-on-delay-us",
|
|
&board_data->power_on_delay_us);
|
|
if (!r) {
|
|
/*1000ms is too large, maybe you have pass a wrong value*/
|
|
if (board_data->power_on_delay_us > 1000 * 1000) {
|
|
ts_err("Power on delay time exceed 1s, please check");
|
|
board_data->power_on_delay_us = 0;
|
|
}
|
|
}
|
|
|
|
r = of_property_read_u32(node, "goodix,power-off-delay-us",
|
|
&board_data->power_off_delay_us);
|
|
if (!r) {
|
|
/* 1000ms is too large, maybe you have pass a wrong value */
|
|
if (board_data->power_off_delay_us > 1000 * 1000) {
|
|
ts_err("Power off delay time exceed 1s, please check");
|
|
board_data->power_off_delay_us = 0;
|
|
}
|
|
}
|
|
|
|
/* get xyz resolutions */
|
|
r = goodix_parse_dt_resolution(node, board_data);
|
|
if (r < 0) {
|
|
ts_err("Failed to parse resolutions:%d", r);
|
|
return r;
|
|
}
|
|
|
|
/* key map */
|
|
prop = of_find_property(node, "goodix,panel-key-map", NULL);
|
|
if (prop && prop->length) {
|
|
if (prop->length / sizeof(u32) > GOODIX_MAX_KEY) {
|
|
ts_err("Size of panel-key-map is invalid");
|
|
return r;
|
|
}
|
|
|
|
board_data->panel_max_key = prop->length / sizeof(u32);
|
|
board_data->tp_key_num = prop->length / sizeof(u32);
|
|
r = of_property_read_u32_array(node,
|
|
"goodix,panel-key-map",
|
|
&board_data->panel_key_map[0],
|
|
board_data->panel_max_key);
|
|
if (r)
|
|
return r;
|
|
}
|
|
|
|
/*get pen-enable switch and pen keys, must after "key map"*/
|
|
board_data->pen_enable = of_property_read_bool(node,
|
|
"goodix,pen-enable");
|
|
if (board_data->pen_enable) {
|
|
prop = of_find_property(node, "goodix,key-of-pen", NULL);
|
|
if (prop && prop->length) {
|
|
if (prop->length / sizeof(u32) > GOODIX_PEN_MAX_KEY) {
|
|
ts_err("Size of key-of-pen is invalid");
|
|
return r;
|
|
}
|
|
r = of_property_read_u32_array(node,
|
|
"goodix,key-of-pen",
|
|
&board_data->panel_key_map[board_data->panel_max_key],
|
|
prop->length / sizeof(u32));
|
|
if (r)
|
|
return r;
|
|
|
|
board_data->panel_max_key +=
|
|
(prop->length / sizeof(u32));
|
|
}
|
|
}
|
|
/* touch filter */
|
|
of_property_read_u32(node, "tpd-filter-enable",
|
|
&(board_data->tpd_filter.enable));
|
|
if (board_data->tpd_filter.enable) {
|
|
of_property_read_u32(node, "tpd-filter-pixel-density",
|
|
&(board_data->tpd_filter.pixel_density));
|
|
if (of_property_read_u32_array(node,
|
|
"tpd-filter-custom-prameters",
|
|
(u32 *)(board_data->tpd_filter.W_W),
|
|
ARRAY_SIZE(board_data->tpd_filter.W_W)))
|
|
ts_info("get tpd-filter-custom-parameters");
|
|
if (of_property_read_u32_array(node,
|
|
"tpd-filter-custom-speed",
|
|
board_data->tpd_filter.VECLOCITY_THRESHOLD,
|
|
ARRAY_SIZE(board_data->tpd_filter.VECLOCITY_THRESHOLD)))
|
|
ts_info("get tpd-filter-custom-speed");
|
|
}
|
|
ts_info("[tpd]tpd-filter-enable = %d, pixel_density = %d\n",
|
|
board_data->tpd_filter.enable,
|
|
board_data->tpd_filter.pixel_density);
|
|
|
|
ts_info("***key:%d, %d, %d, %d, %d",
|
|
board_data->panel_key_map[0],
|
|
board_data->panel_key_map[1],
|
|
board_data->panel_key_map[2],
|
|
board_data->panel_key_map[3],
|
|
board_data->panel_key_map[4]);
|
|
/*add end*/
|
|
|
|
ts_debug("[DT]id:%d, x:%d, y:%d, w:%d, p:%d",
|
|
board_data->panel_max_id,
|
|
board_data->panel_max_x,
|
|
board_data->panel_max_y,
|
|
board_data->panel_max_w,
|
|
board_data->panel_max_p);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* goodix_parse_customize_params - parse sensor independent params
|
|
* @dev: pointer to device data
|
|
* @board_data: board data
|
|
* @sensor_id: sensor ID
|
|
* return: 0 - read ok, < 0 - i2c transter error
|
|
*/
|
|
static int goodix_parse_customize_params(struct goodix_ts_device *dev,
|
|
struct goodix_ts_board_data *board_data,
|
|
unsigned int sensor_id)
|
|
{
|
|
struct device_node *node = dev->dev->of_node;
|
|
char of_node_name[24];
|
|
int r;
|
|
int ret;
|
|
|
|
if (sensor_id > TS_MAX_SENSORID || node == NULL) {
|
|
ts_err("Invalid sensor id");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* parse sensor independent parameters */
|
|
ret = snprintf(of_node_name, sizeof(of_node_name),
|
|
"sensor%u", sensor_id);
|
|
if (ret < 0) {
|
|
ts_err("find sensor id [%u] failed", sensor_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
node = of_find_node_by_name(dev->dev->of_node, of_node_name);
|
|
if (!node) {
|
|
ts_err("Child property[%s] not found", of_node_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* sensor independent resolutions */
|
|
r = goodix_parse_dt_resolution(node, board_data);
|
|
return r;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static int goodix_parse_acpi(struct acpi_device *dev,
|
|
struct goodix_ts_board_data *bdata)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int goodix_parse_acpi_cfg(struct acpi_device *dev,
|
|
char *cfg_type, struct goodix_ts_config *config,
|
|
unsigned int sensor_id)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* goodix_i2c_read_trans - read device register through i2c bus
|
|
* @dev: pointer to device data
|
|
* @addr: register address
|
|
* @data: read buffer
|
|
* @len: bytes to read
|
|
* return: 0 - read ok, < 0 - i2c transter error
|
|
*/
|
|
int goodix_i2c_read_trans(struct goodix_ts_device *dev, unsigned int reg,
|
|
unsigned char *data, unsigned int len)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev->dev);
|
|
unsigned int transfer_length = 0;
|
|
unsigned int pos = 0, address = reg;
|
|
unsigned char get_buf[64], addr_buf[2];
|
|
int retry, r = 0;
|
|
struct i2c_msg msgs[] = {
|
|
{
|
|
.addr = client->addr,
|
|
.flags = !I2C_M_RD,
|
|
.buf = &addr_buf[0],
|
|
.len = TS_ADDR_LENGTH,
|
|
}, {
|
|
.addr = client->addr,
|
|
.flags = I2C_M_RD,
|
|
}
|
|
};
|
|
|
|
if (likely(len < sizeof(get_buf))) {
|
|
/* code optimize, use stack memory */
|
|
msgs[1].buf = &get_buf[0];
|
|
} else {
|
|
msgs[1].buf = kzalloc(len > I2C_MAX_TRANSFER_SIZE
|
|
? I2C_MAX_TRANSFER_SIZE : len, GFP_KERNEL);
|
|
if (msgs[1].buf == NULL) {
|
|
ts_err("Malloc failed");
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
while (pos != len) {
|
|
if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE))
|
|
transfer_length = I2C_MAX_TRANSFER_SIZE;
|
|
else
|
|
transfer_length = len - pos;
|
|
|
|
msgs[0].buf[0] = (address >> 8) & 0xFF;
|
|
msgs[0].buf[1] = address & 0xFF;
|
|
msgs[1].len = transfer_length;
|
|
|
|
for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) {
|
|
if (likely(i2c_transfer(client->adapter, msgs, 2)
|
|
== 2)) {
|
|
memcpy(&data[pos], msgs[1].buf,
|
|
transfer_length);
|
|
pos += transfer_length;
|
|
address += transfer_length;
|
|
break;
|
|
}
|
|
ts_info("I2c read retry[%d]:0x%x", retry + 1, reg);
|
|
msleep(20);
|
|
}
|
|
if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) {
|
|
ts_err("I2c read failed,dev:%02x,reg:%04x,size:%u",
|
|
client->addr, reg, len);
|
|
r = -EBUS;
|
|
goto read_exit;
|
|
}
|
|
}
|
|
|
|
read_exit:
|
|
if (unlikely(len >= sizeof(get_buf)))
|
|
kfree(msgs[1].buf);
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* goodix_i2c_write_trans - write device register through i2c bus
|
|
* @dev: pointer to device data
|
|
* @addr: register address
|
|
* @data: write buffer
|
|
* @len: bytes to write
|
|
* return: 0 - write ok; < 0 - i2c transter error.
|
|
*/
|
|
int goodix_i2c_write_trans(struct goodix_ts_device *dev, unsigned int reg,
|
|
unsigned char *data, unsigned int len)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev->dev);
|
|
unsigned int pos = 0, transfer_length = 0;
|
|
unsigned int address = reg;
|
|
unsigned char put_buf[64];
|
|
int retry, r = 0;
|
|
struct i2c_msg msg = {
|
|
.addr = client->addr,
|
|
.flags = !I2C_M_RD,
|
|
};
|
|
|
|
if (likely(len + TS_ADDR_LENGTH < sizeof(put_buf))) {
|
|
/* code optimize,use stack memory*/
|
|
msg.buf = &put_buf[0];
|
|
} else {
|
|
msg.buf = kmalloc(len + TS_ADDR_LENGTH > I2C_MAX_TRANSFER_SIZE
|
|
? I2C_MAX_TRANSFER_SIZE : len + TS_ADDR_LENGTH, GFP_KERNEL);
|
|
if (msg.buf == NULL) {
|
|
ts_err("Malloc failed");
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
while (pos != len) {
|
|
if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE
|
|
- TS_ADDR_LENGTH))
|
|
transfer_length = I2C_MAX_TRANSFER_SIZE
|
|
- TS_ADDR_LENGTH;
|
|
else
|
|
transfer_length = len - pos;
|
|
|
|
msg.buf[0] = (unsigned char)((address >> 8) & 0xFF);
|
|
msg.buf[1] = (unsigned char)(address & 0xFF);
|
|
msg.len = transfer_length + 2;
|
|
memcpy(&msg.buf[2], &data[pos], transfer_length);
|
|
|
|
for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) {
|
|
if (likely(i2c_transfer(client->adapter, &msg, 1)
|
|
== 1)) {
|
|
pos += transfer_length;
|
|
address += transfer_length;
|
|
break;
|
|
}
|
|
ts_info("I2c write retry[%d]", retry + 1);
|
|
msleep(20);
|
|
}
|
|
if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) {
|
|
ts_err("I2c write failed,dev:%02x,reg:%04x,size:%u",
|
|
client->addr, reg, len);
|
|
r = -EBUS;
|
|
goto write_exit;
|
|
}
|
|
}
|
|
|
|
write_exit:
|
|
if (likely(len + TS_ADDR_LENGTH >= sizeof(put_buf)))
|
|
kfree(msg.buf);
|
|
return r;
|
|
}
|
|
|
|
|
|
/**
|
|
* goodix_set_i2c_doze_mode - disable or enable doze mode
|
|
* @dev: pointer to device data
|
|
* @enable: true/flase
|
|
* return: 0 - ok; < 0 - error.
|
|
* This func must be used in pairs, when you disable doze
|
|
* mode, then you must enable it again.
|
|
* Between set_doze_false and set_doze_true, do not reset
|
|
* IC!
|
|
*/
|
|
int goodix_set_i2c_doze_mode(struct goodix_ts_device *dev, int enable)
|
|
{
|
|
int result = -EINVAL;
|
|
int i;
|
|
u8 w_data, r_data = 0;
|
|
|
|
if (dev->ic_type != IC_TYPE_NORMANDY)
|
|
return 0;
|
|
|
|
mutex_lock(&dev->doze_mode_lock);
|
|
|
|
if (enable) {
|
|
if (dev->doze_mode_set_count != 0)
|
|
dev->doze_mode_set_count--;
|
|
|
|
/*when count equal 0, allow ic enter doze mode*/
|
|
if (dev->doze_mode_set_count == 0) {
|
|
w_data = TS_DOZE_ENABLE_DATA;
|
|
for (i = 0; i < TS_DOZE_ENABLE_RETRY_TIMES; i++) {
|
|
result = goodix_i2c_write_trans(dev,
|
|
TS_REG_DOZE_CTRL, &w_data, 1);
|
|
if (!result) {
|
|
result = 0;
|
|
goto exit;
|
|
}
|
|
usleep_range(1000, 1100);
|
|
}
|
|
if (i >= TS_DOZE_ENABLE_RETRY_TIMES)
|
|
ts_err("i2c doze mode enable failed,"
|
|
TAG_I2C " i2c write fail");
|
|
} else {
|
|
/*ts_info("doze count not euqal 0,*/
|
|
/* so skip doze mode enable");*/
|
|
result = 0;
|
|
goto exit;
|
|
}
|
|
} else {
|
|
dev->doze_mode_set_count++;
|
|
|
|
if (dev->doze_mode_set_count == 1) {
|
|
w_data = TS_DOZE_DISABLE_DATA;
|
|
goodix_i2c_write_trans(dev,
|
|
TS_REG_DOZE_CTRL, &w_data, 1);
|
|
usleep_range(1000, 1100);
|
|
for (i = 0; i < TS_DOZE_DISABLE_RETRY_TIMES; i++) {
|
|
goodix_i2c_read_trans(dev,
|
|
TS_REG_DOZE_STAT, &r_data, 1);
|
|
if (r_data == TS_DOZE_CLOSE_OK_DATA) {
|
|
result = 0;
|
|
goto exit;
|
|
} else if (r_data != 0xAA) {
|
|
w_data = TS_DOZE_DISABLE_DATA;
|
|
goodix_i2c_write_trans(dev,
|
|
TS_REG_DOZE_CTRL, &w_data, 1);
|
|
}
|
|
usleep_range(10000, 10100);
|
|
}
|
|
ts_err("doze mode disable FAILED");
|
|
} else {
|
|
result = 0;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
mutex_unlock(&dev->doze_mode_lock);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* goodix_i2c_write - write device register through i2c bus
|
|
* @dev: pointer to device data
|
|
* @addr: register address
|
|
* @data: write buffer
|
|
* @len: bytes to write
|
|
* return: 0 - write ok; < 0 - i2c transter error.
|
|
*/
|
|
int goodix_i2c_write(struct goodix_ts_device *dev, unsigned int reg,
|
|
unsigned char *data, unsigned int len)
|
|
{
|
|
int r;
|
|
|
|
if (dev->ic_type == IC_TYPE_NORMANDY) {
|
|
if (goodix_set_i2c_doze_mode(dev, false) != 0)
|
|
ts_err("gtx8 i2c write:0x%04x ERROR,"
|
|
TAG_I2C "disable doze mode FAILED", reg);
|
|
}
|
|
|
|
r = goodix_i2c_write_trans(dev, reg, data, len);
|
|
|
|
if (dev->ic_type == IC_TYPE_NORMANDY) {
|
|
if (goodix_set_i2c_doze_mode(dev, true) != 0)
|
|
ts_err("gtx8 i2c write:0x%04x ERROR,"
|
|
TAG_I2C "enable doze mode FAILED", reg);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
/**
|
|
* goodix_i2c_read - read device register through i2c bus
|
|
* @dev: pointer to device data
|
|
* @addr: register address
|
|
* @data: read buffer
|
|
* @len: bytes to read
|
|
* return: 0 - read ok, < 0 - i2c transter error
|
|
*/
|
|
int goodix_i2c_read(struct goodix_ts_device *dev, unsigned int reg,
|
|
unsigned char *data, unsigned int len)
|
|
{
|
|
int r;
|
|
|
|
if (dev->ic_type == IC_TYPE_NORMANDY) {
|
|
if (goodix_set_i2c_doze_mode(dev, false) != 0)
|
|
ts_err("gtx8 i2c read:0x%04x ERROR,"
|
|
TAG_I2C " disable doze mode FAILED", reg);
|
|
}
|
|
|
|
r = goodix_i2c_read_trans(dev, reg, data, len);
|
|
|
|
if (dev->ic_type == IC_TYPE_NORMANDY) {
|
|
if (goodix_set_i2c_doze_mode(dev, true) != 0)
|
|
ts_err("gtx8 i2c read:0x%04x ERROR,"
|
|
TAG_I2C "enable doze mode FAILED", reg);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* goodix_i2c_write_trans_once
|
|
* write device register through i2c bus, no retry
|
|
* @dev: pointer to device data
|
|
* @addr: register address
|
|
* @data: write buffer
|
|
* @len: bytes to write
|
|
* return: 0 - write ok; < 0 - i2c transter error.
|
|
*/
|
|
int goodix_i2c_write_trans_once(struct goodix_ts_device *dev, unsigned int reg,
|
|
unsigned char *data, unsigned int len)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev->dev);
|
|
unsigned int pos = 0, transfer_length = 0;
|
|
unsigned int address = reg;
|
|
unsigned char put_buf[64];
|
|
struct i2c_msg msg = {
|
|
.addr = client->addr,
|
|
.flags = !I2C_M_RD,
|
|
};
|
|
|
|
if (likely(len + TS_ADDR_LENGTH < sizeof(put_buf))) {
|
|
/* code optimize,use stack memory*/
|
|
msg.buf = &put_buf[0];
|
|
} else {
|
|
msg.buf = kmalloc(len + TS_ADDR_LENGTH > I2C_MAX_TRANSFER_SIZE
|
|
? I2C_MAX_TRANSFER_SIZE : len + TS_ADDR_LENGTH,
|
|
GFP_KERNEL);
|
|
if (msg.buf == NULL) {
|
|
ts_err("Malloc failed");
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
while (pos != len) {
|
|
if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE -
|
|
TS_ADDR_LENGTH))
|
|
transfer_length = I2C_MAX_TRANSFER_SIZE -
|
|
TS_ADDR_LENGTH;
|
|
else
|
|
transfer_length = len - pos;
|
|
|
|
msg.buf[0] = (unsigned char)((address >> 8) & 0xFF);
|
|
msg.buf[1] = (unsigned char)(address & 0xFF);
|
|
msg.len = transfer_length + 2;
|
|
memcpy(&msg.buf[2], &data[pos], transfer_length);
|
|
|
|
if (i2c_transfer(client->adapter, &msg, 1) != 1)
|
|
ts_err("%s i2c_transfer err", __func__);
|
|
pos += transfer_length;
|
|
address += transfer_length;
|
|
}
|
|
|
|
if (likely(len + TS_ADDR_LENGTH >= sizeof(put_buf)))
|
|
kfree(msg.buf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static void goodix_cmds_init(struct goodix_ts_cmd *ts_cmd,
|
|
u8 cmds, u8 cmd_data, u32 reg_addr)
|
|
{
|
|
if (reg_addr) {
|
|
ts_cmd->cmd_reg = reg_addr;
|
|
ts_cmd->length = 3;
|
|
ts_cmd->cmds[0] = cmds;
|
|
ts_cmd->cmds[1] = cmd_data;
|
|
ts_cmd->cmds[2] = 0 - cmds - cmd_data;
|
|
ts_cmd->initialized = true;
|
|
} else {
|
|
ts_cmd->initialized = false;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* goodix_send_command - seng cmd to firmware
|
|
*
|
|
* @dev: pointer to device
|
|
* @cmd: pointer to command struct which cotain command data
|
|
* Returns 0 - succeed,<0 - failed
|
|
*/
|
|
int goodix_send_command(struct goodix_ts_device *dev,
|
|
struct goodix_ts_cmd *cmd)
|
|
{
|
|
int ret;
|
|
|
|
if (!cmd || !cmd->initialized)
|
|
return -EINVAL;
|
|
|
|
ret = goodix_i2c_write(dev, cmd->cmd_reg, cmd->cmds,
|
|
cmd->length);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int goodix_read_pid(struct goodix_ts_device *dev,
|
|
struct goodix_ts_version *version)
|
|
{
|
|
u8 buffer[12] = {0};
|
|
int r, retry;
|
|
u8 pid_read_len = 4;
|
|
|
|
for (retry = 0; retry < GOODIX_CHIPID_RETRY_TIMES; retry++) {
|
|
/*read pid*/
|
|
r = goodix_i2c_read(dev, TS_REG_PID,
|
|
buffer, pid_read_len);
|
|
if (!r) {
|
|
if (strcmp(buffer, GOODIX_TS_PID_GT9886) == 0) {
|
|
ts_info("Touch id = GT9886");
|
|
return 0;
|
|
} else if (strcmp(buffer, GOODIX_TS_PID_GT9885) == 0) {
|
|
ts_info("Touch id = GT9885");
|
|
return 0;
|
|
}
|
|
}
|
|
msleep(30);
|
|
ts_info("Touch id = %s, retry = %d", buffer, retry);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int goodix_i2c_test(struct goodix_ts_device *dev)
|
|
{
|
|
#define TEST_ADDR 0x4100
|
|
#define TEST_LEN 1
|
|
struct i2c_client *client = to_i2c_client(dev->dev);
|
|
unsigned char test_buf[TEST_LEN + 1], addr_buf[2];
|
|
struct i2c_msg msgs[] = {
|
|
{
|
|
.addr = client->addr,
|
|
.flags = !I2C_M_RD,
|
|
.buf = &addr_buf[0],
|
|
.len = TS_ADDR_LENGTH,
|
|
}, {
|
|
.addr = client->addr,
|
|
.flags = I2C_M_RD,
|
|
.buf = &test_buf[0],
|
|
.len = TEST_LEN,
|
|
}
|
|
};
|
|
|
|
msgs[0].buf[0] = (TEST_ADDR >> 8) & 0xFF;
|
|
msgs[0].buf[1] = TEST_ADDR & 0xFF;
|
|
|
|
if (i2c_transfer(client->adapter, msgs, 2) == 2)
|
|
return 0;
|
|
|
|
/* test failed */
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* confirm current device is goodix or not.
|
|
* If confirmed 0 will return.
|
|
*/
|
|
static int goodix_ts_dev_confirm(struct goodix_ts_device *dev)
|
|
{
|
|
#define DEV_CONFIRM_RETRY 3
|
|
int retry;
|
|
|
|
for (retry = 0; retry < DEV_CONFIRM_RETRY; retry++) {
|
|
gpio_direction_output(dev->board_data->reset_gpio, 0);
|
|
udelay(2000);
|
|
gpio_direction_output(dev->board_data->reset_gpio, 1);
|
|
mdelay(5);
|
|
if (!goodix_i2c_test(dev)) {
|
|
msleep(95);
|
|
return 0;
|
|
}
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int goodix_read_version(struct goodix_ts_device *dev,
|
|
struct goodix_ts_version *version)
|
|
{
|
|
u8 buffer[12];
|
|
u8 temp_buf[256] = {0}, checksum;
|
|
int r;
|
|
u8 pid_read_len = dev->reg.pid_len;
|
|
u8 vid_read_len = dev->reg.vid_len;
|
|
u8 sensor_id_mask = dev->reg.sensor_id_mask;
|
|
#define IS_CHAR(c) (((c) >= 'A' && (c) <= 'Z')\
|
|
|| ((c) >= 'a' && (c) <= 'z')\
|
|
|| ((c) >= '0' && (c) <= '9'))
|
|
|
|
if (!version) {
|
|
ts_err("pointer of version is NULL");
|
|
return -EINVAL;
|
|
}
|
|
version->valid = false;
|
|
|
|
/*check reg info valid*/
|
|
if (!dev->reg.pid || !dev->reg.sensor_id || !dev->reg.vid) {
|
|
ts_err("reg is NULL, pid:0x%04x, vid:0x%04x, sensor_id:0x%04x",
|
|
dev->reg.pid, dev->reg.vid, dev->reg.sensor_id);
|
|
return -EINVAL;
|
|
}
|
|
if (!pid_read_len || pid_read_len > GOODIX_PID_MAX_LEN
|
|
|| !vid_read_len || vid_read_len > GOODIX_VID_MAX_LEN) {
|
|
ts_err("pid vid read len ERROR, pid_read_len:%d, "
|
|
TAG_I2C "vid_read_len:%d", pid_read_len, vid_read_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*disable doze mode, just valid for normandy*/
|
|
/* this func must be used in pairs*/
|
|
goodix_set_i2c_doze_mode(dev, false);
|
|
|
|
/*check checksum*/
|
|
if (dev->reg.version_base) {
|
|
r = goodix_i2c_read(dev, dev->reg.version_base,
|
|
temp_buf, dev->reg.version_len);
|
|
|
|
if (r < 0) {
|
|
ts_err("Read version base failed, reg:0x%02x, len:%d",
|
|
dev->reg.version_base, dev->reg.version_len);
|
|
if (version)
|
|
version->valid = false;
|
|
goto exit;
|
|
}
|
|
|
|
checksum = checksum_u8(temp_buf, dev->reg.version_len);
|
|
if (checksum) {
|
|
ts_err("checksum error:0x%02x, base:0x%02x, len:%d",
|
|
checksum, dev->reg.version_base,
|
|
dev->reg.version_len);
|
|
ts_err("%*ph",
|
|
(int)(dev->reg.version_len / 2), temp_buf);
|
|
ts_err("%*ph",
|
|
(int)(dev->reg.version_len - dev->reg.version_len / 2),
|
|
&temp_buf[dev->reg.version_len / 2]);
|
|
|
|
if (version)
|
|
version->valid = false;
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
/*read pid*/
|
|
memset(buffer, 0, sizeof(buffer));
|
|
memset(version->pid, 0, sizeof(version->pid));
|
|
r = goodix_i2c_read(dev, dev->reg.pid, buffer, pid_read_len);
|
|
if (r < 0) {
|
|
ts_err("Read pid failed");
|
|
if (version)
|
|
version->valid = false;
|
|
goto exit;
|
|
}
|
|
memcpy(version->pid, buffer, pid_read_len);
|
|
|
|
|
|
/*read vid*/
|
|
memset(buffer, 0, sizeof(buffer));
|
|
memset(version->vid, 0, sizeof(version->vid));
|
|
r = goodix_i2c_read(dev, dev->reg.vid, buffer, vid_read_len);
|
|
if (r < 0) {
|
|
ts_err("Read vid failed");
|
|
if (version)
|
|
version->valid = false;
|
|
goto exit;
|
|
}
|
|
memcpy(version->vid, buffer, vid_read_len);
|
|
|
|
/*read sensor_id*/
|
|
memset(buffer, 0, sizeof(buffer));
|
|
r = goodix_i2c_read(dev, dev->reg.sensor_id, buffer, 1);
|
|
if (r < 0) {
|
|
ts_err("Read sensor_id failed");
|
|
if (version)
|
|
version->valid = false;
|
|
goto exit;
|
|
}
|
|
if (sensor_id_mask != 0) {
|
|
version->sensor_id = buffer[0] & sensor_id_mask;
|
|
ts_info("sensor_id_mask:0x%02x, sensor_id:0x%02x",
|
|
sensor_id_mask, version->sensor_id);
|
|
} else {
|
|
version->sensor_id = buffer[0];
|
|
}
|
|
|
|
version->valid = true;
|
|
|
|
ts_info("PID:%s,SensorID:%d, VID:%*ph",
|
|
version->pid,
|
|
version->sensor_id,
|
|
(int)sizeof(version->vid), version->vid);
|
|
exit:
|
|
/*enable doze mode, just valid for normandy*/
|
|
/* this func must be used in pairs*/
|
|
goodix_set_i2c_doze_mode(dev, true);
|
|
|
|
return r;
|
|
}
|
|
|
|
static int goodix_wait_cfg_cmd_ready(struct goodix_ts_device *dev,
|
|
u8 right_cmd, u8 send_cmd)
|
|
{
|
|
int try_times = 0;
|
|
u8 cmd_flag = 0;
|
|
u8 cmd_buf[3] = {0};
|
|
u16 command_reg = dev->reg.command;
|
|
struct goodix_ts_cmd ts_cmd;
|
|
|
|
goodix_cmds_init(&ts_cmd, send_cmd, 0, command_reg);
|
|
|
|
for (try_times = 0; try_times < TS_WAIT_CFG_READY_RETRY_TIMES;
|
|
try_times++) {
|
|
if (goodix_i2c_read(dev, command_reg, cmd_buf, 3)) {
|
|
ts_err("Read cmd_reg error");
|
|
return -EINVAL;
|
|
}
|
|
cmd_flag = cmd_buf[0];
|
|
if (cmd_flag == right_cmd) {
|
|
return 0;
|
|
} else if (cmd_flag != send_cmd) {
|
|
ts_err("Read cmd_reg data abnormal,return:0x%02X,"
|
|
TAG_I2C " 0x%02X, 0x%02X, send again",
|
|
cmd_buf[0], cmd_buf[1], cmd_buf[2]);
|
|
if (goodix_send_command(dev, &ts_cmd)) {
|
|
ts_err("Resend cmd 0x%02X FAILED", send_cmd);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
usleep_range(10000, 11000);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int goodix_send_small_config(struct goodix_ts_device *dev,
|
|
struct goodix_ts_config *config)
|
|
{
|
|
int r = 0;
|
|
int try_times = 0;
|
|
u8 buf = 0;
|
|
u16 command_reg = dev->reg.command;
|
|
u16 cfg_reg = dev->reg.cfg_addr;
|
|
struct goodix_ts_cmd ts_cmd;
|
|
|
|
/*1. Inquire command_reg until it's free*/
|
|
for (try_times = 0; try_times < TS_WAIT_CMD_FREE_RETRY_TIMES;
|
|
try_times++) {
|
|
if (!goodix_i2c_read(dev, command_reg, &buf, 1) && buf ==
|
|
TS_CMD_REG_READY)
|
|
break;
|
|
usleep_range(10000, 11000);
|
|
}
|
|
if (try_times >= TS_WAIT_CMD_FREE_RETRY_TIMES) {
|
|
ts_err("Send small cfg FAILED, before send,"
|
|
TAG_I2C " reg:0x%04x is not 0xff", command_reg);
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/*2. write cfg data*/
|
|
if (goodix_i2c_write(dev, cfg_reg, config->data, config->length)) {
|
|
ts_err("Send small cfg FAILED, write cfg to fw ERROR");
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/*3. send 0x81 command*/
|
|
goodix_cmds_init(&ts_cmd, COMMAND_SEND_SMALL_CFG, 0, dev->reg.command);
|
|
if (goodix_send_command(dev, &ts_cmd)) {
|
|
ts_err("Send large cfg FAILED,"
|
|
TAG_I2C " send COMMAND_SEND_SMALL_CFG ERROR");
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
r = 0;
|
|
ts_info("send small cfg SUCCESS");
|
|
|
|
exit:
|
|
return r;
|
|
}
|
|
|
|
static int goodix_send_large_config(struct goodix_ts_device *dev,
|
|
struct goodix_ts_config *config)
|
|
{
|
|
int r = 0;
|
|
int try_times = 0;
|
|
u8 buf = 0;
|
|
u16 command_reg = dev->reg.command;
|
|
u16 cfg_reg = dev->reg.cfg_addr;
|
|
struct goodix_ts_cmd ts_cmd;
|
|
|
|
/*1. Inquire command_reg until it's free*/
|
|
for (try_times = 0; try_times < TS_WAIT_CMD_FREE_RETRY_TIMES;
|
|
try_times++) {
|
|
if (!goodix_i2c_read(dev, command_reg, &buf, 1) && buf ==
|
|
TS_CMD_REG_READY)
|
|
break;
|
|
usleep_range(10000, 11000);
|
|
}
|
|
if (try_times >= TS_WAIT_CMD_FREE_RETRY_TIMES) {
|
|
ts_err("Send large cfg FAILED, before send,"
|
|
TAG_I2C " reg:0x%04x is not 0xff", command_reg);
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/*2. send "start write cfg" command*/
|
|
goodix_cmds_init(&ts_cmd, COMMAND_START_SEND_CFG, 0, dev->reg.command);
|
|
if (goodix_send_command(dev, &ts_cmd)) {
|
|
ts_err("Send large cfg FAILED,"
|
|
TAG_I2C " send COMMAND_START_SEND_CFG ERROR");
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/*3. wait ic set command_reg to 0x82*/
|
|
if (goodix_wait_cfg_cmd_ready(dev, COMMAND_SEND_CFG_PREPARE_OK,
|
|
COMMAND_START_SEND_CFG)) {
|
|
ts_err("Send large cfg FAILED, reg:0x%04x is not 0x82",
|
|
command_reg);
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/*4. write cfg*/
|
|
if (goodix_i2c_write(dev, cfg_reg, config->data, config->length)) {
|
|
ts_err("Send large cfg FAILED, write cfg to fw ERROR");
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/*5. send "end send cfg" command*/
|
|
goodix_cmds_init(&ts_cmd, COMMAND_END_SEND_CFG, 0, dev->reg.command);
|
|
if (goodix_send_command(dev, &ts_cmd)) {
|
|
ts_err("Send large cfg FAILED,"
|
|
TAG_I2C " send COMMAND_END_SEND_CFG ERROR");
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/*6. wait ic set command_reg to 0xff*/
|
|
for (try_times = 0; try_times < TS_WAIT_CMD_FREE_RETRY_TIMES;
|
|
try_times++) {
|
|
if (!goodix_i2c_read(dev, command_reg, &buf, 1) && buf ==
|
|
TS_CMD_REG_READY)
|
|
break;
|
|
usleep_range(10000, 11000);
|
|
}
|
|
if (try_times >= TS_WAIT_CMD_FREE_RETRY_TIMES) {
|
|
ts_err("Send large cfg FAILED, after send,"
|
|
TAG_I2C " reg:0x%04x is not 0xff", command_reg);
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ts_info("Send large cfg SUCCESS");
|
|
r = 0;
|
|
|
|
exit:
|
|
return r;
|
|
}
|
|
|
|
static int goodix_check_cfg_valid(struct goodix_ts_device *dev,
|
|
u8 *cfg, u32 length)
|
|
{
|
|
int ret;
|
|
u8 bag_num;
|
|
u8 checksum;
|
|
int i, j;
|
|
int bag_start = 0;
|
|
int bag_end = 0;
|
|
|
|
if (!cfg || length < TS_CFG_HEAD_LEN) {
|
|
ts_err("cfg is INVALID, len:%d", length);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (dev->ic_type == IC_TYPE_NANJING) {
|
|
/*check configuration head checksum*/
|
|
checksum = 0;
|
|
for (i = 0; i < 3; i++)
|
|
checksum += cfg[i];
|
|
|
|
if (checksum != 0) {
|
|
ts_err("cfg head checksum ERROR, ic"
|
|
TAG_I2C " type:nanjing, checksum:0x%02x", checksum);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
bag_num = cfg[1];
|
|
bag_start = 3;
|
|
} else if (dev->ic_type == IC_TYPE_NORMANDY) {
|
|
checksum = 0;
|
|
for (i = 0; i < TS_CFG_HEAD_LEN; i++)
|
|
checksum += cfg[i];
|
|
if (checksum != 0) {
|
|
ts_err("cfg head checksum ERROR, ic"
|
|
TAG_I2C "type:normandy, checksum:0x%02x", checksum);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
bag_num = cfg[TS_CFG_BAG_NUM_INDEX];
|
|
bag_start = TS_CFG_BAG_START_INDEX;
|
|
} else {
|
|
ts_err("cfg check FAILED, unkonw ic_type");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ts_info("cfg bag_num:%d, cfg length:%d", bag_num, length);
|
|
|
|
/*check each bag's checksum*/
|
|
for (j = 0; j < bag_num; j++) {
|
|
if (bag_start >= length - 1) {
|
|
ts_err("ERROR, overflow!!bag_start:%d,"
|
|
TAG_I2C " cfg_len:%d", bag_start, length);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
bag_end = bag_start + cfg[bag_start + 1] + 3;
|
|
if ((j == 0) && (dev->ic_type == IC_TYPE_NANJING))
|
|
/*the first bag of nanjing cfg is different!*/
|
|
bag_end = 336;
|
|
|
|
checksum = 0;
|
|
if (bag_end > length) {
|
|
ts_err("ERROR, overflow!!bag:%d, bag_start:%d,"
|
|
TAG_I2C "bag_end:%d, cfg length:%d",
|
|
j, bag_start, bag_end, length);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
for (i = bag_start; i < bag_end; i++)
|
|
checksum += cfg[i];
|
|
if (checksum != 0) {
|
|
ts_err("cfg INVALID, bag:%d "
|
|
TAG_I2C"checksum ERROR:0x%02x", j, checksum);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
bag_start = bag_end;
|
|
}
|
|
|
|
ret = 0;
|
|
ts_info("configuration check SUCCESS");
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static int goodix_send_config(struct goodix_ts_device *dev,
|
|
struct goodix_ts_config *config)
|
|
{
|
|
int r = 0;
|
|
/*check reg valid*/
|
|
if (!config) {
|
|
ts_err("Null config data");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*check configuration valid*/
|
|
r = goodix_check_cfg_valid(dev, config->data, config->length);
|
|
if (r != 0) {
|
|
ts_err("cfg check FAILED");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ts_info("ver:%02xh,size:%d",
|
|
config->data[0],
|
|
config->length);
|
|
|
|
mutex_lock(&config->lock);
|
|
|
|
if (dev->ic_type == IC_TYPE_NANJING)
|
|
r = goodix_send_large_config(dev, config);
|
|
else if (dev->ic_type == IC_TYPE_NORMANDY) {
|
|
/*disable doze mode*/
|
|
goodix_set_i2c_doze_mode(dev, false);
|
|
|
|
if (config->length > 32)
|
|
r = goodix_send_large_config(dev, config);
|
|
else
|
|
r = goodix_send_small_config(dev, config);
|
|
|
|
/*enable doze mode*/
|
|
goodix_set_i2c_doze_mode(dev, true);
|
|
}
|
|
|
|
if (r != 0)
|
|
ts_err("send_cfg FAILED, ic_type:%d, cfg_len:%d",
|
|
dev->ic_type, config->length);
|
|
|
|
mutex_unlock(&config->lock);
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* goodix_close_hidi2c_mode
|
|
* Called by touch core module when bootup
|
|
* @ts_dev: pointer to touch device
|
|
* return: 0 - no error, <0 error
|
|
*/
|
|
static int goodix_close_hidi2c_mode(struct goodix_ts_device *ts_dev)
|
|
{
|
|
int r = 0;
|
|
int try_times;
|
|
int j;
|
|
unsigned char buffer[1] = {0};
|
|
unsigned char reg_sta = 0;
|
|
struct goodix_ts_cmd ts_cmd;
|
|
|
|
for (try_times = 0; try_times < 10; try_times++) {
|
|
if (goodix_i2c_read(ts_dev, 0x8040, ®_sta, 1) != 0)
|
|
continue;
|
|
else if (reg_sta == 0xff)
|
|
break;
|
|
usleep_range(10000, 11000);
|
|
}
|
|
if (try_times >= 10) {
|
|
ts_info("%s FAILED,0x8040 is not equal to 0xff", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
goodix_cmds_init(&ts_cmd, COMMAND_CLOSE_HID, 0, 0x8040);
|
|
for (try_times = 0; try_times < 3; try_times++) {
|
|
if (ts_cmd.initialized) {
|
|
r = goodix_send_command(ts_dev, &ts_cmd);
|
|
if (r)
|
|
continue;
|
|
|
|
usleep_range(100000, 110000);
|
|
|
|
/*read 0x8040, if it's not 0xFF,continue*/
|
|
for (j = 0; j < 3; j++) {
|
|
if (goodix_i2c_read(ts_dev, 0x8040, buffer, 1)
|
|
!= 0)
|
|
continue;
|
|
else {
|
|
if (buffer[0] != 0xFF) {
|
|
ts_info("try_times:%d:%d,"
|
|
TAG_I2C " read 0x8040:0x%02x",
|
|
try_times,
|
|
j, buffer[0]);
|
|
usleep_range(10000, 11000);
|
|
continue;
|
|
} else
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
if (try_times >= 3) {
|
|
ts_info("close hid_i2c mode FAILED");
|
|
r = -EINVAL;
|
|
} else {
|
|
ts_info("close hid_i2c mode SUCCESS");
|
|
r = 0;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/* success return config length else return -1 */
|
|
static int _goodix_do_read_config(struct goodix_ts_device *dev,
|
|
u32 base_addr, u8 *buf)
|
|
{
|
|
int sub_bags = 0;
|
|
int offset = 0;
|
|
int subbag_len;
|
|
u8 checksum;
|
|
int i;
|
|
int ret;
|
|
|
|
/*disable doze mode*/
|
|
if (dev->ic_type == IC_TYPE_NORMANDY)
|
|
goodix_set_i2c_doze_mode(dev, false);
|
|
|
|
ret = goodix_i2c_read(dev, base_addr, buf, TS_CFG_HEAD_LEN);
|
|
if (ret)
|
|
goto err_out;
|
|
|
|
if (dev->ic_type == IC_TYPE_NANJING) {
|
|
offset = 3;
|
|
sub_bags = buf[1];
|
|
checksum = checksum_u8(buf, 3);
|
|
} else {
|
|
offset = TS_CFG_BAG_START_INDEX;
|
|
sub_bags = buf[TS_CFG_BAG_NUM_INDEX];
|
|
checksum = checksum_u8(buf, TS_CFG_HEAD_LEN);
|
|
}
|
|
if (checksum) {
|
|
ts_err("Config head checksum err:0x%x,data:%*ph",
|
|
checksum, TS_CFG_HEAD_LEN, buf);
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
|
|
ts_info("config_version:%u, vub_bags:%u",
|
|
buf[0], sub_bags);
|
|
for (i = 0; i < sub_bags; i++) {
|
|
/* read sub head [0]: sub bag num, [1]: sub bag length */
|
|
ret = goodix_i2c_read(dev, base_addr + offset, buf + offset, 2);
|
|
if (ret)
|
|
goto err_out;
|
|
|
|
/* read sub bag data */
|
|
if (dev->ic_type == IC_TYPE_NANJING && i == 0)
|
|
subbag_len = buf[offset + 1] + 256;
|
|
else
|
|
subbag_len = buf[offset + 1];
|
|
|
|
ts_info("sub bag num:%u,sub bag length:%u",
|
|
buf[offset], subbag_len);
|
|
ret = goodix_i2c_read(dev, base_addr + offset + 2,
|
|
buf + offset + 2, subbag_len + 1);
|
|
if (ret)
|
|
goto err_out;
|
|
checksum = checksum_u8(buf + offset, subbag_len + 3);
|
|
if (checksum) {
|
|
ts_err("sub bag checksum err:0x%x", checksum);
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
offset += subbag_len + 3;
|
|
ts_debug("sub bag %d, data:%*ph", buf[offset],
|
|
buf[offset + 1] + 3, buf + offset);
|
|
}
|
|
ret = offset;
|
|
|
|
err_out:
|
|
/*enable doze mode*/
|
|
if (dev->ic_type == IC_TYPE_NORMANDY)
|
|
goodix_set_i2c_doze_mode(dev, true);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* success return config_len, <= 0 failed */
|
|
static int goodix_read_config(struct goodix_ts_device *dev,
|
|
u8 *config_data, u32 config_len)
|
|
{
|
|
struct goodix_ts_cmd ts_cmd;
|
|
u8 cmd_flag;
|
|
u32 cmd_reg = dev->reg.command;
|
|
int r = 0;
|
|
int i;
|
|
|
|
if (!config_data || config_len > TS_CFG_MAX_LEN) {
|
|
ts_err("Illegal params");
|
|
return -EINVAL;
|
|
}
|
|
if (!dev->reg.command) {
|
|
ts_err("command register ERROR:0x%04x", dev->reg.command);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*disable doze mode*/
|
|
if (dev->ic_type == IC_TYPE_NORMANDY)
|
|
goodix_set_i2c_doze_mode(dev, false);
|
|
|
|
/* wait for IC in IDLE state */
|
|
for (i = 0; i < TS_WAIT_CMD_FREE_RETRY_TIMES; i++) {
|
|
cmd_flag = 0;
|
|
r = goodix_i2c_read(dev, cmd_reg, &cmd_flag, 1);
|
|
if (r < 0 || cmd_flag == TS_CMD_REG_READY)
|
|
break;
|
|
usleep_range(10000, 11000);
|
|
}
|
|
if (cmd_flag != TS_CMD_REG_READY) {
|
|
ts_err("Wait for IC ready IDEL state timeout:addr 0x%x\n",
|
|
cmd_reg);
|
|
r = -EAGAIN;
|
|
goto exit;
|
|
}
|
|
/* 0x86 read config command */
|
|
goodix_cmds_init(&ts_cmd, COMMAND_START_READ_CFG, 0, cmd_reg);
|
|
r = goodix_send_command(dev, &ts_cmd);
|
|
if (r) {
|
|
ts_err("Failed send read config command");
|
|
goto exit;
|
|
}
|
|
/* wait for config data ready */
|
|
if (goodix_wait_cfg_cmd_ready(dev, COMMAND_READ_CFG_PREPARE_OK,
|
|
COMMAND_START_READ_CFG)) {
|
|
ts_err("Wait for config data ready timeout");
|
|
r = -EAGAIN;
|
|
goto exit;
|
|
}
|
|
if (config_len) {
|
|
r = goodix_i2c_read(dev, cmd_reg + 16, config_data, config_len);
|
|
if (r)
|
|
ts_err("Failed read config data");
|
|
else
|
|
r = config_len;
|
|
} else {
|
|
r = _goodix_do_read_config(dev, cmd_reg + 16, config_data);
|
|
if (r < 0)
|
|
ts_err("Failed read config data");
|
|
}
|
|
if (r > 0)
|
|
ts_info("success read config, len:%d", r);
|
|
/* clear command */
|
|
goodix_cmds_init(&ts_cmd, TS_CMD_REG_READY, 0, cmd_reg);
|
|
goodix_send_command(dev, &ts_cmd);
|
|
|
|
/*enable doze mode*/
|
|
if (dev->ic_type == IC_TYPE_NORMANDY)
|
|
goodix_set_i2c_doze_mode(dev, true);
|
|
|
|
exit:
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* goodix_hw_init - hardware initialize
|
|
* Called by touch core module when bootup
|
|
* @ts_dev: pointer to touch device
|
|
* return: 0 - no error, <0 error
|
|
*/
|
|
static int goodix_hw_init(struct goodix_ts_device *ts_dev)
|
|
{
|
|
int r;
|
|
|
|
BUG_ON(!ts_dev);
|
|
|
|
/* goodix_hw_init may be called many times */
|
|
if (!ts_dev->normal_cfg) {
|
|
ts_dev->normal_cfg = devm_kzalloc(ts_dev->dev,
|
|
sizeof(*ts_dev->normal_cfg), GFP_KERNEL);
|
|
if (!ts_dev->normal_cfg) {
|
|
ts_err("Failed to alloc memory for normal cfg");
|
|
return -ENOMEM;
|
|
}
|
|
mutex_init(&ts_dev->normal_cfg->lock);
|
|
}
|
|
if (!ts_dev->highsense_cfg) {
|
|
ts_dev->highsense_cfg = devm_kzalloc(ts_dev->dev,
|
|
sizeof(*ts_dev->highsense_cfg), GFP_KERNEL);
|
|
if (!ts_dev->highsense_cfg) {
|
|
ts_err("Failed to alloc memory for high sense cfg");
|
|
return -ENOMEM;
|
|
}
|
|
mutex_init(&ts_dev->highsense_cfg->lock);
|
|
}
|
|
|
|
|
|
/*for Nanjing IC, close HID_I2C mode when driver is probed*/
|
|
if (ts_dev->ic_type == IC_TYPE_NANJING) {
|
|
r = goodix_close_hidi2c_mode(ts_dev);
|
|
if (r < 0)
|
|
ts_info("close hid i2c mode FAILED");
|
|
}
|
|
|
|
/* read chip version: PID/VID/sensor ID,etc.*/
|
|
r = goodix_read_version(ts_dev, &ts_dev->chip_version);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* devicetree property like resolution(panel_max_xxx)
|
|
* may be different between sensors, here we try to parse
|
|
* parameters form sensor child node
|
|
*/
|
|
r = goodix_parse_customize_params(ts_dev,
|
|
ts_dev->board_data,
|
|
ts_dev->chip_version.sensor_id);
|
|
if (r < 0)
|
|
ts_info("Cann't find customized parameters");
|
|
|
|
ts_dev->normal_cfg->delay = 500;
|
|
/* send normal-cfg to firmware */
|
|
r = goodix_send_config(ts_dev, ts_dev->normal_cfg);
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* goodix_hw_reset - reset device
|
|
*
|
|
* @dev: pointer to touch device
|
|
* Returns 0 - succeed,<0 - failed
|
|
*/
|
|
int goodix_hw_reset(struct goodix_ts_device *dev)
|
|
{
|
|
u8 data[2] = {0x00};
|
|
int r = 0;
|
|
|
|
ts_info("HW reset");
|
|
|
|
if (dev->ic_type == IC_TYPE_NORMANDY) {
|
|
ts_info("normandy reset");
|
|
gpio_direction_output(dev->board_data->reset_gpio, 0);
|
|
udelay(2000);
|
|
gpio_direction_output(dev->board_data->reset_gpio, 1);
|
|
msleep(100);
|
|
} else if (dev->ic_type == IC_TYPE_NANJING) {
|
|
ts_info("nanjing reset");
|
|
|
|
/*close watch dog*/
|
|
data[0] = 0;
|
|
goodix_i2c_write(dev, 0x40b0, data, 1);
|
|
msleep(10);
|
|
|
|
/*soft reset*/
|
|
data[0] = 1;
|
|
goodix_i2c_write_trans_once(dev, 0x4180, data, 1);
|
|
msleep(250);//msleep can only sleep <20ms
|
|
goodix_close_hidi2c_mode(dev);
|
|
|
|
/*clear coor_reg*/
|
|
data[0] = 0;
|
|
data[1] = 0;
|
|
goodix_i2c_write(dev, 0x824d, data, 2);
|
|
}
|
|
|
|
|
|
/*init static esd*/
|
|
data[0] = GOODIX_ESD_TICK_WRITE_DATA;
|
|
if (dev->ic_type == IC_TYPE_NANJING) {
|
|
r = goodix_i2c_write(dev,
|
|
0x8043, data, 1);
|
|
if (r < 0)
|
|
ts_err("nanjing reset, init static esd FAILED,"
|
|
TAG_I2C " i2c write ERROR");
|
|
}
|
|
|
|
/*init dynamic esd*/
|
|
if (dev->reg.esd) {
|
|
r = goodix_i2c_write_trans(dev,
|
|
dev->reg.esd,
|
|
data, 1);
|
|
if (r < 0)
|
|
ts_err("IC reset, init dynamic esd FAILED,"
|
|
TAG_I2C " i2c write ERROR");
|
|
} else
|
|
ts_info("reg.esd is NULL, skip dynamic esd init");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* goodix_request_handler - handle firmware request
|
|
*
|
|
* @dev: pointer to touch device
|
|
* @request_data: requset information
|
|
* Returns 0 - succeed,<0 - failed
|
|
*/
|
|
static int goodix_request_handler(struct goodix_ts_device *dev,
|
|
struct goodix_request_data *request_data)
|
|
{
|
|
unsigned char buffer[1] = {0};
|
|
int r;
|
|
|
|
r = goodix_i2c_read_trans(dev, dev->reg.fw_request, buffer, 1);
|
|
/*TS_REG_REQUEST*/
|
|
if (r < 0)
|
|
return r;
|
|
|
|
switch (buffer[0]) {
|
|
case REQUEST_CONFIG:
|
|
ts_info("HW request config");
|
|
goodix_send_config(dev, dev->normal_cfg);
|
|
goto clear_requ;
|
|
case REQUEST_BAKREF:
|
|
ts_info("HW request bakref");
|
|
goto clear_requ;
|
|
case REQUEST_RESET:
|
|
ts_info("HW requset reset");
|
|
goto clear_requ;
|
|
case REQUEST_MAINCLK:
|
|
ts_info("HW request mainclk");
|
|
goto clear_requ;
|
|
default:
|
|
ts_info("Unknown hw request:%d", buffer[0]);
|
|
return 0;
|
|
}
|
|
|
|
clear_requ:
|
|
buffer[0] = 0x00;
|
|
r = goodix_i2c_write_trans(dev, dev->reg.fw_request, buffer, 1);
|
|
/*TS_REG_REQUEST*/
|
|
return r;
|
|
}
|
|
|
|
/*goodix_swap_coords - swap coord
|
|
*/
|
|
|
|
static void goodix_swap_coords(struct goodix_ts_device *dev,
|
|
struct goodix_ts_coords *coords,
|
|
int touch_num)
|
|
{
|
|
int i, temp;
|
|
struct goodix_ts_board_data *bdata = dev->board_data;
|
|
|
|
for (i = 0; i < touch_num; i++) {
|
|
if (bdata->swap_axis) {
|
|
temp = coords->x;
|
|
coords->x = coords->y;
|
|
coords->y = temp;
|
|
}
|
|
|
|
if (bdata->x2x)
|
|
coords->x = bdata->panel_max_x - coords->x;
|
|
if (bdata->y2y)
|
|
coords->y = bdata->panel_max_y - coords->y;
|
|
coords++;
|
|
}
|
|
}
|
|
|
|
static int goodix_remap_trace_id(struct goodix_ts_device *dev,
|
|
u8 *coor_buf, u32 coor_buf_len, int touch_num)
|
|
{
|
|
static u8 remap_array[20] = {0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff};
|
|
int i, j;
|
|
int offset = 0;
|
|
bool need_leave = false;
|
|
bool need_add = false;
|
|
u8 temp_buf[BYTES_PER_COORD] = {0x00};
|
|
u8 *small;
|
|
bool need_swap = false;
|
|
int max_touch_num = dev->board_data->panel_max_id;
|
|
|
|
if (touch_num > dev->board_data->panel_max_id) {
|
|
ts_err("touch num error, trace id no remap:%d", touch_num);
|
|
return 0;
|
|
}
|
|
|
|
if (!coor_buf || coor_buf_len > (2 + BYTES_PER_COORD * max_touch_num)) {
|
|
ts_err("touch data buff error, !coor_buf:%d, len:%d",
|
|
!coor_buf, coor_buf_len);
|
|
return 0;
|
|
}
|
|
|
|
/*clear and reset remap_array*/
|
|
if (touch_num == 0) {
|
|
for (i = 0; i < sizeof(remap_array); i++)
|
|
remap_array[i] = 0xff;
|
|
return 0;
|
|
}
|
|
|
|
/*find and add new point*/
|
|
offset = 0;
|
|
for (i = 0; i < touch_num; i++) {
|
|
need_add = true;
|
|
for (j = 0; j < sizeof(remap_array); j++) {
|
|
if (coor_buf[offset] == remap_array[j]) {
|
|
need_add = false;
|
|
break;
|
|
}
|
|
}
|
|
if (need_add == true) {
|
|
for (j = 0; j < sizeof(remap_array); j++) {
|
|
if (remap_array[j] == 0xff) {
|
|
remap_array[j] = coor_buf[offset];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
offset += BYTES_PER_COORD;
|
|
}
|
|
|
|
/*scan remap_array, find and remove leave point*/
|
|
for (i = 0; i < sizeof(remap_array); i++) {
|
|
if (remap_array[i] == 0xff)
|
|
continue;
|
|
else {
|
|
need_leave = true;
|
|
offset = 0;
|
|
for (j = 0; j < touch_num; j++) {
|
|
if (remap_array[i] == coor_buf[offset]) {
|
|
need_leave = false;
|
|
break;
|
|
}
|
|
offset += BYTES_PER_COORD;
|
|
}
|
|
if (need_leave == true) {
|
|
/*ts_info("---leave, trace id:%d:%d",*/
|
|
/* remap_array[i], i);*/
|
|
remap_array[i] = 0xff;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*remap trace id*/
|
|
offset = 0;
|
|
for (i = 0; i < touch_num; i++) {
|
|
/*do not remap pen's trace ID*/
|
|
if (coor_buf[offset] >= 0x80) {
|
|
offset += BYTES_PER_COORD;
|
|
continue;
|
|
} else {
|
|
for (j = 0; j < sizeof(remap_array); j++) {
|
|
if (remap_array[j] == coor_buf[offset]) {
|
|
/*ts_info("***remap, %d--->%d",*/
|
|
/* coor_buf[offset], j);*/
|
|
coor_buf[offset] = j;
|
|
break;
|
|
}
|
|
}
|
|
if (j >= sizeof(remap_array)) {
|
|
ts_err("remap ERROR!!trace id:%d",
|
|
coor_buf[offset]);
|
|
ts_err("remap_array:%*ph",
|
|
(int)sizeof(remap_array), remap_array);
|
|
}
|
|
offset += BYTES_PER_COORD;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
*for (i = 0; i < touch_num; i++) {
|
|
* ts_info("remap data%d:0x%02x,0x%02x,0x%02x,0x%02x,"
|
|
* "0x%02x,0x%02x,0x%02x,0x%02x",
|
|
* i, coor_buf[i * 8], coor_buf[i * 8 + 1],
|
|
* coor_buf[i * 8 + 2], coor_buf[i * 8 + 3],
|
|
* coor_buf[i * 8 + 4], coor_buf[i * 8 + 5],
|
|
* coor_buf[i * 8 + 6], coor_buf[i * 8 + 7]);
|
|
*}
|
|
*/
|
|
/*realign coor data by new trace ID*/
|
|
for (i = 0; i < touch_num - 1; i++) {
|
|
small = &coor_buf[BYTES_PER_COORD * i];
|
|
need_swap = false;
|
|
for (j = i + 1; j < touch_num; j++) {
|
|
if (coor_buf[BYTES_PER_COORD * j] < *small) {
|
|
need_swap = true;
|
|
small = &coor_buf[BYTES_PER_COORD * j];
|
|
}
|
|
}
|
|
/*swap*/
|
|
if (need_swap) {
|
|
memcpy(temp_buf, small, BYTES_PER_COORD);
|
|
memmove(small,
|
|
&coor_buf[BYTES_PER_COORD * i],
|
|
BYTES_PER_COORD);
|
|
memcpy(&coor_buf[BYTES_PER_COORD * i],
|
|
temp_buf,
|
|
BYTES_PER_COORD);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* goodix_event_handler - handle firmware event
|
|
*
|
|
* @dev: pointer to touch device
|
|
* @ts_event: pointer to touch event structure
|
|
* Returns 0 - succeed,<0 - failed
|
|
*/
|
|
|
|
static int goodix_touch_handler(struct goodix_ts_device *dev,
|
|
struct goodix_ts_event *ts_event,
|
|
u8 *pre_buf, u32 pre_buf_len)
|
|
{
|
|
struct goodix_touch_data *touch_data = &ts_event->event_data.touch_data;
|
|
struct goodix_ts_coords *coords = &(touch_data->coords[0]);
|
|
int max_touch_num = dev->board_data->panel_max_id;
|
|
unsigned char buffer[4 + BYTES_PER_COORD * max_touch_num];
|
|
unsigned char coord_sta;
|
|
int touch_num = 0, i;
|
|
int r = 0;
|
|
unsigned char chksum = 0;
|
|
|
|
if (!pre_buf || pre_buf_len != (4 + BYTES_PER_COORD)) {
|
|
r = -EINVAL;
|
|
return r;
|
|
}
|
|
|
|
/*copy data to buffer*/
|
|
memcpy(buffer, pre_buf, pre_buf_len);
|
|
|
|
/* buffer[1]: touch state */
|
|
coord_sta = buffer[1];
|
|
|
|
touch_num = coord_sta & 0x0F;
|
|
|
|
if (unlikely(touch_num > max_touch_num)) {
|
|
touch_num = -EINVAL;
|
|
goto exit_clean_sta;
|
|
} else if (unlikely(touch_num > 1)) {
|
|
r = goodix_i2c_read_trans(dev,
|
|
dev->reg.coor + 4 + BYTES_PER_COORD,
|
|
/*TS_REG_COORDS_BASE*/
|
|
&buffer[4 + BYTES_PER_COORD],
|
|
(touch_num - 1) * BYTES_PER_COORD);
|
|
if (unlikely(r < 0))
|
|
goto exit_clean_sta;
|
|
}
|
|
|
|
/* touch_num * BYTES_PER_COORD + 1(touch event state) */
|
|
/* + 1(checksum) + 1(key value) */
|
|
if (dev->ic_type == IC_TYPE_NANJING) {
|
|
chksum = checksum_u8(&buffer[1],
|
|
touch_num * BYTES_PER_COORD + 3);
|
|
} else {
|
|
chksum = checksum_u8(&buffer[0],
|
|
touch_num * BYTES_PER_COORD + 4);
|
|
}
|
|
if (unlikely(chksum != 0)) {
|
|
ts_err("Checksum error:%X, ic_type:%d", chksum, dev->ic_type);
|
|
r = -EINVAL;
|
|
goto exit_clean_sta;
|
|
}
|
|
|
|
touch_data->have_key = false;/*clear variable*/
|
|
touch_data->key_value = 0;/*clear variable*/
|
|
touch_data->have_key = (coord_sta >> 4) & 0x01;
|
|
if (touch_data->have_key) {
|
|
touch_data->key_value = buffer[touch_num * BYTES_PER_COORD + 2];
|
|
if (dev->board_data->pen_enable)
|
|
touch_data->key_value = (touch_data->key_value & 0x0f) |
|
|
((touch_data->key_value & 0xf0) >>
|
|
(4 - dev->board_data->tp_key_num));
|
|
}
|
|
|
|
/*add end*/
|
|
/*remap trace id*/
|
|
if (dev->ic_type == IC_TYPE_NANJING)
|
|
goodix_remap_trace_id(dev, &buffer[2],
|
|
2 + BYTES_PER_COORD * max_touch_num,
|
|
touch_num);
|
|
|
|
|
|
/*clear buffer*/
|
|
memset(touch_data->coords, 0x00, sizeof(touch_data->coords));
|
|
memset(touch_data->pen_coords, 0x00, sizeof(touch_data->pen_coords));
|
|
|
|
/*"0 ~ touch_num - 2" is finger, "touch_num - 1" may be a finger/pen*/
|
|
/*process "0 ~ touch_num -2"*/
|
|
if (likely(touch_num >= 1)) {
|
|
for (i = 0; i < touch_num - 1; i++) {
|
|
coords->id = buffer[i * BYTES_PER_COORD + 2] & 0x0f;
|
|
coords->x = buffer[i * BYTES_PER_COORD + 3] |
|
|
(buffer[i * BYTES_PER_COORD + 4] << 8);
|
|
coords->y = buffer[i * BYTES_PER_COORD + 5] |
|
|
(buffer[i * BYTES_PER_COORD + 6] << 8);
|
|
coords->w = buffer[i * BYTES_PER_COORD + 7];
|
|
coords->p = coords->w;
|
|
|
|
/*ts_debug("D:[%d](%d, %d)[%d]",*/
|
|
/* coords->id, coords->x, coords->y, coords->w);*/
|
|
coords++;
|
|
}
|
|
|
|
/*process "touch_num - 1", it may be a finger or a pen*/
|
|
/*it's a pen*/
|
|
i = touch_num - 1;
|
|
if (buffer[i * BYTES_PER_COORD + 2] >= 0x80) {
|
|
if (dev->board_data->pen_enable) {/*pen_enable*/
|
|
touch_data->pen_down = true;
|
|
|
|
/*change pen's trace ID,*/
|
|
/* let it equal to "panel_max_id - 1"*/
|
|
/*touch_data->pen_coords[0].id*/
|
|
/* = dev->board_data->panel_max_id - 1;*/
|
|
touch_data->pen_coords[0].id =
|
|
dev->board_data->panel_max_id * 2;
|
|
touch_data->pen_coords[0].x =
|
|
buffer[i * BYTES_PER_COORD + 3] |
|
|
(buffer[i * BYTES_PER_COORD + 4] << 8);
|
|
touch_data->pen_coords[0].y =
|
|
buffer[i * BYTES_PER_COORD + 5] |
|
|
(buffer[i * BYTES_PER_COORD + 6] << 8);
|
|
touch_data->pen_coords[0].w =
|
|
buffer[i * BYTES_PER_COORD + 7];
|
|
touch_data->pen_coords[0].p =
|
|
touch_data->pen_coords[0].w;
|
|
/*
|
|
*ts_debug("EP:[%d](%d, %d)",
|
|
* touch_data->pen_coords[0].id,
|
|
* touch_data->pen_coords[0].x,
|
|
* touch_data->pen_coords[0].y);
|
|
*/
|
|
}
|
|
} else {/*it's a finger*/
|
|
coords->id = buffer[i * BYTES_PER_COORD + 2] & 0x0f;
|
|
coords->x = buffer[i * BYTES_PER_COORD + 3] |
|
|
(buffer[i * BYTES_PER_COORD + 4] << 8);
|
|
coords->y = buffer[i * BYTES_PER_COORD + 5] |
|
|
(buffer[i * BYTES_PER_COORD + 6] << 8);
|
|
coords->w = buffer[i * BYTES_PER_COORD + 7];
|
|
coords->p = coords->w;
|
|
|
|
/*ts_debug("EF:[%d](%d, %d)",*/
|
|
/* coords->id, coords->x, coords->y);*/
|
|
if (touch_data->pen_down == true) {
|
|
touch_data->pen_down = false;
|
|
/*ts_info("***pen leave");*/
|
|
}
|
|
}
|
|
|
|
/*swap coord*/
|
|
goodix_swap_coords(dev, &touch_data->coords[0], touch_num);
|
|
goodix_swap_coords(dev, &touch_data->pen_coords[0], 1);
|
|
}
|
|
touch_data->touch_num = touch_num;
|
|
/* mark this event as touch event */
|
|
ts_event->event_type = EVENT_TOUCH;
|
|
r = 0;
|
|
|
|
exit_clean_sta:
|
|
/* handshake */
|
|
buffer[0] = 0x00;
|
|
goodix_i2c_write_trans(dev, dev->reg.coor, buffer, 1);
|
|
/*TS_REG_COORDS_BASE*/
|
|
return r;
|
|
}
|
|
|
|
static int goodix_event_handler(struct goodix_ts_device *dev,
|
|
struct goodix_ts_event *ts_event)
|
|
{
|
|
unsigned char pre_buf[4 + BYTES_PER_COORD];
|
|
unsigned char event_sta;
|
|
int r;
|
|
|
|
memset(pre_buf, 0, sizeof(pre_buf));
|
|
|
|
r = goodix_i2c_read_trans(dev, dev->reg.coor,
|
|
pre_buf, 4 + BYTES_PER_COORD);
|
|
if (unlikely(r < 0))
|
|
return r;
|
|
|
|
/* buffer[0]: event state */
|
|
event_sta = pre_buf[0];
|
|
if (likely((event_sta & GOODIX_TOUCH_EVENT) == GOODIX_TOUCH_EVENT)) {
|
|
/*handle touch event*/
|
|
goodix_touch_handler(dev,
|
|
ts_event,
|
|
pre_buf,
|
|
4 + BYTES_PER_COORD);
|
|
} else if (unlikely((event_sta & GOODIX_REQUEST_EVENT) ==
|
|
GOODIX_REQUEST_EVENT)) {
|
|
/* handle request event */
|
|
ts_event->event_type = EVENT_REQUEST;
|
|
goodix_request_handler(dev,
|
|
&ts_event->event_data.request_data);
|
|
} else if ((event_sta & GOODIX_GESTURE_EVENT) == GOODIX_GESTURE_EVENT) {
|
|
/* handle gesture event */
|
|
ts_info("Gesture event");
|
|
} else if ((event_sta & GOODIX_HOTKNOT_EVENT) == GOODIX_HOTKNOT_EVENT) {
|
|
/* handle hotknot event */
|
|
ts_info("Hotknot event");
|
|
} else {
|
|
ts_debug("unknown event type");
|
|
r = -EINVAL;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
/**
|
|
* goodix_hw_suspend - Let touch device stay in lowpower mode.
|
|
* @dev: pointer to goodix touch device
|
|
* @return: 0 - succeed, < 0 - failed
|
|
*/
|
|
static int goodix_hw_suspend(struct goodix_ts_device *dev)
|
|
{
|
|
struct goodix_ts_cmd sleep_cmd;
|
|
int r = 0;
|
|
|
|
goodix_cmds_init(&sleep_cmd, COMMAND_SLEEP, 0, dev->reg.command);
|
|
if (sleep_cmd.initialized) {
|
|
r = goodix_send_command(dev, &sleep_cmd);
|
|
if (!r)
|
|
ts_info("Chip in sleep mode");
|
|
} else
|
|
ts_err("Uninitialized sleep command");
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* goodix_hw_resume - Let touch device stay in active mode.
|
|
* @dev: pointer to goodix touch device
|
|
* @return: 0 - succeed, < 0 - failed
|
|
*/
|
|
static int goodix_hw_resume(struct goodix_ts_device *dev)
|
|
{
|
|
int r = 0;
|
|
int i, retry = GOODIX_BUS_RETRY_TIMES;
|
|
u8 temp_buf[256] = {0}, checksum;
|
|
u8 data[2] = {0x00};
|
|
|
|
for (; retry > 0; retry--) {
|
|
/*resume IC*/
|
|
if (dev->ic_type == IC_TYPE_NORMANDY)
|
|
goodix_hw_reset(dev);
|
|
else if (dev->ic_type == IC_TYPE_NANJING) {
|
|
/*1. read 0x8000 to resume nanjing*/
|
|
if (goodix_i2c_read(dev, 0x8000, data, 1))
|
|
ts_info("%s read err", __func__);
|
|
msleep(150);
|
|
/*2. check resume success or not*/
|
|
for (i = 0; i < 10; i++) {
|
|
r = goodix_i2c_read(dev,
|
|
dev->reg.command, data, 1);
|
|
if (!r && data[0] == 0xff)
|
|
break;
|
|
msleep(20);
|
|
}
|
|
|
|
if (i >= 10) {
|
|
ts_err("resume nanjing failed, after read"
|
|
TAG_I2C " 0x8000, 0x8040 not equal 0xff");
|
|
continue;
|
|
}
|
|
/*3. close hid i2c*/
|
|
goodix_close_hidi2c_mode(dev);
|
|
|
|
/*4. clear coor*/
|
|
data[0] = 0;
|
|
data[1] = 0;
|
|
goodix_i2c_write(dev, 0x824d, data, 2);
|
|
}
|
|
|
|
/*read version and check checksum*/
|
|
if (dev->reg.version_base) {
|
|
r = goodix_i2c_read(dev, dev->reg.version_base,
|
|
temp_buf, dev->reg.version_len);
|
|
if (r < 0)
|
|
continue;
|
|
|
|
checksum = checksum_u8(temp_buf, dev->reg.version_len);
|
|
if (!checksum) {
|
|
ts_info("read version SUCCESS");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int goodix_esd_check(struct goodix_ts_device *dev)
|
|
{
|
|
int r;
|
|
u8 data = 0;
|
|
|
|
if (dev->reg.esd == 0) {
|
|
ts_err("esd reg is NULL");
|
|
return 0;
|
|
}
|
|
|
|
/*check dynamic esd*/
|
|
if (dev->ic_type == IC_TYPE_NORMANDY)
|
|
r = dev->hw_ops->read_trans(dev,
|
|
TS_REG_ESD_TICK_R, &data, 1);
|
|
else
|
|
r = dev->hw_ops->read_trans(dev,
|
|
dev->reg.esd, &data, 1);
|
|
|
|
if (r < 0 || (data == GOODIX_ESD_TICK_WRITE_DATA)) {
|
|
ts_info("dynamic esd occur, r:%d, data:0x%02x", r, data);
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/*check static esd*/
|
|
if (dev->ic_type == IC_TYPE_NANJING) {
|
|
r = dev->hw_ops->read_trans(dev,
|
|
0x8043, &data, 1);
|
|
|
|
if (r < 0 || (data != 0xaa)) {
|
|
ts_info("static esd occur, r:%d, data:0x%02x", r, data);
|
|
r = -EINVAL;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
return r;
|
|
}
|
|
|
|
/* hardware opeation funstions */
|
|
static const struct goodix_ts_hw_ops hw_i2c_ops = {
|
|
.init = goodix_hw_init,
|
|
.dev_confirm = goodix_ts_dev_confirm,
|
|
.read = goodix_i2c_read,
|
|
.write = goodix_i2c_write,
|
|
.read_trans = goodix_i2c_read_trans,
|
|
.write_trans = goodix_i2c_write_trans,
|
|
.reset = goodix_hw_reset,
|
|
.event_handler = goodix_event_handler,
|
|
.send_config = goodix_send_config,
|
|
.read_config = goodix_read_config,
|
|
.send_cmd = goodix_send_command,
|
|
.read_version = goodix_read_version,
|
|
.suspend = goodix_hw_suspend,
|
|
.resume = goodix_hw_resume,
|
|
.check_hw = goodix_esd_check,
|
|
.read_pid = goodix_read_pid,
|
|
};
|
|
|
|
static struct platform_device *goodix_pdev;
|
|
|
|
static void goodix_pdev_release(struct device *dev)
|
|
{
|
|
kfree(goodix_pdev);
|
|
}
|
|
|
|
static int goodix_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *dev_id)
|
|
{
|
|
struct goodix_ts_device *ts_device = NULL;
|
|
struct goodix_ts_board_data *ts_bdata = NULL;
|
|
int r = 0;
|
|
|
|
ts_info("%s IN", __func__);
|
|
|
|
r = i2c_check_functionality(client->adapter,
|
|
I2C_FUNC_I2C);
|
|
if (!r)
|
|
return -EIO;
|
|
|
|
/* board data */
|
|
ts_bdata = devm_kzalloc(&client->dev,
|
|
sizeof(struct goodix_ts_board_data), GFP_KERNEL);
|
|
if (!ts_bdata)
|
|
return -ENOMEM;
|
|
|
|
if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) {
|
|
/* parse devicetree property */
|
|
r = goodix_parse_dt(client->dev.of_node, ts_bdata);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
#ifdef CONFIG_ACPI
|
|
else if (ACPI_COMPANION(&client->dev)) {
|
|
r = goodix_parse_acpi(&client->dev, ts_bdata);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
#endif
|
|
else {
|
|
/* use platform data */
|
|
devm_kfree(&client->dev, ts_bdata);
|
|
ts_bdata = client->dev.platform_data;
|
|
}
|
|
|
|
if (!ts_bdata)
|
|
return -ENODEV;
|
|
|
|
ts_device = devm_kzalloc(&client->dev,
|
|
sizeof(struct goodix_ts_device), GFP_KERNEL);
|
|
if (!ts_device)
|
|
return -ENOMEM;
|
|
/* use pinctrl in core.c */
|
|
ts_bdata->pinctrl_dev = client->adapter->dev.parent;
|
|
|
|
ts_device->name = "GT9886 TouchDevcie";
|
|
ts_device->dev = &client->dev;
|
|
ts_device->board_data = ts_bdata;
|
|
ts_device->hw_ops = &hw_i2c_ops;
|
|
touch_filter_bdata = ts_bdata;
|
|
|
|
/* ts core device */
|
|
goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL);
|
|
if (!goodix_pdev)
|
|
return -ENOMEM;
|
|
|
|
goodix_pdev->name = GOODIX_CORE_DRIVER_NAME;
|
|
goodix_pdev->id = 0;
|
|
goodix_pdev->num_resources = 0;
|
|
/*GOODIX_CORE_DRIVER_NAME = mtk-tpd2
|
|
* you could find this platform dev in
|
|
* /sys/devices/platform/GOODIX_CORE_DRIVER_NAME.0
|
|
* goodix_pdev->dev.parent = &client->dev;
|
|
*/
|
|
goodix_pdev->dev.platform_data = ts_device;
|
|
goodix_pdev->dev.release = goodix_pdev_release;
|
|
|
|
/*
|
|
* register platform device, then the goodix_ts_core
|
|
* module will probe the touch device.
|
|
*/
|
|
r = platform_device_register(goodix_pdev);
|
|
if (r) {
|
|
ts_err("failed register gt9886 platform device, %d", r);
|
|
goto err_pdev;
|
|
}
|
|
|
|
/* register platform driver*/
|
|
r = goodix_ts_core_init();
|
|
if (r) {
|
|
ts_err("failed register platform driver, %d", r);
|
|
goto err_pdriver;
|
|
}
|
|
|
|
ts_info("%s OUT", __func__);
|
|
|
|
return r;
|
|
|
|
err_pdriver:
|
|
platform_device_unregister(goodix_pdev);
|
|
|
|
err_pdev:
|
|
kfree(goodix_pdev);
|
|
goodix_pdev = NULL;
|
|
return r;
|
|
|
|
}
|
|
|
|
static int goodix_i2c_remove(struct i2c_client *client)
|
|
{
|
|
platform_device_unregister(goodix_pdev);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id i2c_matchs[] = {
|
|
{.compatible = TS_DT_COMPATIBLE,},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, i2c_matchs);
|
|
#endif
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static const struct acpi_device_id acpi_matchs[] = {
|
|
{.id = "PNPxxx"},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, acpi_matchs);
|
|
#endif
|
|
|
|
static const struct i2c_device_id i2c_id_table[] = {
|
|
{TS_DRIVER_NAME, 0},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, i2c_id_table);
|
|
|
|
static struct i2c_driver goodix_i2c_driver = {
|
|
.driver = {
|
|
.name = TS_DRIVER_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(i2c_matchs),
|
|
#ifdef CONFIG_ACPI
|
|
.acpi_match_table = acpi_matchs,
|
|
#endif
|
|
},
|
|
.probe = goodix_i2c_probe,
|
|
.remove = goodix_i2c_remove,
|
|
.id_table = i2c_id_table,
|
|
};
|
|
|
|
static int __init goodix_i2c_init(void)
|
|
{
|
|
ts_info("GT9886 i2c layer init");
|
|
return i2c_add_driver(&goodix_i2c_driver);
|
|
}
|
|
|
|
static void __exit goodix_i2c_exit(void)
|
|
{
|
|
i2c_del_driver(&goodix_i2c_driver);
|
|
}
|
|
|
|
late_initcall(goodix_i2c_init);
|
|
module_exit(goodix_i2c_exit);
|
|
|
|
MODULE_DESCRIPTION("Goodix GT9886 Touchscreen Hardware Module");
|
|
MODULE_AUTHOR("Goodix, Inc.");
|
|
MODULE_LICENSE("GPL v2");
|