* [PATCH 0/3] i2c: qcom-geni: Add I2C bus recovery support
@ 2026-08-26 9:51 Aniket Randive
2026-08-26 9:51 ` [PATCH v1 1/3] dmaengine: qcom: gpi: Add I2C bus recovery opcode support Aniket Randive
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Aniket Randive @ 2026-08-26 9:51 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Mukesh Kumar Savaliya, Viken Dadhaniya,
Andi Shyti
Cc: linux-arm-msm, dmaengine, linux-kernel, linux-i2c, Aniket Randive
An I2C target that gets interrupted during a transaction can hold SDA low,
leaving the bus stuck and preventing further transfers.
This series adds bus recovery support to the Qualcomm GENI I2C driver using
the hardware native I2C_BUS_CLEAR and I2C_STOP_ON_BUS M_CMD opcodes. These
commands generate up to nine SCL pulses, which is the standard I2C bus
recovery sequence, and then issue a STOP condition to release the bus
without requiring GPIO based recovery.
Recovery is triggered automatically from geni_i2c_xfer() when a transfer
fails and the SE_GENI_IOS register indicates that SDA remains low. The
recovery callback is also registered through i2c_bus_recovery_info, allowing
recovery to be initiated through the I2C core i2c_recover_bus() API.
The patches are organized as follows:
[1/3] dmaengine: qcom: gpi: Add I2C_BUS_CLEAR and I2C_STOP_ON_BUS opcodes
[2/3] i2c: qcom-geni: Add bus recovery support for FIFO mode
[3/3] i2c: qcom-geni: Add bus recovery support for GPI DMA mode
Signed-off-by: Aniket Randive <aniket.randive@oss.qualcomm.com>
---
Aniket Randive (3):
dmaengine: qcom: gpi: Add I2C bus recovery opcode support
i2c: qcom-geni: Add bus recovery support for FIFO mode
i2c: qcom-geni: Add bus recovery support for GPI DMA mode
drivers/dma/qcom/gpi.c | 30 ++++++
drivers/i2c/busses/i2c-qcom-geni.c | 185 ++++++++++++++++++++++++++++++++++++-
include/linux/dma/qcom-gpi-dma.h | 2 +
3 files changed, 215 insertions(+), 2 deletions(-)
---
base-commit: a8406e6c0b793ce0788019683837c40855b55995
change-id: 20260706-i2c_bus_recovery-8f0c20283d99
Best regards,
--
Aniket Randive <aniket.randive@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v1 1/3] dmaengine: qcom: gpi: Add I2C bus recovery opcode support
2026-08-26 9:51 [PATCH 0/3] i2c: qcom-geni: Add I2C bus recovery support Aniket Randive
@ 2026-08-26 9:51 ` Aniket Randive
2026-08-26 9:51 ` [PATCH v1 2/3] i2c: qcom-geni: Add bus recovery support for FIFO mode Aniket Randive
2026-08-26 9:51 ` [PATCH v1 3/3] i2c: qcom-geni: Add bus recovery support for GPI DMA mode Aniket Randive
2 siblings, 0 replies; 6+ messages in thread
From: Aniket Randive @ 2026-08-26 9:51 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Mukesh Kumar Savaliya, Viken Dadhaniya,
Andi Shyti
Cc: linux-arm-msm, dmaengine, linux-kernel, linux-i2c, Aniket Randive
The I2C_BUS_CLEAR and I2C_STOP_ON_BUS GENI sequencer opcodes release a
stuck I2C bus, but the GPI DMA driver has no way to emit these
zero-payload control commands, so an I2C controller using GPI DMA
cannot recover the bus.
Add I2C_BUS_CLEAR and I2C_STOP_ON_BUS to enum i2c_op so I2C drivers can
request them through gpi_i2c_config.op. Handle them in
gpi_create_i2c_tre() by emitting a CONFIG TRE (when set_config is set)
followed by a GO TRE with IEOT set and no DMA TRE, and reserve the
matching number of ring entries in gpi_prep_slave_sg().
Signed-off-by: Aniket Randive <aniket.randive@oss.qualcomm.com>
---
drivers/dma/qcom/gpi.c | 30 ++++++++++++++++++++++++++++++
include/linux/dma/qcom-gpi-dma.h | 2 ++
2 files changed, 32 insertions(+)
diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c
index a5055a6273af..14a74f0a602d 100644
--- a/drivers/dma/qcom/gpi.c
+++ b/drivers/dma/qcom/gpi.c
@@ -1620,6 +1620,17 @@ gpi_peripheral_config(struct dma_chan *chan, struct dma_slave_config *config)
return 0;
}
+static void gpi_create_i2c_go_recovery_tre(struct gpi_i2c_config *i2c,
+ struct gpi_tre *tre)
+{
+ tre->dword[0] = u32_encode_bits(i2c->op, TRE_I2C_GO_CMD);
+ tre->dword[1] = 0;
+ tre->dword[2] = 0;
+
+ tre->dword[3] = u32_encode_bits(TRE_TYPE_GO, TRE_FLAGS_TYPE);
+ tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOT);
+}
+
static int gpi_create_i2c_tre(struct gchan *chan, struct gpi_desc *desc,
struct scatterlist *sgl, enum dma_transfer_direction direction,
unsigned long flags)
@@ -1650,6 +1661,12 @@ static int gpi_create_i2c_tre(struct gchan *chan, struct gpi_desc *desc,
tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_CHAIN);
}
+ if (i2c->op == I2C_BUS_CLEAR || i2c->op == I2C_STOP_ON_BUS) {
+ gpi_create_i2c_go_recovery_tre(i2c, &desc->tre[tre_idx]);
+ tre_idx++;
+ goto log_tre;
+ }
+
/* create the GO tre for Tx */
if (i2c->op == I2C_WRITE) {
tre = &desc->tre[tre_idx];
@@ -1692,6 +1709,7 @@ static int gpi_create_i2c_tre(struct gchan *chan, struct gpi_desc *desc,
tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_BEI);
}
+log_tre:
for (i = 0; i < tre_idx; i++)
dev_dbg(dev, "TRE:%d %x:%x:%x:%x\n", i, desc->tre[i].dword[0],
desc->tre[i].dword[1], desc->tre[i].dword[2], desc->tre[i].dword[3]);
@@ -1825,6 +1843,18 @@ gpi_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
nr_tre = 2;
if (direction == DMA_DEV_TO_MEM) /* rx */
nr_tre = 1;
+ /*
+ * Recovery opcodes do not require DMA data TREs, only CONFIG
+ * (for set_config) and GO TREs. Since gpi_prep_slave_sg() is
+ * shared with SPI, verify the channel is I2C before accessing
+ * the configuration data.
+ */
+ if (gchan->protocol == QCOM_GPI_I2C) {
+ struct gpi_i2c_config *i2c = gchan->config;
+
+ if (i2c->op == I2C_BUS_CLEAR || i2c->op == I2C_STOP_ON_BUS)
+ nr_tre = set_config ? 2 : 1;
+ }
/* calculate # of elements required & available */
nr = gpi_ring_num_elements_avail(ch_ring);
diff --git a/include/linux/dma/qcom-gpi-dma.h b/include/linux/dma/qcom-gpi-dma.h
index 332be28427e4..b9b75c302825 100644
--- a/include/linux/dma/qcom-gpi-dma.h
+++ b/include/linux/dma/qcom-gpi-dma.h
@@ -52,6 +52,8 @@ struct gpi_spi_config {
enum i2c_op {
I2C_WRITE = 1,
I2C_READ,
+ I2C_BUS_CLEAR = 6,
+ I2C_STOP_ON_BUS,
};
/**
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v1 2/3] i2c: qcom-geni: Add bus recovery support for FIFO mode
2026-08-26 9:51 [PATCH 0/3] i2c: qcom-geni: Add I2C bus recovery support Aniket Randive
2026-08-26 9:51 ` [PATCH v1 1/3] dmaengine: qcom: gpi: Add I2C bus recovery opcode support Aniket Randive
@ 2026-08-26 9:51 ` Aniket Randive
2026-09-03 16:17 ` Jyothi Kumar Seerapu
2026-08-26 9:51 ` [PATCH v1 3/3] i2c: qcom-geni: Add bus recovery support for GPI DMA mode Aniket Randive
2 siblings, 1 reply; 6+ messages in thread
From: Aniket Randive @ 2026-08-26 9:51 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Mukesh Kumar Savaliya, Viken Dadhaniya,
Andi Shyti
Cc: linux-arm-msm, dmaengine, linux-kernel, linux-i2c, Aniket Randive
I2C transfers in FIFO mode can fail with -EPROTO, -ETIMEDOUT or
-EAGAIN when a target holds SDA low, leaving the bus stuck and
preventing subsequent transactions.
Add bus recovery support using the I2C_BUS_CLEAR and I2C_STOP_ON_BUS
hardware opcodes to restore the bus to an idle state. Check the SDA
line state through SE_GENI_IOS.RX_DATA_IN and skip recovery when the
bus is already free. Trigger recovery automatically from
geni_i2c_xfer() on -EPROTO, -ETIMEDOUT and -EAGAIN (arbitration-lost)
errors, and register the recovery callback through i2c_bus_recovery_info
to allow recovery via i2c_recover_bus().
This adds bus recovery support for FIFO mode only.
Signed-off-by: Aniket Randive <aniket.randive@oss.qualcomm.com>
---
drivers/i2c/busses/i2c-qcom-geni.c | 109 +++++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)
diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
index 658636c1ee0e..9fa1a8ac400c 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -138,6 +138,7 @@ struct geni_i2c_dev {
u32 num_msgs;
struct geni_i2c_gpi_multi_desc_xfer i2c_multi_desc_config;
const struct geni_i2c_desc *dev_data;
+ struct i2c_bus_recovery_info rinfo;
};
struct geni_i2c_err_log {
@@ -956,6 +957,90 @@ static int geni_i2c_fifo_xfer(struct geni_i2c_dev *gi2c,
return num;
}
+static int geni_i2c_fifo_bus_cmd(struct geni_i2c_dev *gi2c, u32 cmd)
+{
+ unsigned long time_left;
+ unsigned long flags;
+
+ /*
+ * Clear cur so the IRQ handler does not attempt FIFO watermark
+ * filling or draining while the recovery opcode is in flight.
+ * cur and err are shared with geni_i2c_irq(), which reads cur and
+ * writes err under gi2c->lock, so take the lock around this reset.
+ */
+ spin_lock_irqsave(&gi2c->lock, flags);
+ gi2c->cur = NULL;
+ gi2c->err = 0;
+ spin_unlock_irqrestore(&gi2c->lock, flags);
+ geni_se_select_mode(&gi2c->se, GENI_SE_FIFO);
+ reinit_completion(&gi2c->done);
+
+ geni_se_setup_m_cmd(&gi2c->se, cmd, 0);
+ time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ if (!time_left) {
+ dev_err(gi2c->se.dev, "timeout waiting for bus cmd %u\n", cmd);
+ gi2c->abort_done = false;
+ geni_se_abort_m_cmd(&gi2c->se);
+ time_left = ABORT_TIMEOUT;
+ do {
+ time_left = wait_for_completion_timeout(&gi2c->done, time_left);
+ } while (!gi2c->abort_done && time_left);
+
+ if (!time_left)
+ dev_err(gi2c->se.dev, "abort timed out for bus cmd %u\n", cmd);
+
+ return -ETIMEDOUT;
+ }
+
+ /*
+ * ARB_LOST and BUS_PROTO interrupts may be reported while the bus
+ * transitions from stuck to idle during the recovery sequence.
+ * The opcode completed successfully so treat these as success.
+ */
+ if (gi2c->err == -EAGAIN || gi2c->err == -EPROTO)
+ return 0;
+
+ return gi2c->err;
+}
+
+static int geni_i2c_recover_bus(struct i2c_adapter *adap)
+{
+ struct geni_i2c_dev *gi2c = i2c_get_adapdata(adap);
+ int ret;
+
+ ret = pm_runtime_get_sync(gi2c->se.dev);
+ if (ret < 0) {
+ dev_err(gi2c->se.dev, "bus recovery failed, error turning SE resources:%d\n", ret);
+ pm_runtime_put_noidle(gi2c->se.dev);
+ return ret;
+ }
+
+ /* SDA is high means bus is free */
+ if (readl_relaxed(gi2c->se.base + SE_GENI_IOS) & RX_DATA_IN) {
+ pm_runtime_put_autosuspend(gi2c->se.dev);
+ return 0;
+ }
+
+ ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_BUS_CLEAR);
+ if (!ret)
+ ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_STOP_ON_BUS);
+
+ /*
+ * Recovery succeeds only once the slave releases SDA, so the bus
+ * state is the authority: RX_DATA_IN high means the bus is free,
+ * while a clean opcode status with SDA still low is a failed
+ * recovery.
+ */
+ if (readl_relaxed(gi2c->se.base + SE_GENI_IOS) & RX_DATA_IN)
+ ret = 0;
+ else if (!ret)
+ ret = -EBUSY;
+
+ pm_runtime_put_autosuspend(gi2c->se.dev);
+
+ return ret;
+}
+
static int geni_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg msgs[],
int num)
@@ -981,6 +1066,25 @@ static int geni_i2c_xfer(struct i2c_adapter *adap,
else
ret = geni_i2c_fifo_xfer(gi2c, msgs, num);
+ if (!gi2c->gpi_mode &&
+ (ret == -EPROTO || ret == -ETIMEDOUT || ret == -EAGAIN)) {
+ /*
+ * Only attempt recovery if SDA is stuck low. -EPROTO and
+ * -ETIMEDOUT indicate bus errors where the target may be
+ * holding SDA low. ARB_LOST (-EAGAIN) on a single-controller
+ * bus indicates a stuck target, not a real arbitration loss.
+ * GPI DMA mode extends this trigger separately.
+ */
+ if (!(readl_relaxed(gi2c->se.base + SE_GENI_IOS) & RX_DATA_IN)) {
+ int recovery_ret = i2c_recover_bus(adap);
+
+ if (recovery_ret)
+ dev_err(gi2c->se.dev,
+ "bus recovery failed: %d (xfer error: %d)\n",
+ recovery_ret, ret);
+ }
+ }
+
pm_runtime_put_autosuspend(gi2c->se.dev);
gi2c->cur = NULL;
gi2c->err = 0;
@@ -1188,6 +1292,11 @@ static int geni_i2c_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
+ if (!gi2c->gpi_mode) {
+ gi2c->rinfo.recover_bus = geni_i2c_recover_bus;
+ gi2c->adap.bus_recovery_info = &gi2c->rinfo;
+ }
+
ret = i2c_add_adapter(&gi2c->adap);
if (ret)
return dev_err_probe(dev, ret, "Error adding i2c adapter\n");
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v1 3/3] i2c: qcom-geni: Add bus recovery support for GPI DMA mode
2026-08-26 9:51 [PATCH 0/3] i2c: qcom-geni: Add I2C bus recovery support Aniket Randive
2026-08-26 9:51 ` [PATCH v1 1/3] dmaengine: qcom: gpi: Add I2C bus recovery opcode support Aniket Randive
2026-08-26 9:51 ` [PATCH v1 2/3] i2c: qcom-geni: Add bus recovery support for FIFO mode Aniket Randive
@ 2026-08-26 9:51 ` Aniket Randive
2026-09-03 16:14 ` Jyothi Kumar Seerapu
2 siblings, 1 reply; 6+ messages in thread
From: Aniket Randive @ 2026-08-26 9:51 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Mukesh Kumar Savaliya, Viken Dadhaniya,
Andi Shyti
Cc: linux-arm-msm, dmaengine, linux-kernel, linux-i2c, Aniket Randive
I2C transfers in GPI DMA mode can fail with -EIO when a target holds
SDA low, leaving the bus stuck and preventing subsequent transactions.
The GPI completion callback reports a NACK and a genuine bus error
alike as -EIO, and the existing recovery path switches the SE to FIFO
mode which is not valid in GPI DMA mode.
Add bus recovery support using the I2C_BUS_CLEAR and I2C_STOP_ON_BUS
opcodes issued via the GPI TX DMA channel. Guard the -EIO recovery
trigger with a SE_GENI_IOS RX_DATA_IN check to skip recovery when SDA
is already released. Extend the recovery callback to dispatch through
the GPI DMA path when gi2c->gpi_mode is set.
Signed-off-by: Aniket Randive <aniket.randive@oss.qualcomm.com>
---
drivers/i2c/busses/i2c-qcom-geni.c | 104 +++++++++++++++++++++++++++++++------
1 file changed, 88 insertions(+), 16 deletions(-)
diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
index 9fa1a8ac400c..85cb1367958c 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -34,8 +34,6 @@
#define I2C_READ 0x2
#define I2C_WRITE_READ 0x3
#define I2C_ADDR_ONLY 0x4
-#define I2C_BUS_CLEAR 0x6
-#define I2C_STOP_ON_BUS 0x7
/* M_CMD params for I2C */
#define PRE_CMD_DELAY BIT(0)
#define TIMESTAMP_BEFORE BIT(1)
@@ -1003,6 +1001,70 @@ static int geni_i2c_fifo_bus_cmd(struct geni_i2c_dev *gi2c, u32 cmd)
return gi2c->err;
}
+static int geni_i2c_gpi_bus_cmd(struct geni_i2c_dev *gi2c, enum i2c_op cmd)
+{
+ const struct geni_i2c_clk_fld *itr = gi2c->clk_fld;
+ struct dma_async_tx_descriptor *desc;
+ struct gpi_i2c_config peripheral = {};
+ struct dma_slave_config config = {};
+ unsigned long time_left;
+ unsigned long flags;
+ dma_cookie_t cookie;
+
+ config.peripheral_config = &peripheral;
+ config.peripheral_size = sizeof(peripheral);
+
+ peripheral.set_config = 1;
+ peripheral.pack_enable = I2C_PACK_TX | I2C_PACK_RX;
+ peripheral.cycle_count = itr->t_cycle_cnt;
+ peripheral.high_count = itr->t_high_cnt;
+ peripheral.low_count = itr->t_low_cnt;
+ peripheral.clk_div = itr->clk_div;
+ peripheral.op = cmd;
+
+ if (dmaengine_slave_config(gi2c->tx_c, &config)) {
+ dev_err(gi2c->se.dev, "dma config error for bus cmd %u\n", cmd);
+ return -EIO;
+ }
+
+ desc = dmaengine_prep_slave_single(gi2c->tx_c, 0, 0, DMA_MEM_TO_DEV,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc)
+ return -EIO;
+
+ desc->callback_result = i2c_gpi_cb_result;
+ desc->callback_param = gi2c;
+
+ cookie = dmaengine_submit(desc);
+ if (dma_submit_error(cookie))
+ return -EIO;
+
+ /*
+ * cur and err are shared with geni_i2c_irq() and the GPI callback
+ * i2c_gpi_cb_result(); both write err (the IRQ handler under
+ * gi2c->lock). Reset them under the lock before issuing the transfer.
+ */
+ spin_lock_irqsave(&gi2c->lock, flags);
+ gi2c->cur = NULL;
+ gi2c->err = 0;
+ spin_unlock_irqrestore(&gi2c->lock, flags);
+ reinit_completion(&gi2c->done);
+ dma_async_issue_pending(gi2c->tx_c);
+
+ time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ if (!time_left) {
+ dev_err(gi2c->se.dev, "timeout waiting for GPI bus cmd %u\n", cmd);
+ dmaengine_terminate_sync(gi2c->tx_c);
+ return -ETIMEDOUT;
+ }
+
+ /* ARB_LOST and BUS_PROTO may be expected during recovery; treat as success */
+ if (gi2c->err == -EAGAIN || gi2c->err == -EPROTO)
+ return 0;
+
+ return gi2c->err;
+}
+
static int geni_i2c_recover_bus(struct i2c_adapter *adap)
{
struct geni_i2c_dev *gi2c = i2c_get_adapdata(adap);
@@ -1021,15 +1083,23 @@ static int geni_i2c_recover_bus(struct i2c_adapter *adap)
return 0;
}
- ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_BUS_CLEAR);
- if (!ret)
- ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_STOP_ON_BUS);
+ if (gi2c->gpi_mode) {
+ ret = geni_i2c_gpi_bus_cmd(gi2c, I2C_BUS_CLEAR);
+ if (ret)
+ dev_dbg(gi2c->se.dev, "GPI bus clear returned %d, issuing stop anyway\n",
+ ret);
+
+ ret = geni_i2c_gpi_bus_cmd(gi2c, I2C_STOP_ON_BUS);
+ } else {
+ ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_BUS_CLEAR);
+ if (!ret)
+ ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_STOP_ON_BUS);
+ }
/*
- * Recovery succeeds only once the slave releases SDA, so the bus
- * state is the authority: RX_DATA_IN high means the bus is free,
- * while a clean opcode status with SDA still low is a failed
- * recovery.
+ * Recovery succeeds when SDA is released. RX_DATA_IN high means the
+ * bus is free; trust this over the opcode return path, which cannot
+ * flag a benign recovery event in GPI mode.
*/
if (readl_relaxed(gi2c->se.base + SE_GENI_IOS) & RX_DATA_IN)
ret = 0;
@@ -1066,14 +1136,18 @@ static int geni_i2c_xfer(struct i2c_adapter *adap,
else
ret = geni_i2c_fifo_xfer(gi2c, msgs, num);
- if (!gi2c->gpi_mode &&
- (ret == -EPROTO || ret == -ETIMEDOUT || ret == -EAGAIN)) {
+ if (ret == -EPROTO || ret == -ETIMEDOUT || ret == -EAGAIN ||
+ (gi2c->gpi_mode && ret == -EIO)) {
/*
* Only attempt recovery if SDA is stuck low. -EPROTO and
* -ETIMEDOUT indicate bus errors where the target may be
* holding SDA low. ARB_LOST (-EAGAIN) on a single-controller
* bus indicates a stuck target, not a real arbitration loss.
- * GPI DMA mode extends this trigger separately.
+ * In GPI DMA mode the completion callback reports NACK,
+ * BUS_PROTO and ARB_LOST alike as -EIO, so the SE_GENI_IOS
+ * RX_DATA_IN check below is what distinguishes a genuinely
+ * stuck bus from a benign NACK: a NACK leaves SDA released
+ * (high) after the STOP, so recovery is skipped.
*/
if (!(readl_relaxed(gi2c->se.base + SE_GENI_IOS) & RX_DATA_IN)) {
int recovery_ret = i2c_recover_bus(adap);
@@ -1292,10 +1366,8 @@ static int geni_i2c_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
- if (!gi2c->gpi_mode) {
- gi2c->rinfo.recover_bus = geni_i2c_recover_bus;
- gi2c->adap.bus_recovery_info = &gi2c->rinfo;
- }
+ gi2c->rinfo.recover_bus = geni_i2c_recover_bus;
+ gi2c->adap.bus_recovery_info = &gi2c->rinfo;
ret = i2c_add_adapter(&gi2c->adap);
if (ret)
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v1 3/3] i2c: qcom-geni: Add bus recovery support for GPI DMA mode
2026-08-26 9:51 ` [PATCH v1 3/3] i2c: qcom-geni: Add bus recovery support for GPI DMA mode Aniket Randive
@ 2026-09-03 16:14 ` Jyothi Kumar Seerapu
0 siblings, 0 replies; 6+ messages in thread
From: Jyothi Kumar Seerapu @ 2026-09-03 16:14 UTC (permalink / raw)
To: Aniket Randive, Vinod Koul, Frank Li, Mukesh Kumar Savaliya,
Viken Dadhaniya, Andi Shyti
Cc: linux-arm-msm, dmaengine, linux-kernel, linux-i2c
On 8/26/2026 3:21 PM, Aniket Randive wrote:
> I2C transfers in GPI DMA mode can fail with -EIO when a target holds
> SDA low, leaving the bus stuck and preventing subsequent transactions.
> The GPI completion callback reports a NACK and a genuine bus error
> alike as -EIO, and the existing recovery path switches the SE to FIFO
> mode which is not valid in GPI DMA mode.
>
> Add bus recovery support using the I2C_BUS_CLEAR and I2C_STOP_ON_BUS
> opcodes issued via the GPI TX DMA channel. Guard the -EIO recovery
> trigger with a SE_GENI_IOS RX_DATA_IN check to skip recovery when SDA
> is already released. Extend the recovery callback to dispatch through
> the GPI DMA path when gi2c->gpi_mode is set.
>
> Signed-off-by: Aniket Randive <aniket.randive@oss.qualcomm.com>
> ---
> drivers/i2c/busses/i2c-qcom-geni.c | 104 +++++++++++++++++++++++++++++++------
> 1 file changed, 88 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
> index 9fa1a8ac400c..85cb1367958c 100644
> --- a/drivers/i2c/busses/i2c-qcom-geni.c
> +++ b/drivers/i2c/busses/i2c-qcom-geni.c
> @@ -34,8 +34,6 @@
> #define I2C_READ 0x2
> #define I2C_WRITE_READ 0x3
> #define I2C_ADDR_ONLY 0x4
> -#define I2C_BUS_CLEAR 0x6
> -#define I2C_STOP_ON_BUS 0x7
> /* M_CMD params for I2C */
> #define PRE_CMD_DELAY BIT(0)
> #define TIMESTAMP_BEFORE BIT(1)
> @@ -1003,6 +1001,70 @@ static int geni_i2c_fifo_bus_cmd(struct geni_i2c_dev *gi2c, u32 cmd)
> return gi2c->err;
> }
>
> +static int geni_i2c_gpi_bus_cmd(struct geni_i2c_dev *gi2c, enum i2c_op cmd)
> +{
> + const struct geni_i2c_clk_fld *itr = gi2c->clk_fld;
> + struct dma_async_tx_descriptor *desc;
> + struct gpi_i2c_config peripheral = {};
> + struct dma_slave_config config = {};
> + unsigned long time_left;
> + unsigned long flags;
> + dma_cookie_t cookie;
> +
> + config.peripheral_config = &peripheral;
> + config.peripheral_size = sizeof(peripheral);
> +
> + peripheral.set_config = 1;
> + peripheral.pack_enable = I2C_PACK_TX | I2C_PACK_RX;
> + peripheral.cycle_count = itr->t_cycle_cnt;
> + peripheral.high_count = itr->t_high_cnt;
> + peripheral.low_count = itr->t_low_cnt;
> + peripheral.clk_div = itr->clk_div;
> + peripheral.op = cmd;
> +
> + if (dmaengine_slave_config(gi2c->tx_c, &config)) {
> + dev_err(gi2c->se.dev, "dma config error for bus cmd %u\n", cmd);
> + return -EIO;
> + }
> +
> + desc = dmaengine_prep_slave_single(gi2c->tx_c, 0, 0, DMA_MEM_TO_DEV,
> + DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
> + if (!desc)
> + return -EIO;
> +
> + desc->callback_result = i2c_gpi_cb_result;
> + desc->callback_param = gi2c;
> +
> + cookie = dmaengine_submit(desc);
> + if (dma_submit_error(cookie))
> + return -EIO;
> +
> + /*
> + * cur and err are shared with geni_i2c_irq() and the GPI callback
> + * i2c_gpi_cb_result(); both write err (the IRQ handler under
> + * gi2c->lock). Reset them under the lock before issuing the transfer.
> + */
> + spin_lock_irqsave(&gi2c->lock, flags);
> + gi2c->cur = NULL;
> + gi2c->err = 0;
> + spin_unlock_irqrestore(&gi2c->lock, flags);
> + reinit_completion(&gi2c->done);
> + dma_async_issue_pending(gi2c->tx_c);
> +
> + time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
> + if (!time_left) {
> + dev_err(gi2c->se.dev, "timeout waiting for GPI bus cmd %u\n", cmd);
> + dmaengine_terminate_sync(gi2c->tx_c);
> + return -ETIMEDOUT;
> + }
> +
> + /* ARB_LOST and BUS_PROTO may be expected during recovery; treat as success */
> + if (gi2c->err == -EAGAIN || gi2c->err == -EPROTO)
> + return 0;
> +
> + return gi2c->err;
> +}
> +
> static int geni_i2c_recover_bus(struct i2c_adapter *adap)
> {
> struct geni_i2c_dev *gi2c = i2c_get_adapdata(adap);
> @@ -1021,15 +1083,23 @@ static int geni_i2c_recover_bus(struct i2c_adapter *adap)
> return 0;
> }
>
> - ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_BUS_CLEAR);
> - if (!ret)
> - ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_STOP_ON_BUS);
> + if (gi2c->gpi_mode) {
> + ret = geni_i2c_gpi_bus_cmd(gi2c, I2C_BUS_CLEAR);
> + if (ret)
> + dev_dbg(gi2c->se.dev, "GPI bus clear returned %d, issuing stop anyway\n",
> + ret);
> +
> + ret = geni_i2c_gpi_bus_cmd(gi2c, I2C_STOP_ON_BUS);
> + } else {
> + ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_BUS_CLEAR);
> + if (!ret)
> + ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_STOP_ON_BUS);
> + }
>
> /*
> - * Recovery succeeds only once the slave releases SDA, so the bus
> - * state is the authority: RX_DATA_IN high means the bus is free,
> - * while a clean opcode status with SDA still low is a failed
> - * recovery.
> + * Recovery succeeds when SDA is released. RX_DATA_IN high means the
> + * bus is free; trust this over the opcode return path, which cannot
> + * flag a benign recovery event in GPI mode.
> */
> if (readl_relaxed(gi2c->se.base + SE_GENI_IOS) & RX_DATA_IN)
> ret = 0;
> @@ -1066,14 +1136,18 @@ static int geni_i2c_xfer(struct i2c_adapter *adap,
> else
> ret = geni_i2c_fifo_xfer(gi2c, msgs, num);
>
> - if (!gi2c->gpi_mode &&
> - (ret == -EPROTO || ret == -ETIMEDOUT || ret == -EAGAIN)) {
> + if (ret == -EPROTO || ret == -ETIMEDOUT || ret == -EAGAIN ||
> + (gi2c->gpi_mode && ret == -EIO)) {
> /*
> * Only attempt recovery if SDA is stuck low. -EPROTO and
> * -ETIMEDOUT indicate bus errors where the target may be
> * holding SDA low. ARB_LOST (-EAGAIN) on a single-controller
> * bus indicates a stuck target, not a real arbitration loss.
> - * GPI DMA mode extends this trigger separately.
> + * In GPI DMA mode the completion callback reports NACK,
> + * BUS_PROTO and ARB_LOST alike as -EIO, so the SE_GENI_IOS
> + * RX_DATA_IN check below is what distinguishes a genuinely
> + * stuck bus from a benign NACK: a NACK leaves SDA released
> + * (high) after the STOP, so recovery is skipped.
> */
> if (!(readl_relaxed(gi2c->se.base + SE_GENI_IOS) & RX_DATA_IN)) {
> int recovery_ret = i2c_recover_bus(adap);
This does not appear to handle scenarios such as an I2C multi-message
transfer where the stretch bit is set during the first message, but an
error occurs while preparing the second message (for example, during
memory allocation or DMA descriptor preparation). In such cases, the SCL
line may remain asserted low and impact subsequent transfers.
> @@ -1292,10 +1366,8 @@ static int geni_i2c_probe(struct
platform_device *pdev)
> if (ret < 0)
> return ret;
>
> - if (!gi2c->gpi_mode) {
> - gi2c->rinfo.recover_bus = geni_i2c_recover_bus;
> - gi2c->adap.bus_recovery_info = &gi2c->rinfo;
> - }
> + gi2c->rinfo.recover_bus = geni_i2c_recover_bus;
> + gi2c->adap.bus_recovery_info = &gi2c->rinfo;
>
> ret = i2c_add_adapter(&gi2c->adap);
> if (ret)
>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v1 2/3] i2c: qcom-geni: Add bus recovery support for FIFO mode
2026-08-26 9:51 ` [PATCH v1 2/3] i2c: qcom-geni: Add bus recovery support for FIFO mode Aniket Randive
@ 2026-09-03 16:17 ` Jyothi Kumar Seerapu
0 siblings, 0 replies; 6+ messages in thread
From: Jyothi Kumar Seerapu @ 2026-09-03 16:17 UTC (permalink / raw)
To: Aniket Randive, Vinod Koul, Frank Li, Mukesh Kumar Savaliya,
Viken Dadhaniya, Andi Shyti
Cc: linux-arm-msm, dmaengine, linux-kernel, linux-i2c
On 8/26/2026 3:21 PM, Aniket Randive wrote:
> I2C transfers in FIFO mode can fail with -EPROTO, -ETIMEDOUT or
> -EAGAIN when a target holds SDA low, leaving the bus stuck and
> preventing subsequent transactions.
>
> Add bus recovery support using the I2C_BUS_CLEAR and I2C_STOP_ON_BUS
> hardware opcodes to restore the bus to an idle state. Check the SDA
> line state through SE_GENI_IOS.RX_DATA_IN and skip recovery when the
> bus is already free. Trigger recovery automatically from
> geni_i2c_xfer() on -EPROTO, -ETIMEDOUT and -EAGAIN (arbitration-lost)
> errors, and register the recovery callback through i2c_bus_recovery_info
> to allow recovery via i2c_recover_bus().
>
> This adds bus recovery support for FIFO mode only.
>
> Signed-off-by: Aniket Randive <aniket.randive@oss.qualcomm.com>
> ---
> drivers/i2c/busses/i2c-qcom-geni.c | 109 +++++++++++++++++++++++++++++++++++++
> 1 file changed, 109 insertions(+)
>
> diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
> index 658636c1ee0e..9fa1a8ac400c 100644
> --- a/drivers/i2c/busses/i2c-qcom-geni.c
> +++ b/drivers/i2c/busses/i2c-qcom-geni.c
> @@ -138,6 +138,7 @@ struct geni_i2c_dev {
> u32 num_msgs;
> struct geni_i2c_gpi_multi_desc_xfer i2c_multi_desc_config;
> const struct geni_i2c_desc *dev_data;
> + struct i2c_bus_recovery_info rinfo;
> };
>
> struct geni_i2c_err_log {
> @@ -956,6 +957,90 @@ static int geni_i2c_fifo_xfer(struct geni_i2c_dev *gi2c,
> return num;
> }
>
> +static int geni_i2c_fifo_bus_cmd(struct geni_i2c_dev *gi2c, u32 cmd)
> +{
> + unsigned long time_left;
> + unsigned long flags;
> +
> + /*
> + * Clear cur so the IRQ handler does not attempt FIFO watermark
> + * filling or draining while the recovery opcode is in flight.
> + * cur and err are shared with geni_i2c_irq(), which reads cur and
> + * writes err under gi2c->lock, so take the lock around this reset.
> + */
> + spin_lock_irqsave(&gi2c->lock, flags);
> + gi2c->cur = NULL;
> + gi2c->err = 0;
> + spin_unlock_irqrestore(&gi2c->lock, flags);
> + geni_se_select_mode(&gi2c->se, GENI_SE_FIFO);
> + reinit_completion(&gi2c->done);
> +
> + geni_se_setup_m_cmd(&gi2c->se, cmd, 0);
> + time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
> + if (!time_left) {
> + dev_err(gi2c->se.dev, "timeout waiting for bus cmd %u\n", cmd);
> + gi2c->abort_done = false;
> + geni_se_abort_m_cmd(&gi2c->se);
> + time_left = ABORT_TIMEOUT;
> + do {
> + time_left = wait_for_completion_timeout(&gi2c->done, time_left);
> + } while (!gi2c->abort_done && time_left);
> +
> + if (!time_left)
> + dev_err(gi2c->se.dev, "abort timed out for bus cmd %u\n", cmd);
> +
> + return -ETIMEDOUT;
> + }
> +
> + /*
> + * ARB_LOST and BUS_PROTO interrupts may be reported while the bus
> + * transitions from stuck to idle during the recovery sequence.
> + * The opcode completed successfully so treat these as success.
> + */
> + if (gi2c->err == -EAGAIN || gi2c->err == -EPROTO)
> + return 0;
> +
> + return gi2c->err;
> +}
> +
> +static int geni_i2c_recover_bus(struct i2c_adapter *adap)
> +{
> + struct geni_i2c_dev *gi2c = i2c_get_adapdata(adap);
> + int ret;
> +
> + ret = pm_runtime_get_sync(gi2c->se.dev);
> + if (ret < 0) {
> + dev_err(gi2c->se.dev, "bus recovery failed, error turning SE resources:%d\n", ret);
> + pm_runtime_put_noidle(gi2c->se.dev);
> + return ret;
> + }
> +
> + /* SDA is high means bus is free */
What about SCL line ?> + if (readl_relaxed(gi2c->se.base + SE_GENI_IOS)
& RX_DATA_IN) {
> + pm_runtime_put_autosuspend(gi2c->se.dev);
> + return 0;
> + }
> +
> + ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_BUS_CLEAR);
> + if (!ret)
> + ret = geni_i2c_fifo_bus_cmd(gi2c, I2C_STOP_ON_BUS);
> +
> + /*
> + * Recovery succeeds only once the slave releases SDA, so the bus
> + * state is the authority: RX_DATA_IN high means the bus is free,
> + * while a clean opcode status with SDA still low is a failed
> + * recovery.
> + */
> + if (readl_relaxed(gi2c->se.base + SE_GENI_IOS) & RX_DATA_IN)
> + ret = 0;
> + else if (!ret)
> + ret = -EBUSY;
> +
> + pm_runtime_put_autosuspend(gi2c->se.dev);
> +
> + return ret;
> +}
> +
> static int geni_i2c_xfer(struct i2c_adapter *adap,
> struct i2c_msg msgs[],
> int num)
> @@ -981,6 +1066,25 @@ static int geni_i2c_xfer(struct i2c_adapter *adap,
> else
> ret = geni_i2c_fifo_xfer(gi2c, msgs, num);
>
> + if (!gi2c->gpi_mode &&
> + (ret == -EPROTO || ret == -ETIMEDOUT || ret == -EAGAIN)) {
> + /*
> + * Only attempt recovery if SDA is stuck low. -EPROTO and
> + * -ETIMEDOUT indicate bus errors where the target may be
> + * holding SDA low. ARB_LOST (-EAGAIN) on a single-controller
> + * bus indicates a stuck target, not a real arbitration loss.
> + * GPI DMA mode extends this trigger separately.
> + */
> + if (!(readl_relaxed(gi2c->se.base + SE_GENI_IOS) & RX_DATA_IN)) {
> + int recovery_ret = i2c_recover_bus(adap);
> +
> + if (recovery_ret)
> + dev_err(gi2c->se.dev,
> + "bus recovery failed: %d (xfer error: %d)\n",
> + recovery_ret, ret);
> + }
Can we move this to a helper function like "geni_i2c_need_bus_recovery"
?> + }
> +
> pm_runtime_put_autosuspend(gi2c->se.dev);
> gi2c->cur = NULL;
> gi2c->err = 0;
> @@ -1188,6 +1292,11 @@ static int geni_i2c_probe(struct platform_device *pdev)
> if (ret < 0)
> return ret;
>
> + if (!gi2c->gpi_mode) {
> + gi2c->rinfo.recover_bus = geni_i2c_recover_bus;
> + gi2c->adap.bus_recovery_info = &gi2c->rinfo;
> + }
In geni_i2c_xfer(), geni_i2c_recover_bus() is invoked directly, then why
do we need this ?> +
> ret = i2c_add_adapter(&gi2c->adap);
> if (ret)
> return dev_err_probe(dev, ret, "Error adding i2c adapter\n");
>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-03 16:17 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-26 9:51 [PATCH 0/3] i2c: qcom-geni: Add I2C bus recovery support Aniket Randive
2026-08-26 9:51 ` [PATCH v1 1/3] dmaengine: qcom: gpi: Add I2C bus recovery opcode support Aniket Randive
2026-08-26 9:51 ` [PATCH v1 2/3] i2c: qcom-geni: Add bus recovery support for FIFO mode Aniket Randive
2026-09-03 16:17 ` Jyothi Kumar Seerapu
2026-08-26 9:51 ` [PATCH v1 3/3] i2c: qcom-geni: Add bus recovery support for GPI DMA mode Aniket Randive
2026-09-03 16:14 ` Jyothi Kumar Seerapu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox