[PATCH 1/2] i2c: qcom-geni: Add shutdown callback to quiesce hardware on reboot

From: Praveen Talari

Date: Tue Aug 18 2026 - 01:01:41 EST


During system reboot, an active I2C transfer can leave the GENI Serial
Engine in an indeterminate state. On VM-based platforms, if a DMA
transfer is in progress when the VM is shut down, the SMMU can raise
context faults as the DMA engine continues to access IOVAs that have
already been invalidated during VM teardown.

Add a geni_i2c_quiesce() helper that cancels/aborts any in-progress
GENI or GPI DMA transfer and resets the TX/RX FSMs, and call it from a
new shutdown callback before the system resets. This prevents both
hardware state corruption on reboot and SMMU faults during VM
shutdown. dmaengine_terminate_async() is used unconditionally so the
helper is also safe to call from atomic contexts such as a panic
notifier.

Signed-off-by: Praveen Talari <praveen.talari@xxxxxxxxxxxxxxxx>
---
drivers/i2c/busses/i2c-qcom-geni.c | 51 ++++++++++++++++++++++++++++++++++++--
1 file changed, 49 insertions(+), 2 deletions(-)

diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
index 658636c1ee0e..10c6a4b0b825 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -13,6 +13,7 @@
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
@@ -1034,6 +1035,53 @@ static int setup_gpi_dma(struct geni_i2c_dev *gi2c)
return ret;
}

+static void geni_i2c_quiesce(struct geni_i2c_dev *gi2c)
+{
+ void __iomem *base = gi2c->se.base;
+ u32 val;
+
+ /* Make client i2c transfers start failing */
+ i2c_mark_adapter_suspended(&gi2c->adap);
+
+ if (!pm_runtime_active(gi2c->se.dev))
+ return;
+
+ if (gi2c->gpi_mode) {
+ dmaengine_terminate_async(gi2c->tx_c);
+ dmaengine_terminate_async(gi2c->rx_c);
+ return;
+ }
+
+ if (!(readl_relaxed(base + SE_GENI_STATUS) & M_GENI_CMD_ACTIVE))
+ return;
+
+ geni_se_cancel_m_cmd(&gi2c->se);
+
+ if (!readl_poll_timeout_atomic(base + SE_GENI_M_IRQ_STATUS, val,
+ val & M_CMD_CANCEL_EN, 10, 200000)) {
+ writel_relaxed(M_CMD_CANCEL_EN, base + SE_GENI_M_IRQ_CLEAR);
+ return;
+ }
+
+ geni_se_abort_m_cmd(&gi2c->se);
+
+ if (!readl_poll_timeout_atomic(base + SE_GENI_M_IRQ_STATUS, val,
+ val & M_CMD_ABORT_EN, 10, 200000))
+ writel_relaxed(M_CMD_ABORT_EN, base + SE_GENI_M_IRQ_CLEAR);
+
+ if (readl_relaxed(base + SE_GENI_DMA_MODE_EN)) {
+ writel_relaxed(1, base + SE_DMA_TX_FSM_RST);
+ readl_poll_timeout_atomic(base + SE_DMA_TX_IRQ_STAT, val,
+ val & TX_RESET_DONE, 10, 50000);
+ writel_relaxed(val, base + SE_DMA_TX_IRQ_CLR);
+
+ writel_relaxed(1, base + SE_DMA_RX_FSM_RST);
+ readl_poll_timeout_atomic(base + SE_DMA_RX_IRQ_STAT, val,
+ val & RX_RESET_DONE, 10, 50000);
+ writel_relaxed(val, base + SE_DMA_RX_IRQ_CLR);
+ }
+}
+
static int geni_i2c_init(struct geni_i2c_dev *gi2c)
{
u32 proto, tx_depth;
@@ -1209,8 +1257,7 @@ static void geni_i2c_shutdown(struct platform_device *pdev)
{
struct geni_i2c_dev *gi2c = platform_get_drvdata(pdev);

- /* Make client i2c transfers start failing */
- i2c_mark_adapter_suspended(&gi2c->adap);
+ geni_i2c_quiesce(gi2c);
}

static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev)

--
2.34.1