[PATCH 3/4] Input: edt-ft5x06 - allow reading the touch frame one register at a time

From: Alexandre Hamamdjian via B4 Relay

Date: Thu Jul 23 2026 - 07:30:17 EST


From: Teguh Sobirin <teguh@xxxxxxxx>

Every touch interrupt reads the whole touch frame in a single i2c block
read via regmap_bulk_read(). On some boards the i2c controller the panel
is wired to cannot sustain that multi-byte transfer: on the AYANEO Pocket
DS the FocalTech FT5426 sits on a marginal Qualcomm GENI bus that
intermittently aborts a long read with -EAGAIN or -ETIMEDOUT, and the
GENI controller has no bus recovery, so the block read fails on nearly
every interrupt and the panel is unusable.

Honour the "no-regmap-bulk-read" property. When set, the driver reads the
frame one register at a time with a short retry on the transient bus
errors, keeping each transfer small enough to complete. Boards on a
healthy bus keep using the single bulk transfer and are unaffected.

Signed-off-by: Teguh Sobirin <teguh@xxxxxxxx>
Co-developed-by: Alexandre Hamamdjian <azkali.limited@xxxxxxxxx>
Signed-off-by: Alexandre Hamamdjian <azkali.limited@xxxxxxxxx>
---
drivers/input/touchscreen/edt-ft5x06.c | 37 ++++++++++++++++++++++++++++++++--
1 file changed, 35 insertions(+), 2 deletions(-)

diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index d6c3d033b83d..ac61ac44fd64 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -146,6 +146,7 @@ struct edt_ft5x06_ts_data {
enum edt_ver version;
unsigned int crc_errors;
unsigned int header_errors;
+ bool no_regmap_bulk_read;
};

struct edt_i2c_chip_data {
@@ -295,6 +296,31 @@ static const struct regmap_config edt_M06_i2c_regmap_config = {
.write = edt_M06_i2c_write,
};

+static int edt_ft5x06_bulk_read(struct regmap *map, unsigned int start,
+ void *val, size_t len)
+{
+ u8 *dst = val;
+ size_t off;
+
+ for (off = 0; off < len; off++) {
+ unsigned int v;
+ int ret, tries;
+
+ for (tries = 0; tries < 3; tries++) {
+ ret = regmap_read(map, start + off, &v);
+ if (!ret)
+ break;
+ if (ret == -ETIMEDOUT || ret == -EAGAIN)
+ usleep_range(2000, 4000);
+ }
+ if (ret)
+ return ret;
+ dst[off] = v;
+ }
+
+ return 0;
+}
+
static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
{
struct edt_ft5x06_ts_data *tsdata = dev_id;
@@ -304,8 +330,12 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
int error;

memset(rdbuf, 0, sizeof(rdbuf));
- error = regmap_bulk_read(tsdata->regmap, tsdata->tdata_cmd, rdbuf,
- tsdata->tdata_len);
+ if (tsdata->no_regmap_bulk_read)
+ error = edt_ft5x06_bulk_read(tsdata->regmap, tsdata->tdata_cmd,
+ rdbuf, tsdata->tdata_len);
+ else
+ error = regmap_bulk_read(tsdata->regmap, tsdata->tdata_cmd,
+ rdbuf, tsdata->tdata_len);
if (error) {
dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n",
error);
@@ -1212,6 +1242,9 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
return error;
}

+ tsdata->no_regmap_bulk_read =
+ device_property_read_bool(&client->dev, "no-regmap-bulk-read");
+
/*
* Check which sleep modes we can support. Power-off requires the
* reset-pin to ensure correct power-down/power-up behaviour. Start with

--
2.55.0