On Wed May 20 17:30:19 2026 +0530, Rishikesh Donadkar wrote:
> On buffer starvation the DMA is marked IDLE, and the stale data in the
> internal FIFOs gets drained only on the next VIDIOC_QBUF call from the
> userspace. This approach works fine for a single stream case.
>
> But in multistream scenarios, buffer starvation for one stream can
> block the shared HW FIFO of the CSI2RX IP. This can stall the pipeline
> for all other streams, even if buffers are available for them.
>
> This patch introduces a new architecture, that continuously drains data
> from the shared HW FIFO into a small (32KiB) buffer if no buffers are made
> available to the driver from the userspace. This ensures independence
> between different streams, where a slower downstream element for one
> camera does not block streaming for other cameras.
>
> Additionally, after we drain for a stream, the next frame will be a
> partial frame, as a portion of its data will have already been drained
> before a valid buffer is queued by user space to the driver.
> Return the partial frame to user space with VB2_BUF_STATE_ERROR.
>
> Use wait for completion barrier to make sure the shared hardware FIFO
> is cleared of the data at the end of stream after the source has stopped
> sending data.
>
> Reviewed-by: Jai Luthra <[email protected]>
> Reviewed-by: Yemike Abhilash Chandra <[email protected]>
> Signed-off-by: Rishikesh Donadkar <[email protected]>
> Reviewed-by: Tomi Valkeinen <[email protected]>
> Signed-off-by: Sakari Ailus <[email protected]>
Patch committed.
Thanks,
Sakari Ailus
.../media/platform/ti/j721e-csi2rx/j721e-csi2rx.c | 121 ++++++++++-----------
1 file changed, 58 insertions(+), 63 deletions(-)
---
diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index 071ad969dfa6..3142849f9bb9 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -82,8 +82,8 @@ struct ti_csi2rx_buffer {
enum ti_csi2rx_dma_state {
TI_CSI2RX_DMA_STOPPED, /* Streaming not started yet. */
- TI_CSI2RX_DMA_IDLE, /* Streaming but no pending DMA operation. */
TI_CSI2RX_DMA_ACTIVE, /* Streaming and pending DMA operation. */
+ TI_CSI2RX_DMA_DRAINING, /* Dumping all the data in drain buffer */
};
struct ti_csi2rx_dma {
@@ -109,6 +109,7 @@ struct ti_csi2rx_ctx {
struct v4l2_format v_fmt;
struct ti_csi2rx_dma dma;
struct media_pad pad;
+ struct completion drain_complete;
u32 sequence;
u32 idx;
u32 vc;
@@ -249,6 +250,10 @@ static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
struct ti_csi2rx_buffer *buf);
+/* Forward declarations needed by ti_csi2rx_drain_callback. */
+static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx);
+static int ti_csi2rx_dma_submit_pending(struct ti_csi2rx_ctx *ctx);
+
static const struct ti_csi2rx_fmt *find_format_by_fourcc(u32 pixelformat)
{
unsigned int i;
@@ -608,9 +613,32 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
static void ti_csi2rx_drain_callback(void *param)
{
- struct completion *drain_complete = param;
+ struct ti_csi2rx_ctx *ctx = param;
+ struct ti_csi2rx_dma *dma = &ctx->dma;
+ unsigned long flags;
- complete(drain_complete);
+ spin_lock_irqsave(&dma->lock, flags);
+
+ if (dma->state == TI_CSI2RX_DMA_STOPPED) {
+ complete(&ctx->drain_complete);
+ spin_unlock_irqrestore(&dma->lock, flags);
+ return;
+ }
+
+ /*
+ * If dma->queue is empty, it indicates that no buffer has been
+ * provided by user space. In this case, initiate a transactions
+ * to drain the DMA. Since one drain of size DRAIN_BUFFER_SIZE
+ * will be done here, the subsequent frame will be a
+ * partial frame, with a size of frame_size - DRAIN_BUFFER_SIZE
+ */
+ if (list_empty(&dma->queue)) {
+ if (ti_csi2rx_drain_dma(ctx))
+ dev_warn(ctx->csi->dev, "DMA drain failed\n");
+ } else {
+ ti_csi2rx_dma_submit_pending(ctx);
+ }
+ spin_unlock_irqrestore(&dma->lock, flags);
}
/*
@@ -628,12 +656,9 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
{
struct ti_csi2rx_dev *csi = ctx->csi;
struct dma_async_tx_descriptor *desc;
- struct completion drain_complete;
dma_cookie_t cookie;
int ret;
- init_completion(&drain_complete);
-
desc = dmaengine_prep_slave_single(ctx->dma.chan, csi->drain.paddr,
csi->drain.len, DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
@@ -643,7 +668,7 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
}
desc->callback = ti_csi2rx_drain_callback;
- desc->callback_param = &drain_complete;
+ desc->callback_param = ctx;
cookie = dmaengine_submit(desc);
ret = dma_submit_error(cookie);
@@ -652,13 +677,6 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
dma_async_issue_pending(ctx->dma.chan);
- if (!wait_for_completion_timeout(&drain_complete,
- msecs_to_jiffies(DRAIN_TIMEOUT_MS))) {
- dmaengine_terminate_sync(ctx->dma.chan);
- dev_dbg(csi->dev, "DMA transfer timed out for drain buffer\n");
- ret = -ETIMEDOUT;
- goto out;
- }
out:
return ret;
}
@@ -702,14 +720,24 @@ static void ti_csi2rx_dma_callback(void *param)
spin_lock_irqsave(&dma->lock, flags);
WARN_ON(!list_is_first(&buf->list, &dma->submitted));
- vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+
+ if (dma->state == TI_CSI2RX_DMA_DRAINING) {
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ dma->state = TI_CSI2RX_DMA_ACTIVE;
+ } else {
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+ }
+
list_del(&buf->list);
ti_csi2rx_dma_submit_pending(ctx);
- if (list_empty(&dma->submitted))
- dma->state = TI_CSI2RX_DMA_IDLE;
-
+ if (list_empty(&dma->submitted)) {
+ dma->state = TI_CSI2RX_DMA_DRAINING;
+ if (ti_csi2rx_drain_dma(ctx))
+ dev_warn(ctx->csi->dev,
+ "DMA drain failed on one of the
transactions\n");
+ }
spin_unlock_irqrestore(&dma->lock, flags);
}
@@ -754,6 +782,8 @@ static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
dma->state = TI_CSI2RX_DMA_STOPPED;
spin_unlock_irqrestore(&dma->lock, flags);
+ init_completion(&ctx->drain_complete);
+
if (state != TI_CSI2RX_DMA_STOPPED) {
/*
* Normal DMA termination does not clean up pending data on
@@ -762,11 +792,19 @@ static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
* enforced before terminating DMA.
*/
ret = ti_csi2rx_drain_dma(ctx);
- if (ret && ret != -ETIMEDOUT)
+ if (ret)
dev_warn(ctx->csi->dev,
"Failed to drain DMA. Next frame might be
bogus\n");
}
+ /* We wait for the drain to complete so that the stream stops
+ * cleanly, making sure the shared hardware FIFO is cleared of
+ * data from the current stream. No more data will be coming from
+ * the source after this.
+ */
+ wait_for_completion_timeout(&ctx->drain_complete,
+ msecs_to_jiffies(DRAIN_TIMEOUT_MS));
+
ret = dmaengine_terminate_sync(ctx->dma.chan);
if (ret)
dev_err(ctx->csi->dev, "Failed to stop DMA: %d\n", ret);
@@ -829,57 +867,14 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
struct ti_csi2rx_buffer *buf;
struct ti_csi2rx_dma *dma = &ctx->dma;
- bool restart_dma = false;
unsigned long flags = 0;
- int ret;
buf = container_of(vb, struct ti_csi2rx_buffer, vb.vb2_buf);
buf->ctx = ctx;
spin_lock_irqsave(&dma->lock, flags);
- /*
- * Usually the DMA callback takes care of queueing the pending buffers.
- * But if DMA has stalled due to lack of buffers, restart it now.
- */
- if (dma->state == TI_CSI2RX_DMA_IDLE) {
- /*
- * Do not restart DMA with the lock held because
- * ti_csi2rx_drain_dma() might block for completion.
- * There won't be a race on queueing DMA anyway since the
- * callback is not being fired.
- */
- restart_dma = true;
- dma->state = TI_CSI2RX_DMA_ACTIVE;
- } else {
- list_add_tail(&buf->list, &dma->queue);
- }
+ list_add_tail(&buf->list, &dma->queue);
spin_unlock_irqrestore(&dma->lock, flags);
-
- if (restart_dma) {
- /*
- * Once frames start dropping, some data gets stuck in the DMA
- * pipeline somewhere. So the first DMA transfer after frame
- * drops gives a partial frame. This is obviously not useful to
- * the application and will only confuse it. Issue a DMA
- * transaction to drain that up.
- */
- ret = ti_csi2rx_drain_dma(ctx);
- if (ret && ret != -ETIMEDOUT)
- dev_warn(ctx->csi->dev,
- "Failed to drain DMA. Next frame might be
bogus\n");
-
- spin_lock_irqsave(&dma->lock, flags);
- ret = ti_csi2rx_start_dma(ctx, buf);
- if (ret) {
- vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
- dma->state = TI_CSI2RX_DMA_IDLE;
- spin_unlock_irqrestore(&dma->lock, flags);
- dev_err(ctx->csi->dev, "Failed to start DMA: %d\n",
ret);
- } else {
- list_add_tail(&buf->list, &dma->submitted);
- spin_unlock_irqrestore(&dma->lock, flags);
- }
- }
}
static int ti_csi2rx_get_stream(struct ti_csi2rx_ctx *ctx)
_______________________________________________
linuxtv-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]