[PATCH 13/16] i3c: mipi-i3c-hci: Wait for NoOp commands to complete

From: Adrian Hunter

Date: Thu Apr 16 2026 - 14:05:00 EST


When a transfer list is only partially completed due to an error,
hci_dma_dequeue_xfer() overwrites the remaining DMA ring entries with
NoOp commands and restarts the ring to flush them out.

While NoOp commands are expected to complete successfully, they may still
fail to complete if the DMA ring is stuck. Explicitly wait for the NoOp
commands to finish, and trigger controller recovery if they do not
complete or report an error.

This ensures that partially completed transfer lists are reliably
resolved and that a stuck ring is recovered promptly.

Signed-off-by: Adrian Hunter <adrian.hunter@xxxxxxxxx>
---
drivers/i3c/master/mipi-i3c-hci/dma.c | 37 ++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 6 deletions(-)

diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c
index 6811503c869e..053e487b6257 100644
--- a/drivers/i3c/master/mipi-i3c-hci/dma.c
+++ b/drivers/i3c/master/mipi-i3c-hci/dma.c
@@ -694,11 +694,33 @@ static void hci_dma_recovery(struct i3c_hci *hci)
dev_err(&hci->master.dev, "Recovery %s\n", ret ? "failed!" : "done");
}

+static bool hci_dma_wait_for_noop(struct i3c_hci *hci, struct hci_xfer *xfer_list, int n,
+ int noop_pos)
+{
+ struct completion *done = xfer_list->completing_xfer->completion;
+ bool timeout = !wait_for_completion_timeout(done, HZ);
+ u32 error = timeout;
+
+ for (int i = noop_pos; i < n && !error; i++)
+ error = RESP_STATUS(xfer_list[i].response);
+
+ if (!error)
+ return true;
+
+ if (timeout)
+ dev_err(&hci->master.dev, "NoOp timeout error\n");
+ else
+ dev_err(&hci->master.dev, "NoOp error %u\n", error);
+
+ return false;
+}
+
static bool hci_dma_dequeue_xfer(struct i3c_hci *hci,
struct hci_xfer *xfer_list, int n)
{
struct hci_rings_data *rings = hci->io_data;
struct hci_rh_data *rh = &rings->headers[xfer_list[0].ring_number];
+ int noop_pos = -1;
unsigned int i;
bool did_unqueue = false;
u32 ring_status;
@@ -706,7 +728,7 @@ static bool hci_dma_dequeue_xfer(struct i3c_hci *hci,
guard(mutex)(&hci->control_mutex);

spin_lock_irq(&hci->lock);
-
+restart:
ring_status = rh_reg_read(RING_STATUS);
if (ring_status & RING_STATUS_RUNNING) {
/*
@@ -762,11 +784,8 @@ static bool hci_dma_dequeue_xfer(struct i3c_hci *hci,
*ring_data++ = 0;
}

- /* disassociate this xfer struct */
- rh->src_xfers[idx] = NULL;
-
- /* and unmap it */
- hci_dma_unmap_xfer(hci, xfer, 1);
+ if (noop_pos < 0)
+ noop_pos = i;

did_unqueue = true;
}
@@ -799,6 +818,12 @@ static bool hci_dma_dequeue_xfer(struct i3c_hci *hci,

wait_for_completion_timeout(&rh->op_done, HZ);

+ if (did_unqueue && !hci_dma_wait_for_noop(hci, xfer_list, n, noop_pos)) {
+ spin_lock_irq(&hci->lock);
+ hci->recovery_needed = true;
+ goto restart;
+ }
+
return did_unqueue;
}

--
2.51.0