blob: bd9468d0ade769ddf60b58e727d24cc802a932b0 [file] [log] [blame]
/*
* Copyright (C) 2010,Imagis Technology Co. Ltd. All Rights Reserved.
*
* 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 useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/kernel.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/input/mt.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#endif
#include "ist40xx.h"
#include "ist40xx_misc.h"
#include "ist40xx_update.h"
#include "ist40xx_cmcs.h"
#if defined(CONFIG_VBUS_NOTIFIER) || defined(CONFIG_MUIC_NOTIFIER)
#include <linux/muic/muic.h>
#include <linux/muic/muic_notifier.h>
#include <linux/vbus_notifier.h>
#endif
#if defined(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
#include <linux/usb/manager/usb_typec_manager_notifier.h>
#endif
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
#include <linux/t-base-tui.h>
#endif
#ifdef CONFIG_SAMSUNG_TUI
#include "stui_inf.h"
#endif
struct ist40xx_data *ts_data;
#ifdef CONFIG_DISPLAY_SAMSUNG
extern int get_lcd_attached(char *mode);
#endif
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
struct ist40xx_data *tui_tsp_info;
extern int tui_force_close(uint32_t arg);
#endif
#ifdef CONFIG_SAMSUNG_TUI
struct ist40xx_data *stui_tsp_info;
#endif
int ist40xx_log_level = IST40XX_LOG_LEVEL;
void tsp_printk(int level, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
if (ist40xx_log_level < level)
return;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
printk("%s %pV", IST40XX_LOG_TAG, &vaf);
va_end(args);
}
long get_milli_second(struct ist40xx_data *data)
{
ktime_get_ts(&data->t_current);
return data->t_current.tv_sec * 1000 +
data->t_current.tv_nsec / 1000000;
}
void ist40xx_delay(unsigned int ms)
{
if (ms < 20)
usleep_range(ms * 1000, ms * 1000);
else
msleep(ms);
}
int ist40xx_intr_wait(struct ist40xx_data *data, long ms)
{
long start_ms = get_milli_second(data);
long curr_ms = 0;
while (1) {
if (!data->irq_working)
break;
curr_ms = get_milli_second(data);
if ((curr_ms < 0) || (start_ms < 0)
|| (curr_ms - start_ms > ms)) {
input_info(true, &data->client->dev, "%s: timeout(%dms)\n",
__func__, ms);
return -EPERM;
}
ist40xx_delay(2);
}
return 0;
}
void ist40xx_disable_irq(struct ist40xx_data *data)
{
if (data->irq_enabled) {
disable_irq(data->client->irq);
data->irq_enabled = false;
data->status.event_mode = false;
}
}
void ist40xx_enable_irq(struct ist40xx_data *data)
{
if (!data->irq_enabled) {
enable_irq(data->client->irq);
ist40xx_delay(10);
data->irq_enabled = true;
data->status.event_mode = true;
}
}
void ist40xx_scheduled_reset(struct ist40xx_data *data)
{
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
if (TRUSTEDUI_MODE_INPUT_SECURED & trustedui_get_current_mode()) {
input_err(true, &data->client->dev, "%s return, TUI is enabled!\n",
__func__);
return;
}
#endif
#ifdef CONFIG_INPUT_SEC_SECURE_TOUCH
if (atomic_read(&data->st_enabled) == SECURE_TOUCH_ENABLED) {
input_err(true, &data->client->dev,
"%s: TSP no accessible from Linux, TUI is enabled!\n",
__func__);
return;
}
#endif
#ifdef CONFIG_SAMSUNG_TUI
if (STUI_MODE_TOUCH_SEC & stui_get_mode())
return;
#endif
if (data->initialized)
schedule_delayed_work(&data->work_reset_check, 0);
}
static void ist40xx_request_reset(struct ist40xx_data *data)
{
data->irq_err_cnt++;
if (data->irq_err_cnt >= data->max_irq_err_cnt) {
input_info(true, &data->client->dev, "%s\n", __func__);
ist40xx_scheduled_reset(data);
data->irq_err_cnt = 0;
}
}
void ist40xx_start(struct ist40xx_data *data)
{
if (data->initialized) {
data->scan_count = 0;
data->scan_retry = 0;
mod_timer(&data->event_timer,
get_jiffies_64() + EVENT_TIMER_INTERVAL);
}
data->ignore_delay = true;
/* TA mode */
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_MODE_SPECIAL << 16) | (data->noise_mode & 0xFFFF)));
if ((data->noise_mode >> NOISE_MODE_REJECTZONE) & 1) {
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_REJECTZONE_TOP << 16) | data->rejectzone_t));
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_REJECTZONE_BOTTOM << 16) | data->rejectzone_b));
input_info(true, &data->client->dev, "%s: rejectzone T:%d, B:%d\n",
__func__, data->rejectzone_t, data->rejectzone_b);
}
if (data->report_rate >= 0) {
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_TIME_ACTIVE << 16) | (data->report_rate & 0xFFFF)));
input_info(true, &data->client->dev, "%s: active rate : %dus\n",
__func__, data->report_rate);
}
if (data->idle_rate >= 0) {
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_TIME_IDLE << 16) | (data->idle_rate & 0xFFFF)));
input_info(true, &data->client->dev, "%s: idle rate : %dus\n",
__func__, data->idle_rate);
}
if (data->rec_mode) {
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
(eHCOM_SET_REC_MODE << 16) | (IST40XX_ENABLE & 0xFFFF));
input_info(true, &data->client->dev, "%s: rec mode start\n", __func__);
}
if (data->debug_mode || data->rec_mode) {
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
(eHCOM_SLEEP_MODE_EN << 16) | (IST40XX_DISABLE & 0xFFFF));
input_info(true, &data->client->dev, "%s: nosleep mode start\n", __func__);
}
ist40xx_cmd_start_scan(data);
data->ignore_delay = false;
if (data->rec_mode) {
ist40xx_delay(100);
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
(eHCOM_SET_REC_MODE << 16) |
(IST40XX_START_SCAN & 0xFFFF));
}
input_info(true, &data->client->dev, "%s: mode : 0x%x\n", __func__,
data->noise_mode & 0xFFFF);
}
int ist40xx_set_input_device(struct ist40xx_data *data)
{
int ret;
data->input_dev->name = "sec_touchscreen";
data->input_dev->id.bustype = BUS_I2C;
data->input_dev->dev.parent = &data->client->dev;
input_mt_init_slots(data->input_dev, IST40XX_MAX_FINGER_ID, 0);
set_bit(EV_SYN, data->input_dev->evbit);
set_bit(EV_ABS, data->input_dev->evbit);
set_bit(EV_KEY, data->input_dev->evbit);
set_bit(BTN_TOUCH, data->input_dev->keybit);
set_bit(BTN_TOOL_FINGER, data->input_dev->keybit);
set_bit(INPUT_PROP_DIRECT, data->input_dev->propbit);
set_bit(KEY_HOMEPAGE, data->input_dev->keybit);
set_bit(KEY_INT_CANCEL, data->input_dev->keybit);
input_set_abs_params(data->input_dev, ABS_MT_PALM, 0, 1, 0, 0);
input_set_abs_params(data->input_dev, ABS_MT_POSITION_X, 0,
data->tsp_info.width - 1, 0, 0);
input_set_abs_params(data->input_dev, ABS_MT_POSITION_Y, 0,
data->tsp_info.height - 1, 0, 0);
input_set_abs_params(data->input_dev, ABS_MT_TOUCH_MAJOR, 0,
IST40XX_MAX_MAJOR, 0, 0);
input_set_abs_params(data->input_dev, ABS_MT_TOUCH_MINOR, 0,
IST40XX_MAX_MINOR, 0, 0);
input_set_abs_params(data->input_dev, ABS_MT_PRESSURE, 0, IST40XX_MAX_Z,
0, 0);
input_set_capability(data->input_dev, EV_KEY, KEY_BLACK_UI_GESTURE);
input_set_drvdata(data->input_dev, data);
ret = input_register_device(data->input_dev);
input_info(true, &data->client->dev, "%s: input register device:%d\n",
__func__, ret);
if (ret)
return ret;
ret = input_register_device(data->input_dev_proximity);
input_info(true, &data->client->dev, "%s: input proximity register device:%d\n",
__func__, ret);
return ret;
}
int ist40xx_get_info(struct ist40xx_data *data)
{
int ret;
u32 calib_msg;
u8 fod_info[4];
ist40xx_disable_irq(data);
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
(eHCOM_FW_HOLD << 16) | (IST40XX_ENABLE & 0xFFFF));
ist40xx_delay(20);
mutex_lock(&data->lock);
ret = ist40xx_get_tsp_info(data);
if (ret) {
mutex_unlock(&data->lock);
goto err_get_info;
}
mutex_unlock(&data->lock);
ist40xx_print_info(data);
ret = ist40xx_set_input_device(data);
if (ret)
goto err_get_info;
ist40xx_read_cmd(data, eHCOM_GET_CAL_RESULT_S, &calib_msg);
input_info(true, &data->client->dev, "SLF calib result: 0x%08X\n", calib_msg);
ist40xx_read_cmd(data, eHCOM_GET_CAL_RESULT_M, &calib_msg);
input_info(true, &data->client->dev, "MTL calib result: 0x%08X\n", calib_msg);
ist40xx_read_cmd(data, eHCOM_GET_CAL_RESULT_P, &calib_msg);
input_info(true, &data->client->dev, "PROX calib result: 0x%08X\n", calib_msg);
ret = ist40xx_write_cmd(data, IST40XX_HIB_CMD,
(eHCOM_FW_HOLD << 16) | (IST40XX_DISABLE & 0xFFFF));
if (ret) {
input_err(true, &data->client->dev, "%s: fail to disable hold\n",
__func__);
mutex_lock(&data->lock);
ist40xx_reset(data, false);
mutex_unlock(&data->lock);
}
ist40xx_enable_irq(data);
if (data->dt_data->support_fod) {
ist40xx_read_sponge_reg(data, IST40XX_SPONGE_FOD_INFO,
(u16*)&fod_info, 2, true);
data->fod_tx = fod_info[0];
data->fod_rx = fod_info[1];
data->fod_vi_size = fod_info[2];
input_info(true, &data->client->dev, "%s fod_tx[%d] fod_rx[%d] fod_vi_size[%d]\n",
__func__, data->fod_tx, data->fod_rx, data->fod_vi_size);
}
return 0;
err_get_info:
return ret;
}
#define SPECIAL_MAGIC_STRING (0x4170CF00)
#define SPECIAL_MAGIC_MASK (0xFFFFFF00)
#define SPECIAL_MESSAGE_MASK (~SPECIAL_MAGIC_MASK)
#define PARSE_SPECIAL_MESSAGE(n) ((n & SPECIAL_MAGIC_MASK) == SPECIAL_MAGIC_STRING ? \
(n & SPECIAL_MESSAGE_MASK) : -EINVAL)
#define SPECIAL_START_MASK (0x80)
#define SPECIAL_SUCCESS_MASK (0x0F)
void ist40xx_special_cmd(struct ist40xx_data *data, int cmd)
{
gesture_msg g_msg;
if (cmd == 0) {
ist40xx_burst_read(data->client, IST40XX_HIB_GESTURE_MSG, g_msg.full,
sizeof(g_msg.full) / IST40XX_DATA_LEN, true);
input_info(true, &data->client->dev, "g_msg %d, %d, %d\n",
g_msg.b.eid, g_msg.b.gtype, g_msg.b.gid);
if (g_msg.b.eid == EID_GESTURE) {
switch (g_msg.b.gtype) {
case G_TYPE_SWIPE:
if (g_msg.b.gid == G_ID_SWIPE_UP) {
data->scrub_id = SPONGE_EVENT_TYPE_SPAY;
data->scrub_x = 0;
data->scrub_y = 0;
data->all_spay_count++;
input_info(true, &data->client->dev, "Swipe Up Trigger\n");
input_report_key(data->input_dev,
KEY_BLACK_UI_GESTURE,
true);
input_sync(data->input_dev);
input_report_key(data->input_dev,
KEY_BLACK_UI_GESTURE,
false);
input_sync(data->input_dev);
}
break;
case G_TYPE_DOUBLETAP:
if (g_msg.b.gid == G_ID_AOD_DOUBLETAP) {
data->scrub_id = SPONGE_EVENT_TYPE_AOD_DOUBLETAB;
data->scrub_x = (g_msg.b.gdata[0] << 4) |
((g_msg.b.gdata[2] >> 4) & 0xF);
data->scrub_y = (g_msg.b.gdata[1] << 4) |
(g_msg.b.gdata[2] & 0xF);
data->all_aod_tsp_count++;
input_info(true, &data->client->dev,
"Double Tap Trigger (%d, %d)\n",
data->scrub_x, data->scrub_y);
input_report_key(data->input_dev,
KEY_BLACK_UI_GESTURE,
true);
input_sync(data->input_dev);
input_report_key(data->input_dev,
KEY_BLACK_UI_GESTURE,
false);
input_sync(data->input_dev);
} else if (g_msg.b.gid == G_ID_DOUBLETAP_WAKEUP) {
input_info(true, &data->client->dev,
"AOT Double Tap Trigger\n");
input_report_key(data->input_dev,
KEY_HOMEPAGE,
true);
input_sync(data->input_dev);
input_report_key(data->input_dev,
KEY_HOMEPAGE,
false);
input_sync(data->input_dev);
/* request from sensor team */
input_report_abs(data->input_dev_proximity,
ABS_MT_CUSTOM2,
true);
input_sync(data->input_dev_proximity);
input_report_abs(data->input_dev_proximity,
ABS_MT_CUSTOM2,
false);
input_sync(data->input_dev_proximity);
}
break;
case G_TYPE_SINGLETAP:
if (g_msg.b.gid == G_ID_SINGLETAP) {
data->scrub_id = SPONGE_EVENT_TYPE_SINGLETAP;
data->scrub_x = (g_msg.b.gdata[0] << 4) |
((g_msg.b.gdata[2] >> 4) & 0xF);
data->scrub_y = (g_msg.b.gdata[1] << 4) |
(g_msg.b.gdata[2] & 0xF);
data->all_singletap_count++;
input_info(true, &data->client->dev,
"Single Tap Trigger (%d, %d)\n",
data->scrub_x, data->scrub_y);
input_report_key(data->input_dev,
KEY_BLACK_UI_GESTURE,
true);
input_sync(data->input_dev);
input_report_key(data->input_dev,
KEY_BLACK_UI_GESTURE,
false);
input_sync(data->input_dev);
}
break;
case G_TYPE_PRESSURE:
break;
case G_TYPE_PRESS:
if (g_msg.b.gid == G_ID_FOD_LONG || g_msg.b.gid == G_ID_FOD_NORMAL) {
data->scrub_id = SPONGE_EVENT_TYPE_FOD;
input_info(true, &data->client->dev,
"FOD %s\n", g_msg.b.gid ? "normal" : "long");
} else if (g_msg.b.gid == G_ID_FOD_RELEASE) {
data->scrub_id = SPONGE_EVENT_TYPE_FOD_RELEASE;
input_info(true, &data->client->dev,
"FOD release\n");
} else if (g_msg.b.gid == G_ID_FOD_OUT) {
data->scrub_id = SPONGE_EVENT_TYPE_FOD_OUT;
input_info(true, &data->client->dev,
"FOD out\n");
}
input_report_key(data->input_dev,
KEY_BLACK_UI_GESTURE,
true);
input_sync(data->input_dev);
input_report_key(data->input_dev,
KEY_BLACK_UI_GESTURE,
false);
input_sync(data->input_dev);
break;
default:
input_err(true, &data->client->dev,
"Not support gesture type\n");
break;
}
} else {
input_err(true, &data->client->dev, "Not Gesture Msg\n");
}
return;
}
input_err(true, &data->client->dev, "Not support gesture cmd: 0x%02X\n", cmd);
}
static void location_detect(struct ist40xx_data *data, char *loc, int x, int y)
{
if (x < data->dt_data->area_edge)
strncat(loc, "E.", 2);
else if (x < (data->tsp_info.width - data->dt_data->area_edge))
strncat(loc, "C.", 2);
else
strncat(loc, "e.", 2);
if (y < data->dt_data->area_indicator)
strncat(loc, "S", 1);
else if (y < (data->tsp_info.height - data->dt_data->area_navigation))
strncat(loc, "C", 1);
else
strncat(loc, "N", 1);
}
static void release_finger(struct ist40xx_data *data, int id)
{
input_mt_slot(data->input_dev, id);
input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false);
input_report_key(data->input_dev, BTN_TOUCH, 0);
input_report_key(data->input_dev, BTN_TOOL_FINGER, 0);
input_sync(data->input_dev);
input_info(true, &data->client->dev, "forced touch release: %d\n", id);
data->tsp_touched[id] = false;
}
void clear_input_data(struct ist40xx_data *data)
{
int i = 0;
for (i = 0; i < IST40XX_MAX_FINGER_ID; i++) {
if (data->tsp_touched[i] == true)
release_finger(data, i);
}
data->touch_pressed_num = 0;
data->check_multi = 0;
}
#define TOUCH_DOWN_MESSAGE ("p")
#define TOUCH_UP_MESSAGE ("r")
#define TOUCH_MOVE_MESSAGE (" ")
static void report_input_data(struct ist40xx_data *data)
{
int ret;
int i, id;
int idx = 0;
bool press = false;
finger_info *fingers = (finger_info *)data->fingers;
u32 status;
u8 mode = TOUCH_STATUS_NORMAL_MODE;
char location[4] = { 0 };
ret = ist40xx_read_reg(data->client, IST40XX_HIB_TOUCH_STATUS, &status);
if (!ret) {
if ((status & TOUCH_STATUS_MASK) == TOUCH_STATUS_MAGIC) {
if (GET_NOISE_MODE(status))
mode |= TOUCH_STATUS_NOISE_MODE;
if (GET_WET_MODE(status))
mode |= TOUCH_STATUS_WET_MODE;
}
}
for (i = 0; i < IST40XX_MAX_FINGER_ID; i++) {
memset(location, 0, sizeof(location));
id = fingers[idx].b.id - 1;
if (i == id) {
if (fingers[idx].b.status == TOUCH_STA_NONE) {
idx++;
continue;
} else if (fingers[idx].b.status == TOUCH_STA_CSV) {
// No Report CSV Touch
if (data->tsp_touched[id] == true) {
data->move_count[id]++;
/*for getting coordinate of the last point of move event*/
data->r_x[id] = fingers[idx].b.x;
data->r_y[id] = fingers[idx].b.y;
location_detect(data, location, data->r_x[id],
data->r_y[id]);
#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP)
tsp_debug("%s%d (%d, %d) loc:%s (p:%d, ma:%d, mi:%d, z:%d) "
"by CSV\n", TOUCH_MOVE_MESSAGE, id,
fingers[idx].b.x, fingers[idx].b.y, location,
PARSE_PALM_STATUS(data->t_status),
fingers[idx].b.ma, fingers[idx].b.mi,
fingers[idx].b.z);
#else
tsp_debug("%s%d loc:%s (p:%d, ma:%d, mi:%d, z:%d) by CSV\n",
TOUCH_MOVE_MESSAGE, id, location,
PARSE_PALM_STATUS(data->t_status),
fingers[idx].b.ma, fingers[idx].b.mi,
fingers[idx].b.z);
#endif
}
idx++;
continue;
}
press = (fingers[idx].b.status == TOUCH_STA_PRESS) ? true : false;
input_mt_slot(data->input_dev, id);
input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, press);
if (press) {
input_report_abs(data->input_dev, ABS_MT_POSITION_X,
fingers[idx].b.x);
input_report_abs(data->input_dev, ABS_MT_POSITION_Y,
fingers[idx].b.y);
input_report_abs(data->input_dev, ABS_MT_PALM,
PARSE_PALM_STATUS(data->t_status));
input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR,
fingers[idx].b.ma);
input_report_abs(data->input_dev, ABS_MT_TOUCH_MINOR,
fingers[idx].b.mi);
input_report_abs(data->input_dev, ABS_MT_PRESSURE,
fingers[idx].b.z);
data->move_count[id]++;
if (data->tsp_touched[id] == false) {
// Touch Down
data->touch_pressed_num++;
/*for getting coordinate of pressed point*/
data->p_x[id] = data->r_x[id] = fingers[idx].b.x;
data->p_y[id] = data->r_y[id] = fingers[idx].b.y;
location_detect(data, location, data->p_x[id],
data->p_y[id]);
#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP)
input_info(true, &data->client->dev,
"%s%d.%d (%d, %d) loc:%s (p:%d, ma:%d, mi:%d, z:%d) (%d)\n",
TOUCH_DOWN_MESSAGE, id, (data->input_dev->mt->trkid - 1) & TRKID_MAX,
fingers[idx].b.x, fingers[idx].b.y, location,
PARSE_PALM_STATUS(data->t_status),
fingers[idx].b.ma, fingers[idx].b.mi,
fingers[idx].b.z, mode);
#else
input_info(true, &data->client->dev, "%s%d.%d loc:%s (p:%d, ma:%d, mi:%d, z:%d) (%d)\n",
TOUCH_DOWN_MESSAGE, id,
(data->input_dev->mt->trkid - 1) & TRKID_MAX,
location, PARSE_PALM_STATUS(data->t_status),
fingers[idx].b.ma, fingers[idx].b.mi,
fingers[idx].b.z, mode);
#endif
data->tsp_touched[id] = true;
data->all_finger_count++;
} else {
// Touch Move
/*for getting coordinate of the last point of move event*/
data->r_x[id] = fingers[idx].b.x;
data->r_y[id] = fingers[idx].b.y;
location_detect(data, location, data->r_x[id],
data->r_y[id]);
#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP)
tsp_debug("%s%d (%d, %d) loc:%s (p:%d, ma:%d, mi:%d, "
"z:%d)\n", TOUCH_MOVE_MESSAGE, id, fingers[idx].b.x,
fingers[idx].b.y, location,
PARSE_PALM_STATUS(data->t_status),
fingers[idx].b.ma, fingers[idx].b.mi,
fingers[idx].b.z);
#else
tsp_debug("%s%d loc:%s (p:%d, ma:%d, mi:%d, z:%d)\n",
TOUCH_MOVE_MESSAGE, id, location,
PARSE_PALM_STATUS(data->t_status),
fingers[idx].b.ma, fingers[idx].b.mi,
fingers[idx].b.z);
#endif
}
} else {
if (data->tsp_touched[id] == true) {
// Touch Up
data->touch_pressed_num--;
data->r_x[id] = fingers[idx].b.x;
data->r_y[id] = fingers[idx].b.y;
location_detect(data, location, data->r_x[id],
data->r_y[id]);
#ifdef TCLM_CONCEPT
input_info(true, &data->client->dev,
"%s%d loc:%s dX,dY:%d,%d mc:%d (0x%02x) (test_result data :%x) (C%02XT%04X.%4s%s)\n",
TOUCH_UP_MESSAGE, id, location,
data->r_x[id] - data->p_x[id],
data->r_y[id] - data->p_y[id],
data->move_count[id], data->fw.cur.fw_ver,
data->test_result.data[0],
data->tdata->nvdata.cal_count,
data->tdata->nvdata.tune_fix_ver,
data->tdata->tclm_string[data->tdata->nvdata.cal_position].f_name,
(data->tdata->tclm_level == TCLM_LEVEL_LOCKDOWN) ? ".L" : " ");
#else
input_info(true, &data->client->dev,
"%s%d loc:%s dX,dY:%d,%d mc:%d (0x%02x) \n",
TOUCH_UP_MESSAGE, id, location,
data->r_x[id] - data->p_x[id],
data->r_y[id] - data->p_y[id], data->move_count[id],
data->fw.cur.fw_ver);
#endif
data->tsp_touched[id] = false;
}
data->move_count[id] = 0;
}
idx++;
#ifdef IST40XX_FORCE_RELEASE
} else {
if (data->tsp_touched[i] == true) {
input_mt_slot(data->input_dev, i);
input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER,
false);
data->touch_pressed_num--;
location_detect(data, location, data->r_x[id], data->r_y[id]);
#ifdef TCLM_CONCEPT
input_info(true, &data->client->dev,
"%s%d loc:%s dX,dY:%d,%d mc:%d (0x%02x) (test_result data :%x) (C%02XT%04X.%4s%s) by force\n",
TOUCH_UP_MESSAGE, i, location,
data->r_x[i] - data->p_x[i],
data->r_y[i] - data->p_y[i],
data->move_count[i], data->fw.cur.fw_ver,
data->test_result.data[0],
data->tdata->nvdata.cal_count,
data->tdata->nvdata.tune_fix_ver,
data->tdata->tclm_string[data->tdata->nvdata.cal_position].f_name,
(data->tdata->tclm_level == TCLM_LEVEL_LOCKDOWN) ? ".L" : " ");
#else
input_info(true, &data->client->dev,
"%s%d loc:%s dX,dY:%d,%d mc:%d (0x%02x) by force\n",
TOUCH_UP_MESSAGE, i, location,
data->r_x[i] - data->p_x[i],
data->r_y[i] - data->p_y[i], data->move_count[i],
data->fw.cur.fw_ver);
#endif
data->tsp_touched[i] = false;
data->move_count[i] = 0;
}
#endif
}
}
if (data->touch_pressed_num >= 1){
input_report_key(data->input_dev, BTN_TOUCH, 1);
input_report_key(data->input_dev, BTN_TOOL_FINGER, 1);
}
else if (data->touch_pressed_num == 0){
input_report_key(data->input_dev, BTN_TOUCH, 0);
input_report_key(data->input_dev, BTN_TOOL_FINGER, 0);
}
if ((data->touch_pressed_num >= 2) && (data->check_multi == 0)) {
data->check_multi = 1;
data->multi_count++;
}
if (data->touch_pressed_num < 2)
data->check_multi = 0;
data->irq_err_cnt = 0;
data->scan_retry = 0;
input_sync(data->input_dev);
}
#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP)
void recording_data(struct ist40xx_data *data, bool idle)
{
int ret;
u32 scancnt = 0;
u32 addr = 0;
u32 *buf32;
TSP_INFO *tsp = &data->tsp_info;
if (idle && (data->rec_mode > 1))
goto state_idle;
ist40xx_delay(data->rec_delay);
ret = ist40xx_read_reg(data->client, IST40XX_HIB_TOUCH_STATUS,
&scancnt);
if (ret) {
input_err(true, &data->client->dev, "%s failed to read scancnt\n",
__func__);
goto state_idle;
}
if (data->recording_scancnt == scancnt) {
input_err(true, &data->client->dev, "%s same recording scancnt\n",
__func__);
goto state_idle;
}
data->recording_scancnt = scancnt;
buf32 = kzalloc(data->rec_size +
((tsp->node.self_len*2) + tsp->node.len) * sizeof(u32),
GFP_KERNEL);
if (!buf32) {
input_err(true, &data->client->dev, "failed to allocate %s %d\n",
__func__, __LINE__);
goto state_idle;
}
if (data->rec_size > 0) {
addr = IST40XX_DA_ADDR(data->rec_addr);
ret = ist40xx_burst_read(data->client, addr, buf32,
data->rec_size / IST40XX_DATA_LEN,
true);
if (ret)
goto err_rec_fail;
}
if ((data->rec_type == 0) || (data->rec_type == 1)) {
addr = IST40XX_DA_ADDR(data->self_cdc_addr);
ret = ist40xx_burst_read(data->client, addr,
buf32 + (data->rec_size / IST40XX_DATA_LEN),
tsp->node.self_len, true);
if (ret)
goto err_rec_fail;
}
if ((data->rec_type == 0) || (data->rec_type == 2)) {
addr = IST40XX_DA_ADDR(data->cdc_addr);
ret = ist40xx_burst_read(data->client, addr,
buf32 + (data->rec_size / IST40XX_DATA_LEN) +
tsp->node.self_len, tsp->node.len, true);
if (ret)
goto err_rec_fail;
}
if (data->rec_type == 3) {
addr = IST40XX_DA_ADDR(data->prox_cdc_addr);
ret = ist40xx_burst_read(data->client, addr,
buf32 + (data->rec_size / IST40XX_DATA_LEN) +
tsp->node.self_len + tsp->node.len, tsp->node.self_len, true);
if (ret)
goto err_rec_fail;
}
ist40xx_recording_put_frame(data, buf32,
(data->rec_size / IST40XX_DATA_LEN) +
(tsp->node.self_len*2) + tsp->node.len);
err_rec_fail:
kfree(buf32);
state_idle:
data->ignore_delay = true;
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
(eHCOM_SET_REC_MODE << 16) | (IST40XX_START_SCAN &
0xFFFF));
data->ignore_delay = false;
}
#endif
/*
* CMD : CMD_GET_COORD
*
* 1st [31:24] [23:21] [20:16] [15:12] [11] [10] [9:0]
* Checksum KeyCnt KeyStatus FingerCnt Rsvd. Palm FingerStatus
* 2nd [31:28] [27:24] [23:12] [11:0]
* Major Minor X Y
*/
irqreturn_t ist40xx_irq_thread(int irq, void *ptr)
{
int i, ret = 0;
int read_cnt;
int offset = 1;
u32 msg[IST40XX_MAX_FINGER_ID * IST40XX_TOUCH_FRAME_CNT + offset];
u32 *buf32;
u32 ms;
bool idle = false;
struct ist40xx_data *data = (struct ist40xx_data *)ptr;
data->irq_working = true;
memset(msg, 0, sizeof(msg));
if (!data->irq_enabled)
goto irq_ignore;
#if defined(CONFIG_INPUT_SEC_SECURE_TOUCH)
if (ist40xx_filter_interrupt(data) == IRQ_HANDLED) {
ret = wait_for_completion_interruptible_timeout((&data->st_interrupt),
msecs_to_jiffies(10 * MSEC_PER_SEC));
return IRQ_HANDLED;
}
#endif
#ifdef CONFIG_SAMSUNG_TUI
if (STUI_MODE_TOUCH_SEC & stui_get_mode())
return IRQ_HANDLED;
#endif
if (data->status.sys_mode == STATE_LPM) {
pm_wakeup_event(data->input_dev->dev.parent, 2000);
#ifdef CONFIG_PM
ret = wait_for_completion_interruptible_timeout(&data->resume_done, msecs_to_jiffies(500));
if (ret == 0) {
input_err(true, &data->client->dev, "%s: LPM: pm resume is not handled\n", __func__);
return IRQ_HANDLED;
} else if (ret < 0) {
input_err(true, &data->client->dev, "%s: LPM: -ERESTARTSYS if interrupted, %d\n", __func__, ret);
return IRQ_HANDLED;
}
#endif
} else if (data->status.sys_mode == STATE_POWER_OFF)
goto irq_ignore;
ms = get_milli_second(data);
if (data->intr_debug1_size > 0) {
buf32 = kzalloc(data->intr_debug1_size * sizeof(u32), GFP_KERNEL);
if (!buf32) {
input_err(true, &data->client->dev, "failed to allocate %s %d\n",
__func__, __LINE__);
goto irq_err;
}
tsp_debug("Intr_debug1 (addr: 0x%08x)\n", data->intr_debug1_addr);
ist40xx_burst_read(data->client,
IST40XX_DA_ADDR(data->intr_debug1_addr), buf32,
data->intr_debug1_size, true);
for (i = 0; i < data->intr_debug1_size; i++)
tsp_debug(" %08x\n", buf32[i]);
kfree(buf32);
}
if (data->intr_debug2_size > 0) {
buf32 = kzalloc(data->intr_debug2_size * sizeof(u32), GFP_KERNEL);
if (!buf32) {
input_err(true, &data->client->dev, "failed to allocate %s %d\n",
__func__, __LINE__);
goto irq_err;
}
tsp_debug("Intr_debug2 (addr: 0x%08x)\n", data->intr_debug2_addr);
ist40xx_burst_read(data->client,
IST40XX_DA_ADDR(data->intr_debug2_addr), buf32,
data->intr_debug2_size, true);
for (i = 0; i < data->intr_debug2_size; i++)
tsp_debug(" %08x\n", buf32[i]);
kfree(buf32);
}
ret = ist40xx_read_reg(data->client, IST40XX_HIB_INTR_MSG, msg);
if (ret)
goto irq_err;
tsp_verb("intr msg: 0x%08x\n", *msg);
/* TSP End Initial */
if (*msg == IST40XX_INITIAL_VALUE) {
tsp_debug("IC Ready~\n");
goto irq_event;
}
/* TSP Recording */
if (*msg == IST40XX_REC_VALUE) {
idle = true;
goto irq_end;
}
/* TSP IC Exception */
if ((*msg & IST40XX_EXCEPT_MASK) == IST40XX_EXCEPT_VALUE) {
input_err(true, &data->client->dev, "Occurred IC exception(0x%02X)\n",
*msg & 0xFF);
ret = ist40xx_burst_read(data->client, IST40XX_HIB_COORD, &msg[offset],
IST40XX_MAX_EXCEPT_SIZE, true);
if (ret) {
input_err(true, &data->client->dev,
" exception value read error(%d)\n", ret);
} else {
input_err(true, &data->client->dev,
" exception value : 0x%08X, 0x%08X\n", msg[1], msg[2]);
}
goto irq_ic_err;
}
if (*msg == 0 || *msg == 0xFFFFFFFF) /* Unknown CMD */
goto irq_err;
if ((*msg & IST40XX_MISCALIB_MASK) == IST40XX_MISCALIB_MSG) {
input_info(true, &data->client->dev, "Mis calibration End\n");
data->status.miscalib_msg = *msg;
data->status.miscalib_result = IST40XX_MISCALIB_VAL(*msg);
goto irq_event;
}
if ((*msg & CALIB_MSG_MASK) == CALIB_MSG_VALID) {
ret = ist40xx_burst_read(data->client, IST40XX_HIB_INTR_MSG,
data->status.calib_msg, IST40XX_MAX_CALIB_SIZE, true);
input_info(true, &data->client->dev, "Auto calibration\n");
input_info(true, &data->client->dev, "SLF calib status:0x%08X\n",
data->status.calib_msg[0]);
input_info(true, &data->client->dev, "MTL calib status:0x%08X\n",
data->status.calib_msg[1]);
input_info(true, &data->client->dev, "MAX CH status :0x%08X\n",
data->status.calib_msg[2]);
goto irq_event;
}
if ((CMCS_MSG(*msg) == CM_MSG_VALID) || (CMCS_MSG(*msg) == CS_MSG_VALID) ||
(CMCS_MSG(*msg) == CMJIT_MSG_VALID) ||
(CMCS_MSG(*msg) == CRJIT_MSG_VALID) ||
(CMCS_MSG(*msg) == CRJIT2_MSG_VALID)) {
data->status.cmcs = *msg;
data->status.cmcs_result = CMCS_RESULT(*msg);
input_info(true, &data->client->dev, "CMCS notify: 0x%08X\n", *msg);
goto irq_event;
}
ret = PARSE_SPECIAL_MESSAGE(*msg);
if (ret >= 0) {
tsp_debug("special cmd: %d (0x%08X)\n", ret, *msg);
ist40xx_special_cmd(data, ret);
goto irq_event;
}
memset(data->fingers, 0, sizeof(data->fingers));
if ((!CHECK_INTR_STATUS(*msg)))
goto irq_err;
read_cnt = PARSE_TOUCH_CNT(*msg);
if (read_cnt <= 0 && !PARSE_HOVER_NOTI(*msg)) {
input_err(true, &data->client->dev, "report touch is none\n");
goto irq_err;
}
if (PARSE_HOVER_NOTI(*msg)) {
if (data->hover != PARSE_HOVER_VAL(*msg))
data->hover = PARSE_HOVER_VAL(*msg);
input_report_abs(data->input_dev_proximity, ABS_MT_CUSTOM, data->hover);
input_sync(data->input_dev_proximity);
input_info(true, &data->client->dev, "Hover Level %d\n", data->hover);
if (read_cnt <= 0)
goto irq_event;
}
ret = ist40xx_burst_read(data->client, IST40XX_HIB_COORD, &msg[offset],
read_cnt * IST40XX_TOUCH_FRAME_CNT, true);
if (ret)
goto irq_err;
tsp_debug("Read Cnt:%d\n", read_cnt);
for (i = 0; i < read_cnt; i++) {
tsp_verb("%2d:0x%08X\n", i, msg[i * 2 + offset]);
tsp_verb("%2s 0x%08X\n", " ", msg[i * 2 + 1 + offset]);
}
data->t_status = *msg;
memcpy(data->fingers, &msg[offset], sizeof(finger_info) * read_cnt);
report_input_data(data);
if (data->intr_debug3_size > 0) {
buf32 = kzalloc(data->intr_debug3_size * sizeof(u32), GFP_KERNEL);
if (!buf32) {
input_err(true, &data->client->dev, "failed to allocate %s %d\n",
__func__, __LINE__);
goto irq_err;
}
tsp_debug("Intr_debug3 (addr: 0x%08x)\n", data->intr_debug3_addr);
ist40xx_burst_read(data->client,
IST40XX_DA_ADDR(data->intr_debug3_addr), buf32,
data->intr_debug3_size, true);
for (i = 0; i < data->intr_debug3_size; i++)
tsp_debug(" %08x\n", buf32[i]);
kfree(buf32);
}
goto irq_end;
irq_err:
input_err(true, &data->client->dev, "intr msg: 0x%08x, ret: %d\n",
msg[0], ret);
ist40xx_request_reset(data);
goto irq_event;
irq_end:
#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP)
if (data->rec_mode)
recording_data(data, idle);
#endif
irq_event:
irq_ignore:
data->irq_working = false;
data->event_ms = (u32) get_milli_second(data);
if (data->initialized)
mod_timer(&data->event_timer,
get_jiffies_64() + EVENT_TIMER_INTERVAL);
return IRQ_HANDLED;
irq_ic_err:
ist40xx_scheduled_reset(data);
data->irq_working = false;
data->event_ms = (u32) get_milli_second(data);
if (data->initialized)
mod_timer(&data->event_timer,
get_jiffies_64() + EVENT_TIMER_INTERVAL);
return IRQ_HANDLED;
}
#ifdef IST40XX_PINCTRL
static int ist40xx_pinctrl_configure(struct ist40xx_data *data, bool active)
{
struct pinctrl_state *set_state;
int retval;
input_info(true, &data->client->dev, "%s %s\n", __func__,
active ? "ACTIVE" : "SUSPEND");
set_state = pinctrl_lookup_state(data->pinctrl,
active ? "on_state" : "off_state");
if (IS_ERR(set_state)) {
input_err(true, &data->client->dev, "%s cannot get active state\n",
__func__);
return -EINVAL;
}
retval = pinctrl_select_state(data->pinctrl, set_state);
if (retval) {
input_err(true, &data->client->dev, "%s cannot set pinctrl %s state\n",
__func__, active ? "active" : "suspend");
return -EINVAL;
}
return 0;
}
#endif
static int ist40xx_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ist40xx_data *data = i2c_get_clientdata(client);
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
if (TRUSTEDUI_MODE_TUI_SESSION & trustedui_get_current_mode()) {
input_err(true, &data->client->dev, "%s TUI cancel event call!\n",
__func__);
msleep(100);
tui_force_close(1);
msleep(200);
if (TRUSTEDUI_MODE_TUI_SESSION & trustedui_get_current_mode()) {
input_err(true, &data->client->dev, "%s TUI flag force clear!\n",
__func__);
trustedui_clear_mask(
TRUSTEDUI_MODE_VIDEO_SECURED|TRUSTEDUI_MODE_INPUT_SECURED);
trustedui_set_mode(TRUSTEDUI_MODE_OFF);
}
}
#endif
del_timer(&data->event_timer);
cancel_delayed_work_sync(&data->work_reset_check);
#ifdef IST40XX_NOISE_MODE
cancel_delayed_work_sync(&data->work_noise_protect);
#else
cancel_delayed_work_sync(&data->work_force_release);
#endif
mutex_lock(&data->lock);
if (data->lpm_mode || data->fod_lp_mode) {
ist40xx_disable_irq(data);
ist40xx_cmd_gesture(data, IST40XX_ENABLE);
if (device_may_wakeup(&data->client->dev))
enable_irq_wake(data->client->irq);
data->status.sys_mode = STATE_LPM;
ist40xx_enable_irq(data);
} else {
ist40xx_power_off(data);
ist40xx_disable_irq(data);
data->status.sys_mode = STATE_POWER_OFF;
#ifdef IST40XX_PINCTRL
if (data->pinctrl) {
int ret = ist40xx_pinctrl_configure(data, false);
if (ret) {
input_err(true, &data->client->dev,
"%s: cannot set pinctrl state\n", __func__);
}
}
#endif
}
clear_input_data(data);
if (data->prox_power_off) {
input_report_key(data->input_dev, KEY_INT_CANCEL, 1);
input_sync(data->input_dev);
input_report_key(data->input_dev, KEY_INT_CANCEL, 0);
input_sync(data->input_dev);
}
data->prox_power_off = 0;
mutex_unlock(&data->lock);
return 0;
}
static int ist40xx_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ist40xx_data *data = i2c_get_clientdata(client);
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
if (TRUSTEDUI_MODE_TUI_SESSION & trustedui_get_current_mode()) {
input_err(true, &data->client->dev, "%s TUI cancel event call!\n",
__func__);
msleep(100);
tui_force_close(1);
msleep(200);
if (TRUSTEDUI_MODE_TUI_SESSION & trustedui_get_current_mode()) {
input_err(true, &data->client->dev, "%s TUI flag force clear!\n",
__func__);
trustedui_clear_mask(
TRUSTEDUI_MODE_VIDEO_SECURED|TRUSTEDUI_MODE_INPUT_SECURED);
trustedui_set_mode(TRUSTEDUI_MODE_OFF);
}
}
#endif
if (!data->initialized) {
input_err(true, &data->client->dev, "IC initialize is not complete\n");
return 0;
}
data->noise_mode |= (1 << NOISE_MODE_POWER);
if (data->status.sys_mode == STATE_POWER_ON)
return 0;
mutex_lock(&data->lock);
if (data->status.sys_mode == STATE_LPM) {
ist40xx_cmd_gesture(data, IST40XX_DISABLE);
mod_timer(&data->event_timer,
get_jiffies_64() + EVENT_TIMER_INTERVAL);
if (device_may_wakeup(&data->client->dev))
disable_irq_wake(data->client->irq);
data->status.sys_mode = STATE_POWER_ON;
} else {
#ifdef IST40XX_PINCTRL
if (data->pinctrl) {
int ret = ist40xx_pinctrl_configure(data, true);
if (ret) {
input_err(true, &data->client->dev,
"%s: cannot set pinctrl state\n",
__func__);
}
}
#endif
ist40xx_reset(data, false);
ist40xx_enable_irq(data);
ist40xx_start(data);
data->status.sys_mode = STATE_POWER_ON;
}
mutex_unlock(&data->lock);
return 0;
}
#ifdef USE_OPEN_CLOSE
static void ist40xx_ts_close(struct input_dev *dev)
{
struct ist40xx_data *data = input_get_drvdata(dev);
if (!data->info_work_done) {
input_err(true, &data->client->dev, "%s: not finished info work\n",
__func__);
return;
}
input_info(true, &data->client->dev, "%s:\n", __func__);
#ifdef TCLM_CONCEPT
sec_tclm_debug_info(data->tdata);
#endif
#if defined(CONFIG_INPUT_SEC_SECURE_TOUCH)
ist40xx_secure_touch_stop(data, 1);
#endif
ist40xx_suspend(&data->client->dev);
}
static int ist40xx_ts_open(struct input_dev *dev)
{
struct ist40xx_data *data = input_get_drvdata(dev);
if (!data->info_work_done) {
input_err(true, &data->client->dev, "%s: not finished info work\n",
__func__);
return 0;
}
input_info(true, &data->client->dev, "%s:\n", __func__);
return ist40xx_resume(&data->client->dev);
}
#elif defined(CONFIG_HAS_EARLYSUSPEND)
static void ist40xx_early_suspend(struct early_suspend *h)
{
struct ist40xx_data *data = container_of(h, struct ist40xx_data,
early_suspend);
ist40xx_suspend(&data->client->dev);
}
static void ist40xx_late_resume(struct early_suspend *h)
{
struct ist40xx_data *data = container_of(h, struct ist40xx_data,
early_suspend);
ist40xx_resume(&data->client->dev);
}
#endif
void ist40xx_set_ta_mode(bool mode)
{
struct ist40xx_data *data = ts_data;
if (mode == ((data->noise_mode >> NOISE_MODE_TA) & 1))
return;
input_info(true, &data->client->dev, "%s: mode = %d\n", __func__, mode);
if (mode)
data->noise_mode |= (1 << NOISE_MODE_TA);
else
data->noise_mode &= ~(1 << NOISE_MODE_TA);
if (data->initialized && (data->status.sys_mode != STATE_POWER_OFF))
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_MODE_SPECIAL << 16) | (data->noise_mode & 0xFFFF)));
}
EXPORT_SYMBOL(ist40xx_set_ta_mode);
void ist40xx_set_edge_mode(int mode)
{
struct ist40xx_data *data = ts_data;
input_info(true, &data->client->dev, "%s: mode = %d\n", __func__, mode);
if (mode)
data->noise_mode |= (1 << NOISE_MODE_EDGE);
else
data->noise_mode &= ~(1 << NOISE_MODE_EDGE);
if (data->status.sys_mode != STATE_POWER_OFF)
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_MODE_SPECIAL << 16) | (data->noise_mode & 0xFFFF)));
}
EXPORT_SYMBOL(ist40xx_set_edge_mode);
void ist40xx_set_call_mode(int mode)
{
struct ist40xx_data *data = ts_data;
if (mode == ((data->noise_mode >> NOISE_MODE_CALL) & 1))
return;
input_info(true, &data->client->dev, "%s: mode = %d\n", __func__, mode);
if (mode)
data->noise_mode |= (1 << NOISE_MODE_CALL);
else
data->noise_mode &= ~(1 << NOISE_MODE_CALL);
if (data->initialized && (data->status.sys_mode != STATE_POWER_OFF))
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_MODE_SPECIAL << 16) | (data->noise_mode & 0xFFFF)));
}
EXPORT_SYMBOL(ist40xx_set_call_mode);
void ist40xx_set_halfaod_mode(int mode)
{
struct ist40xx_data *data = ts_data;
if (mode == ((data->noise_mode >> NOISE_MODE_HALFAOD) & 1))
return;
input_info(true, &data->client->dev, "%s: mode = %d\n", __func__, mode);
if (mode)
data->noise_mode |= (1 << NOISE_MODE_HALFAOD);
else
data->noise_mode &= ~(1 << NOISE_MODE_HALFAOD);
if (data->initialized && (data->status.sys_mode != STATE_POWER_OFF))
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_MODE_SPECIAL << 16) | (data->noise_mode & 0xFFFF)));
}
EXPORT_SYMBOL(ist40xx_set_halfaod_mode);
void ist40xx_set_cover_mode(int mode)
{
struct ist40xx_data *data = ts_data;
if (mode == ((data->noise_mode >> NOISE_MODE_COVER) & 1))
return;
input_info(true, &data->client->dev, "%s: mode = %d\n", __func__, mode);
if (mode)
data->noise_mode |= (1 << NOISE_MODE_COVER);
else
data->noise_mode &= ~(1 << NOISE_MODE_COVER);
if (data->initialized && (data->status.sys_mode != STATE_POWER_OFF))
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_MODE_SPECIAL << 16) | (data->noise_mode & 0xFFFF)));
}
EXPORT_SYMBOL(ist40xx_set_cover_mode);
void ist40xx_set_glove_mode(int mode)
{
struct ist40xx_data *data = ts_data;
if (mode == ((data->noise_mode >> NOISE_MODE_GLOVE) & 1))
return;
input_info(true, &data->client->dev, "%s: mode = %d\n", __func__, mode);
if (mode)
data->noise_mode |= (1 << NOISE_MODE_GLOVE);
else
data->noise_mode &= ~(1 << NOISE_MODE_GLOVE);
if (data->initialized && (data->status.sys_mode != STATE_POWER_OFF))
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_MODE_SPECIAL << 16) | (data->noise_mode & 0xFFFF)));
}
EXPORT_SYMBOL(ist40xx_set_glove_mode);
int ist40xx_set_rejectzone_mode(int mode, u16 top, u16 bottom)
{
struct ist40xx_data *data = ts_data;
int ret;
input_info(true, &data->client->dev, "%s: mode = %d (T:%d,B:%d)\n",
__func__, mode, top, bottom);
if (mode)
data->noise_mode |= (1 << NOISE_MODE_REJECTZONE);
else
data->noise_mode &= ~(1 << NOISE_MODE_REJECTZONE);
data->rejectzone_t = top;
data->rejectzone_b = bottom;
if (data->initialized && (data->status.sys_mode != STATE_POWER_OFF)) {
ret = ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_MODE_SPECIAL << 16) | (data->noise_mode & 0xFFFF)));
if (ret)
return ret;
ret = ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_REJECTZONE_TOP << 16) | data->rejectzone_t));
if (ret)
return ret;
ret = ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_REJECTZONE_BOTTOM << 16) | data->rejectzone_b));
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL(ist40xx_set_rejectzone_mode);
void ist40xx_set_sensitivity_mode(int mode)
{
struct ist40xx_data *data = ts_data;
if (mode == ((data->noise_mode >> NOISE_MODE_SENSITIVITY) & 1))
return;
input_info(true, &data->client->dev, "%s: mode = %d\n", __func__, mode);
if (mode)
data->noise_mode |= (1 << NOISE_MODE_SENSITIVITY);
else
data->noise_mode &= ~(1 << NOISE_MODE_SENSITIVITY);
if (data->initialized && (data->status.sys_mode != STATE_POWER_OFF))
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_MODE_SPECIAL << 16) | (data->noise_mode & 0xFFFF)));
}
EXPORT_SYMBOL(ist40xx_set_sensitivity_mode);
void ist40xx_set_touchable_mode(int mode)
{
struct ist40xx_data *data = ts_data;
if (mode == ((data->noise_mode >> NOISE_MODE_TOUCHABLE) & 1))
return;
input_info(true, &data->client->dev, "%s: mode = %d\n", __func__, mode);
if (mode)
data->noise_mode |= (1 << NOISE_MODE_TOUCHABLE);
else
data->noise_mode &= ~(1 << NOISE_MODE_TOUCHABLE);
if (data->initialized && (data->status.sys_mode != STATE_POWER_OFF))
ist40xx_write_cmd(data, IST40XX_HIB_CMD,
((eHCOM_SET_MODE_SPECIAL << 16) | (data->noise_mode & 0xFFFF)));
}
EXPORT_SYMBOL(ist40xx_set_touchable_mode);
#if defined(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) || defined(CONFIG_MUIC_NOTIFIER)
static int otg_flag = 0;
#endif
#ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER
static int tsp_ccic_notification(struct notifier_block *nb,
unsigned long action, void *data)
{
CC_NOTI_USB_STATUS_TYPEDEF usb_status =
*(CC_NOTI_USB_STATUS_TYPEDEF *) data;
switch (usb_status.drp) {
case USB_STATUS_NOTIFY_ATTACH_DFP:
otg_flag = 1;
tsp_info("%s : otg_flag 1\n", __func__);
break;
case USB_STATUS_NOTIFY_DETACH:
otg_flag = 0;
break;
default:
break;
}
return 0;
}
#else
#ifdef CONFIG_MUIC_NOTIFIER
static int tsp_muic_notification(struct notifier_block *nb,
unsigned long action, void *data)
{
muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data;
switch (action) {
case MUIC_NOTIFY_CMD_DETACH:
case MUIC_NOTIFY_CMD_LOGICALLY_DETACH:
otg_flag = 0;
break;
case MUIC_NOTIFY_CMD_ATTACH:
case MUIC_NOTIFY_CMD_LOGICALLY_ATTACH:
if (attached_dev == ATTACHED_DEV_OTG_MUIC) {
otg_flag = 1;
tsp_info("%s : otg_flag 1\n", __func__);
}
break;
default:
break;
}
return 0;
}
#endif
#endif
#ifdef CONFIG_VBUS_NOTIFIER
static int tsp_vbus_notification(struct notifier_block *nb,
unsigned long cmd, void *data)
{
vbus_status_t vbus_type = *(vbus_status_t *) data;
tsp_info("%s cmd=%lu, vbus_type=%d\n", __func__, cmd, vbus_type);
switch (vbus_type) {
case STATUS_VBUS_HIGH:
tsp_info("%s : attach\n", __func__);
#if defined(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) || defined(CONFIG_MUIC_NOTIFIER)
if (!otg_flag)
#endif
ist40xx_set_ta_mode(true);
break;
case STATUS_VBUS_LOW:
tsp_info("%s : detach\n", __func__);
ist40xx_set_ta_mode(false);
break;
default:
break;
}
return 0;
}
#endif
static void reset_work_func(struct work_struct *work)
{
struct delayed_work *delayed_work = to_delayed_work(work);
struct ist40xx_data *data = container_of(delayed_work, struct ist40xx_data,
work_reset_check);
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
if (TRUSTEDUI_MODE_INPUT_SECURED & trustedui_get_current_mode()) {
input_err(true, &data->client->dev, "%s: return, TUI is enabled!\n",
__func__);
return;
}
#endif
#ifdef CONFIG_INPUT_SEC_SECURE_TOUCH
if (atomic_read(&data->st_enabled) == SECURE_TOUCH_ENABLED) {
input_err(true, &data->client->dev,
"%s: TSP no accessible from Linux, TUI is enabled!\n",
__func__);
return;
}
#endif
#ifdef CONFIG_SAMSUNG_TUI
if (STUI_MODE_TOUCH_SEC & stui_get_mode())
return;
#endif
if ((data == NULL) || (data->client == NULL))
return;
input_info(true, &data->client->dev, "Request reset function\n");
if ((data->initialized == 1) &&
(data->status.sys_mode != STATE_POWER_OFF) &&
(data->status.update != 1) && (data->status.calib < 1) &&
(data->status.miscalib < 1)) {
mutex_lock(&data->lock);
ist40xx_disable_irq(data);
ist40xx_reset(data, false);
clear_input_data(data);
ist40xx_enable_irq(data);
ist40xx_start(data);
mutex_unlock(&data->lock);
}
}
#ifdef IST40XX_NOISE_MODE
static void noise_work_func(struct work_struct *work)
{
int ret;
u32 touch_status = 0;
u32 scan_count = 0;
struct delayed_work *delayed_work = to_delayed_work(work);
struct ist40xx_data *data = container_of(delayed_work, struct ist40xx_data,
work_noise_protect);
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
if (TRUSTEDUI_MODE_INPUT_SECURED & trustedui_get_current_mode()) {
input_err(true, &data->client->dev, "%s: return, TUI is enabled!\n", __func__);
return;
}
#endif
#ifdef CONFIG_INPUT_SEC_SECURE_TOUCH
if (atomic_read(&data->st_enabled) == SECURE_TOUCH_ENABLED) {
input_err(true, &data->client->dev,
"%s: TSP no accessible from Linux, TUI is enabled!\n",
__func__);
return;
}
#endif
#ifdef CONFIG_SAMSUNG_TUI
if (STUI_MODE_TOUCH_SEC & stui_get_mode())
return;
#endif
ret = ist40xx_read_reg(data->client, IST40XX_HIB_TOUCH_STATUS,
&touch_status);
if (ret) {
input_err(true, &data->client->dev, "Touch status read fail!\n");
goto retry_timer;
}
tsp_verb("Touch Info: 0x%08x\n", touch_status);
/* Check valid scan count */
if ((touch_status & TOUCH_STATUS_MASK) != TOUCH_STATUS_MAGIC) {
input_err(true, &data->client->dev, "Touch status is not corrected! (0x%08x)\n",
touch_status);
goto retry_timer;
}
/* Status of IC is idle */
if (GET_FINGER_ENABLE(touch_status) == 0) {
if (data->touch_pressed_num > 0)
clear_input_data(data);
}
scan_count = GET_SCAN_CNT(touch_status);
/* Status of IC is lock-up */
if (scan_count == data->scan_count) {
input_err(true, &data->client->dev, "TSP IC is not responded! (0x%08x, %8d)\n",
touch_status, scan_count);
goto retry_timer;
}
data->scan_retry = 0;
data->scan_count = scan_count;
return;
retry_timer:
data->scan_retry++;
input_info(true, &data->client->dev, "Retry touch status!(%d)\n", data->scan_retry);
if (data->scan_retry >= data->max_scan_retry) {
ist40xx_scheduled_reset(data);
data->scan_retry = 0;
} else {
mod_timer(&data->event_timer,
get_jiffies_64() + EVENT_TIMER_INTERVAL / 10);
}
}
#else
static void release_work_func(struct work_struct *work)
{
int ret;
u32 touch_status = 0;
struct delayed_work *delayed_work = to_delayed_work(work);
struct ist40xx_data *data =
container_of(delayed_work, struct ist40xx_data,
work_force_release);
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
if (TRUSTEDUI_MODE_INPUT_SECURED & trustedui_get_current_mode()) {
input_err(true, &data->client->dev, "%s: return, TUI is enabled!\n", __func__);
return;
}
#endif
#ifdef CONFIG_INPUT_SEC_SECURE_TOUCH
if (atomic_read(&data->st_enabled) == SECURE_TOUCH_ENABLED) {
input_err(true, &data->client->dev,
"%s: TSP no accessible from Linux, TUI is enabled!\n",
__func__);
return;
}
#endif
#ifdef CONFIG_SAMSUNG_TUI
if (STUI_MODE_TOUCH_SEC & stui_get_mode())
return -EBUSY;
#endif
if (data->touch_pressed_num == 0)
return;
ret = ist40xx_read_reg(data->client, IST40XX_HIB_TOUCH_STATUS,
&touch_status);
if (ret) {
input_err(true, &data->client->dev, "Touch status read fail!\n");
return;
}
tsp_verb("Touch Info: 0x%08x\n", touch_status);
/* Check valid scan count */
if ((touch_status & TOUCH_STATUS_MASK) != TOUCH_STATUS_MAGIC) {
input_err(true, &data->client->dev, "Touch status is not corrected! (0x%08x)\n",
touch_status);
return;
}
/* Status of IC is idle */
if (GET_FINGER_ENABLE(touch_status) == 0)
clear_input_data(data);
}
#endif
void timer_handler(unsigned long timer_data)
{
struct ist40xx_data *data = (struct ist40xx_data *)timer_data;
struct ist40xx_status *status = &data->status;
if (data->irq_working || !data->initialized || data->rec_mode ||
!status->event_mode)
goto restart_timer;
if ((status->sys_mode == STATE_POWER_ON) && (status->update != 1) &&
(status->calib < 1) && (status->miscalib < 1)) {
data->timer_ms = (u32) get_milli_second(data);
if (status->noise_mode) {
/* 100ms after last interrupt */
if (data->timer_ms - data->event_ms > 100) {
#ifdef IST40XX_NOISE_MODE
schedule_delayed_work(&data->work_noise_protect, 0);
#else
schedule_delayed_work(&data->work_force_release, 0);
#endif
}
}
}
restart_timer:
mod_timer(&data->event_timer, get_jiffies_64() + EVENT_TIMER_INTERVAL);
}
#ifdef CONFIG_OF
static int ist40xx_parse_dt(struct device *dev, struct ist40xx_data *data)
{
struct device_node *np = dev->of_node;
u32 px_zone[3];
u32 cm_spec[3];
data->dt_data->irq_gpio = of_get_named_gpio(np, "imagis,irq-gpio", 0);
data->dt_data->is_power_by_gpio = of_property_read_bool(np,
"imagis,power-gpioen");
if (data->dt_data->is_power_by_gpio) {
data->dt_data->power_gpio = of_get_named_gpio(np, "imagis,power-gpio", 0);
} else {
data->dt_data->power_gpio = -1;
if (of_property_read_string(np, "imagis,regulator_avdd",
&data->dt_data->regulator_avdd)) {
input_err(true, dev, "%s Failed to get regulator_avdd name property\n",
__func__);
}
}
if (of_property_read_u32(np, "imagis,fw-bin", &data->dt_data->fw_bin) >= 0) {
input_info(true, dev, "%s: fw-bin: %d\n", __func__, data->dt_data->fw_bin);
}
if (of_property_read_string(np, "imagis,ic-version",
&data->dt_data->ic_version) >= 0) {
input_info(true, dev, "%s: ic_version: %s\n", __func__,
data->dt_data->ic_version);
}
if (of_property_read_string(np, "imagis,project-name",
&data->dt_data->project_name) >= 0) {
input_info(true, dev, "%s: project_name: %s\n", __func__,
data->dt_data->project_name);
}
if (data->dt_data->ic_version && data->dt_data->project_name) {
snprintf(data->dt_data->fw_path, FIRMWARE_PATH_LENGTH, "%s%s_%s.bin",
FIRMWARE_PATH, data->dt_data->ic_version,
data->dt_data->project_name);
input_info(true, dev, "%s: firm path: %s\n", __func__,
data->dt_data->fw_path);
snprintf(data->dt_data->cmcs_path, FIRMWARE_PATH_LENGTH,
"%s%s_%s_cmcs.bin", FIRMWARE_PATH,
data->dt_data->ic_version, data->dt_data->project_name);
input_info(true, dev, "%s: cmcs bin path: %s\n", __func__,
data->dt_data->cmcs_path);
}
if (of_property_read_u32(np, "imagis,octa-hw",
&data->dt_data->octa_hw) >= 0) {
input_info(true, dev, "%s: octa-hw: %d\n", __func__,
data->dt_data->octa_hw);
}
if (of_property_read_u32_array(np, "imagis,area-size", px_zone, 3)) {
input_err(true, dev, "%s: Failed to get zone's size\n", __func__);
data->dt_data->area_indicator = 63;
data->dt_data->area_navigation = 126;
data->dt_data->area_edge = 60;
} else {
data->dt_data->area_indicator = px_zone[0];
data->dt_data->area_navigation = px_zone[1];
data->dt_data->area_edge = px_zone[2];
}
if (of_property_read_u32(np, "imagis,bringup",
&data->dt_data->bringup) < 0)
data->dt_data->bringup = 0;
if (of_property_read_u32(np, "imagis,factory_item_version",
&data->dt_data->item_version) < 0)
data->dt_data->item_version = 0;
data->dt_data->enable_settings_aot = of_property_read_bool(np, "enable_settings_aot");
data->dt_data->support_fod = of_property_read_bool(np, "support_fod");
data->dt_data->enable_fpcb_noise_test = of_property_read_bool(np, "enable_fpcb_noise_test");
data->dt_data->support_open_short_test = of_property_read_bool(np, "support_open_short_test");
data->dt_data->support_mis_calibration_test = of_property_read_bool(np, "support_mis_calibration_test");
if (of_property_read_u32_array(np, "imagis,cm_spec", cm_spec, 3)) {
input_err(true, dev, "%s: Failed to get zone's size\n", __func__);
data->dt_data->cm_min_spec = CM_MIN_SPEC;
data->dt_data->cm_max_spec = CM_MAX_SPEC;
data->dt_data->cm_spec_gap = SPEC_GAP;
} else {
data->dt_data->cm_min_spec = cm_spec[0];
data->dt_data->cm_max_spec = cm_spec[1];
data->dt_data->cm_spec_gap = cm_spec[2];
}
input_info(true, dev, "%s: irq:%d, tsp_ldo: %s\n", __func__,
data->dt_data->irq_gpio, data->dt_data->regulator_avdd);
input_info(true, dev, "%s: power source by gpio:%d, power_gpio: %d\n",
__func__, data->dt_data->is_power_by_gpio, data->dt_data->power_gpio);
return 0;
}
void sec_tclm_parse_dt(struct i2c_client *client, struct sec_tclm_data *tdata)
{
struct device *dev = &client->dev;
struct device_node *np = dev->of_node;
if (of_property_read_u32(np, "imagis,tclm_level", &tdata->tclm_level) < 0) {
tdata->tclm_level = 0;
input_err(true, &client->dev, "%s Failed to get tclm_level property\n",
__func__);
}
if (of_property_read_u32(np, "imagis,afe_base", &tdata->afe_base) < 0) {
tdata->afe_base = 0;
input_err(true, &client->dev, "%s Failed to get afe_base property\n",
__func__);
}
tdata->support_tclm_test = of_property_read_bool(np, "support_tclm_test");
input_err(true, &client->dev, "%s tclm_level %d, afe_base %04X\n", __func__,
tdata->tclm_level, tdata->afe_base);
}
static void ist40xx_request_gpio(struct i2c_client *client,
struct ist40xx_data *data)
{
int ret;
input_info(true, &client->dev, "%s\n", __func__);
if (gpio_is_valid(data->dt_data->irq_gpio)) {
ret = gpio_request(data->dt_data->irq_gpio, "imagis,irq_gpio");
if (ret) {
input_err(true, &client->dev,
"%s unable to request irq_gpio [%d]\n", __func__,
data->dt_data->irq_gpio);
return;
}
ret = gpio_direction_input(data->dt_data->irq_gpio);
if (ret) {
input_err(true, &client->dev,
"%s unable to set direction for gpio [%d]\n",
__func__, data->dt_data->irq_gpio);
}
client->irq = gpio_to_irq(data->dt_data->irq_gpio);
}
if (gpio_is_valid(data->dt_data->power_gpio)) {
ret = gpio_request(data->dt_data->power_gpio, "imagis,power_gpio");
if (ret) {
input_err(true, &client->dev,
"%s unable to request power_gpio [%d]\n", __func__,
data->dt_data->power_gpio);
return;
}
ret = gpio_direction_output(data->dt_data->power_gpio, 1);
if (ret) {
input_err(true, &client->dev,
"%s unable to set direction for gpio [%d]\n",
__func__, data->dt_data->power_gpio);
}
}
}
static void ist40xx_free_gpio(struct ist40xx_data *data)
{
input_info(true, &data->client->dev, "%s\n", __func__);
if (gpio_is_valid(data->dt_data->irq_gpio))
gpio_free(data->dt_data->irq_gpio);
if (gpio_is_valid(data->dt_data->power_gpio))
gpio_free(data->dt_data->power_gpio);
}
#endif
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
void trustedui_mode_ist_on(void)
{
input_info(true, &data->client->dev, "%s release all finger..", __func__);
clear_input_data(tui_tsp_info);
del_timer(&tui_tsp_info->event_timer);
cancel_delayed_work_sync(&tui_tsp_info->work_reset_check);
#ifdef IST40XX_NOISE_MODE
cancel_delayed_work_sync(&tui_tsp_info->work_noise_protect);
#else
cancel_delayed_work_sync(&tui_tsp_info->work_force_release);
#endif
tui_tsp_info->status.noise_mode = false;
}
EXPORT_SYMBOL(trustedui_mode_ist_on);
void trustedui_mode_ist_off(void)
{
input_info(true, &data->client->dev, "%s ", __func__);
tui_tsp_info->status.noise_mode = true;
//EVENT_TIMER_INTERVAL
mod_timer(&tui_tsp_info->event_timer,
get_jiffies_64() + (HZ * tui_tsp_info->timer_period_ms / 1000));
}
EXPORT_SYMBOL(trustedui_mode_ist_off);
#endif
static void ist40xx_run_rawdata(struct ist40xx_data *data)
{
#ifdef CONFIG_TOUCHSCREEN_DUMP_MODE
data->tsp_dump_lock = 1;
#endif
input_raw_data_clear();
input_raw_info(true, &data->client->dev, "%s start ##\n", __func__);
ist40xx_display_booting_dump_log(data);
input_raw_info(true, &data->client->dev, "%s done ##\n", __func__);
#ifdef CONFIG_TOUCHSCREEN_DUMP_MODE
data->tsp_dump_lock = 0;
#endif
}
#if defined(CONFIG_TOUCHSCREEN_DUMP_MODE)
#include <linux/sec_debug.h>
extern struct tsp_dump_callbacks dump_callbacks;
static struct delayed_work *p_ghost_check;
static void ist40xx_check_rawdata(struct work_struct *work)
{
struct ist40xx_data *data = container_of(work, struct ist40xx_data,
ghost_check.work);
if (data->tsp_dump_lock == 1) {
input_info(true, &data->client->dev,
"%s: ignored ## already checking..\n", __func__);
return;
}
if (data->status.sys_mode == STATE_POWER_OFF) {
input_info(true, &data->client->dev,
"%s: ignored ## IC is power off\n", __func__);
return;
}
ist40xx_display_key_dump_log(data);
}
static void dump_tsp_log(void)
{
tsp_info("%s start\n", __func__);
#ifdef CONFIG_BATTERY_SAMSUNG
if (lpcharge == 1) {
tsp_info("%s: ignored ## lpm charging Mode!!\n",__func__);
return;
}
#endif
if (p_ghost_check == NULL) {
tsp_info("%s: ignored ## tsp probe fail!!\n", __func__);
return;
}
schedule_delayed_work(p_ghost_check, msecs_to_jiffies(100));
}
#endif
static void ist40xx_read_info_work(struct work_struct *work)
{
struct ist40xx_data *data = container_of(work, struct ist40xx_data,
work_read_info.work);
#ifdef TCLM_CONCEPT
u32 buff;
int ret;
ist40xx_read_sec_info(data, IST40XX_NVM_OFFSET_FAC_RESULT, &buff, 1);
data->test_result.data[0] = buff & 0xff;
/*this function will call ist40xx_tclm_data_read*/
ret = sec_tclm_check_cal_case(data->tdata);
input_info(true, &data->client->dev,
"%s sec_tclm_check_cal_case result %d; test result %X\n", __func__,
ret, data->test_result.data[0]);
#endif
#ifdef IST40XX_USE_CMCS
run_cmcs_full_test((void *)&data->sec);
#endif
input_log_fix();
// for rawdata
ist40xx_run_rawdata(data);
data->info_work_done = true;
}
static void ist_set_input_prop_proximity(struct ist40xx_data *data, struct input_dev *dev)
{
static char ist_phys[64] = { 0 };
snprintf(ist_phys, sizeof(ist_phys), "%s/input1", dev->name);
dev->phys = ist_phys;
dev->id.bustype = BUS_I2C;
dev->dev.parent = &data->client->dev;
set_bit(EV_SYN, dev->evbit);
set_bit(EV_SW, dev->evbit);
set_bit(INPUT_PROP_DIRECT, dev->propbit);
input_set_abs_params(dev, ABS_MT_CUSTOM, 0, 0xFFFFFFFF, 0, 0);
input_set_abs_params(dev, ABS_MT_CUSTOM2, 0, 0xFFFFFFFF, 0, 0);
input_set_drvdata(dev, data);
}
#ifdef CONFIG_SAMSUNG_TUI
extern int stui_i2c_lock(struct i2c_adapter *adap);
extern int stui_i2c_unlock(struct i2c_adapter *adap);
int stui_tsp_enter(void)
{
int ret = 0;
if (!stui_tsp_info)
return -EINVAL;
disable_irq(stui_tsp_info->client->irq);
clear_input_data(stui_tsp_info);
ret = stui_i2c_lock(stui_tsp_info->client->adapter);
if (ret) {
pr_err("[STUI] stui_i2c_lock failed : %d\n", ret);
enable_irq(stui_tsp_info->client->irq);
return -1;
}
return 0;
}
int stui_tsp_exit(void)
{
int ret = 0;
if (!stui_tsp_info)
return -EINVAL;
ret = stui_i2c_unlock(stui_tsp_info->client->adapter);
if (ret)
pr_err("[STUI] stui_i2c_unlock failed : %d\n", ret);
enable_irq(stui_tsp_info->client->irq);
return ret;
}
#endif
static int ist40xx_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret;
int i;
struct ist40xx_data *data;
struct sec_tclm_data *tdata = NULL;
struct input_dev *input_dev;
input_info(true, &client->dev, "### IMAGIS probe(Slave Addr:0x%02X) ###\n",
client->addr);
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
input_err(true, &client->dev, "i2c_check_functionality error\n");
return -EIO;
}
data = kzalloc(sizeof(struct ist40xx_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
#ifdef CONFIG_OF
// TODO: Load device tree.
data->dt_data = NULL;
if (client->dev.of_node) {
data->dt_data = kzalloc(sizeof(struct ist40xx_dt_data), GFP_KERNEL);
if (!data->dt_data) {
input_err(true, &client->dev, "failed to allocate dt data\n");
goto err_alloc_dev;
}
ret = ist40xx_parse_dt(&client->dev, data);
if (ret)
goto err_alloc_dev;
tdata = kzalloc(sizeof(struct sec_tclm_data), GFP_KERNEL);
if (!tdata)
goto error_alloc_tdata;
sec_tclm_parse_dt(client, tdata);
} else {
data->dt_data = NULL;
input_err(true, &client->dev, "don't exist of_node\n");
goto err_alloc_dev;
}
ist40xx_request_gpio(client, data);
data->client = client;
#ifdef IST40XX_PINCTRL
/* Get pinctrl if target uses pinctrl */
data->pinctrl = devm_pinctrl_get(&client->dev);
if (IS_ERR(data->pinctrl)) {
if (PTR_ERR(data->pinctrl) == -EPROBE_DEFER)
goto err_pinctrl;
input_err(true, &client->dev, "%s Target does not use pinctrl\n",
__func__);
data->pinctrl = NULL;
}
if (data->pinctrl) {
ret = ist40xx_pinctrl_configure(data, true);
if (ret) {
input_err(true, &client->dev,
"%s cannot set pinctrl state\n", __func__);
}
}
#endif
#endif
input_dev = input_allocate_device();
if (!input_dev) {
input_err(true, &client->dev, "input_allocate_device failed\n");
goto err_alloc_dt;
}
data->input_dev = input_dev;
i2c_set_clientdata(client, data);
data->tdata = tdata;
if (!data->tdata)
goto err_null_data;
data->input_dev_proximity = input_allocate_device();
if (!data->input_dev_proximity) {
input_err(true, &client->dev, "%s: allocate input_dev_proximity err!\n", __func__);
ret = -ENOMEM;
goto err_allocate_input_dev_proximity;
}
data->input_dev_proximity->name = "sec_touchproximity";
ist_set_input_prop_proximity(data, data->input_dev_proximity);
#ifdef TCLM_CONCEPT
sec_tclm_initialize(data->tdata);
data->tdata->client = data->client;
data->tdata->tclm_read = ist40xx_tclm_data_read;
data->tdata->tclm_write = ist40xx_tclm_data_write;
data->tdata->tclm_execute_force_calibration = ist40xx_execute_force_calibration;
data->tdata->external_factory = false;
data->tdata->tclm_parse_dt = sec_tclm_parse_dt;
#endif
INIT_DELAYED_WORK(&data->work_read_info, ist40xx_read_info_work);
#ifdef USE_OPEN_CLOSE
input_dev->open = ist40xx_ts_open;
input_dev->close = ist40xx_ts_close;
#endif
#ifdef CONFIG_HAS_EARLYSUSPEND
data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
data->early_suspend.suspend = ist40xx_early_suspend;
data->early_suspend.resume = ist40xx_late_resume;
register_early_suspend(&data->early_suspend);
#endif
data->irq_enabled = false;
data->status.event_mode = false;
mutex_init(&data->lock);
mutex_init(&data->aod_lock);
mutex_init(&data->i2c_lock);
mutex_init(&data->i2c_wc_lock);
ts_data = data;
/* initialize data variable */
data->ignore_delay = false;
data->irq_working = false;
data->max_scan_retry = 2;
data->max_irq_err_cnt = IST40XX_MAX_ERR_CNT;
data->report_rate = -1;
data->idle_rate = -1;
data->timer_period_ms = 5000;
data->status.sys_mode = STATE_POWER_OFF;
data->rec_mode = 0;
data->rec_type = 0;
data->rec_file_name = kzalloc(IST40XX_REC_FILENAME_SIZE, GFP_KERNEL);
data->debug_mode = 0;
data->checksum_result = 0;
data->multi_count = 0;
data->all_finger_count = 0;
data->all_spay_count = 0;
data->all_aod_tsp_count = 0;
data->all_singletap_count = 0;
data->lpm_mode = 0;
data->info_work_done = false;
data->rejectzone_t = 0;
data->rejectzone_b = 0;
data->hover = 0;
data->fod_property = 0;
for (i = 0; i < IST40XX_MAX_FINGER_ID; i++)
data->tsp_touched[i] = false;
for (i = 0; i < 4; i++)
data->rect_data[i] = 0;
/* init irq thread */
input_info(true, &client->dev, "client->irq : %d\n", client->irq);
ret = request_threaded_irq(client->irq, NULL, ist40xx_irq_thread,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "ist40xx_ts",
data);
if (ret)
goto err_init_drv;
/* system power init */
ret = ist40xx_init_system(data);
if (ret) {
input_err(true, &client->dev, "chip initialization failed\n");
goto err_init_drv;
}
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
trustedui_set_tsp_irq(client->irq);
input_info(true, &client->dev, "%s[%d] called!\n", __func__, client->irq);
#endif
ret = ist40xx_auto_bin_update(data);
if (ret == 0)
goto err_irq;
ret = ist40xx_get_info(data);
input_info(true, &client->dev, "Get info: %s\n", (ret == 0 ? "success" : "fail"));
if (ret)
goto err_read_info;
ret = ist40xx_init_update_sysfs(data);
if (ret)
goto err_sysfs;
ret = ist40xx_init_misc_sysfs(data);
if (ret)
goto err_sysfs;
ret = ist40xx_init_cmcs_sysfs(data);
if (ret)
goto err_sysfs;
#ifdef SEC_FACTORY_MODE
ret = sec_touch_sysfs(data);
if (ret)
goto err_sec_sysfs;
#endif
input_info(true, &client->dev, "Create sysfs!!\n");
INIT_DELAYED_WORK(&data->work_reset_check, reset_work_func);
#ifdef IST40XX_NOISE_MODE
INIT_DELAYED_WORK(&data->work_noise_protect, noise_work_func);
#else
INIT_DELAYED_WORK(&data->work_force_release, release_work_func);
#endif
init_timer(&data->event_timer);
data->event_timer.data = (unsigned long)data;
data->event_timer.function = timer_handler;
data->event_timer.expires = jiffies_64 + EVENT_TIMER_INTERVAL;
mod_timer(&data->event_timer, get_jiffies_64() + EVENT_TIMER_INTERVAL);
#ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER
manager_notifier_register(&data->ccic_nb, tsp_ccic_notification,
MANAGER_NOTIFY_CCIC_USB);
#else
#ifdef CONFIG_MUIC_NOTIFIER
muic_notifier_register(&data->muic_nb, tsp_muic_notification,
MUIC_NOTIFY_DEV_CHARGER);
#endif
#endif
#ifdef CONFIG_VBUS_NOTIFIER
vbus_notifier_register(&data->vbus_nb, tsp_vbus_notification,
VBUS_NOTIFY_DEV_CHARGER);
#endif
device_init_wakeup(&client->dev, true);
ist40xx_start(data);
data->initialized = true;
#ifdef CONFIG_TRUSTONIC_TRUSTED_UI
tui_tsp_info = data;
#endif
#ifdef CONFIG_SAMSUNG_TUI
stui_tsp_info = data;
#endif
schedule_delayed_work(&data->work_read_info, msecs_to_jiffies(500));
#if defined(CONFIG_TOUCHSCREEN_DUMP_MODE)
dump_callbacks.inform_dump = dump_tsp_log;
INIT_DELAYED_WORK(&data->ghost_check, ist40xx_check_rawdata);
p_ghost_check = &data->ghost_check;
#endif
#ifdef CONFIG_PM
init_completion(&data->resume_done);
complete_all(&data->resume_done);
#endif
input_info(true, &client->dev, "### IMAGIS probe success ###\n");
return 0;
#ifdef SEC_FACTORY_MODE
err_sec_sysfs:
sec_touch_sysfs_remove(data);
sec_cmd_exit(&data->sec, SEC_CLASS_DEVT_TSP);
#endif
err_sysfs:
class_destroy(ist40xx_class);
err_read_info:
err_irq:
ist40xx_disable_irq(data);
free_irq(client->irq, data);
err_init_drv:
input_free_device(input_dev);
data->status.event_mode = false;
ist40xx_power_off(data);
#if (defined(CONFIG_HAS_EARLYSUSPEND) && !defined(USE_OPEN_CLOSE))
unregister_early_suspend(&data->early_suspend);
#endif
err_allocate_input_dev_proximity:
err_null_data:
err_alloc_dt:
#ifdef IST40XX_PINCTRL
err_pinctrl:
#endif
if (tdata)
kfree(tdata);
if (data->dt_data)
ist40xx_free_gpio(data);
error_alloc_tdata:
if (data->dt_data) {
input_err(true, &client->dev, "Error, ist40xx mem free\n");
kfree(data->dt_data);
}
err_alloc_dev:
#ifdef CONFIG_TOUCHSCREEN_DUMP_MODE
p_ghost_check = NULL;
#endif
kfree(data);
input_err(true, &client->dev, "Error, ist40xx init driver\n");
return -ENODEV;
}
static int ist40xx_remove(struct i2c_client *client)
{
struct ist40xx_data *data = i2c_get_clientdata(client);
#if (defined(CONFIG_HAS_EARLYSUSPEND) && !defined(USE_OPEN_CLOSE))
unregister_early_suspend(&data->early_suspend);
#endif
ist40xx_disable_irq(data);
free_irq(client->irq, data);
ist40xx_power_off(data);
#ifdef SEC_FACTORY_MODE
sec_touch_sysfs_remove(data);
sec_cmd_exit(&data->sec, SEC_CLASS_DEVT_TSP);
#endif
#ifdef CONFIG_OF
if (data->dt_data) {
ist40xx_free_gpio(data);
kfree(data->dt_data);
}
#endif
input_unregister_device(data->input_dev);
input_free_device(data->input_dev);
input_unregister_device(data->input_dev_proximity);
input_free_device(data->input_dev_proximity);
kfree(data);
return 0;
}
static void ist40xx_shutdown(struct i2c_client *client)
{
struct ist40xx_data *data = i2c_get_clientdata(client);
del_timer(&data->event_timer);
cancel_delayed_work_sync(&data->work_reset_check);
#ifdef IST40XX_NOISE_MODE
cancel_delayed_work_sync(&data->work_noise_protect);
#else
cancel_delayed_work_sync(&data->work_force_release);
#endif
mutex_lock(&data->lock);
ist40xx_disable_irq(data);
ist40xx_power_off(data);
clear_input_data(data);
mutex_unlock(&data->lock);
}
static struct i2c_device_id ist40xx_idtable[] = {
{IST40XX_DEV_NAME, 0},
{},
};
MODULE_DEVICE_TABLE(i2c, ist40xx_idtable);
#ifdef CONFIG_OF
static struct of_device_id ist40xx_match_table[] = {
{.compatible = "imagis,ist40xx-ts",},
{},
};
#else
#define ist40xx_match_table NULL
#endif
#if (!defined(CONFIG_HAS_EARLYSUSPEND) && !defined(USE_OPEN_CLOSE))
static const struct dev_pm_ops ist40xx_pm_ops = {
.suspend = ist40xx_suspend,
.resume = ist40xx_resume,
};
#else
#ifdef CONFIG_PM
static int ist40xx_pm_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ist40xx_data *data = i2c_get_clientdata(client);
reinit_completion(&data->resume_done);
return 0;
}
static int ist40xx_pm_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ist40xx_data *data = i2c_get_clientdata(client);
complete_all(&data->resume_done);
return 0;
}
static const struct dev_pm_ops ist40xx_pm_ops = {
.suspend = ist40xx_pm_suspend,
.resume = ist40xx_pm_resume,
};
#endif
#endif
static struct i2c_driver ist40xx_i2c_driver = {
.id_table = ist40xx_idtable,
.probe = ist40xx_probe,
.remove = ist40xx_remove,
.shutdown = ist40xx_shutdown,
.driver = {
.owner = THIS_MODULE,
.name = IST40XX_DEV_NAME,
.of_match_table = ist40xx_match_table,
#if (!defined(CONFIG_HAS_EARLYSUSPEND) && !defined(USE_OPEN_CLOSE))
.pm = &ist40xx_pm_ops,
#else
#ifdef CONFIG_PM
.pm = &ist40xx_pm_ops,
#endif
#endif
},
};
static int __init ist40xx_init(void)
{
#ifdef CONFIG_BATTERY_SAMSUNG
if (lpcharge == 1) {
tsp_info("%s: Do not load driver due to : lpm %d\n", __func__,
lpcharge);
return -ENODEV;
}
#endif
return i2c_add_driver(&ist40xx_i2c_driver);
}
static void __exit ist40xx_exit(void)
{
i2c_del_driver(&ist40xx_i2c_driver);
}
module_init(ist40xx_init);
module_exit(ist40xx_exit);
MODULE_DESCRIPTION("Imagis IST40XX touch driver");
MODULE_LICENSE("GPL");