1034 lines
22 KiB
C
Executable file
1034 lines
22 KiB
C
Executable file
/* drivers/input/sec_input/stm/stm_core.c
|
|
*
|
|
* Copyright (C) 2020 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"
|
|
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH)
|
|
int stm_pm_runtime_get_sync(struct stm_ts_data *ts)
|
|
{
|
|
return pm_runtime_get_sync(ts->client->controller->dev.parent);
|
|
}
|
|
|
|
void stm_pm_runtime_put_sync(struct stm_ts_data *ts)
|
|
{
|
|
pm_runtime_put_sync(ts->client->controller->dev.parent);
|
|
}
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_SAMSUNG_TUI)
|
|
extern int stui_spi_lock(struct spi_master *spi);
|
|
extern int stui_spi_unlock(struct spi_master *spi);
|
|
|
|
int stm_stui_tsp_enter(void)
|
|
{
|
|
struct stm_ts_data *ts = dev_get_drvdata(ptsp);
|
|
int ret = 0;
|
|
|
|
if (!ts)
|
|
return -EINVAL;
|
|
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)
|
|
sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_ENABLE, NULL);
|
|
#endif
|
|
|
|
disable_irq(ts->irq);
|
|
stm_ts_release_all_finger(ts);
|
|
|
|
ret = stui_spi_lock(ts->client->controller);
|
|
if (ret < 0) {
|
|
pr_err("[STUI] stui_spi_lock failed : %d\n", ret);
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)
|
|
sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL);
|
|
#endif
|
|
enable_irq(ts->irq);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int stm_stui_tsp_exit(void)
|
|
{
|
|
struct stm_ts_data *ts = dev_get_drvdata(ptsp);
|
|
int ret = 0;
|
|
|
|
if (!ts)
|
|
return -EINVAL;
|
|
|
|
ret = stui_spi_unlock(ts->client->controller);
|
|
if (ret < 0)
|
|
pr_err("[STUI] stui_spi_unlock failed : %d\n", ret);
|
|
|
|
enable_irq(ts->irq);
|
|
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)
|
|
sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stm_stui_tsp_type(void)
|
|
{
|
|
return STUI_TSP_TYPE_STM;
|
|
}
|
|
#endif
|
|
|
|
#ifdef TCLM_CONCEPT
|
|
int stm_ts_tclm_execute_force_calibration(struct spi_device *client, int cal_mode)
|
|
{
|
|
struct stm_ts_data *ts = (struct stm_ts_data *)spi_get_drvdata(client);
|
|
|
|
return stm_ts_execute_autotune(ts, true);
|
|
}
|
|
|
|
int stm_tclm_data_read(struct stm_ts_data *ts, int address)
|
|
{
|
|
return ts->tdata->tclm_read_spi(ts->tdata->spi, address);
|
|
}
|
|
|
|
int stm_tclm_data_write(struct stm_ts_data *ts, int address)
|
|
{
|
|
return ts->tdata->tclm_write_spi(ts->tdata->spi, address);
|
|
}
|
|
|
|
|
|
int stm_tclm_spi_data_read(struct spi_device *client, int address)
|
|
{
|
|
struct stm_ts_data *ts = (struct stm_ts_data *)spi_get_drvdata(client);
|
|
|
|
return _stm_tclm_data_read(ts, address);
|
|
}
|
|
int stm_tclm_spi_data_write(struct spi_device *client, int address)
|
|
{
|
|
struct stm_ts_data *ts = (struct stm_ts_data *)spi_get_drvdata(client);
|
|
|
|
return _stm_tclm_data_write(ts, address);
|
|
}
|
|
#endif
|
|
int stm_ts_wire_mode_change(struct stm_ts_data *ts, u8 *reg)
|
|
{
|
|
int rc = 0;
|
|
|
|
reg[0] = STM_TS_CMD_REG_W;
|
|
reg[1] = 0x20;
|
|
reg[2] = 0x00;
|
|
reg[3] = 0x00;
|
|
reg[4] = 0x32;
|
|
reg[5] = 0x10;
|
|
rc = ts->stm_ts_write(ts, ®[0], 6, NULL, 0);
|
|
if (rc < 0)
|
|
return rc;
|
|
sec_delay(2);
|
|
|
|
reg[0] = STM_TS_CMD_REG_W;
|
|
reg[1] = 0x20;
|
|
reg[2] = 0x00;
|
|
reg[3] = 0x00;
|
|
reg[4] = 0x34;
|
|
reg[5] = 0x02;
|
|
rc = ts->stm_ts_write(ts, ®[0], 6, NULL, 0);
|
|
if (rc < 0)
|
|
return rc;
|
|
sec_delay(2);
|
|
|
|
reg[0] = STM_TS_CMD_REG_W;
|
|
reg[1] = 0x20;
|
|
reg[2] = 0x00;
|
|
reg[3] = 0x00;
|
|
reg[4] = STM_REG_MISO_PORT_SETTING;
|
|
reg[5] = 0x07;
|
|
rc = ts->stm_ts_write(ts, ®[0], 6, NULL, 0);
|
|
if (rc < 0)
|
|
return rc;
|
|
sec_delay(2);
|
|
|
|
reg[0] = STM_TS_CMD_REG_W;
|
|
reg[1] = 0x20;
|
|
reg[2] = 0x00;
|
|
reg[3] = 0x00;
|
|
reg[4] = 0x3D;
|
|
reg[5] = 0x30;
|
|
rc = ts->stm_ts_write(ts, ®[0], 6, NULL, 0);
|
|
if (rc < 0)
|
|
return rc;
|
|
sec_delay(2);
|
|
|
|
reg[0] = STM_TS_CMD_REG_W;
|
|
reg[1] = 0x20;
|
|
reg[2] = 0x00;
|
|
reg[3] = 0x00;
|
|
reg[4] = 0x2D;
|
|
reg[5] = 0x02;
|
|
rc = ts->stm_ts_write(ts, ®[0], 6, NULL, 0);
|
|
if (rc < 0)
|
|
return rc;
|
|
sec_delay(15);
|
|
|
|
reg[0] = STM_TS_CMD_REG_W;
|
|
reg[1] = 0x20;
|
|
reg[2] = 0x00;
|
|
reg[3] = 0x00;
|
|
reg[4] = 0x1B;
|
|
reg[5] = 0x66;
|
|
rc = ts->stm_ts_write(ts, ®[0], 6, NULL, 0);
|
|
if (rc < 0)
|
|
return rc;
|
|
sec_delay(30);
|
|
|
|
reg[0] = STM_TS_CMD_REG_W;
|
|
reg[1] = 0x20;
|
|
reg[2] = 0x00;
|
|
reg[3] = 0x00;
|
|
reg[4] = 0x68;
|
|
reg[5] = 0x13;
|
|
rc = ts->stm_ts_write(ts, ®[0], 6, NULL, 0);
|
|
if (rc < 0)
|
|
return rc;
|
|
sec_delay(30);
|
|
return 0;
|
|
}
|
|
|
|
void stm_ts_set_spi_mode(struct stm_ts_data *ts)
|
|
{
|
|
if (ts->plat_data->gpio_spi_cs > 0) {
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 1);
|
|
input_err(true, &ts->client->dev, "%s: cs gpio: %d\n",
|
|
__func__, gpio_get_value(ts->plat_data->gpio_spi_cs));
|
|
}
|
|
|
|
ts->client->mode = SPI_MODE_0;
|
|
input_err(true, &ts->client->dev, "%s: mode: %d, max_speed_hz: %d: cs: %d\n", __func__, ts->client->mode, ts->client->max_speed_hz, ts->plat_data->gpio_spi_cs);
|
|
input_err(true, &ts->client->dev, "%s: chip_select: %d, bits_per_word: %d\n", __func__, ts->client->chip_select, ts->client->bits_per_word);
|
|
}
|
|
|
|
int stm_ts_read_from_sponge(struct stm_ts_data *ts, u8 *data, int length)
|
|
{
|
|
struct spi_message *m1;
|
|
struct spi_transfer *t1;
|
|
char *tbuf1;
|
|
struct spi_message *m2;
|
|
struct spi_transfer *t2;
|
|
char *tbuf2;
|
|
char *rbuf2;
|
|
int ret = -ENOMEM;
|
|
|
|
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 (!ts->plat_data->resume_done.done) {
|
|
ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500));
|
|
if (ret <= 0) {
|
|
input_err(true, &ts->client->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH)
|
|
if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_ENABLE) {
|
|
input_err(true, &ts->client->dev, "%s: TUI is enabled\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
#if IS_ENABLED(CONFIG_GH_RM_DRV)
|
|
if (atomic_read(&ts->trusted_touch_enabled) != 0) {
|
|
input_err(true, &ts->client->dev, "%s: TVM is enabled\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
m1 = kzalloc(sizeof(struct spi_message), GFP_KERNEL);
|
|
if (!m1)
|
|
return -ENOMEM;
|
|
|
|
t1 = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
|
|
if (!t1) {
|
|
kfree(m1);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
m2 = kzalloc(sizeof(struct spi_message), GFP_KERNEL);
|
|
if (!m2) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
t2 = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
|
|
if (!t2) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tbuf1 = kzalloc(2, GFP_KERNEL);
|
|
if (!tbuf1) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
kfree(t2);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tbuf1[0] = 0xC0;
|
|
tbuf1[1] = STM_TS_CMD_SPONGE_READ_WRITE_CMD;
|
|
|
|
t1->len = 2;
|
|
t1->tx_buf = tbuf1;
|
|
|
|
mutex_lock(&ts->read_write_mutex);
|
|
|
|
spi_message_init(m1);
|
|
spi_message_add_tail(t1, m1);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 0);
|
|
|
|
ret = spi_sync(ts->client, m1);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: write failed: %d\n", __func__, ret);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 1);
|
|
|
|
if (ts->debug_flag & SEC_TS_DEBUG_PRINT_READ_CMD)
|
|
pr_info("sec_input: spi rsp: W: %02X %02X\n", tbuf1[0], tbuf1[1]);
|
|
|
|
rbuf2 = kzalloc(3 + 1 + length, GFP_KERNEL);
|
|
if (!rbuf2) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
kfree(t2);
|
|
kfree(tbuf1);
|
|
mutex_unlock(&ts->read_write_mutex);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tbuf2 = kzalloc(3 + 1 + length, GFP_KERNEL);
|
|
if (!tbuf2) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
kfree(t2);
|
|
kfree(tbuf1);
|
|
kfree(rbuf2);
|
|
mutex_unlock(&ts->read_write_mutex);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tbuf2[0] = 0xD1;
|
|
tbuf2[1] = data[1]; //offset
|
|
tbuf2[2] = data[0];
|
|
|
|
spi_message_init(m2);
|
|
t2->len = 3 + 1 + length; /* (dummy:D1(1) + offset(2) + length*/
|
|
t2->tx_buf = tbuf2;
|
|
t2->rx_buf = rbuf2;
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0))
|
|
t2->delay_usecs = 10;
|
|
#endif
|
|
spi_message_add_tail(t2, m2);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 0);
|
|
|
|
ret = spi_sync(ts->client, m2);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: read failed%d\n", __func__, ret);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 1);
|
|
|
|
mutex_unlock(&ts->read_write_mutex);
|
|
|
|
if (ts->debug_flag & SEC_TS_DEBUG_PRINT_READ_CMD) {
|
|
int i;
|
|
|
|
pr_info("sec_input: spi rsp: R: ");
|
|
for (i = 0; i < 3; i++)
|
|
pr_cont("%02X ", tbuf2[i]);
|
|
pr_cont("| ");
|
|
for (i = 0; i < (3 + 1 + length); i++) {
|
|
if (i == (3 + 1))
|
|
pr_cont("|| ");
|
|
pr_cont("%02X ", rbuf2[i]);
|
|
}
|
|
pr_cont("\n");
|
|
|
|
}
|
|
memcpy(data, &rbuf2[4], length);
|
|
|
|
kfree(tbuf1);
|
|
kfree(tbuf2);
|
|
kfree(rbuf2);
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
kfree(t2);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_write_to_sponge(struct stm_ts_data *ts, u8 *data, int length)
|
|
{
|
|
struct spi_message *m1;
|
|
struct spi_transfer *t1;
|
|
struct spi_message *m2;
|
|
struct spi_transfer *t2;
|
|
struct spi_message *m3;
|
|
struct spi_transfer *t3;
|
|
char *tbuf1;
|
|
char *tbuf2;
|
|
int ret = -ENOMEM;
|
|
|
|
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 (!ts->plat_data->resume_done.done) {
|
|
ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500));
|
|
if (ret <= 0) {
|
|
input_err(true, &ts->client->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH)
|
|
if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_ENABLE) {
|
|
input_err(true, &ts->client->dev, "%s: TUI is enabled\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
#if IS_ENABLED(CONFIG_GH_RM_DRV)
|
|
if (atomic_read(&ts->trusted_touch_enabled) != 0) {
|
|
input_err(true, &ts->client->dev, "%s: TVM is enabled\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
m1 = kzalloc(sizeof(struct spi_message), GFP_KERNEL);
|
|
if (!m1)
|
|
return -ENOMEM;
|
|
|
|
t1 = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
|
|
if (!t1) {
|
|
kfree(m1);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
m2 = kzalloc(sizeof(struct spi_message), GFP_KERNEL);
|
|
if (!m2) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
t2 = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
|
|
if (!t2) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
m3 = kzalloc(sizeof(struct spi_message), GFP_KERNEL);
|
|
if (!m3) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
kfree(t2);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
t3 = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
|
|
if (!t3) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
kfree(t2);
|
|
kfree(m3);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tbuf1 = kzalloc(2, GFP_KERNEL);
|
|
if (!tbuf1) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
kfree(t2);
|
|
kfree(m3);
|
|
kfree(t3);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tbuf1[0] = 0xC0;
|
|
tbuf1[1] = STM_TS_CMD_SPONGE_READ_WRITE_CMD;
|
|
|
|
t1->len = 2;
|
|
t1->tx_buf = tbuf1;
|
|
|
|
mutex_lock(&ts->read_write_mutex);
|
|
|
|
spi_message_init(m1);
|
|
spi_message_add_tail(t1, m1);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 0);
|
|
|
|
ret = spi_sync(ts->client, m1);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: failed: %d\n", __func__, ret);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 1);
|
|
|
|
if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD)
|
|
pr_info("sec_input: spi wsp: W: %02X %02X\n", tbuf1[0], tbuf1[1]);
|
|
|
|
tbuf2 = kzalloc(3 + length, GFP_KERNEL);
|
|
if (!tbuf2) {
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
kfree(t2);
|
|
kfree(m3);
|
|
kfree(t3);
|
|
kfree(tbuf1);
|
|
mutex_unlock(&ts->read_write_mutex);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tbuf2[0] = 0xD0;
|
|
tbuf2[1] = data[1];
|
|
tbuf2[2] = data[0];
|
|
|
|
memcpy(&tbuf2[3], &data[2], length - 2);
|
|
|
|
t2->len = 1 + length; /* (tbuf2(3) - offset(2) + length*/
|
|
t2->tx_buf = tbuf2;
|
|
|
|
spi_message_init(m2);
|
|
spi_message_add_tail(t2, m2);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 0);
|
|
|
|
ret = spi_sync(ts->client, m2);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: failed: %d\n", __func__, ret);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 1);
|
|
|
|
if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD) {
|
|
int i;
|
|
|
|
pr_info("sec_input: spi wsp: W: ");
|
|
for (i = 0; i < t2->len; i++)
|
|
pr_cont("%02X ", tbuf2[i]);
|
|
pr_cont("\n");
|
|
|
|
}
|
|
|
|
tbuf1[0] = 0xC0;
|
|
tbuf1[1] = STM_TS_CMD_SPONGE_NOTIFY_CMD;
|
|
|
|
t3->len = 2;
|
|
t3->tx_buf = tbuf1;
|
|
|
|
spi_message_init(m3);
|
|
spi_message_add_tail(t3, m3);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 0);
|
|
|
|
ret = spi_sync(ts->client, m3);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: failed: %d\n", __func__, ret);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 1);
|
|
|
|
if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD)
|
|
pr_info("sec_input: spi wsp: W: %02X %02X\n", tbuf1[0], tbuf1[1]);
|
|
|
|
mutex_unlock(&ts->read_write_mutex);
|
|
kfree(tbuf1);
|
|
kfree(tbuf2);
|
|
kfree(m1);
|
|
kfree(t1);
|
|
kfree(m2);
|
|
kfree(t2);
|
|
kfree(m3);
|
|
kfree(t3);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_no_lock_spi_write(struct stm_ts_data *ts, u8 *reg, int tlen, u8 *data, int len)
|
|
{
|
|
struct spi_message *m;
|
|
struct spi_transfer *t;
|
|
char *tbuf;
|
|
int ret = -ENOMEM;
|
|
int wlen;
|
|
|
|
m = kzalloc(sizeof(struct spi_message), GFP_KERNEL);
|
|
if (!m)
|
|
return -ENOMEM;
|
|
|
|
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
|
|
if (!t) {
|
|
kfree(m);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tbuf = kzalloc(tlen + len + 1, GFP_KERNEL);
|
|
if (!tbuf) {
|
|
kfree(m);
|
|
kfree(t);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
switch (reg[0]) {
|
|
case STM_TS_CMD_SCAN_MODE:
|
|
case STM_TS_CMD_FEATURE:
|
|
case STM_TS_CMD_SYSTEM:
|
|
case STM_TS_CMD_FRM_BUFF_R:
|
|
case STM_TS_CMD_REG_W:
|
|
case STM_TS_CMD_REG_R:
|
|
case STM_TS_CMD_SPONGE_W:
|
|
memcpy(&tbuf[0], reg, tlen);
|
|
memcpy(&tbuf[tlen], data, len);
|
|
wlen = tlen + len;
|
|
break;
|
|
default:
|
|
tbuf[0] = 0xC0;
|
|
memcpy(&tbuf[1], reg, tlen);
|
|
memcpy(&tbuf[1 + tlen], data, len);
|
|
wlen = tlen + len + 1;
|
|
break;
|
|
}
|
|
|
|
t->len = wlen;
|
|
t->tx_buf = tbuf;
|
|
|
|
spi_message_init(m);
|
|
spi_message_add_tail(t, m);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 0);
|
|
|
|
ret = spi_sync(ts->client, m);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: failed: %d\n", __func__, ret);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 1);
|
|
|
|
if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD) {
|
|
int i;
|
|
|
|
pr_info("sec_input: spi: W: ");
|
|
for (i = 0; i < wlen; i++)
|
|
pr_cont("%02X ", tbuf[i]);
|
|
pr_cont("\n");
|
|
|
|
}
|
|
|
|
kfree(tbuf);
|
|
kfree(m);
|
|
kfree(t);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_spi_write(struct stm_ts_data *ts, u8 *reg, int tlen, u8 *data, int len)
|
|
{
|
|
int ret = -ENOMEM;
|
|
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_POWER_OFF) {
|
|
input_err(true, &ts->client->dev, "%s: Sensor stopped\n", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
if (!ts->plat_data->resume_done.done) {
|
|
ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500));
|
|
if (ret <= 0) {
|
|
input_err(true, &ts->client->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SAMSUNG_TUI)
|
|
if (STUI_MODE_TOUCH_SEC & stui_get_mode())
|
|
return -EBUSY;
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH)
|
|
if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_ENABLE) {
|
|
input_err(true, &ts->client->dev, "%s: TUI is enabled\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
#if IS_ENABLED(CONFIG_GH_RM_DRV)
|
|
if (atomic_read(&ts->trusted_touch_enabled) != 0) {
|
|
input_err(true, &ts->client->dev, "%s: TVM is enabled\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
#endif
|
|
#endif
|
|
mutex_lock(&ts->read_write_mutex);
|
|
ret = stm_ts_no_lock_spi_write(ts, reg, tlen, data, len);
|
|
mutex_unlock(&ts->read_write_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_spi_read(struct stm_ts_data *ts, u8 *reg, int tlen, u8 *buf, int rlen)
|
|
{
|
|
struct spi_message *m;
|
|
struct spi_transfer *t;
|
|
char *tbuf, *rbuf;
|
|
int ret = -ENOMEM;
|
|
int wmsg = 0;
|
|
int buf_len = tlen;
|
|
int mem_len;
|
|
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_POWER_OFF) {
|
|
input_err(true, &ts->client->dev, "%s: Sensor stopped\n", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
if (!ts->plat_data->resume_done.done) {
|
|
ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500));
|
|
if (ret <= 0) {
|
|
input_err(true, &ts->client->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SAMSUNG_TUI)
|
|
if (STUI_MODE_TOUCH_SEC & stui_get_mode())
|
|
return -EBUSY;
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH)
|
|
if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_ENABLE) {
|
|
input_err(true, &ts->client->dev, "%s: TUI is enabled\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
#if IS_ENABLED(CONFIG_GH_RM_DRV)
|
|
if (atomic_read(&ts->trusted_touch_enabled) != 0) {
|
|
input_err(true, &ts->client->dev, "%s: TVM is enabled\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
#endif
|
|
#endif
|
|
m = kzalloc(sizeof(struct spi_message), GFP_KERNEL);
|
|
if (!m)
|
|
return -ENOMEM;
|
|
|
|
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
|
|
if (!t) {
|
|
kfree(m);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (tlen > 3)
|
|
mem_len = rlen + 1 + tlen;
|
|
else
|
|
mem_len = rlen + 1 + 3;
|
|
|
|
tbuf = kzalloc(mem_len, GFP_KERNEL);
|
|
if (!tbuf) {
|
|
kfree(m);
|
|
kfree(t);
|
|
return -ENOMEM;
|
|
}
|
|
rbuf = kzalloc(mem_len, GFP_KERNEL);
|
|
if (!rbuf) {
|
|
kfree(m);
|
|
kfree(t);
|
|
kfree(tbuf);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mutex_lock(&ts->read_write_mutex);
|
|
|
|
switch (reg[0]) {
|
|
case 0x87:
|
|
case STM_TS_CMD_FRM_BUFF_R:
|
|
case STM_TS_CMD_SYSTEM:
|
|
case STM_TS_CMD_REG_W:
|
|
case STM_TS_CMD_REG_R:
|
|
memcpy(tbuf, reg, tlen);
|
|
wmsg = 0;
|
|
break;
|
|
case 0x75:
|
|
buf_len = 3;
|
|
tbuf[0] = 0xD1;
|
|
if (tlen == 1) {
|
|
tbuf[1] = 0x00;
|
|
tbuf[2] = 0x00;
|
|
} else if (tlen == 2) {
|
|
tbuf[1] = 0x00;
|
|
tbuf[2] = reg[0];
|
|
} else if (tlen == 3) {
|
|
tbuf[1] = reg[1];
|
|
tbuf[2] = reg[0];
|
|
} else {
|
|
input_err(true, &ts->client->dev, "%s: tlen is mismatched: %d\n", __func__, tlen);
|
|
kfree(m);
|
|
kfree(t);
|
|
kfree(tbuf);
|
|
kfree(rbuf);
|
|
mutex_unlock(&ts->read_write_mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
wmsg = 0;
|
|
break;
|
|
default:
|
|
buf_len = 3;
|
|
tbuf[0] = 0xD1;
|
|
|
|
if (tlen == 1) {
|
|
tbuf[1] = 0x00;
|
|
tbuf[2] = 0x00;
|
|
} else if (tlen == 2) {
|
|
tbuf[1] = 0x00;
|
|
tbuf[2] = reg[0];
|
|
} else if (tlen == 3) {
|
|
tbuf[1] = reg[1];
|
|
tbuf[2] = reg[0];
|
|
} else {
|
|
input_err(true, &ts->client->dev, "%s: tlen is mismatched: %d\n", __func__, tlen);
|
|
kfree(m);
|
|
kfree(t);
|
|
kfree(tbuf);
|
|
kfree(rbuf);
|
|
mutex_unlock(&ts->read_write_mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
wmsg = 1;
|
|
break;
|
|
}
|
|
|
|
if (wmsg)
|
|
stm_ts_no_lock_spi_write(ts, reg, 1, NULL, 0);
|
|
|
|
spi_message_init(m);
|
|
|
|
t->len = rlen + 1 + buf_len;
|
|
t->tx_buf = tbuf;
|
|
t->rx_buf = rbuf;
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0))
|
|
t->delay_usecs = 10;
|
|
#endif
|
|
spi_message_add_tail(t, m);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 0);
|
|
|
|
ret = spi_sync(ts->client, m);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev, "%s: %d\n", __func__, ret);
|
|
|
|
if (ts->plat_data->gpio_spi_cs > 0)
|
|
gpio_direction_output(ts->plat_data->gpio_spi_cs, 1);
|
|
|
|
mutex_unlock(&ts->read_write_mutex);
|
|
|
|
if (ts->debug_flag & SEC_TS_DEBUG_PRINT_READ_CMD) {
|
|
int i;
|
|
|
|
pr_info("sec_input: spi: R: ");
|
|
for (i = 0; i < buf_len; i++)
|
|
pr_cont("%02X ", tbuf[i]);
|
|
pr_cont("| ");
|
|
for (i = 0; i < rlen + 1 + buf_len; i++) {
|
|
if (i == buf_len + 1)
|
|
pr_cont("|| ");
|
|
pr_cont("%02X ", rbuf[i]);
|
|
}
|
|
pr_cont("\n");
|
|
|
|
}
|
|
memcpy(buf, &rbuf[1 + buf_len], rlen);
|
|
|
|
kfree(rbuf);
|
|
kfree(tbuf);
|
|
kfree(m);
|
|
kfree(t);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int stm_ts_spi_probe(struct spi_device *client)
|
|
{
|
|
struct stm_ts_data *ts;
|
|
struct sec_ts_plat_data *pdata;
|
|
struct sec_tclm_data *tdata;
|
|
int ret = 0;
|
|
|
|
input_info(true, &client->dev, "%s\n", __func__);
|
|
|
|
ts = devm_kzalloc(&client->dev, sizeof(struct stm_ts_data), GFP_KERNEL);
|
|
if (!ts) {
|
|
ret = -ENOMEM;
|
|
goto error_allocate_mem;
|
|
}
|
|
|
|
pdata = devm_kzalloc(&client->dev,
|
|
sizeof(struct sec_ts_plat_data), GFP_KERNEL);
|
|
if (!pdata) {
|
|
ret = -ENOMEM;
|
|
goto error_allocate_pdata;
|
|
}
|
|
|
|
tdata = devm_kzalloc(&client->dev,
|
|
sizeof(struct sec_tclm_data), GFP_KERNEL);
|
|
if (!tdata) {
|
|
ret = -ENOMEM;
|
|
goto error_allocate_tdata;
|
|
}
|
|
client->dev.platform_data = pdata;
|
|
|
|
ts->client = client;
|
|
ts->plat_data = pdata;
|
|
ts->stm_ts_read = stm_ts_spi_read;
|
|
ts->stm_ts_write = stm_ts_spi_write;
|
|
ts->stm_ts_read_sponge = stm_ts_read_from_sponge;
|
|
ts->stm_ts_write_sponge = stm_ts_write_to_sponge;
|
|
ts->tdata = tdata;
|
|
#ifdef TCLM_CONCEPT
|
|
ts->tdata->spi = ts->client;
|
|
ts->tdata->tclm_read_spi = stm_tclm_spi_data_read;
|
|
ts->tdata->tclm_write_spi = stm_tclm_spi_data_write;
|
|
ts->tdata->tclm_execute_force_calibration_spi = stm_ts_tclm_execute_force_calibration;
|
|
#endif
|
|
spi_set_drvdata(client, ts);
|
|
ret = spi_setup(client);
|
|
input_info(true, &client->dev, "%s: spi setup: %d\n", __func__, ret);
|
|
|
|
|
|
#if IS_ENABLED(CONFIG_SAMSUNG_TUI)
|
|
ts->plat_data->stui_tsp_enter = stm_stui_tsp_enter;
|
|
ts->plat_data->stui_tsp_exit = stm_stui_tsp_exit;
|
|
ts->plat_data->stui_tsp_type = stm_stui_tsp_type;
|
|
#endif
|
|
ret = stm_ts_probe(ts);
|
|
|
|
return ret;
|
|
|
|
error_allocate_tdata:
|
|
error_allocate_pdata:
|
|
error_allocate_mem:
|
|
return ret;
|
|
}
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0))
|
|
void stm_ts_spi_remove(struct spi_device *client)
|
|
#else
|
|
int stm_ts_spi_remove(struct spi_device *client)
|
|
#endif
|
|
|
|
{
|
|
struct stm_ts_data *ts = spi_get_drvdata(client);
|
|
int ret = 0;
|
|
|
|
ret = stm_ts_remove(ts);
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0))
|
|
return;
|
|
#else
|
|
return ret;
|
|
#endif
|
|
|
|
}
|
|
|
|
void stm_ts_spi_shutdown(struct spi_device *client)
|
|
{
|
|
struct stm_ts_data *ts = spi_get_drvdata(client);
|
|
|
|
stm_ts_shutdown(ts);
|
|
}
|
|
|
|
|
|
#if IS_ENABLED(CONFIG_PM)
|
|
static int stm_ts_spi_pm_suspend(struct device *dev)
|
|
{
|
|
struct stm_ts_data *ts = dev_get_drvdata(dev);
|
|
|
|
stm_ts_pm_suspend(ts);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm_ts_spi_pm_resume(struct device *dev)
|
|
{
|
|
struct stm_ts_data *ts = dev_get_drvdata(dev);
|
|
|
|
stm_ts_pm_resume(ts);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
static const struct spi_device_id stm_ts_spi_id[] = {
|
|
{ STM_TS_SPI_NAME, 0 },
|
|
{ },
|
|
};
|
|
|
|
#if IS_ENABLED(CONFIG_PM)
|
|
static const struct dev_pm_ops stm_ts_dev_pm_ops = {
|
|
.suspend = stm_ts_spi_pm_suspend,
|
|
.resume = stm_ts_spi_pm_resume,
|
|
};
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_OF)
|
|
static const struct of_device_id stm_ts_match_table[] = {
|
|
{ .compatible = "stm,stm_ts_spi",},
|
|
{ },
|
|
};
|
|
#else
|
|
#define stm_ts_match_table NULL
|
|
#endif
|
|
|
|
static struct spi_driver stm_ts_driver = {
|
|
.probe = stm_ts_spi_probe,
|
|
.remove = stm_ts_spi_remove,
|
|
.shutdown = stm_ts_spi_shutdown,
|
|
.id_table = stm_ts_spi_id,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = STM_TS_SPI_NAME,
|
|
#if IS_ENABLED(CONFIG_OF)
|
|
.of_match_table = stm_ts_match_table,
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_PM)
|
|
.pm = &stm_ts_dev_pm_ops,
|
|
#endif
|
|
},
|
|
};
|
|
|
|
module_spi_driver(stm_ts_driver);
|
|
|
|
|
|
MODULE_SOFTDEP("pre: acpm-mfd-bus");
|
|
MODULE_DESCRIPTION("stm TouchScreen driver");
|
|
MODULE_LICENSE("GPL");
|