* [PATCH 0/7] Input: edt-ft5x06 - add SPI transport and FocalTech FT8722 support
@ 2026-10-17 14:07 Luka Panio
2026-10-17 14:07 ` [PATCH 1/7] Input: edt-ft5x06 - split the bus independent core from the I2C driver Luka Panio
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Luka Panio @ 2026-10-17 14:07 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Douglas Anderson, linux-input, devicetree, dri-devel,
linux-kernel, Luka Panio
The FocalTech FT8722 is a touch and display driver (TDDI) found in the
Nexphone (QCM6490). Its touch controller speaks the usual FocalTech
register protocol handled by edt-ft5x06, but it is connected over SPI,
has no flash and runs its firmware from RAM, and shares its power and
reset with the display panel.
This series:
- splits the bus independent core of edt-ft5x06 from the I2C driver
(patch 1) and moves the controller bring-up into a helper (patch 2),
- adds an optional per-chip boot hook, used to download the firmware
after every reset (patch 3), and a per-chip touch data register
(patch 4),
- documents the FT8722 touch controller as focaltech,ft8722-ts, keeping
it distinct from the display side of the chip (patch 5),
- adds an SPI transport with the FT8722 firmware loader (patch 6),
- lets the driver follow a DRM panel, like i2c-hid does, so the touch
controller is powered down and re-initialised together with the panel
(patch 7).
Tested on the Nexphone by Nex Computers. A board DTS using it will
follow separately.
Luka Panio (7):
Input: edt-ft5x06 - split the bus independent core from the I2C driver
Input: edt-ft5x06 - move the controller bring-up into a helper
Input: edt-ft5x06 - add an optional controller boot hook
Input: edt-ft5x06 - allow a chip specific touch data register
dt-bindings: input: touchscreen: edt-ft5x06: Add FocalTech FT8722
Input: edt-ft5x06 - add SPI transport and FT8722 support
Input: edt-ft5x06 - support following a DRM panel
.../input/touchscreen/edt-ft5x06.yaml | 37 ++
drivers/input/touchscreen/Kconfig | 15 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/edt-ft5x06-spi.c | 475 ++++++++++++++
drivers/input/touchscreen/edt-ft5x06.c | 585 ++++++++++++------
drivers/input/touchscreen/edt-ft5x06.h | 33 +
6 files changed, 957 insertions(+), 189 deletions(-)
create mode 100644 drivers/input/touchscreen/edt-ft5x06-spi.c
create mode 100644 drivers/input/touchscreen/edt-ft5x06.h
base-commit: d419d682257d813f6cb7a28e42b35d0edf4ad038
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/7] Input: edt-ft5x06 - split the bus independent core from the I2C driver
2026-10-17 14:07 [PATCH 0/7] Input: edt-ft5x06 - add SPI transport and FocalTech FT8722 support Luka Panio
@ 2026-10-17 14:07 ` Luka Panio
2026-10-17 14:07 ` [PATCH 2/7] Input: edt-ft5x06 - move the controller bring-up into a helper Luka Panio
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Luka Panio @ 2026-10-17 14:07 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Douglas Anderson, linux-input, devicetree, dri-devel,
linux-kernel, Luka Panio
Some FocalTech controllers handled by this driver, such as the FT8722
TDDI, are connected over SPI. Prepare for an SPI transport by moving
everything that depends on the i2c_client into a small I2C probe.
The only visible change for the I2C devices is that the interrupt is now
requested with the device name (e.g. "1-0038") instead of the I2C client
name, as the core no longer has access to the latter.
Signed-off-by: Luka Panio <lukapanio@gmail.com>
---
drivers/input/touchscreen/edt-ft5x06.c | 276 +++++++++++++------------
drivers/input/touchscreen/edt-ft5x06.h | 30 +++
2 files changed, 179 insertions(+), 127 deletions(-)
create mode 100644 drivers/input/touchscreen/edt-ft5x06.h
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index e69d64c3db2d..543cc62088c7 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -34,6 +34,8 @@
#include <linux/unaligned.h>
+#include "edt-ft5x06.h"
+
#define WORK_REGISTER_THRESHOLD 0x00
#define WORK_REGISTER_REPORT_RATE 0x08
#define WORK_REGISTER_GAIN 0x30
@@ -106,7 +108,9 @@ struct edt_reg_addr {
};
struct edt_ft5x06_ts_data {
- struct i2c_client *client;
+ struct device *dev;
+ int irq;
+ struct dentry *debugfs;
struct input_dev *input;
struct touchscreen_properties prop;
u16 num_x;
@@ -148,15 +152,6 @@ struct edt_ft5x06_ts_data {
unsigned int header_errors;
};
-struct edt_i2c_chip_data {
- int max_support_points;
-};
-
-static const struct regmap_config edt_ft5x06_i2c_regmap_config = {
- .reg_bits = 8,
- .val_bits = 8,
-};
-
static bool edt_ft5x06_ts_check_crc(struct edt_ft5x06_ts_data *tsdata,
u8 *buf, int buflen)
{
@@ -168,7 +163,7 @@ static bool edt_ft5x06_ts_check_crc(struct edt_ft5x06_ts_data *tsdata,
if (crc != buf[buflen - 1]) {
tsdata->crc_errors++;
- dev_err_ratelimited(&tsdata->client->dev,
+ dev_err_ratelimited(tsdata->dev,
"crc error: 0x%02x expected, got 0x%02x\n",
crc, buf[buflen - 1]);
return false;
@@ -182,7 +177,7 @@ static int edt_M06_i2c_read(void *context, const void *reg_buf, size_t reg_size,
{
struct device *dev = context;
struct i2c_client *i2c = to_i2c_client(dev);
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(i2c);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
struct i2c_msg xfer[2];
bool reg_read = false;
u8 addr;
@@ -258,7 +253,7 @@ static int edt_M06_i2c_write(void *context, const void *data, size_t count)
{
struct device *dev = context;
struct i2c_client *i2c = to_i2c_client(dev);
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(i2c);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
u8 addr, val;
u8 wbuf[4];
struct i2c_msg xfer;
@@ -298,7 +293,7 @@ static const struct regmap_config edt_M06_i2c_regmap_config = {
static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
{
struct edt_ft5x06_ts_data *tsdata = dev_id;
- struct device *dev = &tsdata->client->dev;
+ struct device *dev = tsdata->dev;
u8 rdbuf[63];
int i, type, x, y, id;
int error;
@@ -376,8 +371,7 @@ static ssize_t edt_ft5x06_setting_show(struct device *dev,
struct device_attribute *dattr,
char *buf)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
struct edt_ft5x06_attribute *attr =
container_of(dattr, struct edt_ft5x06_attribute, dattr);
u8 *field = (u8 *)tsdata + attr->field_offset;
@@ -412,7 +406,7 @@ static ssize_t edt_ft5x06_setting_show(struct device *dev,
if (addr != NO_REGISTER) {
error = regmap_read(tsdata->regmap, addr, &val);
if (error) {
- dev_err(&tsdata->client->dev,
+ dev_err(tsdata->dev,
"Failed to fetch attribute %s, error %d\n",
dattr->attr.name, error);
return error;
@@ -422,7 +416,7 @@ static ssize_t edt_ft5x06_setting_show(struct device *dev,
}
if (val != *field) {
- dev_warn(&tsdata->client->dev,
+ dev_warn(tsdata->dev,
"%s: read (%d) and stored value (%d) differ\n",
dattr->attr.name, val, *field);
*field = val;
@@ -435,8 +429,7 @@ static ssize_t edt_ft5x06_setting_store(struct device *dev,
struct device_attribute *dattr,
const char *buf, size_t count)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
struct edt_ft5x06_attribute *attr =
container_of(dattr, struct edt_ft5x06_attribute, dattr);
u8 *field = (u8 *)tsdata + attr->field_offset;
@@ -478,7 +471,7 @@ static ssize_t edt_ft5x06_setting_store(struct device *dev,
if (addr != NO_REGISTER) {
error = regmap_write(tsdata->regmap, addr, val);
if (error) {
- dev_err(&tsdata->client->dev,
+ dev_err(tsdata->dev,
"Failed to update attribute %s, error: %d\n",
dattr->attr.name, error);
return error;
@@ -511,8 +504,7 @@ static EDT_ATTR(report_rate, S_IWUSR | S_IRUGO, WORK_REGISTER_REPORT_RATE,
static ssize_t model_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
return sysfs_emit(buf, "%s\n", tsdata->name);
}
@@ -522,8 +514,7 @@ static DEVICE_ATTR_RO(model);
static ssize_t fw_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
return sysfs_emit(buf, "%s\n", tsdata->fw_version);
}
@@ -534,8 +525,7 @@ static DEVICE_ATTR_RO(fw_version);
static ssize_t header_errors_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
return sysfs_emit(buf, "%d\n", tsdata->header_errors);
}
@@ -546,8 +536,7 @@ static DEVICE_ATTR_RO(header_errors);
static ssize_t crc_errors_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
return sysfs_emit(buf, "%d\n", tsdata->crc_errors);
}
@@ -567,7 +556,16 @@ static struct attribute *edt_ft5x06_attrs[] = {
&dev_attr_crc_errors.attr,
NULL
};
-ATTRIBUTE_GROUPS(edt_ft5x06);
+
+static const struct attribute_group edt_ft5x06_group = {
+ .attrs = edt_ft5x06_attrs,
+};
+
+const struct attribute_group *edt_ft5x06_groups[] = {
+ &edt_ft5x06_group,
+ NULL
+};
+EXPORT_SYMBOL_NS_GPL(edt_ft5x06_groups, "EDT_FT5X06");
static void edt_ft5x06_restore_reg_parameters(struct edt_ft5x06_ts_data *tsdata)
{
@@ -590,18 +588,17 @@ static void edt_ft5x06_restore_reg_parameters(struct edt_ft5x06_ts_data *tsdata)
#ifdef CONFIG_DEBUG_FS
static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
{
- struct i2c_client *client = tsdata->client;
int retries = EDT_SWITCH_MODE_RETRIES;
unsigned int val;
int error;
if (tsdata->version != EDT_M06) {
- dev_err(&client->dev,
+ dev_err(tsdata->dev,
"No factory mode support for non-M06 devices\n");
return -EINVAL;
}
- disable_irq(client->irq);
+ disable_irq(tsdata->irq);
if (!tsdata->raw_buffer) {
tsdata->raw_bufsize = tsdata->num_x * tsdata->num_y *
@@ -616,7 +613,7 @@ static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
/* mode register is 0x3c when in the work mode */
error = regmap_write(tsdata->regmap, WORK_REGISTER_OPMODE, 0x03);
if (error) {
- dev_err(&client->dev,
+ dev_err(tsdata->dev,
"failed to switch to factory mode, error %d\n", error);
goto err_out;
}
@@ -632,7 +629,7 @@ static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
} while (--retries > 0);
if (retries == 0) {
- dev_err(&client->dev, "not in factory mode after %dms.\n",
+ dev_err(tsdata->dev, "not in factory mode after %dms.\n",
EDT_SWITCH_MODE_RETRIES * EDT_SWITCH_MODE_DELAY);
error = -EIO;
goto err_out;
@@ -644,14 +641,13 @@ static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
kfree(tsdata->raw_buffer);
tsdata->raw_buffer = NULL;
tsdata->factory_mode = false;
- enable_irq(client->irq);
+ enable_irq(tsdata->irq);
return error;
}
static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
{
- struct i2c_client *client = tsdata->client;
int retries = EDT_SWITCH_MODE_RETRIES;
unsigned int val;
int error;
@@ -659,7 +655,7 @@ static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
/* mode register is 0x01 when in the factory mode */
error = regmap_write(tsdata->regmap, FACTORY_REGISTER_OPMODE, 0x1);
if (error) {
- dev_err(&client->dev,
+ dev_err(tsdata->dev,
"failed to switch to work mode, error: %d\n", error);
return error;
}
@@ -675,7 +671,7 @@ static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
} while (--retries > 0);
if (retries == 0) {
- dev_err(&client->dev, "not in work mode after %dms.\n",
+ dev_err(tsdata->dev, "not in work mode after %dms.\n",
EDT_SWITCH_MODE_RETRIES * EDT_SWITCH_MODE_DELAY);
tsdata->factory_mode = true;
return -EIO;
@@ -685,7 +681,7 @@ static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
tsdata->raw_buffer = NULL;
edt_ft5x06_restore_reg_parameters(tsdata);
- enable_irq(client->irq);
+ enable_irq(tsdata->irq);
return 0;
}
@@ -723,7 +719,6 @@ static ssize_t edt_ft5x06_debugfs_raw_data_read(struct file *file,
loff_t *off)
{
struct edt_ft5x06_ts_data *tsdata = file->private_data;
- struct i2c_client *client = tsdata->client;
int retries = EDT_RAW_DATA_RETRIES;
unsigned int val;
int i, error;
@@ -741,7 +736,7 @@ static ssize_t edt_ft5x06_debugfs_raw_data_read(struct file *file,
error = regmap_write(tsdata->regmap, 0x08, 0x01);
if (error) {
- dev_err(&client->dev,
+ dev_err(tsdata->dev,
"failed to write 0x08 register, error %d\n", error);
return error;
}
@@ -750,7 +745,7 @@ static ssize_t edt_ft5x06_debugfs_raw_data_read(struct file *file,
usleep_range(EDT_RAW_DATA_DELAY, EDT_RAW_DATA_DELAY + 100);
error = regmap_read(tsdata->regmap, 0x08, &val);
if (error) {
- dev_err(&client->dev,
+ dev_err(tsdata->dev,
"failed to read 0x08 register, error %d\n",
error);
return error;
@@ -761,7 +756,7 @@ static ssize_t edt_ft5x06_debugfs_raw_data_read(struct file *file,
} while (--retries > 0);
if (retries == 0) {
- dev_err(&client->dev,
+ dev_err(tsdata->dev,
"timed out waiting for register to settle\n");
return -ETIMEDOUT;
}
@@ -793,7 +788,10 @@ static const struct file_operations debugfs_raw_data_fops = {
static void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata)
{
- struct dentry *debug_dir = tsdata->client->debugfs;
+ struct dentry *debug_dir = tsdata->debugfs;
+
+ if (!debug_dir)
+ return;
debugfs_create_u16("num_x", S_IRUSR, debug_dir, &tsdata->num_x);
debugfs_create_u16("num_y", S_IRUSR, debug_dir, &tsdata->num_y);
@@ -829,9 +827,9 @@ static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
#endif /* CONFIG_DEBUGFS */
-static int edt_ft5x06_ts_identify(struct i2c_client *client,
- struct edt_ft5x06_ts_data *tsdata)
+static int edt_ft5x06_ts_identify(struct edt_ft5x06_ts_data *tsdata)
{
+ struct i2c_client *client;
u8 rdbuf[EDT_NAME_LEN];
char *p;
int error;
@@ -854,6 +852,11 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
if (!strncasecmp(rdbuf + 1, "EP0", 3)) {
tsdata->version = EDT_M06;
+ /* M06 uses its own register protocol, only over I2C */
+ client = i2c_verify_client(tsdata->dev);
+ if (!client)
+ return -ENODEV;
+
/* remove last '$' end marker */
rdbuf[EDT_NAME_LEN - 1] = '\0';
if (rdbuf[EDT_NAME_LEN - 2] == '$')
@@ -870,7 +873,7 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
tsdata->regmap = regmap_init_i2c(client,
&edt_M06_i2c_regmap_config);
if (IS_ERR(tsdata->regmap)) {
- dev_err(&client->dev, "regmap allocation failed\n");
+ dev_err(tsdata->dev, "regmap allocation failed\n");
return PTR_ERR(tsdata->regmap);
}
} else if (!strncasecmp(rdbuf, "EP0", 3)) {
@@ -1113,10 +1116,10 @@ static void edt_ft5x06_disable_regulators(void *arg)
regulator_disable(data->iovcc);
}
-static int edt_ft5x06_ts_probe(struct i2c_client *client)
+int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
+ const struct edt_ft5x06_chip_data *chip_data, u16 bustype,
+ struct dentry *debugfs)
{
- const struct i2c_device_id *id = i2c_client_get_device_id(client);
- const struct edt_i2c_chip_data *chip_data;
struct edt_ft5x06_ts_data *tsdata;
unsigned int val;
struct input_dev *input;
@@ -1124,57 +1127,50 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
int error;
u32 report_rate;
- dev_dbg(&client->dev, "probing for EDT FT5x06 I2C\n");
-
- tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL);
+ tsdata = devm_kzalloc(dev, sizeof(*tsdata), GFP_KERNEL);
if (!tsdata) {
- dev_err(&client->dev, "failed to allocate driver data.\n");
+ regmap_exit(regmap);
return -ENOMEM;
}
- tsdata->regmap = regmap_init_i2c(client, &edt_ft5x06_i2c_regmap_config);
- if (IS_ERR(tsdata->regmap)) {
- dev_err(&client->dev, "regmap allocation failed\n");
- return PTR_ERR(tsdata->regmap);
- }
+ tsdata->dev = dev;
+ tsdata->irq = irq;
+ tsdata->debugfs = debugfs;
+ tsdata->regmap = regmap;
/*
- * We are not using devm_regmap_init_i2c() and instead install a
- * custom action because we may replace regmap with M06-specific one
- * and we need to make sure that it will not be released too early.
+ * The bus driver hands over a regmap that is not device managed
+ * because we may replace it with the M06-specific one and we need to
+ * make sure that it will not be released too early.
*/
- error = devm_add_action_or_reset(&client->dev, edt_ft5x06_exit_regmap,
- tsdata);
+ error = devm_add_action_or_reset(dev, edt_ft5x06_exit_regmap, tsdata);
if (error)
return error;
- chip_data = device_get_match_data(&client->dev);
- if (!chip_data)
- chip_data = (const struct edt_i2c_chip_data *)id->driver_data;
if (!chip_data || !chip_data->max_support_points) {
- dev_err(&client->dev, "invalid or missing chip data\n");
+ dev_err(dev, "invalid or missing chip data\n");
return -EINVAL;
}
tsdata->max_support_points = chip_data->max_support_points;
- tsdata->vcc = devm_regulator_get(&client->dev, "vcc");
+ tsdata->vcc = devm_regulator_get(dev, "vcc");
if (IS_ERR(tsdata->vcc))
- return dev_err_probe(&client->dev, PTR_ERR(tsdata->vcc),
+ return dev_err_probe(dev, PTR_ERR(tsdata->vcc),
"failed to request regulator\n");
- tsdata->iovcc = devm_regulator_get(&client->dev, "iovcc");
+ tsdata->iovcc = devm_regulator_get(dev, "iovcc");
if (IS_ERR(tsdata->iovcc)) {
error = PTR_ERR(tsdata->iovcc);
if (error != -EPROBE_DEFER)
- dev_err(&client->dev,
+ dev_err(dev,
"failed to request iovcc regulator: %d\n", error);
return error;
}
error = regulator_enable(tsdata->iovcc);
if (error < 0) {
- dev_err(&client->dev, "failed to enable iovcc: %d\n", error);
+ dev_err(dev, "failed to enable iovcc: %d\n", error);
return error;
}
@@ -1183,32 +1179,30 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
error = regulator_enable(tsdata->vcc);
if (error < 0) {
- dev_err(&client->dev, "failed to enable vcc: %d\n", error);
+ dev_err(dev, "failed to enable vcc: %d\n", error);
regulator_disable(tsdata->iovcc);
return error;
}
- error = devm_add_action_or_reset(&client->dev,
- edt_ft5x06_disable_regulators,
+ error = devm_add_action_or_reset(dev, edt_ft5x06_disable_regulators,
tsdata);
if (error)
return error;
- tsdata->reset_gpio = devm_gpiod_get_optional(&client->dev,
- "reset", GPIOD_OUT_HIGH);
+ tsdata->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
if (IS_ERR(tsdata->reset_gpio)) {
error = PTR_ERR(tsdata->reset_gpio);
- dev_err(&client->dev,
- "Failed to request GPIO reset pin, error %d\n", error);
+ dev_err(dev, "Failed to request GPIO reset pin, error %d\n",
+ error);
return error;
}
- tsdata->wake_gpio = devm_gpiod_get_optional(&client->dev,
- "wake", GPIOD_OUT_LOW);
+ tsdata->wake_gpio = devm_gpiod_get_optional(dev, "wake", GPIOD_OUT_LOW);
if (IS_ERR(tsdata->wake_gpio)) {
error = PTR_ERR(tsdata->wake_gpio);
- dev_err(&client->dev,
- "Failed to request GPIO wake pin, error %d\n", error);
+ dev_err(dev, "Failed to request GPIO wake pin, error %d\n",
+ error);
return error;
}
@@ -1237,21 +1231,20 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
msleep(300);
}
- input = devm_input_allocate_device(&client->dev);
+ input = devm_input_allocate_device(dev);
if (!input) {
- dev_err(&client->dev, "failed to allocate input device.\n");
+ dev_err(dev, "failed to allocate input device.\n");
return -ENOMEM;
}
mutex_init(&tsdata->mutex);
- tsdata->client = client;
tsdata->input = input;
tsdata->factory_mode = false;
- i2c_set_clientdata(client, tsdata);
+ dev_set_drvdata(dev, tsdata);
- error = edt_ft5x06_ts_identify(client, tsdata);
+ error = edt_ft5x06_ts_identify(tsdata);
if (error) {
- dev_err(&client->dev, "touchscreen probe failed\n");
+ dev_err(dev, "touchscreen probe failed\n");
return error;
}
@@ -1263,19 +1256,18 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
edt_ft5x06_ts_set_tdata_parameters(tsdata);
edt_ft5x06_ts_set_regs(tsdata);
- edt_ft5x06_ts_get_defaults(&client->dev, tsdata);
+ edt_ft5x06_ts_get_defaults(dev, tsdata);
edt_ft5x06_ts_get_parameters(tsdata);
if (tsdata->reg_addr.reg_report_rate != NO_REGISTER &&
- !device_property_read_u32(&client->dev,
- "report-rate-hz", &report_rate)) {
+ !device_property_read_u32(dev, "report-rate-hz", &report_rate)) {
if (tsdata->version == EDT_M06)
tsdata->report_rate = clamp_val(report_rate, 30, 140);
else
tsdata->report_rate = clamp_val(report_rate, 1, 255);
if (report_rate != tsdata->report_rate)
- dev_warn(&client->dev,
+ dev_warn(dev,
"report-rate %dHz is unsupported, use %dHz\n",
report_rate, tsdata->report_rate);
@@ -1286,12 +1278,11 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
tsdata->report_rate);
}
- dev_dbg(&client->dev,
- "Model \"%s\", Rev. \"%s\", %dx%d sensors\n",
+ dev_dbg(dev, "Model \"%s\", Rev. \"%s\", %dx%d sensors\n",
tsdata->name, tsdata->fw_version, tsdata->num_x, tsdata->num_y);
input->name = tsdata->name;
- input->id.bustype = BUS_I2C;
+ input->id.bustype = bustype;
input_set_abs_params(input, ABS_MT_POSITION_X,
0, tsdata->num_x * 64 - 1, 0, 0);
@@ -1303,20 +1294,19 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
error = input_mt_init_slots(input, tsdata->max_support_points,
INPUT_MT_DIRECT);
if (error) {
- dev_err(&client->dev, "Unable to init MT slots.\n");
+ dev_err(dev, "Unable to init MT slots.\n");
return error;
}
- irq_flags = irq_get_trigger_type(client->irq);
+ irq_flags = irq_get_trigger_type(irq);
if (irq_flags == IRQF_TRIGGER_NONE)
irq_flags = IRQF_TRIGGER_FALLING;
irq_flags |= IRQF_ONESHOT;
- error = devm_request_threaded_irq(&client->dev, client->irq,
- NULL, edt_ft5x06_ts_isr, irq_flags,
- client->name, tsdata);
+ error = devm_request_threaded_irq(dev, irq, NULL, edt_ft5x06_ts_isr,
+ irq_flags, dev_name(dev), tsdata);
if (error) {
- dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
+ dev_err(dev, "Unable to request touchscreen IRQ.\n");
return error;
}
@@ -1326,26 +1316,27 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
edt_ft5x06_ts_prepare_debugfs(tsdata);
- dev_dbg(&client->dev,
+ dev_dbg(dev,
"EDT FT5x06 initialized: IRQ %d, WAKE pin %d, Reset pin %d.\n",
- client->irq,
+ irq,
tsdata->wake_gpio ? desc_to_gpio(tsdata->wake_gpio) : -1,
tsdata->reset_gpio ? desc_to_gpio(tsdata->reset_gpio) : -1);
return 0;
}
+EXPORT_SYMBOL_NS_GPL(edt_ft5x06_probe, "EDT_FT5X06");
-static void edt_ft5x06_ts_remove(struct i2c_client *client)
+void edt_ft5x06_remove(struct device *dev)
{
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
edt_ft5x06_ts_teardown_debugfs(tsdata);
}
+EXPORT_SYMBOL_NS_GPL(edt_ft5x06_remove, "EDT_FT5X06");
static int edt_ft5x06_ts_suspend(struct device *dev)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
struct gpio_desc *reset_gpio = tsdata->reset_gpio;
int ret;
@@ -1370,7 +1361,7 @@ static int edt_ft5x06_ts_suspend(struct device *dev)
* settings. Disable the irq to avoid adjusting each host till the
* device is back in a full functional state.
*/
- disable_irq(tsdata->client->irq);
+ disable_irq(tsdata->irq);
gpiod_set_value_cansleep(reset_gpio, 1);
usleep_range(1000, 2000);
@@ -1387,8 +1378,7 @@ static int edt_ft5x06_ts_suspend(struct device *dev)
static int edt_ft5x06_ts_resume(struct device *dev)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
+ struct edt_ft5x06_ts_data *tsdata = dev_get_drvdata(dev);
int ret = 0;
if (device_may_wakeup(dev))
@@ -1432,7 +1422,7 @@ static int edt_ft5x06_ts_resume(struct device *dev)
msleep(300);
edt_ft5x06_restore_reg_parameters(tsdata);
- enable_irq(tsdata->client->irq);
+ enable_irq(tsdata->irq);
if (tsdata->factory_mode)
ret = edt_ft5x06_factory_mode(tsdata);
@@ -1447,38 +1437,38 @@ static int edt_ft5x06_ts_resume(struct device *dev)
return ret;
}
-static DEFINE_SIMPLE_DEV_PM_OPS(edt_ft5x06_ts_pm_ops,
- edt_ft5x06_ts_suspend, edt_ft5x06_ts_resume);
+EXPORT_NS_GPL_SIMPLE_DEV_PM_OPS(edt_ft5x06_pm_ops, edt_ft5x06_ts_suspend,
+ edt_ft5x06_ts_resume, EDT_FT5X06);
-static const struct edt_i2c_chip_data edt_ft5x06_data = {
+static const struct edt_ft5x06_chip_data edt_ft5x06_data = {
.max_support_points = 5,
};
-static const struct edt_i2c_chip_data edt_ft3518_data = {
+static const struct edt_ft5x06_chip_data edt_ft3518_data = {
.max_support_points = 10,
};
-static const struct edt_i2c_chip_data edt_ft5452_data = {
+static const struct edt_ft5x06_chip_data edt_ft5452_data = {
.max_support_points = 5,
};
-static const struct edt_i2c_chip_data edt_ft5506_data = {
+static const struct edt_ft5x06_chip_data edt_ft5506_data = {
.max_support_points = 10,
};
-static const struct edt_i2c_chip_data edt_ft6236_data = {
+static const struct edt_ft5x06_chip_data edt_ft6236_data = {
.max_support_points = 2,
};
-static const struct edt_i2c_chip_data edt_ft8201_data = {
+static const struct edt_ft5x06_chip_data edt_ft8201_data = {
.max_support_points = 10,
};
-static const struct edt_i2c_chip_data edt_ft8716_data = {
+static const struct edt_ft5x06_chip_data edt_ft8716_data = {
.max_support_points = 10,
};
-static const struct edt_i2c_chip_data edt_ft8719_data = {
+static const struct edt_ft5x06_chip_data edt_ft8719_data = {
.max_support_points = 10,
};
@@ -1515,17 +1505,49 @@ static const struct of_device_id edt_ft5x06_of_match[] = {
};
MODULE_DEVICE_TABLE(of, edt_ft5x06_of_match);
+static const struct regmap_config edt_ft5x06_i2c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static int edt_ft5x06_i2c_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ const struct edt_ft5x06_chip_data *chip_data;
+ struct regmap *regmap;
+
+ dev_dbg(&client->dev, "probing for EDT FT5x06 I2C\n");
+
+ chip_data = device_get_match_data(&client->dev);
+ if (!chip_data && id)
+ chip_data = (const struct edt_ft5x06_chip_data *)id->driver_data;
+
+ regmap = regmap_init_i2c(client, &edt_ft5x06_i2c_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "regmap allocation failed\n");
+ return PTR_ERR(regmap);
+ }
+
+ return edt_ft5x06_probe(&client->dev, regmap, client->irq, chip_data,
+ BUS_I2C, client->debugfs);
+}
+
+static void edt_ft5x06_i2c_remove(struct i2c_client *client)
+{
+ edt_ft5x06_remove(&client->dev);
+}
+
static struct i2c_driver edt_ft5x06_ts_driver = {
.driver = {
.name = "edt_ft5x06",
.dev_groups = edt_ft5x06_groups,
.of_match_table = edt_ft5x06_of_match,
- .pm = pm_sleep_ptr(&edt_ft5x06_ts_pm_ops),
+ .pm = pm_sleep_ptr(&edt_ft5x06_pm_ops),
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.id_table = edt_ft5x06_ts_id,
- .probe = edt_ft5x06_ts_probe,
- .remove = edt_ft5x06_ts_remove,
+ .probe = edt_ft5x06_i2c_probe,
+ .remove = edt_ft5x06_i2c_remove,
};
module_i2c_driver(edt_ft5x06_ts_driver);
diff --git a/drivers/input/touchscreen/edt-ft5x06.h b/drivers/input/touchscreen/edt-ft5x06.h
new file mode 100644
index 000000000000..deadc756cd74
--- /dev/null
+++ b/drivers/input/touchscreen/edt-ft5x06.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Bus independent core of the EDT FT5x06 touchscreen driver
+ *
+ * Copyright (C) 2026 Luka Panio, <lukapanio@gmail.com>
+ */
+#ifndef _EDT_FT5X06_H
+#define _EDT_FT5X06_H
+
+#include <linux/types.h>
+
+struct attribute_group;
+struct dentry;
+struct dev_pm_ops;
+struct device;
+struct regmap;
+
+struct edt_ft5x06_chip_data {
+ int max_support_points;
+};
+
+int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
+ const struct edt_ft5x06_chip_data *chip_data, u16 bustype,
+ struct dentry *debugfs);
+void edt_ft5x06_remove(struct device *dev);
+
+extern const struct dev_pm_ops edt_ft5x06_pm_ops;
+extern const struct attribute_group *edt_ft5x06_groups[];
+
+#endif
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/7] Input: edt-ft5x06 - move the controller bring-up into a helper
2026-10-17 14:07 [PATCH 0/7] Input: edt-ft5x06 - add SPI transport and FocalTech FT8722 support Luka Panio
2026-10-17 14:07 ` [PATCH 1/7] Input: edt-ft5x06 - split the bus independent core from the I2C driver Luka Panio
@ 2026-10-17 14:07 ` Luka Panio
2026-10-17 14:07 ` [PATCH 3/7] Input: edt-ft5x06 - add an optional controller boot hook Luka Panio
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Luka Panio @ 2026-10-17 14:07 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Douglas Anderson, linux-input, devicetree, dri-devel,
linux-kernel, Luka Panio
Move the part of probe that takes the controller out of reset,
identifies it and registers the input device into a helper, so it
can also be run after the probe.
Signed-off-by: Luka Panio <lukapanio@gmail.com>
---
drivers/input/touchscreen/edt-ft5x06.c | 154 ++++++++++++++-----------
1 file changed, 86 insertions(+), 68 deletions(-)
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index 543cc62088c7..536d633d97a7 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -1116,16 +1116,96 @@ static void edt_ft5x06_disable_regulators(void *arg)
regulator_disable(data->iovcc);
}
+/*
+ * Take the controller out of reset, identify it and register the input
+ * device.
+ */
+static int edt_ft5x06_ts_init_hw(struct edt_ft5x06_ts_data *tsdata)
+{
+ struct device *dev = tsdata->dev;
+ struct input_dev *input = tsdata->input;
+ unsigned int val;
+ u32 report_rate;
+ int error;
+
+ if (tsdata->reset_gpio) {
+ usleep_range(5000, 6000);
+ gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
+ msleep(300);
+ }
+
+ error = edt_ft5x06_ts_identify(tsdata);
+ if (error) {
+ dev_err(dev, "touchscreen probe failed\n");
+ return error;
+ }
+
+ /*
+ * Dummy read access. EP0700MLP1 returns bogus data on the first
+ * register read access and ignores writes.
+ */
+ regmap_read(tsdata->regmap, 0x00, &val);
+
+ edt_ft5x06_ts_set_tdata_parameters(tsdata);
+ edt_ft5x06_ts_set_regs(tsdata);
+ edt_ft5x06_ts_get_defaults(dev, tsdata);
+ edt_ft5x06_ts_get_parameters(tsdata);
+
+ if (tsdata->reg_addr.reg_report_rate != NO_REGISTER &&
+ !device_property_read_u32(dev, "report-rate-hz", &report_rate)) {
+ if (tsdata->version == EDT_M06)
+ tsdata->report_rate = clamp_val(report_rate, 30, 140);
+ else
+ tsdata->report_rate = clamp_val(report_rate, 1, 255);
+
+ if (report_rate != tsdata->report_rate)
+ dev_warn(dev,
+ "report-rate %dHz is unsupported, use %dHz\n",
+ report_rate, tsdata->report_rate);
+
+ if (tsdata->version == EDT_M06)
+ tsdata->report_rate /= 10;
+
+ regmap_write(tsdata->regmap, tsdata->reg_addr.reg_report_rate,
+ tsdata->report_rate);
+ }
+
+ dev_dbg(dev, "Model \"%s\", Rev. \"%s\", %dx%d sensors\n",
+ tsdata->name, tsdata->fw_version, tsdata->num_x, tsdata->num_y);
+
+ input->name = tsdata->name;
+
+ input_set_abs_params(input, ABS_MT_POSITION_X,
+ 0, tsdata->num_x * 64 - 1, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y,
+ 0, tsdata->num_y * 64 - 1, 0, 0);
+
+ touchscreen_parse_properties(input, true, &tsdata->prop);
+
+ error = input_mt_init_slots(input, tsdata->max_support_points,
+ INPUT_MT_DIRECT);
+ if (error) {
+ dev_err(dev, "Unable to init MT slots.\n");
+ return error;
+ }
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ edt_ft5x06_ts_prepare_debugfs(tsdata);
+
+ return 0;
+}
+
int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
const struct edt_ft5x06_chip_data *chip_data, u16 bustype,
struct dentry *debugfs)
{
struct edt_ft5x06_ts_data *tsdata;
- unsigned int val;
struct input_dev *input;
unsigned long irq_flags;
int error;
- u32 report_rate;
tsdata = devm_kzalloc(dev, sizeof(*tsdata), GFP_KERNEL);
if (!tsdata) {
@@ -1225,78 +1305,22 @@ int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
usleep_range(5000, 6000);
}
- if (tsdata->reset_gpio) {
- usleep_range(5000, 6000);
- gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
- msleep(300);
- }
-
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device.\n");
return -ENOMEM;
}
+ input->id.bustype = bustype;
+
mutex_init(&tsdata->mutex);
tsdata->input = input;
tsdata->factory_mode = false;
dev_set_drvdata(dev, tsdata);
- error = edt_ft5x06_ts_identify(tsdata);
- if (error) {
- dev_err(dev, "touchscreen probe failed\n");
- return error;
- }
-
- /*
- * Dummy read access. EP0700MLP1 returns bogus data on the first
- * register read access and ignores writes.
- */
- regmap_read(tsdata->regmap, 0x00, &val);
-
- edt_ft5x06_ts_set_tdata_parameters(tsdata);
- edt_ft5x06_ts_set_regs(tsdata);
- edt_ft5x06_ts_get_defaults(dev, tsdata);
- edt_ft5x06_ts_get_parameters(tsdata);
-
- if (tsdata->reg_addr.reg_report_rate != NO_REGISTER &&
- !device_property_read_u32(dev, "report-rate-hz", &report_rate)) {
- if (tsdata->version == EDT_M06)
- tsdata->report_rate = clamp_val(report_rate, 30, 140);
- else
- tsdata->report_rate = clamp_val(report_rate, 1, 255);
-
- if (report_rate != tsdata->report_rate)
- dev_warn(dev,
- "report-rate %dHz is unsupported, use %dHz\n",
- report_rate, tsdata->report_rate);
-
- if (tsdata->version == EDT_M06)
- tsdata->report_rate /= 10;
-
- regmap_write(tsdata->regmap, tsdata->reg_addr.reg_report_rate,
- tsdata->report_rate);
- }
-
- dev_dbg(dev, "Model \"%s\", Rev. \"%s\", %dx%d sensors\n",
- tsdata->name, tsdata->fw_version, tsdata->num_x, tsdata->num_y);
-
- input->name = tsdata->name;
- input->id.bustype = bustype;
-
- input_set_abs_params(input, ABS_MT_POSITION_X,
- 0, tsdata->num_x * 64 - 1, 0, 0);
- input_set_abs_params(input, ABS_MT_POSITION_Y,
- 0, tsdata->num_y * 64 - 1, 0, 0);
-
- touchscreen_parse_properties(input, true, &tsdata->prop);
-
- error = input_mt_init_slots(input, tsdata->max_support_points,
- INPUT_MT_DIRECT);
- if (error) {
- dev_err(dev, "Unable to init MT slots.\n");
+ error = edt_ft5x06_ts_init_hw(tsdata);
+ if (error)
return error;
- }
irq_flags = irq_get_trigger_type(irq);
if (irq_flags == IRQF_TRIGGER_NONE)
@@ -1310,12 +1334,6 @@ int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
return error;
}
- error = input_register_device(input);
- if (error)
- return error;
-
- edt_ft5x06_ts_prepare_debugfs(tsdata);
-
dev_dbg(dev,
"EDT FT5x06 initialized: IRQ %d, WAKE pin %d, Reset pin %d.\n",
irq,
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 3/7] Input: edt-ft5x06 - add an optional controller boot hook
2026-10-17 14:07 [PATCH 0/7] Input: edt-ft5x06 - add SPI transport and FocalTech FT8722 support Luka Panio
2026-10-17 14:07 ` [PATCH 1/7] Input: edt-ft5x06 - split the bus independent core from the I2C driver Luka Panio
2026-10-17 14:07 ` [PATCH 2/7] Input: edt-ft5x06 - move the controller bring-up into a helper Luka Panio
@ 2026-10-17 14:07 ` Luka Panio
2026-10-17 14:07 ` [PATCH 4/7] Input: edt-ft5x06 - allow a chip specific touch data register Luka Panio
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Luka Panio @ 2026-10-17 14:07 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Douglas Anderson, linux-input, devicetree, dri-devel,
linux-kernel, Luka Panio
Some FocalTech TDDI controllers have no flash and run their firmware
from RAM, which is lost whenever the controller is reset or powered
down. Allow the chip data to provide a boot callback that is run right
after the controller is taken out of reset, both at probe time before
the controller is identified and when resuming from the power-off sleep
mode before the register settings are restored.
The boot ROM of these controllers only accepts commands shortly after
reset, so skip the 300 ms settle delay for them and pass the reset GPIO
to the callback, letting it restart the boot ROM when it retries.
Signed-off-by: Luka Panio <lukapanio@gmail.com>
---
drivers/input/touchscreen/edt-ft5x06.c | 25 +++++++++++++++++++++++--
drivers/input/touchscreen/edt-ft5x06.h | 2 ++
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index 536d633d97a7..e57611e48217 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -148,6 +148,7 @@ struct edt_ft5x06_ts_data {
struct edt_reg_addr reg_addr;
enum edt_ver version;
+ int (*boot)(struct device *dev, struct gpio_desc *reset_gpio);
unsigned int crc_errors;
unsigned int header_errors;
};
@@ -1131,7 +1132,16 @@ static int edt_ft5x06_ts_init_hw(struct edt_ft5x06_ts_data *tsdata)
if (tsdata->reset_gpio) {
usleep_range(5000, 6000);
gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
- msleep(300);
+ /* Controllers with a boot callback handle their own timing */
+ if (!tsdata->boot)
+ msleep(300);
+ }
+
+ if (tsdata->boot) {
+ error = tsdata->boot(dev, tsdata->reset_gpio);
+ if (error)
+ return dev_err_probe(dev, error,
+ "failed to boot the controller\n");
}
error = edt_ft5x06_ts_identify(tsdata);
@@ -1233,6 +1243,7 @@ int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
}
tsdata->max_support_points = chip_data->max_support_points;
+ tsdata->boot = chip_data->boot;
tsdata->vcc = devm_regulator_get(dev, "vcc");
if (IS_ERR(tsdata->vcc))
@@ -1437,7 +1448,17 @@ static int edt_ft5x06_ts_resume(struct device *dev)
usleep_range(1000, 2000);
gpiod_set_value_cansleep(reset_gpio, 0);
- msleep(300);
+
+ if (tsdata->boot) {
+ ret = tsdata->boot(dev, reset_gpio);
+ if (ret) {
+ dev_err(dev, "Failed to boot the controller: %d\n",
+ ret);
+ return ret;
+ }
+ } else {
+ msleep(300);
+ }
edt_ft5x06_restore_reg_parameters(tsdata);
enable_irq(tsdata->irq);
diff --git a/drivers/input/touchscreen/edt-ft5x06.h b/drivers/input/touchscreen/edt-ft5x06.h
index deadc756cd74..7eaeb562d11f 100644
--- a/drivers/input/touchscreen/edt-ft5x06.h
+++ b/drivers/input/touchscreen/edt-ft5x06.h
@@ -13,10 +13,12 @@ struct attribute_group;
struct dentry;
struct dev_pm_ops;
struct device;
+struct gpio_desc;
struct regmap;
struct edt_ft5x06_chip_data {
int max_support_points;
+ int (*boot)(struct device *dev, struct gpio_desc *reset_gpio);
};
int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 4/7] Input: edt-ft5x06 - allow a chip specific touch data register
2026-10-17 14:07 [PATCH 0/7] Input: edt-ft5x06 - add SPI transport and FocalTech FT8722 support Luka Panio
` (2 preceding siblings ...)
2026-10-17 14:07 ` [PATCH 3/7] Input: edt-ft5x06 - add an optional controller boot hook Luka Panio
@ 2026-10-17 14:07 ` Luka Panio
2026-10-17 14:07 ` [PATCH 5/7] dt-bindings: input: touchscreen: edt-ft5x06: Add FocalTech FT8722 Luka Panio
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Luka Panio @ 2026-10-17 14:07 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Douglas Anderson, linux-input, devicetree, dri-devel,
linux-kernel, Luka Panio
The generic FocalTech path reads the touch report starting at register
0x00 and expects the points from register 0x03 onwards. Over SPI the
FT8722 only returns the report when the read starts at register 0x01;
a read from 0x00 returns the static register block instead, the
interrupt is never acknowledged and no further touch events arrive.
Let the chip data select the register the report is read from, keeping
the points at register 0x03.
Signed-off-by: Luka Panio <lukapanio@gmail.com>
---
drivers/input/touchscreen/edt-ft5x06.c | 6 ++++--
drivers/input/touchscreen/edt-ft5x06.h | 1 +
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index e57611e48217..7bcc48e07d62 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -149,6 +149,7 @@ struct edt_ft5x06_ts_data {
struct edt_reg_addr reg_addr;
enum edt_ver version;
int (*boot)(struct device *dev, struct gpio_desc *reset_gpio);
+ u8 tdata_reg;
unsigned int crc_errors;
unsigned int header_errors;
};
@@ -1037,8 +1038,8 @@ static void edt_ft5x06_ts_set_tdata_parameters(struct edt_ft5x06_ts_data *tsdata
tsdata->point_len = 4;
crclen = 1;
} else {
- tsdata->tdata_cmd = 0x0;
- tsdata->tdata_offset = 3;
+ tsdata->tdata_cmd = tsdata->tdata_reg;
+ tsdata->tdata_offset = 3 - tsdata->tdata_reg;
tsdata->point_len = 6;
crclen = 0;
}
@@ -1244,6 +1245,7 @@ int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
tsdata->max_support_points = chip_data->max_support_points;
tsdata->boot = chip_data->boot;
+ tsdata->tdata_reg = chip_data->tdata_reg;
tsdata->vcc = devm_regulator_get(dev, "vcc");
if (IS_ERR(tsdata->vcc))
diff --git a/drivers/input/touchscreen/edt-ft5x06.h b/drivers/input/touchscreen/edt-ft5x06.h
index 7eaeb562d11f..c55686e288b6 100644
--- a/drivers/input/touchscreen/edt-ft5x06.h
+++ b/drivers/input/touchscreen/edt-ft5x06.h
@@ -18,6 +18,7 @@ struct regmap;
struct edt_ft5x06_chip_data {
int max_support_points;
+ u8 tdata_reg;
int (*boot)(struct device *dev, struct gpio_desc *reset_gpio);
};
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 5/7] dt-bindings: input: touchscreen: edt-ft5x06: Add FocalTech FT8722
2026-10-17 14:07 [PATCH 0/7] Input: edt-ft5x06 - add SPI transport and FocalTech FT8722 support Luka Panio
` (3 preceding siblings ...)
2026-10-17 14:07 ` [PATCH 4/7] Input: edt-ft5x06 - allow a chip specific touch data register Luka Panio
@ 2026-10-17 14:07 ` Luka Panio
2026-10-17 14:07 ` [PATCH 6/7] Input: edt-ft5x06 - add SPI transport and FT8722 support Luka Panio
2026-10-17 14:07 ` [PATCH 7/7] Input: edt-ft5x06 - support following a DRM panel Luka Panio
6 siblings, 0 replies; 8+ messages in thread
From: Luka Panio @ 2026-10-17 14:07 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Douglas Anderson, linux-input, devicetree, dri-devel,
linux-kernel, Luka Panio
The FT8722 is a touch and display driver (TDDI) whose touch controller
is connected over SPI and runs its firmware from RAM. Document its
compatible, allow the SPI peripheral properties and add a
firmware-name property for the firmware to download at power up.
The "-ts" suffix keeps the compatible distinct from the one of the
display side of the same chip, which is a separate DSI device. The
reset GPIO is required, as the firmware can only be downloaded while
the boot ROM is running right after a reset.
Signed-off-by: Luka Panio <lukapanio@gmail.com>
---
.../input/touchscreen/edt-ft5x06.yaml | 37 +++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.yaml b/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.yaml
index 68b2f1601654..51d22ff42675 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.yaml
+++ b/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.yaml
@@ -19,6 +19,7 @@ maintainers:
allOf:
- $ref: touchscreen.yaml#
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
- if:
properties:
compatible:
@@ -30,6 +31,17 @@ allOf:
properties:
offset-x: true
offset-y: true
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: focaltech,ft8722-ts
+ then:
+ required:
+ - reset-gpios
+ else:
+ properties:
+ firmware-name: false
properties:
compatible:
@@ -47,6 +59,7 @@ properties:
- focaltech,ft8201
- focaltech,ft8716
- focaltech,ft8719
+ - focaltech,ft8722-ts
- items:
- const: focaltech,ft3519
- const: focaltech,ft3518
@@ -60,6 +73,12 @@ properties:
reset-gpios:
maxItems: 1
+ firmware-name:
+ maxItems: 1
+ description:
+ Firmware to download at power up, for controllers that run their
+ firmware from RAM, such as the FT8722.
+
wake-gpios:
maxItems: 1
@@ -134,5 +153,23 @@ examples:
wake-gpios = <&gpio4 9 GPIO_ACTIVE_HIGH>;
};
};
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ touchscreen@0 {
+ compatible = "focaltech,ft8722-ts";
+ reg = <0>;
+ spi-max-frequency = <6000000>;
+ interrupt-parent = <&tlmm>;
+ interrupts = <81 IRQ_TYPE_EDGE_FALLING>;
+ reset-gpios = <&tlmm 105 GPIO_ACTIVE_LOW>;
+ firmware-name = "qcom/qcm6490/nexphone/ft8722.bin";
+ touchscreen-size-x = <1080>;
+ touchscreen-size-y = <2408>;
+ };
+ };
...
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 6/7] Input: edt-ft5x06 - add SPI transport and FT8722 support
2026-10-17 14:07 [PATCH 0/7] Input: edt-ft5x06 - add SPI transport and FocalTech FT8722 support Luka Panio
` (4 preceding siblings ...)
2026-10-17 14:07 ` [PATCH 5/7] dt-bindings: input: touchscreen: edt-ft5x06: Add FocalTech FT8722 Luka Panio
@ 2026-10-17 14:07 ` Luka Panio
2026-10-17 14:07 ` [PATCH 7/7] Input: edt-ft5x06 - support following a DRM panel Luka Panio
6 siblings, 0 replies; 8+ messages in thread
From: Luka Panio @ 2026-10-17 14:07 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Douglas Anderson, linux-input, devicetree, dri-devel,
linux-kernel, Luka Panio
Add an SPI bus driver and FT8722 support to edt-ft5x06.
The FT8722 TDDI has no flash, so add a boot hook that downloads the
firmware every time the controller comes out of reset: enter the boot
ROM, write the program and data RAM, verify both with the ROM's ECC
and start the application.
Signed-off-by: Luka Panio <lukapanio@gmail.com>
---
drivers/input/touchscreen/Kconfig | 14 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/edt-ft5x06-spi.c | 475 +++++++++++++++++++++
3 files changed, 490 insertions(+)
create mode 100644 drivers/input/touchscreen/edt-ft5x06-spi.c
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 9b9ae8ac3f7f..92840ea9c65f 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -805,6 +805,20 @@ config TOUCHSCREEN_EDT_FT5X06
To compile this driver as a module, choose M here: the
module will be called edt-ft5x06.
+config TOUCHSCREEN_EDT_FT5X06_SPI
+ tristate "EDT FocalTech FT5x06 SPI Touchscreen support"
+ depends on TOUCHSCREEN_EDT_FT5X06 && SPI
+ select CRC_CCITT
+ help
+ Say Y here if you have a FocalTech touchscreen controller based
+ on the FocalTech FT5x06 family of controllers that is connected over
+ SPI.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called edt-ft5x06-spi.
+
config TOUCHSCREEN_RASPBERRYPI_FW
tristate "Raspberry Pi's firmware base touch screen support"
depends on RASPBERRYPI_FIRMWARE || (RASPBERRYPI_FIRMWARE=n && COMPILE_TEST)
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index bfd9de83389d..263c7e8039af 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034-ts.o
obj-$(CONFIG_TOUCHSCREEN_DA9052) += da9052_tsi.o
obj-$(CONFIG_TOUCHSCREEN_DYNAPRO) += dynapro.o
obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06) += edt-ft5x06.o
+obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06_SPI) += edt-ft5x06-spi.o
obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE) += hampshire.o
obj-$(CONFIG_TOUCHSCREEN_HYCON_HY46XX) += hycon-hy46xx.o
obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o
diff --git a/drivers/input/touchscreen/edt-ft5x06-spi.c b/drivers/input/touchscreen/edt-ft5x06-spi.c
new file mode 100644
index 000000000000..d0ae0d4b621d
--- /dev/null
+++ b/drivers/input/touchscreen/edt-ft5x06-spi.c
@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Luka Panio, <lukapanio@gmail.com>
+ *
+ * SPI transport for FocalTech touchscreen controllers handled by the
+ * edt-ft5x06 driver, with firmware download for the RAM-only FT8722.
+ *
+ * Protocol and boot sequence based on the FocalTech vendor driver:
+ * Copyright (c) 2012-2020, FocalTech Systems, Ltd.
+ */
+
+#include <linux/crc-ccitt.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+
+#include "edt-ft5x06.h"
+
+#define FTS_SPI_CTRL_WRITE 0x00
+#define FTS_SPI_CTRL_READ BIT(7)
+#define FTS_SPI_CTRL_CRC BIT(5)
+#define FTS_SPI_STATUS_ERR (BIT(7) | BIT(5))
+#define FTS_SPI_HEADER_LEN 4
+#define FTS_SPI_DUMMY_LEN 3
+#define FTS_SPI_CRC_LEN 2
+#define FTS_SPI_RETRIES 3
+#define FTS_SPI_CS_HIGH_US 150
+
+/* Boot ROM commands */
+#define FTS_BOOT_CMD_START 0x55
+#define FTS_BOOT_CMD_READ_ID 0x90
+#define FTS_BOOT_CMD_START_APP 0x08
+#define FTS_BOOT_CMD_SET_ADDR 0xad
+#define FTS_BOOT_CMD_WRITE 0xae
+#define FTS_BOOT_CMD_ECC 0xcc
+#define FTS_BOOT_CMD_ECC_READ 0xcd
+#define FTS_BOOT_CMD_ECC_FINISH 0xce
+#define FTS_BOOT_ECC_FINISH_OK 0xa5
+#define FTS_BOOT_ECC_POLY 0x8408
+
+#define FTS_REG_CHIP_ID 0xa3
+
+/* Firmware image layout */
+#define FTS_FW_INFO_OFFSET 0x100
+#define FTS_FW_MIN_LEN 0x120
+
+#define FTS_BOOT_RETRIES 3
+#define FTS_ECC_RETRIES 100
+
+struct fts_ram_chip {
+ u8 boot_id[2];
+ u8 chip_id;
+ u8 len_unit;
+ u32 pram_max;
+ u32 dram_base;
+ u32 packet_len;
+ unsigned int boot_delay_ms;
+ const char *default_fw;
+};
+
+static int fts_spi_xfer(struct spi_device *spi, const u8 *tx, u8 *rx,
+ size_t len)
+{
+ struct spi_transfer xfer = {
+ .tx_buf = tx,
+ .rx_buf = rx,
+ .len = len,
+ };
+ int ret;
+
+ ret = spi_sync_transfer(spi, &xfer, 1);
+ fsleep(FTS_SPI_CS_HIGH_US);
+
+ return ret;
+}
+
+static int fts_spi_write(struct spi_device *spi, u8 reg, const void *val,
+ size_t len)
+{
+ size_t xlen = FTS_SPI_HEADER_LEN + (len ? FTS_SPI_DUMMY_LEN + len : 0);
+ u8 *tx, *rx;
+ int ret, i;
+
+ if (len > U16_MAX)
+ return -EINVAL;
+
+ tx = kzalloc(2 * xlen, GFP_KERNEL);
+ if (!tx)
+ return -ENOMEM;
+ rx = tx + xlen;
+
+ tx[0] = reg;
+ tx[1] = FTS_SPI_CTRL_WRITE;
+ put_unaligned_be16(len, &tx[2]);
+ if (len)
+ memcpy(&tx[FTS_SPI_HEADER_LEN + FTS_SPI_DUMMY_LEN], val, len);
+
+ for (i = 0; i < FTS_SPI_RETRIES; i++) {
+ ret = fts_spi_xfer(spi, tx, rx, xlen);
+ if (!ret && !(rx[3] & FTS_SPI_STATUS_ERR))
+ break;
+ if (!ret)
+ ret = -EIO;
+ }
+
+ kfree(tx);
+ return ret;
+}
+
+static int fts_spi_read(struct spi_device *spi, u8 reg, void *val,
+ size_t len)
+{
+ size_t dp = FTS_SPI_HEADER_LEN + FTS_SPI_DUMMY_LEN;
+ size_t xlen = dp + len + FTS_SPI_CRC_LEN;
+ u8 *tx, *rx;
+ int ret, i;
+
+ if (!len || len > U16_MAX)
+ return -EINVAL;
+
+ tx = kzalloc(2 * xlen, GFP_KERNEL);
+ if (!tx)
+ return -ENOMEM;
+ rx = tx + xlen;
+
+ tx[0] = reg;
+ tx[1] = FTS_SPI_CTRL_READ | FTS_SPI_CTRL_CRC;
+ put_unaligned_be16(len, &tx[2]);
+
+ for (i = 0; i < FTS_SPI_RETRIES; i++) {
+ ret = fts_spi_xfer(spi, tx, rx, xlen);
+ if (ret)
+ continue;
+
+ if (rx[3] & FTS_SPI_STATUS_ERR) {
+ ret = -EIO;
+ continue;
+ }
+
+ if (crc_ccitt(0xffff, &rx[dp], len) !=
+ get_unaligned_le16(&rx[dp + len])) {
+ ret = -EBADMSG;
+ continue;
+ }
+
+ memcpy(val, &rx[dp], len);
+ break;
+ }
+
+ kfree(tx);
+ return ret;
+}
+
+static int fts_spi_regmap_write(void *context, const void *data,
+ size_t count)
+{
+ const u8 *buf = data;
+
+ return fts_spi_write(context, buf[0], buf + 1, count - 1);
+}
+
+static int fts_spi_regmap_read(void *context, const void *reg,
+ size_t reg_size, void *val, size_t val_size)
+{
+ return fts_spi_read(context, *(const u8 *)reg, val, val_size);
+}
+
+static const struct regmap_bus fts_spi_regmap_bus = {
+ .write = fts_spi_regmap_write,
+ .read = fts_spi_regmap_read,
+};
+
+static const struct regmap_config fts_spi_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static int fts_boot_enter(struct spi_device *spi, struct gpio_desc *reset,
+ const struct fts_ram_chip *chip)
+{
+ u8 id[2] = { };
+ int ret, i;
+
+ for (i = 0; i < FTS_BOOT_RETRIES; i++) {
+ /* The boot ROM only listens shortly after reset */
+ if (i && reset) {
+ gpiod_set_value_cansleep(reset, 1);
+ usleep_range(1000, 2000);
+ gpiod_set_value_cansleep(reset, 0);
+ }
+ fsleep(chip->boot_delay_ms * USEC_PER_MSEC);
+
+ ret = fts_spi_write(spi, FTS_BOOT_CMD_START, NULL, 0);
+ if (ret)
+ continue;
+
+ fsleep(chip->boot_delay_ms * USEC_PER_MSEC);
+
+ ret = fts_spi_read(spi, FTS_BOOT_CMD_READ_ID, id, sizeof(id));
+ if (ret)
+ continue;
+
+ if (!memcmp(id, chip->boot_id, sizeof(id)))
+ return 0;
+
+ ret = -ENODEV;
+ }
+
+ dev_err(&spi->dev, "boot ROM not responding, last id %02x%02x: %d\n",
+ id[0], id[1], ret);
+ return ret;
+}
+
+static int fts_boot_write_ram(struct spi_device *spi,
+ const struct fts_ram_chip *chip, u32 base,
+ const u8 *data, u32 len)
+{
+ u8 addr[3];
+ u32 off, n;
+ int ret;
+
+ for (off = 0; off < len; off += n) {
+ n = min(len - off, chip->packet_len);
+
+ put_unaligned_be24(base + off, addr);
+ ret = fts_spi_write(spi, FTS_BOOT_CMD_SET_ADDR, addr,
+ sizeof(addr));
+ if (ret)
+ return ret;
+
+ ret = fts_spi_write(spi, FTS_BOOT_CMD_WRITE, data + off, n);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static u16 fts_boot_ecc(const u8 *data, u32 len)
+{
+ u16 ecc = 0;
+ u32 i;
+ int j;
+
+ for (i = 0; i + 1 < len; i += 2) {
+ ecc ^= get_unaligned_be16(data + i);
+ for (j = 0; j < 16; j++)
+ ecc = (ecc & 1) ? (ecc >> 1) ^ FTS_BOOT_ECC_POLY :
+ ecc >> 1;
+ }
+
+ return ecc;
+}
+
+static int fts_boot_check_ecc(struct spi_device *spi, const u8 *data,
+ u32 len)
+{
+ u8 cmd[6], val[2];
+ int ret, i;
+
+ put_unaligned_be24(0, &cmd[0]);
+ put_unaligned_be24(len, &cmd[3]);
+ ret = fts_spi_write(spi, FTS_BOOT_CMD_ECC, cmd, sizeof(cmd));
+ if (ret)
+ return ret;
+
+ usleep_range(2000, 3000);
+
+ for (i = 0; i < FTS_ECC_RETRIES; i++) {
+ ret = fts_spi_read(spi, FTS_BOOT_CMD_ECC_FINISH, val, 1);
+ if (ret)
+ return ret;
+ if (val[0] == FTS_BOOT_ECC_FINISH_OK)
+ break;
+ usleep_range(1000, 2000);
+ }
+ if (i == FTS_ECC_RETRIES)
+ return -ETIMEDOUT;
+
+ ret = fts_spi_read(spi, FTS_BOOT_CMD_ECC_READ, val, sizeof(val));
+ if (ret)
+ return ret;
+
+ if (get_unaligned_be16(val) != fts_boot_ecc(data, len)) {
+ dev_err(&spi->dev, "ECC mismatch: %04x != %04x\n",
+ get_unaligned_be16(val), fts_boot_ecc(data, len));
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static u32 fts_fw_len(const struct firmware *fw, unsigned int off,
+ const struct fts_ram_chip *chip)
+{
+ const u8 *info = fw->data + FTS_FW_INFO_OFFSET + off;
+ u16 len = get_unaligned_be16(info);
+
+ if ((u16)(len + get_unaligned_be16(info + 2)) != 0xffff)
+ return 0;
+
+ return len * chip->len_unit;
+}
+
+static int fts_ram_boot(struct device *dev, struct gpio_desc *reset,
+ const struct fts_ram_chip *chip)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ const struct firmware *fw;
+ const char *fw_name = chip->default_fw;
+ u32 pram_len, dram_len;
+ u8 id;
+ int ret, i;
+
+ device_property_read_string(dev, "firmware-name", &fw_name);
+
+ ret = request_firmware(&fw, fw_name, dev);
+ if (ret)
+ return ret;
+
+ ret = -EINVAL;
+ if (fw->size < FTS_FW_MIN_LEN) {
+ dev_err(dev, "firmware %s too short\n", fw_name);
+ goto out;
+ }
+
+ pram_len = fts_fw_len(fw, 0, chip);
+ dram_len = fts_fw_len(fw, 8, chip);
+ if (!pram_len || pram_len > chip->pram_max ||
+ pram_len + dram_len > fw->size) {
+ dev_err(dev, "invalid firmware %s\n", fw_name);
+ goto out;
+ }
+
+ ret = fts_boot_enter(spi, reset, chip);
+ if (ret)
+ goto out;
+
+ ret = fts_boot_write_ram(spi, chip, 0, fw->data, pram_len);
+ if (!ret)
+ ret = fts_boot_check_ecc(spi, fw->data, pram_len);
+ if (ret) {
+ dev_err(dev, "failed to load program RAM: %d\n", ret);
+ goto out;
+ }
+
+ if (dram_len) {
+ ret = fts_boot_write_ram(spi, chip, chip->dram_base,
+ fw->data + pram_len, dram_len);
+ if (!ret)
+ ret = fts_boot_check_ecc(spi, fw->data + pram_len,
+ dram_len);
+ if (ret) {
+ dev_err(dev, "failed to load data RAM: %d\n", ret);
+ goto out;
+ }
+ }
+
+ ret = fts_spi_write(spi, FTS_BOOT_CMD_START_APP, NULL, 0);
+ if (ret)
+ goto out;
+
+ /* Wait for the application to report the chip ID */
+ for (i = 0; i < 20; i++) {
+ fsleep(10 * USEC_PER_MSEC);
+ if (!fts_spi_read(spi, FTS_REG_CHIP_ID, &id, 1) &&
+ id == chip->chip_id)
+ break;
+ }
+ if (i == 20) {
+ dev_err(dev, "firmware did not start\n");
+ ret = -EIO;
+ goto out;
+ }
+
+ dev_dbg(dev, "loaded %s (%u + %u bytes)\n", fw_name, pram_len,
+ dram_len);
+out:
+ release_firmware(fw);
+ return ret;
+}
+
+static const struct fts_ram_chip fts_ft8722 = {
+ .boot_id = { 0x87, 0x22 },
+ .chip_id = 0x87,
+ .len_unit = 2,
+ .pram_max = 88 * 1024,
+ .dram_base = 0xd00000,
+ .packet_len = 32 * 1024 - 16,
+ .boot_delay_ms = 8,
+ .default_fw = "focaltech/ft8722.bin",
+};
+
+static int ft8722_boot(struct device *dev, struct gpio_desc *reset)
+{
+ return fts_ram_boot(dev, reset, &fts_ft8722);
+}
+
+static const struct edt_ft5x06_chip_data edt_ft8722_data = {
+ .max_support_points = 10,
+ .tdata_reg = 0x01,
+ .boot = ft8722_boot,
+};
+
+static int edt_ft5x06_spi_probe(struct spi_device *spi)
+{
+ const struct edt_ft5x06_chip_data *chip_data;
+ struct regmap *regmap;
+ int ret;
+
+ chip_data = spi_get_device_match_data(spi);
+ if (!chip_data)
+ return -ENODEV;
+
+ if (!spi->bits_per_word)
+ spi->bits_per_word = 8;
+ ret = spi_setup(spi);
+ if (ret)
+ return ret;
+
+ regmap = regmap_init(&spi->dev, &fts_spi_regmap_bus, spi,
+ &fts_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&spi->dev, PTR_ERR(regmap),
+ "failed to init regmap\n");
+
+ return edt_ft5x06_probe(&spi->dev, regmap, spi->irq, chip_data,
+ BUS_SPI, NULL);
+}
+
+static void edt_ft5x06_spi_remove(struct spi_device *spi)
+{
+ edt_ft5x06_remove(&spi->dev);
+}
+
+static const struct of_device_id edt_ft5x06_spi_of_match[] = {
+ { .compatible = "focaltech,ft8722-ts", .data = &edt_ft8722_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, edt_ft5x06_spi_of_match);
+
+static const struct spi_device_id edt_ft5x06_spi_ids[] = {
+ { "ft8722-ts", (kernel_ulong_t)&edt_ft8722_data },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, edt_ft5x06_spi_ids);
+
+static struct spi_driver edt_ft5x06_spi_driver = {
+ .driver = {
+ .name = "edt_ft5x06_spi",
+ .dev_groups = edt_ft5x06_groups,
+ .of_match_table = edt_ft5x06_spi_of_match,
+ .pm = pm_sleep_ptr(&edt_ft5x06_pm_ops),
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = edt_ft5x06_spi_ids,
+ .probe = edt_ft5x06_spi_probe,
+ .remove = edt_ft5x06_spi_remove,
+};
+module_spi_driver(edt_ft5x06_spi_driver);
+
+MODULE_IMPORT_NS("EDT_FT5X06");
+MODULE_AUTHOR("Luka Panio <lukapanio@gmail.com>");
+MODULE_DESCRIPTION("EDT FT5x06 SPI Touchscreen Driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("focaltech/ft8722.bin");
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 7/7] Input: edt-ft5x06 - support following a DRM panel
2026-10-17 14:07 [PATCH 0/7] Input: edt-ft5x06 - add SPI transport and FocalTech FT8722 support Luka Panio
` (5 preceding siblings ...)
2026-10-17 14:07 ` [PATCH 6/7] Input: edt-ft5x06 - add SPI transport and FT8722 support Luka Panio
@ 2026-10-17 14:07 ` Luka Panio
6 siblings, 0 replies; 8+ messages in thread
From: Luka Panio @ 2026-10-17 14:07 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Douglas Anderson, linux-input, devicetree, dri-devel,
linux-kernel, Luka Panio
On touch and display driver (TDDI) chips such as the FT8722 the touch
controller shares its supply and reset with the display panel, so it
can only be accessed while the panel is powered and loses its state,
and for RAM-only parts its firmware, every time the panel goes off.
When the device tree links the touchscreen to its panel, register as a
DRM panel follower, like i2c-hid does.
Signed-off-by: Luka Panio <lukapanio@gmail.com>
---
drivers/input/touchscreen/Kconfig | 1 +
drivers/input/touchscreen/edt-ft5x06.c | 156 ++++++++++++++++++++++++-
2 files changed, 151 insertions(+), 6 deletions(-)
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 92840ea9c65f..b4fd55028a04 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -794,6 +794,7 @@ config TOUCHSCREEN_PENMOUNT
config TOUCHSCREEN_EDT_FT5X06
tristate "EDT FocalTech FT5x06 I2C Touchscreen support"
depends on I2C
+ depends on DRM_PANEL || !DRM_PANEL
select REGMAP_I2C
help
Say Y here if you have an EDT "Polytouch" touchscreen based
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index 7bcc48e07d62..b2fca6c37205 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -4,6 +4,7 @@
* Daniel Wagener <daniel.wagener@kernelconcepts.de> (M09 firmware support)
* Lothar Waßmann <LW@KARO-electronics.de> (DT support)
* Dario Binacchi <dario.binacchi@amarulasolutions.com> (regmap support)
+ * Luka Panio <lukapanio@gmail.com> (SPI and DRM panel support)
*/
/*
@@ -33,6 +34,9 @@
#include <linux/uaccess.h>
#include <linux/unaligned.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_panel.h>
#include "edt-ft5x06.h"
@@ -150,6 +154,12 @@ struct edt_ft5x06_ts_data {
enum edt_ver version;
int (*boot)(struct device *dev, struct gpio_desc *reset_gpio);
u8 tdata_reg;
+
+ struct drm_panel_follower panel_follower;
+ struct work_struct panel_work;
+ bool is_panel_follower;
+ bool powered_off;
+ bool hw_initialized;
unsigned int crc_errors;
unsigned int header_errors;
};
@@ -383,7 +393,7 @@ static ssize_t edt_ft5x06_setting_show(struct device *dev,
guard(mutex)(&tsdata->mutex);
- if (tsdata->factory_mode)
+ if (tsdata->factory_mode || tsdata->powered_off)
return -EIO;
switch (tsdata->version) {
@@ -441,7 +451,7 @@ static ssize_t edt_ft5x06_setting_store(struct device *dev,
guard(mutex)(&tsdata->mutex);
- if (tsdata->factory_mode)
+ if (tsdata->factory_mode || tsdata->powered_off)
return -EIO;
error = kstrtouint(buf, 0, &val);
@@ -1120,7 +1130,8 @@ static void edt_ft5x06_disable_regulators(void *arg)
/*
* Take the controller out of reset, identify it and register the input
- * device.
+ * device. Called from probe, or once the panel is up for controllers
+ * following a panel.
*/
static int edt_ft5x06_ts_init_hw(struct edt_ft5x06_ts_data *tsdata)
{
@@ -1206,9 +1217,117 @@ static int edt_ft5x06_ts_init_hw(struct edt_ft5x06_ts_data *tsdata)
edt_ft5x06_ts_prepare_debugfs(tsdata);
+ tsdata->hw_initialized = true;
+
+ return 0;
+}
+
+/*
+ * TDDI controllers share their power and reset with the display panel. When
+ * following a panel, power the touch controller down before the panel goes
+ * off and bring it back once the panel is up again.
+ */
+static void edt_ft5x06_panel_work(struct work_struct *work)
+{
+ struct edt_ft5x06_ts_data *tsdata =
+ container_of(work, struct edt_ft5x06_ts_data, panel_work);
+ int error;
+
+ guard(mutex)(&tsdata->mutex);
+
+ if (!tsdata->powered_off)
+ return;
+
+ if (!tsdata->hw_initialized) {
+ error = edt_ft5x06_ts_init_hw(tsdata);
+ if (error) {
+ /* try again the next time the panel comes up */
+ gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
+ dev_err(tsdata->dev,
+ "Failed to initialize the controller: %d\n",
+ error);
+ return;
+ }
+ goto out;
+ }
+
+ gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
+
+ if (tsdata->boot) {
+ error = tsdata->boot(tsdata->dev, tsdata->reset_gpio);
+ if (error) {
+ dev_err(tsdata->dev,
+ "Failed to boot the controller: %d\n", error);
+ return;
+ }
+ } else {
+ msleep(300);
+ }
+
+ edt_ft5x06_restore_reg_parameters(tsdata);
+out:
+ tsdata->powered_off = false;
+ enable_irq(tsdata->irq);
+}
+
+static int edt_ft5x06_panel_prepared(struct drm_panel_follower *follower)
+{
+ struct edt_ft5x06_ts_data *tsdata =
+ container_of(follower, struct edt_ft5x06_ts_data, panel_follower);
+
+ /* Downloading firmware takes a while, don't hold up the panel */
+ schedule_work(&tsdata->panel_work);
+
return 0;
}
+static int edt_ft5x06_panel_unpreparing(struct drm_panel_follower *follower)
+{
+ struct edt_ft5x06_ts_data *tsdata =
+ container_of(follower, struct edt_ft5x06_ts_data, panel_follower);
+
+ cancel_work_sync(&tsdata->panel_work);
+
+ guard(mutex)(&tsdata->mutex);
+
+ if (tsdata->powered_off)
+ return 0;
+
+ disable_irq(tsdata->irq);
+ gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
+ tsdata->powered_off = true;
+
+ return 0;
+}
+
+static const struct drm_panel_follower_funcs edt_ft5x06_panel_follower_funcs = {
+ .panel_prepared = edt_ft5x06_panel_prepared,
+ .panel_unpreparing = edt_ft5x06_panel_unpreparing,
+};
+
+static void edt_ft5x06_cancel_panel_work(void *data)
+{
+ struct edt_ft5x06_ts_data *tsdata = data;
+
+ cancel_work_sync(&tsdata->panel_work);
+}
+
+static int edt_ft5x06_register_panel_follower(struct edt_ft5x06_ts_data *tsdata)
+{
+ struct device *dev = tsdata->dev;
+ int error;
+
+ INIT_WORK(&tsdata->panel_work, edt_ft5x06_panel_work);
+ error = devm_add_action_or_reset(dev, edt_ft5x06_cancel_panel_work,
+ tsdata);
+ if (error)
+ return error;
+
+ tsdata->panel_follower.funcs = &edt_ft5x06_panel_follower_funcs;
+
+ return devm_drm_panel_add_follower(dev, &tsdata->panel_follower);
+}
+
int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
const struct edt_ft5x06_chip_data *chip_data, u16 bustype,
struct dentry *debugfs)
@@ -1331,14 +1450,24 @@ int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
tsdata->factory_mode = false;
dev_set_drvdata(dev, tsdata);
- error = edt_ft5x06_ts_init_hw(tsdata);
- if (error)
- return error;
+ /*
+ * A controller sharing its power with a panel can only be accessed
+ * once the panel is up, so leave it in reset and do the rest of the
+ * initialization from the panel follower callback.
+ */
+ tsdata->is_panel_follower = drm_is_panel_follower(dev);
+ if (!tsdata->is_panel_follower) {
+ error = edt_ft5x06_ts_init_hw(tsdata);
+ if (error)
+ return error;
+ }
irq_flags = irq_get_trigger_type(irq);
if (irq_flags == IRQF_TRIGGER_NONE)
irq_flags = IRQF_TRIGGER_FALLING;
irq_flags |= IRQF_ONESHOT;
+ if (tsdata->is_panel_follower)
+ irq_flags |= IRQF_NO_AUTOEN;
error = devm_request_threaded_irq(dev, irq, NULL, edt_ft5x06_ts_isr,
irq_flags, dev_name(dev), tsdata);
@@ -1347,6 +1476,14 @@ int edt_ft5x06_probe(struct device *dev, struct regmap *regmap, int irq,
return error;
}
+ if (tsdata->is_panel_follower) {
+ tsdata->powered_off = true;
+ error = edt_ft5x06_register_panel_follower(tsdata);
+ if (error)
+ return dev_err_probe(dev, error,
+ "failed to follow the panel\n");
+ }
+
dev_dbg(dev,
"EDT FT5x06 initialized: IRQ %d, WAKE pin %d, Reset pin %d.\n",
irq,
@@ -1374,6 +1511,10 @@ static int edt_ft5x06_ts_suspend(struct device *dev)
if (device_may_wakeup(dev))
return 0;
+ /* The panel powers us down and up */
+ if (tsdata->is_panel_follower)
+ return 0;
+
if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED)
return 0;
@@ -1415,6 +1556,9 @@ static int edt_ft5x06_ts_resume(struct device *dev)
if (device_may_wakeup(dev))
return 0;
+ if (tsdata->is_panel_follower)
+ return 0;
+
if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED)
return 0;
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-17 14:08 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-17 14:07 [PATCH 0/7] Input: edt-ft5x06 - add SPI transport and FocalTech FT8722 support Luka Panio
2026-10-17 14:07 ` [PATCH 1/7] Input: edt-ft5x06 - split the bus independent core from the I2C driver Luka Panio
2026-10-17 14:07 ` [PATCH 2/7] Input: edt-ft5x06 - move the controller bring-up into a helper Luka Panio
2026-10-17 14:07 ` [PATCH 3/7] Input: edt-ft5x06 - add an optional controller boot hook Luka Panio
2026-10-17 14:07 ` [PATCH 4/7] Input: edt-ft5x06 - allow a chip specific touch data register Luka Panio
2026-10-17 14:07 ` [PATCH 5/7] dt-bindings: input: touchscreen: edt-ft5x06: Add FocalTech FT8722 Luka Panio
2026-10-17 14:07 ` [PATCH 6/7] Input: edt-ft5x06 - add SPI transport and FT8722 support Luka Panio
2026-10-17 14:07 ` [PATCH 7/7] Input: edt-ft5x06 - support following a DRM panel Luka Panio
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®