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 {
struct v4l2_format v_fmt;
struct ti_csi2rx_dma dma;
struct media_pad pad;
+ struct completion drain_complete;
u32 sequence;
u32 idx;
u32 vc;
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;
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);
}
/*
{
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);
}
desc->callback = ti_csi2rx_drain_callback;
- desc->callback_param = &drain_complete;
+ desc->callback_param = ctx;
cookie = dmaengine_submit(desc);
ret = dma_submit_error(cookie);
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;
}
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);
}
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
* 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);
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)