[PATCH v7 2/2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency

From: Aniket Randive

Date: Thu Aug 13 2026 - 01:48:30 EST


The driver uses a static XFER_TIMEOUT of HZ (1 second) for all transfers
regardless of message length or bus frequency, causing unnecessary
delays on error paths.

Use i2c_update_timeout() from i2c-core to compute the timeout dynamically
from the message length and the bus frequency, then read adap->timeout at
each wait site. For FIFO, SE-DMA, and GPI single-descriptor paths the
timeout is computed per message using that message's length. For the GPI
multi-descriptor path, the maximum message length across the batch is used
since one completion covers the entire BEI batch and the timeout must
cover the longest individual message.

A 10x safety margin over the theoretical wire time is applied, with a
300ms floor to account for I2C clock stretching and other situations where
a slave may keep SCL asserted for an extended period, including faulty
devices holding the bus. Both constants remain private to this driver.

Signed-off-by: Aniket Randive <aniket.randive@xxxxxxxxxxxxxxxx>
---
drivers/i2c/busses/i2c-qcom-geni.c | 44 ++++++++++++++++++++++++++++++--------
1 file changed, 35 insertions(+), 9 deletions(-)

diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
index 658636c1ee0e..c98d9e7b250c 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -79,9 +79,14 @@ enum geni_i2c_err_code {

#define ABORT_TIMEOUT HZ
#define CANCEL_TIMEOUT HZ
-#define XFER_TIMEOUT HZ
#define RST_TIMEOUT HZ

+/* 9 bits per byte (8 data + 1 ACK), 10x safety margin */
+#define I2C_TIMEOUT_SAFETY_COEFFICIENT 10
+
+/* 300ms floor: budget for clock stretching; slave may hold SCL low indefinitely */
+#define I2C_TIMEOUT_MIN_USEC 300000
+
struct geni_i2c_desc {
bool no_dma_support;
unsigned int tx_fifo_depth;
@@ -513,7 +518,9 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
}

cur = gi2c->cur;
- time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ i2c_update_timeout(&gi2c->adap, gi2c->clk_freq_out, len,
+ I2C_TIMEOUT_SAFETY_COEFFICIENT, I2C_TIMEOUT_MIN_USEC);
+ time_left = wait_for_completion_timeout(&gi2c->done, gi2c->adap.timeout);
if (!time_left || (gi2c->err && gi2c->err != gi2c_log[ADDR_NACK].err))
geni_i2c_cancel_xfer(gi2c);

@@ -555,7 +562,9 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
writel_relaxed(1, se->base + SE_GENI_TX_WATERMARK_REG);

cur = gi2c->cur;
- time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ i2c_update_timeout(&gi2c->adap, gi2c->clk_freq_out, len,
+ I2C_TIMEOUT_SAFETY_COEFFICIENT, I2C_TIMEOUT_MIN_USEC);
+ time_left = wait_for_completion_timeout(&gi2c->done, gi2c->adap.timeout);
if (!time_left || (gi2c->err && gi2c->err != gi2c_log[ADDR_NACK].err))
geni_i2c_cancel_xfer(gi2c);

@@ -633,7 +642,7 @@ static void geni_i2c_gpi_multi_desc_unmap(struct geni_i2c_dev *gi2c, struct i2c_
* geni_i2c_gpi_multi_xfer_timeout_handler() - Handles multi message transfer timeout
* @dev: Pointer to the corresponding dev node
* @multi_xfer: Pointer to the geni_i2c_gpi_multi_desc_xfer
- * @transfer_timeout_msecs: Timeout value in milliseconds
+ * @timeout_jiffies: Per-message completion timeout in jiffies
* @transfer_comp: Completion object of the transfer
*
* This function waits for the completion of each processed transfer messages
@@ -643,18 +652,18 @@ static void geni_i2c_gpi_multi_desc_unmap(struct geni_i2c_dev *gi2c, struct i2c_
*/
static int geni_i2c_gpi_multi_xfer_timeout_handler(struct device *dev,
struct geni_i2c_gpi_multi_desc_xfer *multi_xfer,
- u32 transfer_timeout_msecs,
+ unsigned long timeout_jiffies,
struct completion *transfer_comp)
{
int i;
- u32 time_left;
+ unsigned long time_left;

for (i = 0; i < multi_xfer->msg_idx_cnt - 1; i++) {
reinit_completion(transfer_comp);

if (multi_xfer->msg_idx_cnt != multi_xfer->irq_cnt) {
time_left = wait_for_completion_timeout(transfer_comp,
- transfer_timeout_msecs);
+ timeout_jiffies);
if (!time_left) {
dev_err(dev, "%s: Transfer timeout\n", __func__);
return -ETIMEDOUT;
@@ -778,8 +787,22 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
dma_async_issue_pending(gi2c->tx_c);

if ((msg_idx == (gi2c->num_msgs - 1)) || flags & DMA_PREP_INTERRUPT) {
+ size_t max_len = 0;
+ int j;
+
+ /*
+ * Use the longest message as the timeout base: one completion
+ * covers the whole BEI batch, so the budget must fit the worst
+ * case single-message wire time.
+ */
+ for (j = 0; j < gi2c->num_msgs; j++)
+ max_len = max_t(size_t, max_len, msgs[j].len);
+ i2c_update_timeout(&gi2c->adap, gi2c->clk_freq_out, max_len,
+ I2C_TIMEOUT_SAFETY_COEFFICIENT,
+ I2C_TIMEOUT_MIN_USEC);
ret = geni_i2c_gpi_multi_xfer_timeout_handler(gi2c->se.dev, gi2c_gpi_xfer,
- XFER_TIMEOUT, &gi2c->done);
+ gi2c->adap.timeout,
+ &gi2c->done);
if (ret) {
dev_err(gi2c->se.dev,
"I2C multi write msg transfer timeout: %d\n",
@@ -899,7 +922,10 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i

if (!gi2c->is_tx_multi_desc_xfer) {
dma_async_issue_pending(gi2c->tx_c);
- time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ i2c_update_timeout(&gi2c->adap, gi2c->clk_freq_out, msgs[i].len,
+ I2C_TIMEOUT_SAFETY_COEFFICIENT,
+ I2C_TIMEOUT_MIN_USEC);
+ time_left = wait_for_completion_timeout(&gi2c->done, gi2c->adap.timeout);
if (!time_left) {
dev_err(gi2c->se.dev, "%s:I2C timeout\n", __func__);
gi2c->err = -ETIMEDOUT;

--
2.34.1