kernel_samsung_a34x-permissive/drivers/input/touchscreen/gt9895/goodix_brl_i2c.c
2024-04-28 15:51:13 +02:00

440 lines
11 KiB
C

/*
* Goodix Touchscreen Driver
* Copyright (C) 2020 - 2021 Goodix, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be a reference
* to you, when you are integrating the GOODiX's CTP IC into your system,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#if IS_ENABLED(CONFIG_SAMSUNG_TUI)
#include <linux/input/stui_inf.h>
#endif
#include "goodix_ts_core.h"
#define TS_DRIVER_NAME "goodix_i2c"
#define I2C_MAX_TRANSFER_SIZE 256
#define GOODIX_BUS_RETRY_TIMES 3
#define BERLIN_REG_ADDR_SIZE 4
#define NORMANDY_REG_ADDR_SIZE 2
static struct platform_device *goodix_pdev;
struct goodix_bus_interface goodix_i2c_bus;
/* Berlin read/write ops */
static int goodix_i2c_read(struct device *dev, unsigned int reg,
unsigned char *data, unsigned int len)
{
struct i2c_client *client = to_i2c_client(dev);
struct goodix_ts_core *core_data = dev_get_drvdata(dev);
unsigned int transfer_length = 0;
unsigned int pos = 0, address = reg;
unsigned char get_buf[128], addr_buf[BERLIN_REG_ADDR_SIZE];
int retry, r = 0;
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = !I2C_M_RD,
.buf = &addr_buf[0],
.len = BERLIN_REG_ADDR_SIZE,
}, {
.addr = client->addr,
.flags = I2C_M_RD,
}
};
if (!core_data)
return -ENODEV;
if (core_data->plat_data->power_enabled == false) {
ts_err("IC power is off");
return -EIO;
}
#if IS_ENABLED(CONFIG_SAMSUNG_TUI)
if (STUI_MODE_TOUCH_SEC & stui_get_mode())
return -EBUSY;
#endif
if (core_data->resume_done.done) {
int ret = wait_for_completion_interruptible_timeout(&core_data->resume_done, msecs_to_jiffies(500));
if (ret <= 0) {
ts_err("LPM: pm resume is not handled:%d", ret);
return -EIO;
}
}
if (likely(len < sizeof(get_buf))) {
/* code optimize, use stack memory */
msgs[1].buf = &get_buf[0];
} else {
msgs[1].buf = kzalloc(len, GFP_KERNEL);
if (msgs[1].buf == NULL)
return -ENOMEM;
}
while (pos != len) {
if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE))
transfer_length = I2C_MAX_TRANSFER_SIZE;
else
transfer_length = len - pos;
msgs[0].buf[0] = (address >> 24) & 0xFF;
msgs[0].buf[1] = (address >> 16) & 0xFF;
msgs[0].buf[2] = (address >> 8) & 0xFF;
msgs[0].buf[3] = address & 0xFF;
msgs[1].len = transfer_length;
for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) {
if (likely(i2c_transfer(client->adapter,
msgs, 2) == 2)) {
memcpy(&data[pos], msgs[1].buf, transfer_length);
if (core_data->debug_flag & GOODIX_TS_DEBUG_PRINT_I2C_READ_CMD) {
int i;
pr_info("sec_input : i2c_cmd: R: lenth(%d) pos(%d) 0x%02X | ", transfer_length, pos, reg);
for (i = 0; i < transfer_length; i++)
pr_cont("%02X ", data[pos + i]);
pr_cont("\n");
}
pos += transfer_length;
address += transfer_length;
break;
}
ts_info("I2c read retry[%d]:0x%x", retry + 1, reg);
msleep(20);
}
if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) {
ts_err("I2c read failed,dev:%02x,reg:%04x,size:%u",
client->addr, reg, len);
r = -EAGAIN;
goto read_exit;
}
}
read_exit:
if (unlikely(len >= sizeof(get_buf)))
kfree(msgs[1].buf);
return r;
}
static int goodix_i2c_write(struct device *dev, unsigned int reg,
unsigned char *data, unsigned int len)
{
struct i2c_client *client = to_i2c_client(dev);
struct goodix_ts_core *core_data = dev_get_drvdata(dev);
unsigned int pos = 0, transfer_length = 0;
unsigned int address = reg;
unsigned char put_buf[128];
int retry, r = 0;
struct i2c_msg msg = {
.addr = client->addr,
.flags = !I2C_M_RD,
};
if (!core_data)
return -ENODEV;
if (core_data->plat_data->power_enabled == false) {
ts_err("IC power is off");
return -EIO;
}
#if IS_ENABLED(CONFIG_SAMSUNG_TUI)
if (STUI_MODE_TOUCH_SEC & stui_get_mode())
return -EBUSY;
#endif
if (core_data->resume_done.done) {
int ret = wait_for_completion_interruptible_timeout(&core_data->resume_done, msecs_to_jiffies(500));
if (ret <= 0) {
ts_err("LPM: pm resume is not handled:%d", ret);
return -EIO;
}
}
if (likely(len + BERLIN_REG_ADDR_SIZE < sizeof(put_buf))) {
/* code optimize,use stack memory*/
msg.buf = &put_buf[0];
} else {
msg.buf = kmalloc(len + BERLIN_REG_ADDR_SIZE, GFP_KERNEL);
if (msg.buf == NULL)
return -ENOMEM;
}
while (pos != len) {
if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE -
BERLIN_REG_ADDR_SIZE))
transfer_length = I2C_MAX_TRANSFER_SIZE -
BERLIN_REG_ADDR_SIZE;
else
transfer_length = len - pos;
msg.buf[0] = (address >> 24) & 0xFF;
msg.buf[1] = (address >> 16) & 0xFF;
msg.buf[2] = (address >> 8) & 0xFF;
msg.buf[3] = address & 0xFF;
msg.len = transfer_length + BERLIN_REG_ADDR_SIZE;
memcpy(&msg.buf[BERLIN_REG_ADDR_SIZE],
&data[pos], transfer_length);
for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) {
if (likely(i2c_transfer(client->adapter,
&msg, 1) == 1)) {
pos += transfer_length;
address += transfer_length;
break;
}
ts_debug("I2c write retry[%d]", retry + 1);
msleep(20);
}
if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) {
ts_err("I2c write failed,dev:%02x,reg:%04x,size:%u",
client->addr, reg, len);
r = -EAGAIN;
goto write_exit;
}
}
if (core_data->debug_flag & GOODIX_TS_DEBUG_PRINT_I2C_WRITE_CMD) {
int i;
pr_info("sec_input : i2c_cmd: W: 0x%02X | ", reg);
for (i = 0; i < len; i++)
pr_cont("%02X ", data[i]);
pr_cont("\n");
}
write_exit:
if (likely(len + BERLIN_REG_ADDR_SIZE >= sizeof(put_buf)))
kfree(msg.buf);
return r;
}
static void goodix_pdev_release(struct device *dev)
{
ts_info("goodix pdev released");
kfree(goodix_pdev);
}
static int goodix_i2c_init(struct i2c_client *client)
{
struct goodix_ts_core *core_data = NULL;
struct sec_ts_plat_data *pdata;
#if 0
struct sec_tclm_data *tdata = NULL;
#endif
int ret = 0;
if (client->dev.of_node) {
pdata = devm_kzalloc(&client->dev,
sizeof(struct sec_ts_plat_data), GFP_KERNEL);
if (!pdata) {
ret = -ENOMEM;
goto error_allocate_pdata;
}
client->dev.platform_data = pdata;
ret = sec_input_parse_dt(&client->dev);
if (ret) {
input_err(true, &client->dev, "%s: Failed to parse dt\n", __func__);
goto error_allocate_mem;
}
#if 0
tdata = devm_kzalloc(&client->dev,
sizeof(struct sec_tclm_data), GFP_KERNEL);
if (!tdata) {
ret = -ENOMEM;
goto error_allocate_tdata;
}
#ifdef TCLM_CONCEPT
sec_tclm_parse_dt(&client->dev, tdata);
#endif
#endif
} else {
pdata = client->dev.platform_data;
if (!pdata) {
ret = -ENOMEM;
input_err(true, &client->dev, "%s: No platform data found\n", __func__);
goto error_allocate_pdata;
}
}
core_data = devm_kzalloc(&client->dev,
sizeof(struct goodix_ts_core), GFP_KERNEL);
if (!core_data) {
ret = -ENOMEM;
input_err(true, &client->dev, "%s: Failed to allocate memory for core data\n", __func__);
goto error_allocate_mem;
}
i2c_set_clientdata(client, core_data);
pdata->pinctrl = devm_pinctrl_get(&client->dev);
if (IS_ERR(pdata->pinctrl))
input_err(true, &client->dev, "%s: could not get pinctrl\n", __func__);
ptsp = &client->dev;
#if 0
if (!tdata) {
ret = -ENOMEM;
goto err_null_tdata;
}
#ifdef TCLM_CONCEPT
sec_tclm_initialize(tdata);
tdata->client = client;
tdata->tclm_read = sec_tclm_data_read;
tdata->tclm_write = sec_tclm_data_write;
tdata->tclm_execute_force_calibration = sec_tclm_execute_force_calibration;
tdata->tclm_parse_dt = sec_tclm_parse_dt;
#endif
#endif
input_info(true, &client->dev, "%s: init resource\n", __func__);
return 0;
err_null_tdata:
error_allocate_mem:
if (!pdata->not_support_io_ldo)
regulator_put(pdata->dvdd);
regulator_put(pdata->avdd);
error_allocate_tdata:
error_allocate_pdata:
input_err(true, &client->dev, "%s: failed(%d)\n", __func__, ret);
input_log_fix();
return ret;
}
static int goodix_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
int ret = 0;
ts_info("goodix i2c probe in");
ret = i2c_check_functionality(client->adapter,
I2C_FUNC_I2C);
if (!ret)
return -EIO;
ret = goodix_i2c_init(client);
if (ret < 0) {
ts_err("%s: fail to init resource\n", __func__);
return ret;
}
goodix_i2c_bus.bus_type = GOODIX_BUS_TYPE_I2C;
goodix_i2c_bus.dev = &client->dev;
goodix_i2c_bus.read = goodix_i2c_read;
goodix_i2c_bus.write = goodix_i2c_write;
/* ts core device */
goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL);
if (!goodix_pdev)
return -ENOMEM;
goodix_pdev->name = GOODIX_CORE_DRIVER_NAME;
goodix_pdev->id = 0;
goodix_pdev->num_resources = 0;
/*
* you can find this platform dev in
* /sys/devices/platform/goodix_ts.0
* goodix_pdev->dev.parent = &client->dev;
*/
goodix_pdev->dev.platform_data = &goodix_i2c_bus;
goodix_pdev->dev.release = goodix_pdev_release;
/* register platform device, then the goodix_ts_core
* module will probe the touch device.
*/
ret = platform_device_register(goodix_pdev);
if (ret) {
ts_err("failed register goodix platform device, %d", ret);
goto err_pdev;
}
ts_info("i2c probe out");
return ret;
err_pdev:
kfree(goodix_pdev);
goodix_pdev = NULL;
ts_info("i2c probe out, %d", ret);
return ret;
}
static int goodix_i2c_remove(struct i2c_client *client)
{
if (!goodix_pdev)
return 0;
ts_info("called");
platform_device_unregister(goodix_pdev);
goodix_pdev = NULL;
return 0;
}
static void goodix_i2c_shutdown(struct i2c_client *client)
{
ts_info("called");
goodix_i2c_remove(client);
}
#ifdef CONFIG_OF
static const struct of_device_id i2c_matches[] = {
{.compatible = "goodix,berlin",},
{.compatible = "goodix,gt9897",},
{.compatible = "goodix,gt9966",},
{.compatible = "goodix,gt9916",},
{.compatible = "goodix,gt9885",},
{.compatible = "goodix,gt9886",},
{},
};
MODULE_DEVICE_TABLE(of, i2c_matches);
#endif
static const struct i2c_device_id i2c_id_table[] = {
{TS_DRIVER_NAME, 0},
{},
};
MODULE_DEVICE_TABLE(i2c, i2c_id_table);
static struct i2c_driver goodix_i2c_driver = {
.driver = {
.name = TS_DRIVER_NAME,
//.owner = THIS_MODULE,
.of_match_table = of_match_ptr(i2c_matches),
},
.probe = goodix_i2c_probe,
.remove = goodix_i2c_remove,
.shutdown = goodix_i2c_shutdown,
.id_table = i2c_id_table,
};
int goodix_i2c_bus_init(void)
{
ts_info("Goodix i2c driver init");
return i2c_add_driver(&goodix_i2c_driver);
}
void goodix_i2c_bus_exit(void)
{
ts_info("Goodix i2c driver exit");
i2c_del_driver(&goodix_i2c_driver);
}