1918 lines
57 KiB
C
Executable file
1918 lines
57 KiB
C
Executable file
/* drivers/input/sec_input/stm/stm_fn.c
|
|
*
|
|
* Copyright (C) 2011 Samsung Electronics Co., Ltd.
|
|
*
|
|
* Core file for Samsung TSC driver
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include "stm_dev.h"
|
|
#include "stm_reg.h"
|
|
|
|
int stm_ts_set_custom_library(struct stm_ts_data *ts)
|
|
{
|
|
u8 data[3] = { 0 };
|
|
int ret;
|
|
u8 force_fod_enable = 0;
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
/* enable FOD when LCD on state */
|
|
if (ts->plat_data->support_fod && ts->plat_data->enabled)
|
|
force_fod_enable = SEC_TS_MODE_SPONGE_PRESS;
|
|
#endif
|
|
|
|
input_err(true, &ts->client->dev, "%s: Sponge (0x%02x)%s\n",
|
|
__func__, ts->plat_data->lowpower_mode,
|
|
force_fod_enable ? ", force fod enable" : "");
|
|
|
|
if (ts->plat_data->prox_power_off) {
|
|
data[2] = (ts->plat_data->lowpower_mode | force_fod_enable) &
|
|
~SEC_TS_MODE_SPONGE_DOUBLETAP_TO_WAKEUP;
|
|
input_info(true, &ts->client->dev, "%s: prox off. disable AOT\n", __func__);
|
|
} else {
|
|
data[2] = ts->plat_data->lowpower_mode | force_fod_enable;
|
|
}
|
|
|
|
ret = ts->stm_ts_write_sponge(ts, data, 3);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to write sponge\n", __func__);
|
|
|
|
#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)
|
|
/* inf dump enable */
|
|
data[0] = STM_TS_CMD_SPONGE_OFFSET_MODE_01;
|
|
data[2] = ts->plat_data->sponge_mode |= SEC_TS_MODE_SPONGE_INF_DUMP;
|
|
|
|
ret = ts->stm_ts_write_sponge(ts, data, 3);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to write sponge\n", __func__);
|
|
#endif
|
|
/* read dump info */
|
|
data[0] = STM_TS_CMD_SPONGE_LP_DUMP;
|
|
|
|
ret = ts->stm_ts_read_sponge(ts, data, 2);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to read dump_data\n", __func__);
|
|
|
|
ts->sponge_inf_dump = (data[0] & SEC_TS_SPONGE_DUMP_INF_MASK) >> SEC_TS_SPONGE_DUMP_INF_SHIFT;
|
|
ts->sponge_dump_format = data[0] & SEC_TS_SPONGE_DUMP_EVENT_MASK;
|
|
ts->sponge_dump_event = data[1];
|
|
ts->sponge_dump_border = SEC_TS_CMD_SPONGE_LP_DUMP_EVENT
|
|
+ (ts->sponge_dump_format * ts->sponge_dump_event);
|
|
ts->sponge_dump_border_lsb = ts->sponge_dump_border & 0xFF;
|
|
ts->sponge_dump_border_msb = (ts->sponge_dump_border & 0xFF00) >> 8;
|
|
|
|
input_info(true, &ts->client->dev, "%s: sponge_inf_dump:%d, sponge_dump_format:%d, sponge_dump_event:%d\n", __func__,
|
|
ts->sponge_inf_dump, ts->sponge_dump_format, ts->sponge_dump_event);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void stm_ts_get_custom_library(struct stm_ts_data *ts)
|
|
{
|
|
u8 data[6] = { 0 };
|
|
int ret, i;
|
|
|
|
data[0] = SEC_TS_CMD_SPONGE_AOD_ACTIVE_INFO;
|
|
ret = ts->stm_ts_read_sponge(ts, data, 6);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: Failed to read aod active area\n", __func__);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < 3; i++)
|
|
ts->plat_data->aod_data.active_area[i] = (data[i * 2 + 1] & 0xFF) << 8 | (data[i * 2] & 0xFF);
|
|
|
|
input_info(true, &ts->client->dev, "%s: aod_active_area - top:%d, edge:%d, bottom:%d\n",
|
|
__func__, ts->plat_data->aod_data.active_area[0],
|
|
ts->plat_data->aod_data.active_area[1], ts->plat_data->aod_data.active_area[2]);
|
|
|
|
memset(data, 0x00, 6);
|
|
|
|
data[0] = SEC_TS_CMD_SPONGE_FOD_INFO;
|
|
ret = ts->stm_ts_read_sponge(ts, data, 4);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: Failed to read fod info\n", __func__);
|
|
return;
|
|
}
|
|
|
|
sec_input_set_fod_info(&ts->client->dev, data[0], data[1], data[2], data[3]);
|
|
|
|
data[0] = STM_TS_CMD_SPONGE_OFFSET_MODE_01;
|
|
ret = ts->stm_ts_read_sponge(ts, data, 2);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: Failed to read mode 01 info\n", __func__);
|
|
return;
|
|
}
|
|
ts->plat_data->sponge_mode = data[0];
|
|
input_info(true, &ts->client->dev, "%s: sponge_mode %x\n", __func__, ts->plat_data->sponge_mode);
|
|
}
|
|
|
|
void stm_ts_set_fod_finger_merge(struct stm_ts_data *ts)
|
|
{
|
|
int ret;
|
|
u8 address[2] = {STM_TS_CMD_SET_FOD_FINGER_MERGE, 0};
|
|
|
|
if (!ts->plat_data->support_fod)
|
|
return;
|
|
|
|
if (ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_PRESS)
|
|
address[1] = 1;
|
|
else
|
|
address[1] = 0;
|
|
|
|
mutex_lock(&ts->sponge_mutex);
|
|
input_info(true, &ts->client->dev, "%s: %d\n", __func__, address[1]);
|
|
|
|
ret = ts->stm_ts_write(ts, address, 2, NULL, 0);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: failed\n", __func__);
|
|
mutex_unlock(&ts->sponge_mutex);
|
|
}
|
|
|
|
void stm_ts_read_chip_id(struct stm_ts_data *ts)
|
|
{
|
|
u8 address = STM_TS_READ_DEVICE_ID;
|
|
u8 data[5] = {0};
|
|
int ret;
|
|
|
|
ret = ts->stm_ts_read(ts, &address, 1, &data[0], 5);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: failed. ret: %d\n", __func__, ret);
|
|
return;
|
|
}
|
|
|
|
ts->chip_id = (data[2] << 16) + (data[3] << 8) + data[4];
|
|
|
|
input_info(true, &ts->client->dev, "%s: %c %c %02X %02X %02X (%x)\n",
|
|
__func__, data[0], data[1], data[2], data[3], data[4], ts->chip_id);
|
|
}
|
|
|
|
void stm_ts_read_chip_id_hw(struct stm_ts_data *ts)
|
|
{
|
|
u8 address[5] = {STM_TS_CMD_REG_R, 0x20, 0x00, 0x00, 0x00 };
|
|
u8 data[8] = {0};
|
|
int ret;
|
|
|
|
ret = ts->stm_ts_read(ts, address, 5, &data[0], 8);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: failed. ret: %d\n", __func__, ret);
|
|
}
|
|
|
|
input_info(true, &ts->client->dev, "%s: %02X %02X %02X %02X %02X %02X %02X %02X\n",
|
|
__func__, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
|
|
}
|
|
|
|
int stm_ts_get_channel_info(struct stm_ts_data *ts)
|
|
{
|
|
int rc = -1;
|
|
u8 address = 0;
|
|
u8 data[STM_TS_EVENT_BUFF_SIZE] = { 0 };
|
|
|
|
memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE);
|
|
|
|
address = STM_TS_READ_PANEL_INFO;
|
|
rc = ts->stm_ts_read(ts, &address, 1, data, 11);
|
|
if (rc < 0) {
|
|
ts->tx_count = 0;
|
|
ts->rx_count = 0;
|
|
input_err(true, &ts->client->dev, "%s: Get channel info Read Fail!!\n", __func__);
|
|
return rc;
|
|
}
|
|
|
|
ts->tx_count = data[8]; // Number of TX CH
|
|
ts->rx_count = data[9]; // Number of RX CH
|
|
|
|
if (!(ts->tx_count > 0 && ts->tx_count < STM_TS_MAX_NUM_FORCE &&
|
|
ts->rx_count > 0 && ts->rx_count < STM_TS_MAX_NUM_SENSE)) {
|
|
ts->tx_count = STM_TS_MAX_NUM_FORCE;
|
|
ts->rx_count = STM_TS_MAX_NUM_SENSE;
|
|
input_err(true, &ts->client->dev, "%s: set channel num based on max value, check it!\n", __func__);
|
|
}
|
|
|
|
ts->plat_data->x_node_num = ts->tx_count;
|
|
ts->plat_data->y_node_num = ts->rx_count;
|
|
|
|
ts->resolution_x = (data[0] << 8) | data[1]; // X resolution of IC
|
|
ts->resolution_y = (data[2] << 8) | data[3]; // Y resolution of IC
|
|
|
|
input_info(true, &ts->client->dev, "%s: RX:Sense(%02d) TX:Force(%02d) resolution:(IC)x:%d y:%d, (DT)x:%d,y:%d\n",
|
|
__func__, ts->rx_count, ts->tx_count,
|
|
ts->resolution_x, ts->resolution_y, ts->plat_data->max_x, ts->plat_data->max_y);
|
|
|
|
if (ts->resolution_x > 0 && ts->resolution_x < STM_TS_MAX_X_RESOLUTION &&
|
|
ts->resolution_y > 0 && ts->resolution_y < STM_TS_MAX_Y_RESOLUTION &&
|
|
ts->plat_data->max_x != ts->resolution_x && ts->plat_data->max_y != ts->resolution_y) {
|
|
ts->plat_data->max_x = ts->resolution_x;
|
|
ts->plat_data->max_y = ts->resolution_y;
|
|
input_err(true, &ts->client->dev, "%s: set resolution based on ic value, check it!\n", __func__);
|
|
}
|
|
|
|
if (data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x00 && data[8] == 0x00 && data[9] == 0x00) {
|
|
input_err(true, &ts->client->dev, "%s: channel number and resoltion value is zero. return ENODEV\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF && data[8] == 0xFF && data[9] == 0xFF) {
|
|
input_err(true, &ts->client->dev, "%s: channel number and resoltion value is FF. return ENODEV\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int stm_ts_get_sysinfo_data(struct stm_ts_data *ts, u8 sysinfo_addr, u8 read_cnt, u8 *data)
|
|
{
|
|
int ret;
|
|
int rc = 0;
|
|
u8 *buff = NULL;
|
|
|
|
u8 address[3] = { 0xA4, 0x06, 0x01 }; // request system information
|
|
|
|
ret = stm_ts_wait_for_echo_event(ts, &address[0], 3, 0);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: timeout wait for event\n", __func__);
|
|
rc = -1;
|
|
goto ERROR;
|
|
}
|
|
|
|
address[0] = 0xA6;
|
|
address[1] = 0x00;
|
|
address[2] = sysinfo_addr;
|
|
|
|
buff = kzalloc(read_cnt, GFP_KERNEL);
|
|
if (!buff) {
|
|
rc = -2;
|
|
goto ERROR;
|
|
}
|
|
|
|
ret = ts->stm_ts_read(ts, &address[0], 3, &buff[0], read_cnt);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: failed. ret: %d\n",
|
|
__func__, ret);
|
|
kfree(buff);
|
|
rc = -3;
|
|
goto ERROR;
|
|
}
|
|
|
|
memcpy(data, &buff[0], read_cnt);
|
|
kfree(buff);
|
|
|
|
ERROR:
|
|
return rc;
|
|
}
|
|
|
|
int stm_ts_get_version_info(struct stm_ts_data *ts)
|
|
{
|
|
int rc;
|
|
u8 address = STM_TS_READ_FW_VERSION;
|
|
u8 data[STM_TS_VERSION_SIZE] = { 0 };
|
|
|
|
memset(data, 0x0, STM_TS_VERSION_SIZE);
|
|
|
|
rc = ts->stm_ts_read(ts, &address, 1, (u8 *)data, STM_TS_VERSION_SIZE);
|
|
|
|
ts->fw_version_of_ic = (data[0] << 8) + data[1];
|
|
ts->config_version_of_ic = (data[2] << 8) + data[3];
|
|
ts->fw_main_version_of_ic = data[4] + (data[5] << 8);
|
|
ts->project_id_of_ic = data[6];
|
|
ts->ic_name_of_ic = data[7];
|
|
ts->module_version_of_ic = data[8];
|
|
|
|
ts->plat_data->img_version_of_ic[2] = ts->module_version_of_ic;
|
|
ts->plat_data->img_version_of_ic[3] = ts->fw_main_version_of_ic & 0xFF;
|
|
|
|
input_info(true, &ts->client->dev,
|
|
"%s: [IC] Firmware Ver: 0x%04X, Config Ver: 0x%04X, Main Ver: 0x%04X\n",
|
|
__func__, ts->fw_version_of_ic,
|
|
ts->config_version_of_ic, ts->fw_main_version_of_ic);
|
|
input_info(true, &ts->client->dev,
|
|
"%s: [IC] Project ID: 0x%02X, IC Name: 0x%02X, Module Ver: 0x%02X\n",
|
|
__func__, ts->project_id_of_ic,
|
|
ts->ic_name_of_ic, ts->module_version_of_ic);
|
|
|
|
return rc;
|
|
}
|
|
|
|
void stm_ts_command(struct stm_ts_data *ts, u8 cmd, bool checkecho)
|
|
{
|
|
int ret = 0;
|
|
|
|
|
|
if (checkecho)
|
|
ret = stm_ts_wait_for_echo_event(ts, &cmd, 1, 100);
|
|
else
|
|
ret = ts->stm_ts_write(ts, &cmd, 1, 0, 0);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev,
|
|
"%s: failed to write command(0x%02X), ret = %d\n", __func__, cmd, ret);
|
|
|
|
}
|
|
|
|
int stm_ts_systemreset(struct stm_ts_data *ts, unsigned int msec)
|
|
{
|
|
u8 address = STM_TS_CMD_REG_W;
|
|
u8 data[5] = { 0x20, 0x00, 0x00, 0x24, 0x81 };
|
|
int rc;
|
|
|
|
input_info(true, &ts->client->dev, "%s\n", __func__);
|
|
|
|
disable_irq(ts->irq);
|
|
|
|
ts->stm_ts_write(ts, &address, 1, &data[0], 5);
|
|
|
|
sec_delay(msec + 10);
|
|
|
|
rc = stm_ts_wait_for_ready(ts);
|
|
|
|
stm_ts_release_all_finger(ts);
|
|
|
|
enable_irq(ts->irq);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int stm_ts_fix_active_mode(struct stm_ts_data *ts, int mode)
|
|
{
|
|
u8 address[3] = {0xA0, 0x00, 0x00};
|
|
int ret = -EINVAL;
|
|
|
|
switch (mode) {
|
|
case STM_TS_ACTIVE_FALSE:
|
|
address[1] = 0x00;
|
|
address[2] = 0x01;
|
|
ts->stm_ts_command(ts, STM_TS_CMD_SENSE_OFF, false);
|
|
sec_delay(10);
|
|
break;
|
|
case STM_TS_ACTIVE_TRUE:
|
|
address[1] = 0x03;
|
|
address[2] = 0x00;
|
|
break;
|
|
case STM_TS_ACTIVE_FALSE_SNR:
|
|
address[1] = 0x00;
|
|
address[2] = 0x01;
|
|
break;
|
|
default:
|
|
input_err(true, &ts->client->dev, "%s: err data mode: %d\n", __func__, mode);
|
|
return ret;
|
|
}
|
|
|
|
ret = ts->stm_ts_write(ts, &address[0], 3, NULL, 0);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: err: %d, mode: %d\n", __func__, ret, mode);
|
|
else
|
|
input_info(true, &ts->client->dev, "%s: %d STM_TS_ACTIVE_%s\n", __func__, mode,
|
|
(mode == STM_TS_ACTIVE_TRUE) ? "TRUE" :
|
|
(mode == STM_TS_ACTIVE_FALSE) ? "FALSE" : "FALSE_SNR");
|
|
|
|
sec_delay(10);
|
|
return ret;
|
|
}
|
|
|
|
void stm_ts_change_scan_rate(struct stm_ts_data *ts, u8 rate)
|
|
{
|
|
u8 address = STM_TS_CMD_SET_GET_REPORT_RATE;
|
|
u8 data = rate;
|
|
int ret = 0;
|
|
|
|
ret = ts->stm_ts_write(ts, &address, 1, &data, 1);
|
|
|
|
input_dbg(true, &ts->client->dev, "%s: scan rate (%d Hz), ret = %d\n", __func__, address, ret);
|
|
}
|
|
|
|
int stm_ts_fw_corruption_check(struct stm_ts_data *ts)
|
|
{
|
|
u8 address[6] = { 0, };
|
|
u8 val = 0;
|
|
int ret;
|
|
|
|
ret = ts->stm_ts_systemreset(ts, 0);
|
|
if (ret < 0) {
|
|
input_info(true, &ts->client->dev, "%s: stm_ts_systemreset fail (%d)\n", __func__, ret);
|
|
goto out;
|
|
}
|
|
|
|
/* Firmware Corruption Check */
|
|
address[0] = STM_TS_CMD_REG_R;
|
|
address[1] = 0x20;
|
|
address[2] = 0x00;
|
|
address[3] = 0x00;
|
|
address[4] = 0x78;
|
|
ret = ts->stm_ts_read(ts, address, 5, &val, 1);
|
|
if (ret < 0) {
|
|
ret = -STM_TS_I2C_ERROR;
|
|
goto out;
|
|
}
|
|
if (val & 0x03) { // Check if crc error
|
|
input_err(true, &ts->client->dev, "%s: firmware corruption. CRC status:%02X\n",
|
|
__func__, val & 0x03);
|
|
ret = -STM_TS_ERROR_FW_CORRUPTION;
|
|
} else {
|
|
ret = 0;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_wait_for_ready(struct stm_ts_data *ts)
|
|
{
|
|
struct stm_ts_event_status *p_event_status;
|
|
int rc;
|
|
u8 address = STM_TS_READ_ONE_EVENT;
|
|
u8 data[STM_TS_EVENT_BUFF_SIZE];
|
|
int retry = 0;
|
|
int err_cnt = 0;
|
|
|
|
mutex_lock(&ts->fn_mutex);
|
|
|
|
memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE);
|
|
|
|
rc = -1;
|
|
while (ts->stm_ts_read(ts, &address, 1, (u8 *)data, STM_TS_EVENT_BUFF_SIZE) >= 0) {
|
|
p_event_status = (struct stm_ts_event_status *) &data[0];
|
|
|
|
if ((p_event_status->stype == STM_TS_EVENT_STATUSTYPE_INFO) &&
|
|
(p_event_status->status_id == STM_TS_INFO_READY_STATUS)) {
|
|
rc = 0;
|
|
break;
|
|
}
|
|
|
|
if (data[0] == 0xff && data[1] == 0xff && data[2] == 0xff) {
|
|
rc = -STM_TS_ERROR_BROKEN_FW;
|
|
break;
|
|
}
|
|
|
|
if (data[0] == STM_TS_EVENT_ERROR_REPORT) {
|
|
input_err(true, &ts->client->dev,
|
|
"%s: Err detected %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X\n",
|
|
__func__, data[0], data[1], data[2], data[3],
|
|
data[4], data[5], data[6], data[7]);
|
|
|
|
// check if config / cx / panel configuration area is corrupted
|
|
if (((data[1] >= 0x20) && (data[1] <= 0x23)) || ((data[1] >= 0xA0) && (data[1] <= 0xA8))) {
|
|
rc = -STM_TS_ERROR_FW_CORRUPTION;
|
|
ts->plat_data->hw_param.checksum_result = 1;
|
|
ts->fw_corruption = true;
|
|
input_err(true, &ts->client->dev, "%s: flash corruption\n", __func__);
|
|
break;
|
|
}
|
|
if (data[1] == 0x24 || data[1] == 0x25 || data[1] == 0x29 || data[1] == 0x2A || data[1] == 0x2D || data[1] == 0x34) {
|
|
input_err(true, &ts->client->dev, "%s: osc trim is broken\n", __func__);
|
|
rc = -STM_TS_ERROR_BROKEN_OSC_TRIM;
|
|
ts->fw_corruption = true;
|
|
break;
|
|
}
|
|
|
|
if (err_cnt++ > 32) {
|
|
rc = -STM_TS_ERROR_EVENT_ID;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (retry++ > STM_TS_RETRY_COUNT * 15) {
|
|
rc = -STM_TS_ERROR_TIMEOUT;
|
|
if (data[0] == 0 && data[1] == 0 && data[2] == 0)
|
|
rc = -STM_TS_ERROR_TIMEOUT_ZERO;
|
|
|
|
input_err(true, &ts->client->dev, "%s: Time Over\n", __func__);
|
|
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_LPM)
|
|
schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(10));
|
|
break;
|
|
}
|
|
sec_delay(20);
|
|
}
|
|
|
|
input_info(true, &ts->client->dev,
|
|
"%s: %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X\n",
|
|
__func__, data[0], data[1], data[2], data[3],
|
|
data[4], data[5], data[6], data[7]);
|
|
|
|
mutex_unlock(&ts->fn_mutex);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
int stm_ts_wait_for_echo_event(struct stm_ts_data *ts, u8 *cmd, u8 cmd_cnt, int delay)
|
|
{
|
|
int rc;
|
|
int i;
|
|
bool matched = false;
|
|
u8 reg = STM_TS_READ_ONE_EVENT;
|
|
u8 data[STM_TS_EVENT_BUFF_SIZE];
|
|
int retry = 0;
|
|
|
|
mutex_lock(&ts->fn_mutex);
|
|
disable_irq(ts->irq);
|
|
|
|
rc = ts->stm_ts_write(ts, cmd, cmd_cnt, NULL, 0);
|
|
if (rc < 0) {
|
|
input_err(true, &ts->client->dev, "%s: failed to write command\n", __func__);
|
|
enable_irq(ts->irq);
|
|
mutex_unlock(&ts->fn_mutex);
|
|
return rc;
|
|
}
|
|
|
|
if (delay)
|
|
sec_delay(delay);
|
|
else
|
|
sec_delay(5);
|
|
|
|
memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE);
|
|
|
|
rc = -EIO;
|
|
|
|
while (ts->stm_ts_read(ts, ®, 1, (u8 *)data, STM_TS_EVENT_BUFF_SIZE) >= 0) {
|
|
if (data[0] != 0x00)
|
|
input_info(true, &ts->client->dev,
|
|
"%s: event %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X,"
|
|
" %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X\n",
|
|
__func__, data[0], data[1], data[2], data[3],
|
|
data[4], data[5], data[6], data[7],
|
|
data[8], data[9], data[10], data[11],
|
|
data[12], data[13], data[14], data[15]);
|
|
|
|
if ((data[0] == STM_TS_EVENT_STATUS_REPORT) && (data[1] == 0x01)) { // Check command ECHO
|
|
int loop_cnt;
|
|
|
|
if (cmd_cnt > 4)
|
|
loop_cnt = 4;
|
|
else
|
|
loop_cnt = cmd_cnt;
|
|
|
|
for (i = 0; i < loop_cnt; i++) {
|
|
if (data[i + 2] != cmd[i]) {
|
|
matched = false;
|
|
break;
|
|
}
|
|
matched = true;
|
|
}
|
|
|
|
if (matched == true) {
|
|
rc = 0;
|
|
break;
|
|
}
|
|
} else if (data[0] == STM_TS_EVENT_ERROR_REPORT) {
|
|
input_info(true, &ts->client->dev, "%s: Error detected %02X,%02X,%02X,%02X,%02X,%02X\n",
|
|
__func__, data[0], data[1], data[2], data[3], data[4], data[5]);
|
|
|
|
if (retry >= STM_TS_RETRY_COUNT)
|
|
break;
|
|
}
|
|
|
|
if (retry++ > STM_TS_RETRY_COUNT * 50) {
|
|
input_err(true, &ts->client->dev, "%s: Time Over (%02X,%02X,%02X,%02X,%02X,%02X)\n",
|
|
__func__, data[0], data[1], data[2], data[3], data[4], data[5]);
|
|
break;
|
|
}
|
|
sec_delay(20);
|
|
}
|
|
|
|
enable_irq(ts->irq);
|
|
mutex_unlock(&ts->fn_mutex);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int stm_ts_set_scanmode(struct stm_ts_data *ts, u8 scan_mode)
|
|
{
|
|
u8 address[3] = { 0xA0, 0x00, scan_mode };
|
|
int rc;
|
|
|
|
rc = stm_ts_wait_for_echo_event(ts, &address[0], 3, 20);
|
|
if (rc < 0) {
|
|
input_info(true, &ts->client->dev, "%s: timeout, ret = %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
input_info(true, &ts->client->dev, "%s: 0x%02X\n", __func__, scan_mode);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* optional reg : SEC_TS_CMD_LPM_AOD_OFF_ON(0x9B) */
|
|
/* 0 : Async base scan (default on lp mode) */
|
|
/* 1 : sync base scan */
|
|
int stm_ts_set_hsync_scanmode(struct stm_ts_data *ts, u8 scan_mode)
|
|
{
|
|
u8 address[2] = { STM_TS_CMD_SET_LPM_AOD_OFF_ON, 0};
|
|
int rc;
|
|
|
|
input_info(true, &ts->client->dev, "%s: mode:%x\n", __func__, scan_mode);
|
|
address[1] = scan_mode;
|
|
|
|
rc = ts->stm_ts_write(ts, &address[0], 2, NULL, 0);
|
|
if (rc < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to send command: %x/%x",
|
|
__func__, address[0], address[1]);
|
|
return rc;
|
|
}
|
|
|
|
int stm_ts_set_touch_function(struct stm_ts_data *ts)
|
|
{
|
|
int ret = 0;
|
|
u8 address = 0;
|
|
u8 data[2] = { 0 };
|
|
|
|
address = STM_TS_CMD_SET_GET_TOUCHTYPE;
|
|
data[0] = (u8)(ts->plat_data->touch_functions & 0xFF);
|
|
data[1] = (u8)(ts->plat_data->touch_functions >> 8);
|
|
ret = ts->stm_ts_write(ts, &address, 1, data, 2);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to send command(0x%x)",
|
|
__func__, STM_TS_CMD_SET_GET_TOUCHTYPE);
|
|
|
|
if (!ts->plat_data->shutdown_called)
|
|
schedule_delayed_work(&ts->work_read_functions, msecs_to_jiffies(30));
|
|
|
|
return ret;
|
|
}
|
|
|
|
void stm_ts_get_touch_function(struct work_struct *work)
|
|
{
|
|
struct stm_ts_data *ts = container_of(work, struct stm_ts_data,
|
|
work_read_functions.work);
|
|
int ret = 0;
|
|
u8 address = 0;
|
|
u8 data[2] = { 0 };
|
|
|
|
address = STM_TS_CMD_SET_GET_TOUCHTYPE;
|
|
data[0] = (u8)(ts->plat_data->touch_functions & 0xFF);
|
|
data[1] = (u8)(ts->plat_data->touch_functions >> 8);
|
|
ret = ts->stm_ts_read(ts, &address, 1, (u8 *)&(ts->plat_data->ic_status), 2);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev,
|
|
"%s: failed to read touch functions(%d)\n",
|
|
__func__, ret);
|
|
return;
|
|
}
|
|
|
|
input_info(true, &ts->client->dev,
|
|
"%s: touch_functions:%x ic_status:%x\n", __func__,
|
|
ts->plat_data->touch_functions, ts->plat_data->ic_status);
|
|
}
|
|
|
|
|
|
int stm_ts_osc_trim_recovery(struct stm_ts_data *ts)
|
|
{
|
|
u8 address[3];
|
|
int rc;
|
|
|
|
input_info(true, &ts->client->dev, "%s\n", __func__);
|
|
|
|
/* OSC trim error recovery command. */
|
|
address[0] = 0xA4;
|
|
address[1] = 0x00;
|
|
address[2] = 0x05;
|
|
|
|
rc = stm_ts_wait_for_echo_event(ts, &address[0], 3, 800);
|
|
if (rc < 0) {
|
|
rc = -STM_TS_ERROR_BROKEN_OSC_TRIM;
|
|
goto out;
|
|
}
|
|
|
|
/* save panel configuration area */
|
|
address[0] = 0xA4;
|
|
address[1] = 0x05;
|
|
address[2] = 0x04;
|
|
|
|
rc = stm_ts_wait_for_echo_event(ts, &address[0], 3, 100);
|
|
if (rc < 0) {
|
|
rc = -STM_TS_ERROR_BROKEN_OSC_TRIM;
|
|
goto out;
|
|
}
|
|
|
|
sec_delay(500);
|
|
rc = ts->stm_ts_systemreset(ts, 0);
|
|
sec_delay(50);
|
|
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
int stm_ts_set_vvc_mode(struct stm_ts_data *ts, bool enable)
|
|
{
|
|
int ret = 0;
|
|
u8 data[3];
|
|
|
|
/* register */
|
|
data[0] = 0xC1;
|
|
data[1] = 0x17;
|
|
if (enable)
|
|
data[2] = ts->vvc_mode;
|
|
else
|
|
data[2] = 0x00;
|
|
|
|
ret = ts->stm_ts_write(ts, data, 3, NULL, 0);
|
|
input_info(true, &ts->client->dev, "%s: vvc mode write: %02X, ret: %d\n", __func__, data[2], ret);
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_set_opmode(struct stm_ts_data *ts, u8 mode)
|
|
{
|
|
int ret;
|
|
u8 address[2] = {STM_TS_CMD_SET_GET_OPMODE, mode};
|
|
|
|
ret = ts->stm_ts_write(ts, &address[0], 2, NULL, 0);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to write opmode", __func__);
|
|
|
|
sec_delay(5);
|
|
|
|
if (ts->plat_data->lowpower_mode) {
|
|
address[0] = STM_TS_CMD_WRITE_WAKEUP_GESTURE;
|
|
address[1] = 0x02;
|
|
ret = ts->stm_ts_write(ts, &address[0], 1, &address[1], 1);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to send lowpower flag command", __func__);
|
|
}
|
|
|
|
input_info(true, &ts->client->dev, "%s: opmode %d", __func__, mode);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void stm_ts_set_utc_sponge(struct stm_ts_data *ts)
|
|
{
|
|
struct timespec64 current_time;
|
|
u8 data[6] = {STM_TS_CMD_SPONGE_OFFSET_UTC, 0};
|
|
int ret = 0;
|
|
|
|
ktime_get_real_ts64(¤t_time);
|
|
data[2] = (0xFF & (u8)((current_time.tv_sec) >> 0));
|
|
data[3] = (0xFF & (u8)((current_time.tv_sec) >> 8));
|
|
data[4] = (0xFF & (u8)((current_time.tv_sec) >> 16));
|
|
data[5] = (0xFF & (u8)((current_time.tv_sec) >> 24));
|
|
input_info(true, &ts->client->dev, "Write UTC to Sponge = %X\n", (int)(current_time.tv_sec));
|
|
|
|
|
|
ret = ts->stm_ts_write_sponge(ts, data, 6);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to write sponge\n", __func__);
|
|
}
|
|
|
|
|
|
int stm_ts_set_lowpowermode(void *data, u8 mode)
|
|
{
|
|
struct stm_ts_data *ts = (struct stm_ts_data *)data;
|
|
int ret;
|
|
int retrycnt = 0;
|
|
char para = 0;
|
|
u8 address = 0;
|
|
|
|
input_err(true, &ts->client->dev, "%s: %s(%X)\n", __func__,
|
|
mode == TO_LOWPOWER_MODE ? "ENTER" : "EXIT", ts->plat_data->lowpower_mode);
|
|
|
|
if (mode) {
|
|
stm_ts_set_utc_sponge(ts);
|
|
|
|
#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE)
|
|
if (ts->sponge_inf_dump) {
|
|
if (ts->sponge_dump_delayed_flag) {
|
|
stm_ts_sponge_dump_flush(ts, ts->sponge_dump_delayed_area);
|
|
ts->sponge_dump_delayed_flag = false;
|
|
input_info(true, &ts->client->dev, "%s : Sponge dump flush delayed work have procceed\n", __func__);
|
|
}
|
|
}
|
|
#endif
|
|
ret = stm_ts_set_custom_library(ts);
|
|
if (ret < 0)
|
|
goto error;
|
|
} else {
|
|
if (!ts->plat_data->shutdown_called)
|
|
stm_ts_get_touch_function(&ts->work_read_functions.work);
|
|
}
|
|
|
|
retry_pmode:
|
|
if (mode) {
|
|
stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, false);
|
|
ret = stm_ts_set_opmode(ts, STM_TS_OPMODE_LOWPOWER);
|
|
} else {
|
|
ret = stm_ts_set_opmode(ts, STM_TS_OPMODE_NORMAL);
|
|
}
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: stm_ts_set_opmode failed!\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
sec_delay(ts->lpmode_change_delay);
|
|
|
|
address = STM_TS_CMD_SET_GET_OPMODE;
|
|
ret = ts->stm_ts_read(ts, &address, 1, ¶, 1);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: read power mode failed!\n", __func__);
|
|
retrycnt++;
|
|
if (retrycnt < 10)
|
|
goto retry_pmode;
|
|
}
|
|
|
|
input_info(true, &ts->client->dev, "%s: write(%d) read(%d) retry %d\n", __func__, mode, para, retrycnt);
|
|
|
|
if (mode != para) {
|
|
retrycnt++;
|
|
ts->plat_data->hw_param.mode_change_failed_count++;
|
|
if (retrycnt < 10)
|
|
goto retry_pmode;
|
|
}
|
|
|
|
stm_ts_locked_release_all_finger(ts);
|
|
|
|
if (device_may_wakeup(&ts->client->dev)) {
|
|
if (mode)
|
|
enable_irq_wake(ts->irq);
|
|
else
|
|
disable_irq_wake(ts->irq);
|
|
}
|
|
|
|
if (mode == TO_LOWPOWER_MODE)
|
|
ts->plat_data->power_state = SEC_INPUT_STATE_LPM;
|
|
else
|
|
ts->plat_data->power_state = SEC_INPUT_STATE_POWER_ON;
|
|
|
|
error:
|
|
input_info(true, &ts->client->dev, "%s: end %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void stm_ts_release_all_finger(struct stm_ts_data *ts)
|
|
{
|
|
sec_input_release_all_finger(&ts->client->dev);
|
|
}
|
|
|
|
void stm_ts_locked_release_all_finger(struct stm_ts_data *ts)
|
|
{
|
|
mutex_lock(&ts->eventlock);
|
|
|
|
stm_ts_release_all_finger(ts);
|
|
|
|
mutex_unlock(&ts->eventlock);
|
|
}
|
|
|
|
void stm_ts_reset(struct stm_ts_data *ts, unsigned int ms)
|
|
{
|
|
input_info(true, &ts->client->dev, "%s: Recover IC, discharge time:%d\n", __func__, ms);
|
|
|
|
if (ts->plat_data->power)
|
|
ts->plat_data->power(&ts->client->dev, false);
|
|
|
|
sec_delay(ms);
|
|
|
|
if (ts->plat_data->power)
|
|
ts->plat_data->power(&ts->client->dev, true);
|
|
|
|
sec_delay(TOUCH_POWER_ON_DWORK_TIME);
|
|
}
|
|
|
|
int stm_ts_reset_work_from_preparation_to_completion(struct stm_ts_data *ts, bool prepare, bool need_to_reset)
|
|
{
|
|
if (prepare) {
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH)
|
|
if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_ENABLE) {
|
|
input_err(true, &ts->client->dev, "%s: secure touch enabled\n", __func__);
|
|
return -1;
|
|
}
|
|
#endif
|
|
if (ts->reset_is_on_going) {
|
|
input_err(true, &ts->client->dev, "%s: reset is ongoing\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
mutex_lock(&ts->modechange);
|
|
__pm_stay_awake(ts->plat_data->sec_ws);
|
|
|
|
ts->reset_is_on_going = true;
|
|
} else {
|
|
char result[32];
|
|
|
|
ts->reset_is_on_going = false;
|
|
|
|
if (need_to_reset) {
|
|
cancel_delayed_work(&ts->reset_work);
|
|
if (!ts->plat_data->shutdown_called)
|
|
schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(TOUCH_RESET_DWORK_TIME));
|
|
}
|
|
|
|
snprintf(result, sizeof(result), "RESULT=RESET");
|
|
if (ts->probe_done)
|
|
sec_cmd_send_event_to_user(&ts->sec, NULL, result);
|
|
|
|
mutex_unlock(&ts->modechange);
|
|
__pm_relax(ts->plat_data->sec_ws);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void stm_ts_reset_work(struct work_struct *work)
|
|
{
|
|
struct stm_ts_data *ts = container_of(work, struct stm_ts_data, reset_work.work);
|
|
int ret;
|
|
bool prepare = true;
|
|
bool need_to_reset = false;
|
|
|
|
ret = stm_ts_reset_work_from_preparation_to_completion(ts, prepare, need_to_reset);
|
|
if (ret < 0)
|
|
return;
|
|
|
|
prepare = false;
|
|
input_info(true, &ts->client->dev, "%s\n", __func__);
|
|
|
|
ts->plat_data->stop_device(ts);
|
|
sec_delay(TOUCH_POWER_ON_DWORK_TIME);
|
|
|
|
ret = ts->plat_data->start_device(ts);
|
|
if (ret < 0) {
|
|
/* for ACT recovery fail test */
|
|
char result[32];
|
|
char test[32];
|
|
snprintf(test, sizeof(test), "TEST=RECOVERY");
|
|
snprintf(result, sizeof(result), "RESULT=FAIL");
|
|
if (ts->probe_done)
|
|
sec_cmd_send_event_to_user(&ts->sec, test, result);
|
|
|
|
input_err(true, &ts->client->dev, "%s: Reset failure, ret:%d\n", __func__, ret);
|
|
need_to_reset = true;
|
|
goto reset_work_completion;
|
|
}
|
|
|
|
if (!ts->plat_data->enabled) {
|
|
input_err(true, &ts->client->dev, "%s: call input_close\n", __func__);
|
|
|
|
if (ts->plat_data->lowpower_mode || ts->plat_data->ed_enable ||
|
|
ts->plat_data->pocket_mode || ts->plat_data->fod_lp_mode) {
|
|
ret = ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: Reset failure, ret:%d\n", __func__, ret);
|
|
need_to_reset = true;
|
|
goto reset_work_completion;
|
|
}
|
|
|
|
if (ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_AOD)
|
|
stm_ts_set_aod_rect(ts);
|
|
} else {
|
|
ts->plat_data->stop_device(ts);
|
|
}
|
|
}
|
|
|
|
if ((ts->plat_data->power_state == SEC_INPUT_STATE_POWER_ON) && (ts->fix_active_mode))
|
|
stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_TRUE);
|
|
|
|
reset_work_completion:
|
|
stm_ts_reset_work_from_preparation_to_completion(ts, prepare, need_to_reset);
|
|
}
|
|
|
|
void stm_ts_print_info_work(struct work_struct *work)
|
|
{
|
|
struct stm_ts_data *ts = container_of(work, struct stm_ts_data,
|
|
work_print_info.work);
|
|
|
|
sec_input_print_info(&ts->client->dev, ts->tdata);
|
|
|
|
if (ts->sec.cmd_is_running)
|
|
input_err(true, &ts->client->dev, "%s: skip set temperature, cmd running\n", __func__);
|
|
else
|
|
sec_input_set_temperature(&ts->client->dev, SEC_INPUT_SET_TEMPERATURE_NORMAL);
|
|
|
|
if (!ts->plat_data->shutdown_called)
|
|
schedule_delayed_work(&ts->work_print_info, msecs_to_jiffies(TOUCH_PRINT_INFO_DWORK_TIME));
|
|
}
|
|
|
|
#ifdef ENABLE_RAWDATA_SERVICE
|
|
void stm_ts_read_rawdata_address(struct stm_ts_data *ts)
|
|
{
|
|
u8 reg[8];
|
|
u8 header[16];
|
|
int ret;
|
|
int retry = 0;
|
|
|
|
input_info(true, &ts->client->dev, "%s\n", __func__);
|
|
|
|
disable_irq(ts->irq);
|
|
|
|
reg[0] = 0xA4;
|
|
reg[1] = 0x06;
|
|
reg[2] = 0x01;
|
|
|
|
ret = ts->stm_ts_write(ts, reg, 3, NULL, 0);
|
|
sec_delay(50);
|
|
do {
|
|
memset(header, 0x00, 16);
|
|
reg[0] = 0xA7;
|
|
reg[1] = 0x00;
|
|
reg[2] = 0x00;
|
|
ret = ts->stm_ts_read(ts, reg, 3, header, 16);
|
|
if (header[0] == 0xA5 && header[1] == 0x1)
|
|
break;
|
|
sec_delay(30);
|
|
} while (retry--);
|
|
|
|
reg[0] = 0xA7;
|
|
reg[1] = 0x00;
|
|
if (ts->raw_mode == 2)//RAW
|
|
reg[2] = 0x88;
|
|
else if (ts->raw_mode == 1)//STRENGTH
|
|
reg[2] = 0x8C;
|
|
memset(header, 0x00, 16);
|
|
ret = ts->stm_ts_read(ts, reg, 3, header, 2);
|
|
|
|
ts->raw_addr_h = header[1];
|
|
ts->raw_addr_l = header[0];
|
|
|
|
reg[0] = 0xA7;
|
|
reg[1] = ts->raw_addr_h;
|
|
reg[2] = ts->raw_addr_l;
|
|
|
|
ret = ts->stm_ts_read(ts, reg, 3, ts->raw_u8, ts->tx_count * ts->rx_count * 2);
|
|
enable_irq(ts->irq);
|
|
}
|
|
#endif
|
|
|
|
void stm_ts_read_info_work(struct work_struct *work)
|
|
{
|
|
struct stm_ts_data *ts = container_of(work, struct stm_ts_data,
|
|
work_read_info.work);
|
|
int ret;
|
|
|
|
#ifdef TCLM_CONCEPT
|
|
ret = sec_tclm_check_cal_case(ts->tdata);
|
|
input_info(true, &ts->client->dev, "%s: sec_tclm_check_cal_case ret: %d \n", __func__, ret);
|
|
#endif
|
|
ret = stm_ts_get_tsp_test_result(ts);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: failed to get result\n",
|
|
__func__);
|
|
input_raw_info_d(true, &ts->client->dev, "%s: fac test result %02X\n",
|
|
__func__, ts->test_result.data[0]);
|
|
|
|
stm_ts_run_rawdata_all(ts);
|
|
|
|
/* read cmoffset & fail history data at booting time */
|
|
input_info(true, &ts->client->dev, "%s: read cm data in tsp ic\n", __func__);
|
|
if (ts->plat_data->bringup == 0) {
|
|
get_cmoffset_dump(ts, ts->cmoffset_sdc_proc, OFFSET_FW_SDC);
|
|
get_cmoffset_dump(ts, ts->cmoffset_sub_proc, OFFSET_FW_SUB);
|
|
get_cmoffset_dump(ts, ts->cmoffset_main_proc, OFFSET_FW_MAIN);
|
|
}
|
|
|
|
ts->info_work_done = true;
|
|
|
|
/* reinit */
|
|
ts->plat_data->init(ts);
|
|
|
|
if (ts->plat_data->shutdown_called) {
|
|
input_err(true, &ts->client->dev, "%s done, do not run work\n", __func__);
|
|
return;
|
|
}
|
|
|
|
schedule_work(&ts->work_print_info.work);
|
|
|
|
if (ts->change_flip_status) {
|
|
input_info(true, &ts->client->dev, "%s: re-try switching after reading info\n", __func__);
|
|
schedule_work(&ts->switching_work.work);
|
|
}
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)
|
|
void stm_ts_interrupt_notify(struct work_struct *work)
|
|
{
|
|
struct sec_ts_plat_data *pdata = container_of(work, struct sec_ts_plat_data,
|
|
interrupt_notify_work.work);
|
|
struct sec_input_notify_data data;
|
|
data.dual_policy = MAIN_TOUCHSCREEN;
|
|
if (pdata->touch_count > 0)
|
|
sec_input_notify(NULL, NOTIFIER_LCD_VRR_LFD_LOCK_REQUEST, &data);
|
|
else
|
|
sec_input_notify(NULL, NOTIFIER_LCD_VRR_LFD_LOCK_RELEASE, &data);
|
|
}
|
|
#endif
|
|
|
|
void stm_ts_set_cover_type(struct stm_ts_data *ts, bool enable)
|
|
{
|
|
int ret;
|
|
u8 address;
|
|
u8 cover_cmd;
|
|
u8 data[2] = { 0 };
|
|
|
|
input_info(true, &ts->client->dev, "%s: %d\n", __func__, ts->plat_data->cover_type);
|
|
|
|
cover_cmd = sec_input_check_cover_type(&ts->client->dev) & 0xFF;
|
|
|
|
if (enable) {
|
|
address = STM_TS_CMD_SET_GET_COVERTYPE;
|
|
data[0] = cover_cmd;
|
|
ret = ts->stm_ts_write(ts, &address, 1, data, 1);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: Failed to send covertype command: %d",
|
|
__func__, cover_cmd);
|
|
}
|
|
|
|
ts->plat_data->touch_functions = ts->plat_data->touch_functions | STM_TS_TOUCHTYPE_BIT_COVER;
|
|
|
|
} else {
|
|
ts->plat_data->touch_functions = (ts->plat_data->touch_functions & (~STM_TS_TOUCHTYPE_BIT_COVER));
|
|
}
|
|
|
|
ret = stm_ts_set_touch_function(ts);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: Failed to send touch type command: 0x%02X%02X",
|
|
__func__, data[0], data[1]);
|
|
}
|
|
|
|
}
|
|
EXPORT_SYMBOL(stm_ts_set_cover_type);
|
|
|
|
int stm_ts_set_temperature(struct device *dev, u8 temperature_data)
|
|
{
|
|
struct stm_ts_data *ts = dev_get_drvdata(dev);
|
|
u8 address;
|
|
|
|
address = STM_TS_CMD_SET_LOWTEMPERATURE_MODE;
|
|
|
|
return ts->stm_ts_write(ts, &address, 1, &temperature_data, 1);
|
|
}
|
|
int stm_ts_set_aod_rect(struct stm_ts_data *ts)
|
|
{
|
|
u8 data[10] = {0x02, 0};
|
|
int ret, i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
data[i * 2 + 2] = ts->plat_data->aod_data.rect_data[i] & 0xFF;
|
|
data[i * 2 + 3] = (ts->plat_data->aod_data.rect_data[i] >> 8) & 0xFF;
|
|
}
|
|
|
|
ret = ts->stm_ts_write_sponge(ts, data, 10);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to write sponge\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_set_press_property(struct stm_ts_data *ts)
|
|
{
|
|
u8 data[3] = { SEC_TS_CMD_SPONGE_PRESS_PROPERTY, 0 };
|
|
int ret;
|
|
|
|
if (!ts->plat_data->support_fod)
|
|
return 0;
|
|
|
|
data[2] = ts->plat_data->fod_data.press_prop;
|
|
|
|
ret = ts->stm_ts_write_sponge(ts, data, 3);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to write sponge\n", __func__);
|
|
|
|
input_info(true, &ts->client->dev, "%s: %d\n", __func__, ts->plat_data->fod_data.press_prop);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_set_fod_rect(struct stm_ts_data *ts)
|
|
{
|
|
u8 data[10] = {0x4b, 0};
|
|
int ret, i;
|
|
|
|
input_info(true, &ts->client->dev, "%s: l:%d, t:%d, r:%d, b:%d\n",
|
|
__func__, ts->plat_data->fod_data.rect_data[0], ts->plat_data->fod_data.rect_data[1],
|
|
ts->plat_data->fod_data.rect_data[2], ts->plat_data->fod_data.rect_data[3]);
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
data[i * 2 + 2] = ts->plat_data->fod_data.rect_data[i] & 0xFF;
|
|
data[i * 2 + 3] = (ts->plat_data->fod_data.rect_data[i] >> 8) & 0xFF;
|
|
}
|
|
|
|
ret = ts->stm_ts_write_sponge(ts, data, 10);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: Failed to write sponge\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_set_wirelesscharger_mode(struct stm_ts_data *ts)
|
|
{
|
|
int ret;
|
|
u8 address;
|
|
u8 data;
|
|
|
|
if (ts->plat_data->wirelesscharger_mode == TYPE_WIRELESS_CHARGER_NONE) {
|
|
data = STM_TS_BIT_CHARGER_MODE_NORMAL;
|
|
} else if (ts->plat_data->wirelesscharger_mode == TYPE_WIRELESS_CHARGER) {
|
|
data = STM_TS_BIT_CHARGER_MODE_WIRELESS_CHARGER;
|
|
} else if (ts->plat_data->wirelesscharger_mode == TYPE_WIRELESS_BATTERY_PACK) {
|
|
data = STM_TS_BIT_CHARGER_MODE_WIRELESS_BATTERY_PACK;
|
|
} else {
|
|
input_err(true, &ts->client->dev, "%s: not supported mode %d\n",
|
|
__func__, ts->plat_data->wirelesscharger_mode);
|
|
return SEC_ERROR;
|
|
}
|
|
|
|
address = STM_TS_CMD_SET_GET_WIRELESSCHARGER_MODE;
|
|
ret = ts->stm_ts_write(ts, &address, 1, &data, 1);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev,
|
|
"%s: Failed to write mode 0x%02X (cmd:%d), ret=%d\n",
|
|
__func__, address, ts->plat_data->wirelesscharger_mode, ret);
|
|
else
|
|
input_info(true, &ts->client->dev, "%s: %sabled, mode=%d\n", __func__,
|
|
ts->plat_data->wirelesscharger_mode == TYPE_WIRELESS_CHARGER_NONE ? "dis" : "en",
|
|
ts->plat_data->wirelesscharger_mode);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_set_wirecharger_mode(struct stm_ts_data *ts)
|
|
{
|
|
int ret;
|
|
u8 address;
|
|
u8 data;
|
|
|
|
if (ts->charger_mode == TYPE_WIRE_CHARGER_NONE) {
|
|
data = STM_TS_BIT_CHARGER_MODE_NORMAL;
|
|
} else if (ts->charger_mode == TYPE_WIRE_CHARGER) {
|
|
data = STM_TS_BIT_CHARGER_MODE_WIRE_CHARGER;
|
|
} else {
|
|
input_err(true, &ts->client->dev, "%s: not supported mode %d\n",
|
|
__func__, ts->plat_data->wirelesscharger_mode);
|
|
return SEC_ERROR;
|
|
}
|
|
|
|
address = STM_TS_CMD_SET_GET_WIRECHARGER_MODE;
|
|
ret = ts->stm_ts_write(ts, &address, 1, &data, 1);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev,
|
|
"%s: Failed to write mode 0x%02X (cmd:%d), ret=%d\n",
|
|
__func__, address, ts->charger_mode, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_VBUS_NOTIFIER)
|
|
int stm_ts_vbus_notification(struct notifier_block *nb, unsigned long cmd, void *data)
|
|
{
|
|
struct stm_ts_data *ts = container_of(nb, struct stm_ts_data, vbus_nb);
|
|
vbus_status_t vbus_type = *(vbus_status_t *)data;
|
|
|
|
if (ts->plat_data->shutdown_called)
|
|
return 0;
|
|
|
|
switch (vbus_type) {
|
|
case STATUS_VBUS_HIGH:
|
|
ts->charger_mode = TYPE_WIRE_CHARGER;
|
|
break;
|
|
case STATUS_VBUS_LOW:
|
|
ts->charger_mode = TYPE_WIRE_CHARGER_NONE;
|
|
break;
|
|
default:
|
|
goto out;
|
|
}
|
|
|
|
input_info(true, &ts->client->dev, "%s: %sabled\n", __func__,
|
|
ts->charger_mode == TYPE_WIRE_CHARGER_NONE ? "dis" : "en");
|
|
|
|
stm_ts_set_wirecharger_mode(ts);
|
|
|
|
out:
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* flag 1 : set edge handler
|
|
* 2 : set (portrait, normal) edge zone data
|
|
* 4 : set (portrait, normal) dead zone data
|
|
* 8 : set landscape mode data
|
|
* 16 : mode clear
|
|
* data
|
|
* 0xAA, FFF (y start), FFF (y end), FF(direction)
|
|
* 0xAB, FFFF (edge zone)
|
|
* 0xAC, FF (up x), FF (down x), FFFF (up y), FF (bottom x), FFFF (down y)
|
|
* 0xAD, FF (mode), FFF (edge), FFF (dead zone x), FF (dead zone top y), FF (dead zone bottom y)
|
|
* case
|
|
* edge handler set : 0xAA....
|
|
* booting time : 0xAA... + 0xAB...
|
|
* normal mode : 0xAC... (+0xAB...)
|
|
* landscape mode : 0xAD...
|
|
* landscape -> normal (if same with old data) : 0xAD, 0
|
|
* landscape -> normal (etc) : 0xAC.... + 0xAD, 0
|
|
*/
|
|
|
|
void stm_set_grip_data_to_ic(struct device *dev, u8 flag)
|
|
{
|
|
struct stm_ts_data *ts = dev_get_drvdata(dev);
|
|
|
|
u8 data[9] = { 0 };
|
|
u8 address[2] = {STM_TS_CMD_SET_FUNCTION_ONOFF,};
|
|
|
|
input_info(true, &ts->client->dev, "%s: flag: %02X (clr,lan,nor,edg,han)\n", __func__, flag);
|
|
|
|
if (flag & G_SET_EDGE_HANDLER) {
|
|
if (ts->plat_data->grip_data.edgehandler_direction == 0) {
|
|
data[0] = 0x0;
|
|
data[1] = 0x0;
|
|
data[2] = 0x0;
|
|
data[3] = 0x0;
|
|
} else {
|
|
data[0] = (ts->plat_data->grip_data.edgehandler_start_y >> 4) & 0xFF;
|
|
data[1] = (ts->plat_data->grip_data.edgehandler_start_y << 4 & 0xF0)
|
|
| ((ts->plat_data->grip_data.edgehandler_end_y >> 8) & 0xF);
|
|
data[2] = ts->plat_data->grip_data.edgehandler_end_y & 0xFF;
|
|
data[3] = ts->plat_data->grip_data.edgehandler_direction & 0x3;
|
|
}
|
|
address[1] = STM_TS_FUNCTION_EDGE_HANDLER;
|
|
ts->stm_ts_write(ts, address, 2, data, 4);
|
|
input_info(true, &ts->client->dev, "%s: 0x%02X %02X,%02X,%02X,%02X\n",
|
|
__func__, STM_TS_FUNCTION_EDGE_HANDLER, data[0], data[1], data[2], data[3]);
|
|
}
|
|
|
|
if (flag & G_SET_EDGE_ZONE) {
|
|
data[0] = (ts->plat_data->grip_data.edge_range >> 8) & 0xFF;
|
|
data[1] = ts->plat_data->grip_data.edge_range & 0xFF;
|
|
data[2] = (ts->plat_data->grip_data.edge_range >> 8) & 0xFF;
|
|
data[3] = ts->plat_data->grip_data.edge_range & 0xFF;
|
|
address[1] = STM_TS_FUNCTION_EDGE_AREA;
|
|
ts->stm_ts_write(ts, address, 2, data, 4);
|
|
input_info(true, &ts->client->dev, "%s: 0x%02X %02X,%02X,%02X,%02X\n",
|
|
__func__, STM_TS_FUNCTION_EDGE_AREA, data[0], data[1], data[2], data[3]);
|
|
}
|
|
|
|
if (flag & G_SET_NORMAL_MODE) {
|
|
data[0] = ts->plat_data->grip_data.deadzone_up_x & 0xFF;
|
|
data[1] = ts->plat_data->grip_data.deadzone_dn_x & 0xFF;
|
|
data[2] = (ts->plat_data->grip_data.deadzone_y >> 8) & 0xFF;
|
|
data[3] = ts->plat_data->grip_data.deadzone_y & 0xFF;
|
|
address[1] = STM_TS_FUNCTION_DEAD_ZONE;
|
|
ts->stm_ts_write(ts, address, 2, data, 4);
|
|
input_info(true, &ts->client->dev, "%s: 0x%02X %02X,%02X,%02X,%02X\n",
|
|
__func__, STM_TS_FUNCTION_DEAD_ZONE, data[0], data[1], data[2], data[3]);
|
|
}
|
|
|
|
if (flag & G_SET_LANDSCAPE_MODE) {
|
|
data[0] = ts->plat_data->grip_data.landscape_mode;
|
|
data[1] = (ts->plat_data->grip_data.landscape_edge >> 8) & 0xFF;
|
|
data[2] = ts->plat_data->grip_data.landscape_edge & 0xFF;
|
|
data[3] = (ts->plat_data->grip_data.landscape_edge >> 8) & 0xFF;
|
|
data[4] = ts->plat_data->grip_data.landscape_edge & 0xFF;
|
|
data[5] = (ts->plat_data->grip_data.landscape_deadzone >> 8) & 0xFF;
|
|
data[6] = ts->plat_data->grip_data.landscape_deadzone & 0xFF;
|
|
|
|
address[1] = STM_TS_FUNCTION_LANDSCAPE_MODE;
|
|
ts->stm_ts_write(ts, address, 2, data, 7);
|
|
input_info(true, &ts->client->dev, "%s: 0x%02X %02X,%02X,%02X,%02X, %02X,%02X,%02X\n",
|
|
__func__, STM_TS_FUNCTION_LANDSCAPE_MODE, data[0], data[1], data[2], data[3], data[4], data[5], data[6]);
|
|
|
|
|
|
|
|
data[0] = ts->plat_data->grip_data.landscape_mode;
|
|
data[1] = (ts->plat_data->grip_data.landscape_top_gripzone >> 8)& 0xFF;
|
|
data[2] = ts->plat_data->grip_data.landscape_top_gripzone & 0xFF;
|
|
data[3] = (ts->plat_data->grip_data.landscape_bottom_gripzone >> 8)& 0xFF;
|
|
data[4] = ts->plat_data->grip_data.landscape_bottom_gripzone & 0xFF;
|
|
data[5] = (ts->plat_data->grip_data.landscape_top_deadzone >> 8)& 0xFF;
|
|
data[6] = ts->plat_data->grip_data.landscape_top_deadzone & 0xFF;
|
|
data[7] = (ts->plat_data->grip_data.landscape_bottom_deadzone >> 8)& 0xFF;
|
|
data[8] = ts->plat_data->grip_data.landscape_bottom_deadzone & 0xFF;
|
|
|
|
address[1] = STM_TS_FUNCTION_LANDSCAPE_TOP_BOTTOM;
|
|
ts->stm_ts_write(ts, address, 2, data, 9);
|
|
input_info(true, &ts->client->dev, "%s: 0x%02X %02X,%02X,%02X,%02X, %02X,%02X,%02X,%02X,%02X\n",
|
|
__func__, STM_TS_FUNCTION_LANDSCAPE_TOP_BOTTOM, data[0], data[1], data[2], data[3],
|
|
data[4], data[5], data[6], data[7], data[8]);
|
|
}
|
|
|
|
if (flag & G_CLR_LANDSCAPE_MODE) {
|
|
memset(data, 0x00, 9);
|
|
data[0] = ts->plat_data->grip_data.landscape_mode;
|
|
address[1] = STM_TS_FUNCTION_LANDSCAPE_MODE;
|
|
ts->stm_ts_write(ts, address, 2, data, 7);
|
|
input_info(true, &ts->client->dev, "%s: 0x%02X %02X\n",
|
|
__func__, STM_TS_FUNCTION_LANDSCAPE_MODE, data[0]);
|
|
|
|
address[1] = STM_TS_FUNCTION_LANDSCAPE_TOP_BOTTOM;
|
|
ts->stm_ts_write(ts, address, 2, data, 9);
|
|
input_info(true, &ts->client->dev, "%s: 0x%02X %02X\n",
|
|
__func__, STM_TS_FUNCTION_LANDSCAPE_TOP_BOTTOM, data[0]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Enable or disable external_noise_mode
|
|
*
|
|
* If mode has EXT_NOISE_MODE_MAX,
|
|
* then write enable cmd for all enabled mode. (set as ts->plat_data->external_noise_mode bit value)
|
|
* This routine need after IC power reset. TSP IC need to be re-wrote all enabled modes.
|
|
*
|
|
* Else if mode has specific value like EXT_NOISE_MODE_MONITOR,
|
|
* then write enable/disable cmd about for that mode's latest setting value.
|
|
*
|
|
* If you want to add new mode,
|
|
* please define new enum value like EXT_NOISE_MODE_MONITOR,
|
|
* then set cmd for that mode like below. (it is in this function)
|
|
* noise_mode_cmd[EXT_NOISE_MODE_MONITOR] = stm_TS_CMD_SET_MONITOR_NOISE_MODE;
|
|
*/
|
|
int stm_ts_set_external_noise_mode(struct stm_ts_data *ts, u8 mode)
|
|
{
|
|
int i, ret, fail_count = 0;
|
|
u8 mode_bit_to_set, check_bit, mode_enable;
|
|
u8 noise_mode_cmd[EXT_NOISE_MODE_MAX] = { 0 };
|
|
u8 address = STM_TS_CMD_SET_FUNCTION_ONOFF;
|
|
u8 data[2] = { 0 };
|
|
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_POWER_OFF) {
|
|
input_err(true, &ts->client->dev, "%s: Touch is stopped!\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (mode == EXT_NOISE_MODE_MAX) {
|
|
/* write all enabled mode */
|
|
mode_bit_to_set = ts->plat_data->external_noise_mode;
|
|
} else {
|
|
/* make enable or disable the specific mode */
|
|
mode_bit_to_set = 1 << mode;
|
|
}
|
|
|
|
input_info(true, &ts->client->dev, "%s: %sable %d\n", __func__,
|
|
ts->plat_data->external_noise_mode & mode_bit_to_set ? "en" : "dis", mode_bit_to_set);
|
|
|
|
/* set cmd for each mode */
|
|
noise_mode_cmd[EXT_NOISE_MODE_MONITOR] = STM_TS_FUNCTION_SET_MONITOR_NOISE_MODE;
|
|
|
|
/* write mode */
|
|
for (i = EXT_NOISE_MODE_NONE + 1; i < EXT_NOISE_MODE_MAX; i++) {
|
|
check_bit = 1 << i;
|
|
if (mode_bit_to_set & check_bit) {
|
|
mode_enable = !!(ts->plat_data->external_noise_mode & check_bit);
|
|
data[0] = noise_mode_cmd[i];
|
|
data[1] = mode_enable;
|
|
ret = ts->stm_ts_write(ts, &address, 1, data, 2);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: failed to set 0x%02X %d\n",
|
|
__func__, noise_mode_cmd[i], mode_enable);
|
|
fail_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fail_count != 0)
|
|
return -EIO;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int stm_ts_set_touchable_area(struct stm_ts_data *ts)
|
|
{
|
|
int ret;
|
|
u8 address[2] = {STM_TS_CMD_SET_FUNCTION_ONOFF, STM_TS_FUNCTION_SET_TOUCHABLE_AREA};
|
|
input_info(true, &ts->client->dev,
|
|
"%s: set 16:9 mode %s\n", __func__,
|
|
ts->plat_data->touchable_area ? "enable" : "disable");
|
|
|
|
ret = ts->stm_ts_write(ts, address, 2, &ts->plat_data->touchable_area, 1);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev,
|
|
"%s: failed to set 16:9 mode, ret=%d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_ear_detect_enable(struct stm_ts_data *ts, u8 enable)
|
|
{
|
|
int ret;
|
|
u8 address = STM_TS_CMD_SET_EAR_DETECT;
|
|
u8 data = enable;
|
|
|
|
input_info(true, &ts->client->dev, "%s: enable:%d\n", __func__, enable);
|
|
|
|
/* 00: off, 01:Mutual, 10:Self, 11: Mutual+Self */
|
|
ret = ts->stm_ts_write(ts, &address, 1, &data, 1);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev,
|
|
"%s: failed to set ed_enable %d, ret=%d\n", __func__, data, ret);
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_pocket_mode_enable(struct stm_ts_data *ts, u8 enable)
|
|
{
|
|
int ret;
|
|
u8 address = STM_TS_CMD_SET_POCKET_MODE;
|
|
u8 data = enable;
|
|
|
|
input_info(true, &ts->client->dev, "%s: %s\n",
|
|
__func__, ts->plat_data->pocket_mode ? "enable" : "disable");
|
|
|
|
ret = ts->stm_ts_write(ts, &address, 1, &data, 1);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev,
|
|
"%s: failed to pocket mode%d, ret=%d\n", __func__, data, ret);
|
|
return ret;
|
|
}
|
|
|
|
#define NVM_CMD(mtype, moffset, mlength) .type = mtype, .offset = moffset, .length = mlength
|
|
struct stm_ts_nvm_data_map nvm_data[] = {
|
|
{NVM_CMD(0, 0x00, 0),},
|
|
{NVM_CMD(STM_TS_NVM_OFFSET_FAC_RESULT, 0x00, 1),}, /* SEC */
|
|
{NVM_CMD(STM_TS_NVM_OFFSET_CAL_COUNT, 0x01, 1),}, /* SEC */
|
|
{NVM_CMD(STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT, 0x02, 1),}, /* SEC */
|
|
{NVM_CMD(STM_TS_NVM_OFFSET_TUNE_VERSION, 0x03, 2),}, /* SEC */
|
|
{NVM_CMD(STM_TS_NVM_OFFSET_CAL_POSITION, 0x05, 1),}, /* SEC */
|
|
{NVM_CMD(STM_TS_NVM_OFFSET_HISTORY_QUEUE_COUNT, 0x06, 1),}, /* SEC */
|
|
{NVM_CMD(STM_TS_NVM_OFFSET_HISTORY_QUEUE_LASTP, 0x07, 1),}, /* SEC */
|
|
{NVM_CMD(STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO, 0x08, 20),}, /* SEC */
|
|
{NVM_CMD(STM_TS_NVM_OFFSET_CAL_FAIL_FLAG, 0x1C, 1),}, /* SEC */
|
|
{NVM_CMD(STM_TS_NVM_OFFSET_CAL_FAIL_COUNT, 0x1D, 1),}, /* SEC */
|
|
};
|
|
|
|
int get_nvm_data(struct stm_ts_data *ts, int type, u8 *nvdata)
|
|
{
|
|
int size = sizeof(nvm_data) / sizeof(struct stm_ts_nvm_data_map);
|
|
|
|
if (type >= size)
|
|
return -EINVAL;
|
|
|
|
return get_nvm_data_by_size(ts, nvm_data[type].offset, nvm_data[type].length, nvdata);
|
|
}
|
|
|
|
int set_nvm_data(struct stm_ts_data *ts, u8 type, u8 *buf)
|
|
{
|
|
return set_nvm_data_by_size(ts, nvm_data[type].offset, nvm_data[type].length, buf);
|
|
}
|
|
|
|
|
|
int get_nvm_data_by_size(struct stm_ts_data *ts, u8 offset, int length, u8 *nvdata)
|
|
{
|
|
u8 address[3] = {0};
|
|
u8 data[128] = { 0 };
|
|
int ret;
|
|
|
|
sec_delay(200);
|
|
ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); // Clear FIFO
|
|
|
|
stm_ts_release_all_finger(ts);
|
|
|
|
// Request SEC factory debug data from flash
|
|
address[0] = 0xA4;
|
|
address[1] = 0x06;
|
|
address[2] = 0x90;
|
|
|
|
ret = stm_ts_wait_for_echo_event(ts, &address[0], 3, 50);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev,
|
|
"%s: timeout. ret: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
address[0] = STM_TS_CMD_FRM_BUFF_R;
|
|
address[1] = 0x00;
|
|
address[2] = offset;
|
|
|
|
ret = ts->stm_ts_read(ts, &address[0], 3, data, length + 1);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev,
|
|
"%s: read failed. ret: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
memcpy(nvdata, &data[0], length);
|
|
|
|
input_raw_info_d(true, &ts->client->dev, "%s: offset [%d], length [%d]\n",
|
|
__func__, offset, length);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int set_nvm_data_by_size(struct stm_ts_data *ts, u8 offset, int length, u8 *buf)
|
|
{
|
|
u8 buff[256] = { 0 };
|
|
u8 remaining, index, sendinglength;
|
|
int ret;
|
|
|
|
sec_delay(200);
|
|
ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); // Clear FIFO
|
|
|
|
stm_ts_release_all_finger(ts);
|
|
|
|
remaining = length;
|
|
index = 0;
|
|
sendinglength = 0;
|
|
|
|
while (remaining) {
|
|
buff[0] = 0xC7;
|
|
buff[1] = 0x01;
|
|
buff[2] = offset + index;
|
|
|
|
// write data up to 12 bytes available
|
|
if (remaining < 13) {
|
|
memcpy(&buff[3], &buf[index], remaining);
|
|
sendinglength = remaining;
|
|
} else {
|
|
memcpy(&buff[3], &buf[index], 12);
|
|
index += 12;
|
|
sendinglength = 12;
|
|
}
|
|
|
|
ret = ts->stm_ts_write(ts, &buff[0], sendinglength + 3, NULL, 0);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev,
|
|
"%s: write failed. ret: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
remaining -= sendinglength;
|
|
}
|
|
|
|
// Save to flash
|
|
buff[0] = 0xA4;
|
|
buff[1] = 0x05;
|
|
buff[2] = 0x04; // panel configuration area
|
|
|
|
ret = stm_ts_wait_for_echo_event(ts, &buff[0], 3, 200);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev,
|
|
"%s: failed to get echo. ret: %d\n", __func__, ret);
|
|
return ret;
|
|
|
|
}
|
|
|
|
int _stm_tclm_data_read(struct stm_ts_data *ts, int address)
|
|
{
|
|
int ret = 0;
|
|
int i = 0;
|
|
u8 nbuff[STM_TS_NVM_OFFSET_ALL];
|
|
u16 ic_version;
|
|
|
|
switch (address) {
|
|
case SEC_TCLM_NVM_OFFSET_IC_FIRMWARE_VER:
|
|
ret = stm_ts_get_version_info(ts);
|
|
ic_version = (ts->module_version_of_ic << 8) | (ts->fw_main_version_of_ic & 0xFF);
|
|
return ic_version;
|
|
case SEC_TCLM_NVM_ALL_DATA:
|
|
ret = get_nvm_data_by_size(ts, nvm_data[STM_TS_NVM_OFFSET_FAC_RESULT].offset,
|
|
STM_TS_NVM_OFFSET_ALL, nbuff);
|
|
if (ret < 0)
|
|
return ret;
|
|
ts->tdata->nvdata.cal_count = nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_COUNT].offset];
|
|
ts->tdata->nvdata.tune_fix_ver = (nbuff[nvm_data[STM_TS_NVM_OFFSET_TUNE_VERSION].offset] << 8) |
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_TUNE_VERSION].offset + 1];
|
|
ts->tdata->nvdata.cal_position = nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_POSITION].offset];
|
|
ts->tdata->nvdata.cal_pos_hist_cnt = nbuff[nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_COUNT].offset];
|
|
ts->tdata->nvdata.cal_pos_hist_lastp = nbuff[nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_LASTP].offset];
|
|
for (i = nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset;
|
|
i < nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset +
|
|
nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].length; i++)
|
|
ts->tdata->nvdata.cal_pos_hist_queue[i - nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset] = nbuff[i];
|
|
|
|
ts->tdata->nvdata.cal_fail_falg = nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_FAIL_FLAG].offset];
|
|
ts->tdata->nvdata.cal_fail_cnt= nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_FAIL_COUNT].offset];
|
|
ts->fac_nv = nbuff[nvm_data[STM_TS_NVM_OFFSET_FAC_RESULT].offset];
|
|
ts->disassemble_count = nbuff[nvm_data[STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT].offset];
|
|
return ret;
|
|
case SEC_TCLM_NVM_TEST:
|
|
input_info(true, &ts->client->dev, "%s: dt: tclm_level [%d] afe_base [%04X]\n",
|
|
__func__, ts->tdata->tclm_level, ts->tdata->afe_base);
|
|
ret = get_nvm_data_by_size(ts, STM_TS_NVM_OFFSET_ALL + SEC_TCLM_NVM_OFFSET,
|
|
SEC_TCLM_NVM_OFFSET_LENGTH, ts->tdata->tclm);
|
|
if (ts->tdata->tclm[0] != 0xFF) {
|
|
ts->tdata->tclm_level = ts->tdata->tclm[0];
|
|
ts->tdata->afe_base = (ts->tdata->tclm[1] << 8) | ts->tdata->tclm[2];
|
|
input_info(true, &ts->client->dev, "%s: nv: tclm_level [%d] afe_base [%04X]\n",
|
|
__func__, ts->tdata->tclm_level, ts->tdata->afe_base);
|
|
}
|
|
return ret;
|
|
default:
|
|
return ret;
|
|
}
|
|
|
|
}
|
|
|
|
int _stm_tclm_data_write(struct stm_ts_data *ts, int address)
|
|
{
|
|
int ret = 1;
|
|
int i = 0;
|
|
u8 nbuff[STM_TS_NVM_OFFSET_ALL];
|
|
|
|
switch (address) {
|
|
case SEC_TCLM_NVM_ALL_DATA:
|
|
memset(nbuff, 0x00, STM_TS_NVM_OFFSET_ALL);
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_FAC_RESULT].offset] = ts->fac_nv;
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT].offset] = ts->disassemble_count;
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_COUNT].offset] = ts->tdata->nvdata.cal_count;
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_TUNE_VERSION].offset] = (u8)(ts->tdata->nvdata.tune_fix_ver >> 8);
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_TUNE_VERSION].offset + 1] = (u8)(0xff & ts->tdata->nvdata.tune_fix_ver);
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_POSITION].offset] = ts->tdata->nvdata.cal_position;
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_COUNT].offset] = ts->tdata->nvdata.cal_pos_hist_cnt;
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_LASTP].offset] = ts->tdata->nvdata.cal_pos_hist_lastp;
|
|
for (i = nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset;
|
|
i < nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset +
|
|
nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].length; i++)
|
|
nbuff[i] = ts->tdata->nvdata.cal_pos_hist_queue[i - nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset];
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_FAIL_FLAG].offset] = ts->tdata->nvdata.cal_fail_falg;
|
|
nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_FAIL_COUNT].offset] = ts->tdata->nvdata.cal_fail_cnt;
|
|
ret = set_nvm_data_by_size(ts, nvm_data[STM_TS_NVM_OFFSET_FAC_RESULT].offset, STM_TS_NVM_OFFSET_ALL, nbuff);
|
|
return ret;
|
|
case SEC_TCLM_NVM_TEST:
|
|
ret = set_nvm_data_by_size(ts, STM_TS_NVM_OFFSET_ALL + SEC_TCLM_NVM_OFFSET,
|
|
SEC_TCLM_NVM_OFFSET_LENGTH, ts->tdata->tclm);
|
|
return ret;
|
|
default:
|
|
return ret;
|
|
|
|
}
|
|
}
|
|
|
|
void stm_chk_tsp_ic_status(struct stm_ts_data *ts, int call_pos)
|
|
{
|
|
u8 data[3] = { 0 };
|
|
|
|
if (!ts->probe_done) {
|
|
input_info(true, &ts->client->dev, "%s not finished probe_done\n", __func__);
|
|
return;
|
|
}
|
|
|
|
mutex_lock(&ts->status_mutex);
|
|
|
|
input_info(true, &ts->client->dev,
|
|
"%s: START: pos[%d] power_state[0x%X] lowpower_flag[0x%X] %sfolding\n",
|
|
__func__, call_pos, ts->plat_data->power_state, ts->plat_data->lowpower_mode,
|
|
ts->flip_status_current ? "" : "un");
|
|
|
|
if (ts->plat_data->support_dual_foldable == MAIN_TOUCH) {
|
|
if (call_pos == STM_TS_STATE_CHK_POS_OPEN) {
|
|
input_dbg(true, &ts->client->dev, "%s(main): OPEN : Nothing\n", __func__);
|
|
} else if (call_pos == STM_TS_STATE_CHK_POS_CLOSE) {
|
|
input_dbg(true, &ts->client->dev, "%s(main): CLOSE: Nothing\n", __func__);
|
|
} else if (call_pos == STM_TS_STATE_CHK_POS_HALL) {
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_LPM && ts->flip_status_current == STM_TS_STATUS_FOLDING) {
|
|
input_info(true, &ts->client->dev, "%s(main): HALL : TSP IC LP => IC OFF\n", __func__);
|
|
ts->plat_data->stop_device(ts);
|
|
|
|
} else {
|
|
input_info(true, &ts->client->dev, "%s(main): HALL : Nothing\n", __func__);
|
|
}
|
|
} else if (call_pos == STM_TS_STATE_CHK_POS_SYSFS) {
|
|
if (ts->flip_status_current == STM_TS_STATUS_UNFOLDING) {
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_POWER_OFF && ts->plat_data->lowpower_mode) {
|
|
input_info(true, &ts->client->dev, "%s(main): SYSFS: TSP IC OFF => LP mode[0x%X]\n",
|
|
__func__, ts->plat_data->lowpower_mode);
|
|
ts->plat_data->start_device(ts);
|
|
ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE);
|
|
|
|
} else if (ts->plat_data->power_state == SEC_INPUT_STATE_LPM && ts->plat_data->lowpower_mode == 0) {
|
|
input_info(true, &ts->client->dev, "%s(main): SYSFS: LP mode [0x0] => TSP IC OFF\n",
|
|
__func__);
|
|
ts->plat_data->stop_device(ts);
|
|
} else {
|
|
input_info(true, &ts->client->dev, "%s(main): SYSFS: set lowpower_flag again[0x%X]\n",
|
|
__func__, ts->plat_data->lowpower_mode);
|
|
|
|
data[2] = ts->plat_data->lowpower_mode;
|
|
ts->stm_ts_write_sponge(ts, data, 3);
|
|
}
|
|
} else {
|
|
input_info(true, &ts->client->dev, "%s(main): SYSFS: folding nothing[0x%X]\n", __func__, ts->plat_data->lowpower_mode);
|
|
}
|
|
} else {
|
|
input_info(true, &ts->client->dev, "%s(main): ETC : nothing!\n", __func__);
|
|
}
|
|
} else if (ts->plat_data->support_dual_foldable == SUB_TOUCH) {
|
|
if (call_pos == STM_TS_STATE_CHK_POS_OPEN) {
|
|
input_dbg(true, &ts->client->dev, "%s(sub): OPEN : Notthing\n", __func__);
|
|
|
|
} else if (call_pos == STM_TS_STATE_CHK_POS_CLOSE) {
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_LPM && ts->flip_status_current == STM_TS_STATUS_UNFOLDING) {
|
|
input_info(true, &ts->client->dev, "%s(sub): HALL : TSP IC LP => IC OFF\n", __func__);
|
|
ts->plat_data->stop_device(ts);
|
|
}
|
|
} else if (call_pos == STM_TS_STATE_CHK_POS_HALL) {
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_LPM && ts->flip_status_current == STM_TS_STATUS_UNFOLDING) {
|
|
input_info(true, &ts->client->dev, "%s(sub): HALL : TSP IC LP => IC OFF\n", __func__);
|
|
ts->plat_data->stop_device(ts);
|
|
} else if (ts->plat_data->power_state == SEC_INPUT_STATE_POWER_OFF && ts->flip_status_current == STM_TS_STATUS_FOLDING && ts->plat_data->lowpower_mode != 0) {
|
|
input_info(true, &ts->client->dev, "%s(sub): HALL : TSP IC OFF => LP[0x%X]\n", __func__, ts->plat_data->lowpower_mode);
|
|
ts->plat_data->start_device(ts);
|
|
ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE);
|
|
} else {
|
|
input_info(true, &ts->client->dev, "%s(sub): HALL : nothing!\n", __func__);
|
|
}
|
|
|
|
} else if (call_pos == STM_TS_STATE_CHK_POS_SYSFS) {
|
|
if (ts->flip_status_current == STM_TS_STATUS_FOLDING) {
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_POWER_OFF && ts->plat_data->lowpower_mode) {
|
|
input_info(true, &ts->client->dev, "%s(sub): SYSFS : TSP IC OFF => LP mode[0x%X]\n", __func__, ts->plat_data->lowpower_mode);
|
|
ts->plat_data->start_device(ts);
|
|
ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE);
|
|
|
|
} else if (ts->plat_data->power_state == SEC_INPUT_STATE_LPM && ts->plat_data->lowpower_mode == 0) {
|
|
input_info(true, &ts->client->dev, "%s(sub): SYSFS : LP mode [0x0] => IC OFF\n", __func__);
|
|
ts->plat_data->stop_device(ts);
|
|
|
|
} else if (ts->plat_data->power_state == SEC_INPUT_STATE_LPM && ts->plat_data->lowpower_mode != 0) {
|
|
input_info(true, &ts->client->dev, "%s(sub): SYSFS : call LP mode again\n", __func__);
|
|
ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE);
|
|
|
|
} else {
|
|
input_info(true, &ts->client->dev, "%s(sub): SYSFS : nothing!\n", __func__);
|
|
}
|
|
} else {
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_LPM) {
|
|
input_info(true, &ts->client->dev, "%s(sub): SYSFS : rear selfie off => IC OFF\n", __func__);
|
|
ts->plat_data->stop_device(ts);
|
|
} else {
|
|
input_info(true, &ts->client->dev, "%s(sub): SYSFS : unfolding nothing[0x%X]\n", __func__, ts->plat_data->lowpower_mode);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
input_info(true, &ts->client->dev, "%s(sub): Abnormal case\n", __func__);
|
|
}
|
|
}
|
|
|
|
input_info(true, &ts->client->dev, "%s: END : pos[%d] power_state[0x%X] lowpower_flag[0x%X]\n",
|
|
__func__, call_pos, ts->plat_data->power_state, ts->plat_data->lowpower_mode);
|
|
|
|
mutex_unlock(&ts->status_mutex);
|
|
}
|
|
|
|
void stm_switching_work(struct work_struct *work)
|
|
{
|
|
struct stm_ts_data *ts = container_of(work, struct stm_ts_data,
|
|
switching_work.work);
|
|
|
|
if (ts == NULL) {
|
|
input_err(true, NULL, "%s: tsp ts is null\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (ts->flip_status != ts->flip_status_current) {
|
|
if (!ts->info_work_done) {
|
|
input_err(true, &ts->client->dev, "%s: info_work is not done yet\n", __func__);
|
|
ts->change_flip_status = 1;
|
|
return;
|
|
}
|
|
ts->change_flip_status = 0;
|
|
|
|
mutex_lock(&ts->switching_mutex);
|
|
ts->flip_status = ts->flip_status_current;
|
|
|
|
if (ts->flip_status == 0) {
|
|
/* open : main_tsp on */
|
|
} else {
|
|
/* close : main_tsp off */
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH)
|
|
// secure_touch_stop(ts, 1);
|
|
#endif
|
|
}
|
|
|
|
stm_chk_tsp_ic_status(ts, STM_TS_STATE_CHK_POS_HALL);
|
|
mutex_unlock(&ts->switching_mutex);
|
|
} else if (ts->plat_data->support_flex_mode && (ts->plat_data->support_dual_foldable == MAIN_TOUCH)) {
|
|
input_info(true, &ts->client->dev, "%s support_flex_mode\n", __func__);
|
|
|
|
mutex_lock(&ts->switching_mutex);
|
|
stm_chk_tsp_ic_status(ts, STM_TS_STATE_CHK_POS_SYSFS);
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)
|
|
sec_input_notify(&ts->stm_input_nb, NOTIFIER_MAIN_TOUCH_ON, NULL);
|
|
#endif
|
|
mutex_unlock(&ts->switching_mutex);
|
|
}
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_HALL_NOTIFIER)
|
|
int stm_hall_ic_notify(struct notifier_block *nb,
|
|
unsigned long flip_cover, void *v)
|
|
{
|
|
struct stm_ts_data *ts = container_of(nb, struct stm_ts_data,
|
|
hall_ic_nb);
|
|
struct hall_notifier_context *hall_notifier;
|
|
|
|
if (ts == NULL) {
|
|
input_err(true, NULL, "%s: tsp info is null\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
hall_notifier = v;
|
|
|
|
if (strncmp(hall_notifier->name, "flip", 4)) {
|
|
input_info(true, &ts->client->dev, "%s: %s\n", __func__, hall_notifier->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
input_info(true, &ts->client->dev, "%s: %s\n", __func__,
|
|
flip_cover ? "close" : "open");
|
|
|
|
cancel_delayed_work(&ts->switching_work);
|
|
|
|
ts->flip_status_current = flip_cover;
|
|
|
|
schedule_work(&ts->switching_work.work);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
MODULE_LICENSE("GPL");
|