[PATCH 13/13] HID: ft260: i2c: abort in-flight transfers with STOP before reset

From: Michael Zaidman

Date: Sat Aug 22 2026 - 17:46:29 EST


A transfer that ends without STOP leaves the FT260 I2C controller
with BUS_BUSY set. I2C reset (AN_394 section 4.4.15) restarts the
master but, with the driver still bound, status stays 0x60
(CTRL_IDLE | BUS_BUSY) even when SCL and SDA are pulled up. The
next START then fails with 0x72 (arbitration lost + error) and
further resets do not recover; only a USB replug did.

Add ft260_i2c_abort(): a STOP-only write (flag 0x04, zero payload)
then FT260_SET_I2C_RESET. Use it wherever the host tears down an
open transaction:

- read wait timeout
- read status check failure after the HID report arrived
- write status poll failure after the report was sent
- SMBus block read with an invalid count (count phase has no STOP)

Leave ft260_i2c_reset() alone for HID output failure (the request
may never have reached the controller), sysfs i2c_reset, and probe.

Tested on a UMFT260EV1A with a 24LC512 at 0x51. Five 1 ms read
timeouts each followed by STOP+reset left bus_status 0x20; raising
the timeout to 25 ms on the same loaded module then read 16 bytes
successfully.

This matters more with the 25/75 ms completion timeout from
"HID: ft260: i2c: reduce driver module loading time", which makes
read timeouts more likely than mainline's 5000 ms wait.

Signed-off-by: Michael Zaidman <michael.zaidman@xxxxxxxxx>
---
drivers/hid/hid-ft260.c | 43 ++++++++++++++++++++++++++++++++++-------
1 file changed, 36 insertions(+), 7 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 9ae688f6208f..a35a1af2d7e5 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -538,6 +538,33 @@ static int ft260_i2c_reset(struct hid_device *hdev)
return ret;
}

+static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
+ size_t len);
+
+/*
+ * STOP with no START and no payload. Used by ft260_i2c_abort() when
+ * tearing down an in-flight transfer: I2C reset alone can leave
+ * BUS_BUSY set even when SCL/SDA are idle, and the next START then
+ * fails with arbitration lost.
+ */
+static int ft260_i2c_stop(struct hid_device *hdev, u8 addr)
+{
+ u8 buf[4] = {
+ FT260_I2C_REPORT_MIN,
+ addr,
+ FT260_FLAG_STOP,
+ 0,
+ };
+
+ return ft260_hid_output_report(hdev, buf, sizeof(buf));
+}
+
+static int ft260_i2c_abort(struct hid_device *hdev, u8 addr)
+{
+ ft260_i2c_stop(hdev, addr);
+ return ft260_i2c_reset(hdev);
+}
+
static int ft260_xfer_status(struct ft260_device *dev, u8 bus_busy)
{
struct hid_device *hdev = dev->hdev;
@@ -607,7 +634,8 @@ static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
}

static int ft260_hid_output_report_check_status(struct ft260_device *dev,
- u8 *data, int len, u8 bus_busy)
+ u8 *data, int len, u8 addr,
+ u8 bus_busy)
{
int ret, usec, try = 100;
struct hid_device *hdev = dev->hdev;
@@ -636,7 +664,7 @@ static int ft260_hid_output_report_check_status(struct ft260_device *dev,
if (ret == 0)
return 0;

- ft260_i2c_reset(hdev);
+ ft260_i2c_abort(hdev, addr);
return -EIO;
}

@@ -675,7 +703,8 @@ static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
rep->flag, data[0]);

ret = ft260_hid_output_report_check_status(dev, (u8 *)rep,
- wr_len + 4, bus_busy);
+ wr_len + 4, addr,
+ bus_busy);
if (ret < 0) {
ft260_dbg("%s: failed with %d\n", __func__, ret);
return ret;
@@ -716,7 +745,7 @@ static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
rep->report, addr, cmd, rep->length, len);

- ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len,
+ ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len, addr,
(flag & FT260_FLAG_STOP) ?
FT260_I2C_STATUS_BUS_BUSY : 0);
if (ret < 0)
@@ -784,7 +813,7 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
timeout_jiffies = msecs_to_jiffies(timeout);
if (!wait_for_completion_timeout(&dev->wait, timeout_jiffies)) {
ret = -ETIMEDOUT;
- ft260_i2c_reset(hdev);
+ ft260_i2c_abort(hdev, addr);
goto ft260_i2c_read_exit;
}

@@ -798,7 +827,7 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
ret = ft260_xfer_status(dev, bus_busy);
if (ret < 0) {
ret = -EIO;
- ft260_i2c_reset(hdev);
+ ft260_i2c_abort(hdev, addr);
goto ft260_i2c_read_exit;
}

@@ -982,7 +1011,7 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
hid_warn(hdev,
"smbus block read: invalid count %u from slave 0x%02x\n",
count, addr);
- ft260_i2c_reset(hdev);
+ ft260_i2c_abort(hdev, addr);
ret = -EPROTO;
goto smbus_exit;
}
--
2.43.0