/* 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 */