450 lines
12 KiB
C
450 lines
12 KiB
C
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||
|
/*
|
||
|
* Copyright (C) 2016 MediaTek Inc.
|
||
|
*/
|
||
|
|
||
|
#include "focaltech_core.h"
|
||
|
|
||
|
#if FTS_ESDCHECK_EN
|
||
|
/*****************************************************************************
|
||
|
* Private constant and macro definitions using #define
|
||
|
*****************************************************************************/
|
||
|
#define ESDCHECK_WAIT_TIME 1000 /* ms */
|
||
|
#define LCD_ESD_PATCH 0
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Private enumerations, structures and unions using typedef
|
||
|
*****************************************************************************/
|
||
|
struct fts_esdcheck_st {
|
||
|
u8 mode : 1; /* 1- need check esd 0- no esd check */
|
||
|
u8 suspend : 1;
|
||
|
u8 proc_debug : 1; /* apk or adb use */
|
||
|
u8 intr : 1; /* 1- Interrupt trigger */
|
||
|
u8 unused : 4;
|
||
|
u8 flow_work_hold_cnt;
|
||
|
|
||
|
u8 flow_work_cnt_last; /* Save Flow Work Cnt(reg0x91) value */
|
||
|
u32 hardware_reset_cnt;
|
||
|
u32 nack_cnt;
|
||
|
u32 dataerror_cnt;
|
||
|
};
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Static variables
|
||
|
*****************************************************************************/
|
||
|
static struct fts_esdcheck_st fts_esdcheck_data;
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Global variable or extern global variabls/functions
|
||
|
*****************************************************************************/
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Static function prototypes
|
||
|
*****************************************************************************/
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* functions body
|
||
|
*****************************************************************************/
|
||
|
#if LCD_ESD_PATCH
|
||
|
int lcd_need_reset;
|
||
|
static int tp_need_recovery; /* LCD reset cause Tp reset */
|
||
|
int idc_esdcheck_lcderror(struct fts_ts_data *ts_data)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
u8 val = 0;
|
||
|
|
||
|
FTS_DEBUG("[ESD]Check LCD ESD");
|
||
|
if ((tp_need_recovery == 1) && (lcd_need_reset == 0)) {
|
||
|
tp_need_recovery = 0;
|
||
|
/* LCD reset, need recover TP state */
|
||
|
fts_release_all_finger();
|
||
|
fts_tp_state_recovery(ts_data);
|
||
|
}
|
||
|
|
||
|
ret = fts_read_reg(FTS_REG_ESD_SATURATE, &val);
|
||
|
if (ret < 0) {
|
||
|
FTS_ERROR("[ESD]: Read ESD_SATURATE(0xED) failed ret=%d!", ret);
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
if (val == 0xAA) {
|
||
|
/*
|
||
|
* 1. Set flag lcd_need_reset = 1;
|
||
|
* 2. LCD driver need reset(recovery) LCD and set lcd_need_reset
|
||
|
* to 0
|
||
|
* 3. recover TP state
|
||
|
*/
|
||
|
FTS_INFO("LCD ESD, Execute LCD reset!");
|
||
|
lcd_need_reset = 1;
|
||
|
tp_need_recovery = 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static int fts_esdcheck_tp_reset(struct fts_ts_data *ts_data)
|
||
|
{
|
||
|
FTS_FUNC_ENTER();
|
||
|
|
||
|
fts_esdcheck_data.flow_work_hold_cnt = 0;
|
||
|
fts_esdcheck_data.hardware_reset_cnt++;
|
||
|
|
||
|
fts_reset_proc(200);
|
||
|
fts_release_all_finger();
|
||
|
fts_tp_state_recovery(ts_data);
|
||
|
|
||
|
FTS_FUNC_EXIT();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Name: get_chip_id
|
||
|
* Brief: Read Chip Id 3 times
|
||
|
* Input:
|
||
|
* Output:
|
||
|
* Return: 1(true) - Read Chip Id 3 times failed
|
||
|
* 0(false) - Read Chip Id pass
|
||
|
*****************************************************************************/
|
||
|
static bool get_chip_id(struct fts_ts_data *ts_data)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
int i = 0;
|
||
|
u8 reg_value = 0;
|
||
|
u8 reg_addr = 0;
|
||
|
u8 chip_id = ts_data->ic_info.ids.chip_idh;
|
||
|
|
||
|
for (i = 0; i < 3; i++) {
|
||
|
reg_addr = FTS_REG_CHIP_ID;
|
||
|
ret = fts_read(®_addr, 1, ®_value, 1);
|
||
|
if (ret < 0) {
|
||
|
FTS_ERROR("[ESD]: Read Reg 0xA3 failed ret = %d!!",
|
||
|
ret);
|
||
|
fts_esdcheck_data.nack_cnt++;
|
||
|
} else {
|
||
|
if (reg_value == chip_id)
|
||
|
break;
|
||
|
|
||
|
FTS_DEBUG("read chip_id:%x,retry:%d", reg_value,
|
||
|
i);
|
||
|
fts_esdcheck_data.dataerror_cnt++;
|
||
|
}
|
||
|
}
|
||
|
msleep(20);
|
||
|
}
|
||
|
|
||
|
/* if can't get correct data in 3 times, then need hardware reset */
|
||
|
if (i >= 3) {
|
||
|
FTS_ERROR(
|
||
|
"[ESD]: Read Chip id 3 times failed, need execute TP reset!!");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Name: get_flow_cnt
|
||
|
* Brief: Read flow cnt(0x91)
|
||
|
* Input:
|
||
|
* Output:
|
||
|
* Return: 1(true) - Reg 0x91(flow cnt) abnormal: hold a value for 5 times
|
||
|
* 0(false) - Reg 0x91(flow cnt) normal
|
||
|
*****************************************************************************/
|
||
|
static bool get_flow_cnt(struct fts_ts_data *ts_data)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
u8 reg_value = 0;
|
||
|
u8 reg_addr = 0;
|
||
|
|
||
|
reg_addr = FTS_REG_FLOW_WORK_CNT;
|
||
|
ret = fts_read(®_addr, 1, ®_value, 1);
|
||
|
if (ret < 0) {
|
||
|
FTS_ERROR("[ESD]: Read Reg 0x91 failed ret = %d!!", ret);
|
||
|
fts_esdcheck_data.nack_cnt++;
|
||
|
} else {
|
||
|
if (reg_value == fts_esdcheck_data.flow_work_cnt_last)
|
||
|
fts_esdcheck_data.flow_work_hold_cnt++;
|
||
|
else
|
||
|
fts_esdcheck_data.flow_work_hold_cnt = 0;
|
||
|
|
||
|
|
||
|
fts_esdcheck_data.flow_work_cnt_last = reg_value;
|
||
|
}
|
||
|
|
||
|
/* if read flow work cnt 5 times and the value are all the same, then */
|
||
|
/* need hardware_reset */
|
||
|
if (fts_esdcheck_data.flow_work_hold_cnt >= 5) {
|
||
|
FTS_DEBUG(
|
||
|
"[ESD]: Flow Work Cnt(reg0x91) keep a value for 5 times, need execute TP reset!!");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static int esdcheck_algorithm(struct fts_ts_data *ts_data)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
u8 reg_value = 0;
|
||
|
u8 reg_addr = 0;
|
||
|
bool hardware_reset = 0;
|
||
|
|
||
|
/* 1. esdcheck is interrupt, then return */
|
||
|
if (fts_esdcheck_data.intr == 1) {
|
||
|
FTS_DEBUG(
|
||
|
"[ESD]: In interrupt state, not check esd, return immediately!!");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* 2. check power state, if suspend, no need check esd */
|
||
|
if (fts_esdcheck_data.suspend == 1) {
|
||
|
FTS_DEBUG(
|
||
|
"[ESD]: In suspend, not check esd, return immediately!!");
|
||
|
/* because in suspend state, adb can be used, when upgrade FW,
|
||
|
* will active ESD check(active = 1)
|
||
|
* But in suspend, then will don't queue_delayed_work, when
|
||
|
* resume, don't check ESD again
|
||
|
*/
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* 3. check fts_esdcheck_data.proc_debug state, if 1-proc busy, no need */
|
||
|
/* check esd*/
|
||
|
if (fts_esdcheck_data.proc_debug == 1) {
|
||
|
FTS_INFO(
|
||
|
"[ESD]: In apk or adb command mode, not check esd, return immediately!!");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* 4. In factory mode, can't check esd */
|
||
|
reg_addr = FTS_REG_WORKMODE;
|
||
|
ret = fts_read_reg(reg_addr, ®_value);
|
||
|
if (ret < 0) {
|
||
|
fts_esdcheck_data.nack_cnt++;
|
||
|
} else if ((reg_value & 0x70) != FTS_REG_WORKMODE_WORK_VALUE) {
|
||
|
FTS_DEBUG(
|
||
|
"[ESD]: not in work mode, no check esd, return immediately!!");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* 5. IDC esd check lcd default:close */
|
||
|
#if LCD_ESD_PATCH
|
||
|
idc_esdcheck_lcderror(ts_data);
|
||
|
#endif
|
||
|
|
||
|
/* 6. Get Chip ID */
|
||
|
hardware_reset = get_chip_id(ts_data);
|
||
|
|
||
|
/* 7. get Flow work cnt: 0x91 If no change for 5 times, then ESD and */
|
||
|
/* reset */
|
||
|
if (!hardware_reset)
|
||
|
hardware_reset = get_flow_cnt(ts_data);
|
||
|
|
||
|
|
||
|
/* 8. If need hardware reset, then handle it here */
|
||
|
if (hardware_reset == 1) {
|
||
|
FTS_DEBUG("[ESD]: NoACK=%d, Error Data=%d, Hardware Reset=%d",
|
||
|
fts_esdcheck_data.nack_cnt,
|
||
|
fts_esdcheck_data.dataerror_cnt,
|
||
|
fts_esdcheck_data.hardware_reset_cnt);
|
||
|
fts_esdcheck_tp_reset(ts_data);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void esdcheck_func(struct work_struct *work)
|
||
|
{
|
||
|
u8 val = 0;
|
||
|
struct fts_ts_data *ts_data =
|
||
|
container_of(work, struct fts_ts_data, esdcheck_work.work);
|
||
|
|
||
|
if (fts_esdcheck_data.mode == ENABLE) {
|
||
|
if (ts_data->ic_info.is_incell) {
|
||
|
fts_read_reg(FTS_REG_ESDCHECK_DISABLE, &val);
|
||
|
if (val == 0xA5) {
|
||
|
fts_esdcheck_data.mode = DISABLE;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
esdcheck_algorithm(ts_data);
|
||
|
queue_delayed_work(ts_data->ts_workqueue,
|
||
|
&ts_data->esdcheck_work,
|
||
|
msecs_to_jiffies(ESDCHECK_WAIT_TIME));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int fts_esdcheck_set_intr(bool intr)
|
||
|
{
|
||
|
/* interrupt don't add debug message */
|
||
|
fts_esdcheck_data.intr = intr;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fts_esdcheck_get_status(void)
|
||
|
{
|
||
|
/* interrupt don't add debug message */
|
||
|
return fts_esdcheck_data.mode;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Name: fts_esdcheck_proc_busy
|
||
|
* Brief: When APK or ADB command access TP via driver, then need set
|
||
|
*proc_debug,
|
||
|
* then will not check ESD.
|
||
|
* Input:
|
||
|
* Output:
|
||
|
* Return:
|
||
|
*****************************************************************************/
|
||
|
int fts_esdcheck_proc_busy(bool proc_debug)
|
||
|
{
|
||
|
fts_esdcheck_data.proc_debug = proc_debug;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Name: fts_esdcheck_switch
|
||
|
* Brief: FTS esd check function switch.
|
||
|
* Input: enable: 1 - Enable esd check
|
||
|
* 0 - Disable esd check
|
||
|
* Output:
|
||
|
* Return:
|
||
|
*****************************************************************************/
|
||
|
int fts_esdcheck_switch(bool enable)
|
||
|
{
|
||
|
struct fts_ts_data *ts_data = fts_data;
|
||
|
|
||
|
FTS_FUNC_ENTER();
|
||
|
if (fts_esdcheck_data.mode == ENABLE) {
|
||
|
if (enable) {
|
||
|
FTS_DEBUG("[ESD]: ESD check start!!");
|
||
|
fts_esdcheck_data.flow_work_hold_cnt = 0;
|
||
|
fts_esdcheck_data.flow_work_cnt_last = 0;
|
||
|
queue_delayed_work(
|
||
|
ts_data->ts_workqueue, &ts_data->esdcheck_work,
|
||
|
msecs_to_jiffies(ESDCHECK_WAIT_TIME));
|
||
|
} else {
|
||
|
FTS_DEBUG("[ESD]: ESD check stop!!");
|
||
|
cancel_delayed_work_sync(&ts_data->esdcheck_work);
|
||
|
}
|
||
|
} else {
|
||
|
FTS_DEBUG("[ESD]: ESD should disable!!");
|
||
|
cancel_delayed_work_sync(&ts_data->esdcheck_work);
|
||
|
}
|
||
|
|
||
|
FTS_FUNC_EXIT();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fts_esdcheck_suspend(void)
|
||
|
{
|
||
|
FTS_FUNC_ENTER();
|
||
|
fts_esdcheck_switch(DISABLE);
|
||
|
fts_esdcheck_data.suspend = 1;
|
||
|
FTS_FUNC_EXIT();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fts_esdcheck_resume(void)
|
||
|
{
|
||
|
FTS_FUNC_ENTER();
|
||
|
fts_esdcheck_switch(ENABLE);
|
||
|
fts_esdcheck_data.suspend = 0;
|
||
|
FTS_FUNC_EXIT();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t fts_esd_mode_store(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct input_dev *input_dev = fts_data->input_dev;
|
||
|
|
||
|
mutex_lock(&input_dev->mutex);
|
||
|
if (FTS_SYSFS_ECHO_ON(buf)) {
|
||
|
FTS_DEBUG("enable esdcheck");
|
||
|
fts_esdcheck_data.mode = ENABLE;
|
||
|
fts_esdcheck_switch(ENABLE);
|
||
|
} else if (FTS_SYSFS_ECHO_OFF(buf)) {
|
||
|
FTS_DEBUG("disable esdcheck");
|
||
|
fts_esdcheck_data.mode = DISABLE;
|
||
|
fts_esdcheck_switch(DISABLE);
|
||
|
}
|
||
|
mutex_unlock(&input_dev->mutex);
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static ssize_t fts_esd_mode_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
int count;
|
||
|
struct input_dev *input_dev = fts_data->input_dev;
|
||
|
|
||
|
mutex_lock(&input_dev->mutex);
|
||
|
count = snprintf(buf, PAGE_SIZE, "Esd check: %s\n",
|
||
|
fts_esdcheck_get_status() ? "On" : "Off");
|
||
|
mutex_unlock(&input_dev->mutex);
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
/* sysfs esd node
|
||
|
* read example: cat fts_esd_mode ---read esd mode
|
||
|
* write example:echo 01 > fts_esd_mode ---make esdcheck enable
|
||
|
*
|
||
|
*/
|
||
|
static DEVICE_ATTR_RW(fts_esd_mode, 0644, fts_esd_mode_show,
|
||
|
fts_esd_mode_store);
|
||
|
|
||
|
static struct attribute *fts_esd_mode_attrs[] = {
|
||
|
|
||
|
&dev_attr_fts_esd_mode.attr, NULL,
|
||
|
};
|
||
|
|
||
|
static struct attribute_group fts_esd_group = {
|
||
|
.attrs = fts_esd_mode_attrs,
|
||
|
};
|
||
|
|
||
|
int fts_create_esd_sysfs(struct device *dev)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
ret = sysfs_create_group(&dev->kobj, &fts_esd_group);
|
||
|
if (ret != 0) {
|
||
|
FTS_ERROR("%s(sysfs) create failed!", __func__);
|
||
|
sysfs_remove_group(&dev->kobj, &fts_esd_group);
|
||
|
return ret;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fts_esdcheck_init(struct fts_ts_data *ts_data)
|
||
|
{
|
||
|
FTS_FUNC_ENTER();
|
||
|
|
||
|
if (ts_data->ts_workqueue) {
|
||
|
INIT_DELAYED_WORK(&ts_data->esdcheck_work, esdcheck_func);
|
||
|
} else {
|
||
|
FTS_ERROR(
|
||
|
"fts workqueue is NULL, can't run esd check function");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
memset((u8 *)&fts_esdcheck_data, 0, sizeof(struct fts_esdcheck_st));
|
||
|
|
||
|
fts_esdcheck_data.mode = ENABLE;
|
||
|
fts_esdcheck_switch(ENABLE);
|
||
|
fts_create_esd_sysfs(ts_data->dev);
|
||
|
FTS_FUNC_EXIT();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fts_esdcheck_exit(struct fts_ts_data *ts_data)
|
||
|
{
|
||
|
sysfs_remove_group(&ts_data->dev->kobj, &fts_esd_group);
|
||
|
return 0;
|
||
|
}
|
||
|
#endif /* FTS_ESDCHECK_EN */
|