kernel-brax3-ubuntu-touch/drivers/input/touchscreen/focaltech_ft8725_spi/focaltech_spi_v2.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

628 lines
16 KiB
C
Executable file

/*
*
* FocalTech TouchScreen driver.
*
* Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
/************************************************************************
*
* File Name: focaltech_spi.c
*
* Author: FocalTech Driver Team
*
* Created: 2019-03-21
*
* Abstract: new spi protocol communication with TP
*
* Version: v2.0
*
* Revision History:
*
************************************************************************/
/*****************************************************************************
* Included header files
*****************************************************************************/
#include "focaltech_core.h"
/*****************************************************************************
* Private constant and macro definitions using #define
*****************************************************************************/
#define SPI_RETRY_NUMBER 3
#define CS_HIGH_DELAY 150 /* unit: us */
#define DATA_CRC_EN 0x20
#define WRITE_CMD 0x00
#define READ_CMD (0x80 | DATA_CRC_EN)
#define SPI_DUMMY_BYTE 3
#define SPI_HEADER_LENGTH 6 /*CRC*/
/*****************************************************************************
* Private enumerations, structures and unions using typedef
*****************************************************************************/
/*****************************************************************************
* Static variables
*****************************************************************************/
/*****************************************************************************
* Global variable or extern global variabls/functions
*****************************************************************************/
/*****************************************************************************
* Static function prototypes
*****************************************************************************/
/*****************************************************************************
* functions body
*****************************************************************************/
/* spi interface */
static int fts_spi_transfer(u8 *tx_buf, u8 *rx_buf, u32 len)
{
int ret = 0;
struct spi_device *spi = fts_data->spi;
struct spi_message msg;
struct spi_transfer xfer = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = len,
};
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
ret = spi_sync(spi, &msg);
if (ret) {
FTS_ERROR("spi_sync fail,ret:%d", ret);
return ret;
}
return ret;
}
static void fts_spi_buf_show(u8 *data, int datalen)
{
int i = 0;
int count = 0;
int size = 0;
char *tmpbuf = NULL;
if (!data || (datalen <= 0)) {
FTS_ERROR("data/datalen is invalid");
return;
}
size = (datalen > 256) ? 256 : datalen;
tmpbuf = kzalloc(1024, GFP_KERNEL);
if (!tmpbuf) {
FTS_ERROR("tmpbuf zalloc fail");
return;
}
for (i = 0; i < size; i++)
count += snprintf(tmpbuf + count, 1024 - count, "%02X ", data[i]);
FTS_DEBUG("%s", tmpbuf);
if (tmpbuf) {
kfree(tmpbuf);
tmpbuf = NULL;
}
}
static void crckermit(u8 *data, u32 len, u16 *crc_out)
{
u32 i = 0;
u32 j = 0;
u16 crc = 0xFFFF;
for ( i = 0; i < len; i++) {
crc ^= data[i];
for (j = 0; j < 8; j++) {
if (crc & 0x01)
crc = (crc >> 1) ^ 0x8408;
else
crc = (crc >> 1);
}
}
*crc_out = crc;
}
static int rdata_check(u8 *rdata, u32 rlen)
{
u16 crc_calc = 0;
u16 crc_read = 0;
crckermit(rdata, rlen - 2, &crc_calc);
crc_read = (u16)(rdata[rlen - 1] << 8) + rdata[rlen - 2];
if (crc_calc != crc_read) {
fts_spi_buf_show(rdata, rlen);
return -EIO;
}
return 0;
}
int fts_write(u8 *writebuf, u32 writelen)
{
int ret = 0;
int i = 0;
struct fts_ts_data *ts_data = fts_data;
u8 *txbuf = NULL;
u8 *rxbuf = NULL;
u32 txlen = 0;
u32 txlen_need = writelen + SPI_HEADER_LENGTH + ts_data->dummy_byte;
u32 datalen = writelen - 1;
if (!writebuf || !writelen) {
FTS_ERROR("writebuf/len is invalid");
return -EINVAL;
}
mutex_lock(&ts_data->bus_lock);
if (txlen_need > FTS_MAX_BUS_BUF) {
txbuf = kzalloc(txlen_need, GFP_KERNEL);
if (NULL == txbuf) {
FTS_ERROR("txbuf malloc fail");
ret = -ENOMEM;
goto err_write;
}
rxbuf = kzalloc(txlen_need, GFP_KERNEL);
if (NULL == rxbuf) {
FTS_ERROR("rxbuf malloc fail");
ret = -ENOMEM;
goto err_write;
}
} else {
txbuf = ts_data->bus_tx_buf;
rxbuf = ts_data->bus_rx_buf;
memset(txbuf, 0x0, FTS_MAX_BUS_BUF);
memset(rxbuf, 0x0, FTS_MAX_BUS_BUF);
}
txbuf[txlen++] = writebuf[0];
txbuf[txlen++] = WRITE_CMD;
txbuf[txlen++] = (datalen >> 8) & 0xFF;
txbuf[txlen++] = datalen & 0xFF;
if (datalen > 0) {
txlen = txlen + SPI_DUMMY_BYTE;
memcpy(&txbuf[txlen], &writebuf[1], datalen);
txlen = txlen + datalen;
}
for (i = 0; i < SPI_RETRY_NUMBER; i++) {
ret = fts_spi_transfer(txbuf, rxbuf, txlen);
if ((0 == ret) && ((rxbuf[3] & 0xA0) == 0)) {
break;
} else {
FTS_DEBUG("data write(addr:%x),status:%x,retry:%d,ret:%d",
writebuf[0], rxbuf[3], i, ret);
ret = -EIO;
udelay(CS_HIGH_DELAY);
}
}
if (ret < 0) {
FTS_ERROR("data write(addr:%x) fail,status:%x,ret:%d",
writebuf[0], rxbuf[3], ret);
}
err_write:
if (txlen_need > FTS_MAX_BUS_BUF) {
if (txbuf) {
kfree(txbuf);
txbuf = NULL;
}
if (rxbuf) {
kfree(rxbuf);
rxbuf = NULL;
}
}
udelay(CS_HIGH_DELAY);
mutex_unlock(&ts_data->bus_lock);
return ret;
}
int fts_write_reg(u8 addr, u8 value)
{
u8 writebuf[2] = { 0 };
writebuf[0] = addr;
writebuf[1] = value;
return fts_write(writebuf, 2);
}
int fts_read(u8 *cmd, u32 cmdlen, u8 *data, u32 datalen)
{
int ret = 0;
int i = 0;
struct fts_ts_data *ts_data = fts_data;
u8 *txbuf = NULL;
u8 *rxbuf = NULL;
u32 txlen = 0;
u32 txlen_need = datalen + SPI_HEADER_LENGTH + ts_data->dummy_byte;
u8 ctrl = READ_CMD;
u32 dp = 0;
if (!cmd || !cmdlen || !data || !datalen) {
FTS_ERROR("cmd/cmdlen/data/datalen is invalid");
return -EINVAL;
}
mutex_lock(&ts_data->bus_lock);
if (txlen_need > FTS_MAX_BUS_BUF) {
txbuf = kzalloc(txlen_need, GFP_KERNEL);
if (NULL == txbuf) {
FTS_ERROR("txbuf malloc fail");
ret = -ENOMEM;
goto err_read;
}
rxbuf = kzalloc(txlen_need, GFP_KERNEL);
if (NULL == rxbuf) {
FTS_ERROR("rxbuf malloc fail");
ret = -ENOMEM;
goto err_read;
}
} else {
txbuf = ts_data->bus_tx_buf;
rxbuf = ts_data->bus_rx_buf;
memset(txbuf, 0x0, FTS_MAX_BUS_BUF);
memset(rxbuf, 0x0, FTS_MAX_BUS_BUF);
}
txbuf[txlen++] = cmd[0];
txbuf[txlen++] = ctrl;
txbuf[txlen++] = (datalen >> 8) & 0xFF;
txbuf[txlen++] = datalen & 0xFF;
dp = txlen + SPI_DUMMY_BYTE;
txlen = dp + datalen;
if (ctrl & DATA_CRC_EN) {
txlen = txlen + 2;
}
for (i = 0; i < SPI_RETRY_NUMBER; i++) {
ret = fts_spi_transfer(txbuf, rxbuf, txlen);
if ((0 == ret) && ((rxbuf[3] & 0xA0) == 0)) {
memcpy(data, &rxbuf[dp], datalen);
/* crc check */
if (ctrl & DATA_CRC_EN) {
ret = rdata_check(&rxbuf[dp], txlen - dp);
if (ret < 0) {
FTS_DEBUG("data read(addr:%x) crc abnormal,retry:%d",
cmd[0], i);
udelay(CS_HIGH_DELAY);
continue;
}
}
break;
} else {
FTS_DEBUG("data read(addr:%x) status:%x,retry:%d,ret:%d",
cmd[0], rxbuf[3], i, ret);
ret = -EIO;
udelay(CS_HIGH_DELAY);
}
}
if (ret < 0) {
FTS_ERROR("data read(addr:%x) %s,status:%x,ret:%d", cmd[0],
(i >= SPI_RETRY_NUMBER) ? "crc abnormal" : "fail",
rxbuf[3], ret);
}
err_read:
if (txlen_need > FTS_MAX_BUS_BUF) {
if (txbuf) {
kfree(txbuf);
txbuf = NULL;
}
if (rxbuf) {
kfree(rxbuf);
rxbuf = NULL;
}
}
udelay(CS_HIGH_DELAY);
mutex_unlock(&ts_data->bus_lock);
return ret;
}
int fts_read_reg(u8 addr, u8 *value)
{
return fts_read(&addr, 1, value, 1);
}
int fts_bus_transfer_direct(u8 *writebuf, u32 writelen, u8 *readbuf, u32 readlen)
{
int ret = 0;
struct fts_ts_data *ts_data = fts_data;
u8 *txbuf = NULL;
u8 *rxbuf = NULL;
bool read_cmd = (readbuf && readlen) ? 1 : 0;
u32 txlen = (read_cmd) ? readlen : writelen;
if (!writebuf || !writelen) {
FTS_ERROR("writebuf/len is invalid");
return -EINVAL;
}
mutex_lock(&ts_data->bus_lock);
if (txlen > FTS_MAX_BUS_BUF) {
txbuf = kzalloc(txlen, GFP_KERNEL);
if (NULL == txbuf) {
FTS_ERROR("txbuf malloc fail");
ret = -ENOMEM;
goto err_spi_dir;
}
rxbuf = kzalloc(txlen, GFP_KERNEL);
if (NULL == rxbuf) {
FTS_ERROR("rxbuf malloc fail");
ret = -ENOMEM;
goto err_spi_dir;
}
} else {
txbuf = ts_data->bus_tx_buf;
rxbuf = ts_data->bus_rx_buf;
memset(txbuf, 0x0, FTS_MAX_BUS_BUF);
memset(rxbuf, 0x0, FTS_MAX_BUS_BUF);
}
memcpy(txbuf, writebuf, writelen);
ret = fts_spi_transfer(txbuf, rxbuf, txlen);
if (ret < 0) {
FTS_ERROR("data read(addr:%x) fail,status:%x,ret:%d", txbuf[0], rxbuf[3], ret);
goto err_spi_dir;
}
if (read_cmd) {
memcpy(readbuf, rxbuf, txlen);
}
ret = 0;
err_spi_dir:
if (txlen > FTS_MAX_BUS_BUF) {
if (txbuf) {
kfree(txbuf);
txbuf = NULL;
}
if (rxbuf) {
kfree(rxbuf);
rxbuf = NULL;
}
}
udelay(CS_HIGH_DELAY);
mutex_unlock(&ts_data->bus_lock);
return ret;
}
int fts_bus_configure(struct fts_ts_data *ts_data, u8 *buf, u32 size)
{
int ret = 0;
FTS_FUNC_ENTER();
if (ts_data->spi && buf && size) {
ts_data->spi->mode = buf[0];
ts_data->spi->bits_per_word = buf[1];
ts_data->spi->max_speed_hz = *(u32 *)(buf + 3);
FTS_INFO("spi,mode=%d,bits=%d,speed=%d", ts_data->spi->mode,
ts_data->spi->bits_per_word, ts_data->spi->max_speed_hz);
ret = spi_setup(ts_data->spi);
if (ret < 0) {
FTS_ERROR("spi setup fail,ret:%d", ret);
}
}
FTS_FUNC_EXIT();
return ret;
}
int fts_bus_set_speed(struct fts_ts_data *ts_data, u32 speed)
{
int ret = 0;
if (!ts_data) {
FTS_ERROR("ts_data is null");
return -EINVAL;
}
mutex_lock(&ts_data->bus_lock);
if (speed > 0) {
ts_data->spi->max_speed_hz = speed;
} else {
ts_data->spi->max_speed_hz = ts_data->spi_speed;
}
ret = spi_setup(ts_data->spi);
if (ret < 0) {
FTS_ERROR("spi speed set fail,ret:%d", ret);
}
mutex_unlock(&ts_data->bus_lock);
return ret;
}
//drv Change the cs_setup time to 200ns moment ft8725 requires the time to be 200ns-pzp-20240523-start
struct spi_delay cs_setup_delay = {
.value = 200, // 20 ns
.unit = SPI_DELAY_UNIT_NSECS
};
//drv Change the cs_setup time to 200ns moment ft8725 requires the time to be 200ns-pzp-20240523-end
/*****************************************************************************
* TP Driver
*****************************************************************************/
static int fts_ts_probe(struct spi_device *spi)
{
int ret = 0;
struct fts_ts_data *ts_data = NULL;
FTS_INFO("Touch Screen(SPI-2 BUS) driver prboe...");
//drv Change the cs_setup time to 200ns moment ft8725 requires the time to be 200ns-pzp-20240523-start
spi->cs_setup = cs_setup_delay;
//drv Change the cs_setup time to 200ns moment ft8725 requires the time to be 200ns-pzp-20240523-end
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
ret = spi_setup(spi);
if (ret < 0) {
FTS_ERROR("spi setup fail");
return ret;
}
/* malloc memory for global struct variable */
ts_data = (struct fts_ts_data *)kzalloc(sizeof(*ts_data), GFP_KERNEL);
if (!ts_data) {
FTS_ERROR("allocate memory for fts_data fail");
return -ENOMEM;
}
ts_data->spi = spi;
ts_data->dev = &spi->dev;
ts_data->log_level = 1;
ts_data->bus_type = BUS_TYPE_SPI;
ts_data->bus_ver = BUS_VER_V2;
ts_data->dummy_byte = SPI_DUMMY_BYTE;
spi_set_drvdata(spi, ts_data);
ts_data->spi_speed = spi->max_speed_hz;
ret = fts_ts_probe_entry(ts_data);
if (ret) {
FTS_ERROR("Touch Screen(SPI BUS) driver probe fail");
spi_set_drvdata(spi, NULL);
kfree_safe(ts_data);
return ret;
}
FTS_INFO("Touch Screen(SPI BUS) driver prboe successfully");
return 0;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0))
static void fts_ts_remove(struct spi_device *spi)
{
struct fts_ts_data *ts_data = spi_get_drvdata(spi);
FTS_FUNC_ENTER();
if (ts_data) {
fts_ts_remove_entry(ts_data);
spi_set_drvdata(spi, NULL);
kfree_safe(ts_data);
}
FTS_FUNC_EXIT();
}
#else
static int fts_ts_remove(struct spi_device *spi)
{
struct fts_ts_data *ts_data = spi_get_drvdata(spi);
FTS_FUNC_ENTER();
if (ts_data) {
fts_ts_remove_entry(ts_data);
spi_set_drvdata(spi, NULL);
kfree_safe(ts_data);
}
FTS_FUNC_EXIT();
return 0;
}
#endif
static void fts_ts_shutdown(struct spi_device *spi)
{
struct fts_ts_data *ts_data = spi_get_drvdata(spi);
FTS_FUNC_ENTER();
if (ts_data) {
fts_ts_remove_entry(ts_data);
spi_set_drvdata(spi, NULL);
kfree_safe(ts_data);
}
FTS_FUNC_EXIT();
}
#if IS_ENABLED(CONFIG_PM) && FTS_PATCH_COMERR_PM
static int fts_pm_suspend(struct device *dev)
{
struct fts_ts_data *ts_data = dev_get_drvdata(dev);
FTS_INFO("system enters into pm_suspend");
ts_data->pm_suspend = true;
reinit_completion(&ts_data->pm_completion);
return 0;
}
static int fts_pm_resume(struct device *dev)
{
struct fts_ts_data *ts_data = dev_get_drvdata(dev);
FTS_INFO("system resumes from pm_suspend");
ts_data->pm_suspend = false;
complete(&ts_data->pm_completion);
return 0;
}
static const struct dev_pm_ops fts_dev_pm_ops = {
.suspend = fts_pm_suspend,
.resume = fts_pm_resume,
};
#endif
static const struct spi_device_id fts_ts_id[] = {
{FTS_DRIVER_NAME, 0},
{},
};
static const struct of_device_id fts_dt_match[] = {
{.compatible = "focaltech,fts", },
{},
};
MODULE_DEVICE_TABLE(of, fts_dt_match);
static struct spi_driver fts_ts_spi_driver = {
.probe = fts_ts_probe,
.remove = fts_ts_remove,
.shutdown = fts_ts_shutdown,
.driver = {
.name = FTS_DRIVER_NAME,
.owner = THIS_MODULE,
#if IS_ENABLED(CONFIG_PM) && FTS_PATCH_COMERR_PM
.pm = &fts_dev_pm_ops,
#endif
.of_match_table = of_match_ptr(fts_dt_match),
},
.id_table = fts_ts_id,
};
static int __init fts_ts_spi_init(void)
{
int ret = 0;
FTS_FUNC_ENTER();
ret = spi_register_driver(&fts_ts_spi_driver);
if ( ret < 0 ) {
FTS_ERROR("Focaltech touch screen driver init failed!");
}
FTS_FUNC_EXIT();
return ret;
}
static void __exit fts_ts_spi_exit(void)
{
spi_unregister_driver(&fts_ts_spi_driver);
}
module_init(fts_ts_spi_init);
module_exit(fts_ts_spi_exit);
MODULE_AUTHOR("FocalTech Driver Team");
MODULE_DESCRIPTION("FocalTech Touchscreen Driver(SPI)");
MODULE_LICENSE("GPL v2");