#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dmaengine.h>
+#include <linux/genalloc.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
struct vb2_v4l2_buffer vb;
bool prepared;
struct sg_table sgt;
+ struct sg_table sgt_mdma;
struct dma_async_tx_descriptor *dma_desc;
+ struct dma_async_tx_descriptor *mdma_desc;
size_t size;
struct list_head list;
};
struct dma_chan *dma_chan;
dma_cookie_t dma_cookie;
u32 dma_max_burst;
+
+ /* Elements for the MDMA - DMA chaining */
+ struct gen_pool *sram_pool;
+ struct dma_chan *mdma_chan;
+ void *sram_buf;
+ u32 sram_buf_size;
+ dma_addr_t sram_dma_buf;
+ dma_cookie_t mdma_cookie;
+
u32 misr;
int errors_count;
int overrun_count;
struct dcmi_buf *buf)
{
/* Push current DMA transaction in the pending queue */
+ if (dcmi->mdma_chan) {
+ dcmi->mdma_cookie = dmaengine_submit(buf->mdma_desc);
+ if (dma_submit_error(dcmi->mdma_cookie)) {
+ dev_err(dcmi->dev, "%s: MDMA submission failed\n", __func__);
+ return -ENXIO;
+ }
+ }
+
dcmi->dma_cookie = dmaengine_submit(buf->dma_desc);
if (dma_submit_error(dcmi->dma_cookie)) {
dev_err(dcmi->dev, "%s: DMA submission failed\n", __func__);
return -ENXIO;
}
+ if (dcmi->mdma_chan)
+ dma_async_issue_pending(dcmi->mdma_chan);
dma_async_issue_pending(dcmi->dma_chan);
return 0;
static void dcmi_process_frame(struct stm32_dcmi *dcmi)
{
- struct dma_tx_state state;
+ struct dma_tx_state state, state_dma;
+ size_t bytes_used;
+
enum dma_status status;
struct dcmi_buf *buf = dcmi->active;
return;
/*
- * At the time FRAME interrupt is received, all dma req have been sent to the DMA,
- * however DMA might still be transferring data hence first synchronize prior to
- * getting the status of the DMA transfer.
- * Then DMA tx status gives the amount of data transferred to memory, which is then
- * returned to V4L2 through the active buffer payload.
+ * Pause the DMA transfer, leading to trigger of the MDMA channel read while
+ * keeping a valid residue value on the dma channel
*/
+ if (dcmi->mdma_chan) {
+ spin_unlock_irq(&dcmi->irqlock);
+ dmaengine_pause(dcmi->dma_chan);
+ spin_lock_irq(&dcmi->irqlock);
+
+ do {
+ status = dmaengine_tx_status(dcmi->mdma_chan, dcmi->mdma_cookie, &state);
+ cpu_relax();
+ } while (status != DMA_ERROR && status != DMA_COMPLETE &&
+ state.residue && state.in_flight_bytes);
+ } else {
+ status = dmaengine_tx_status(dcmi->dma_chan, dcmi->dma_cookie, &state);
+ }
- spin_unlock_irq(&dcmi->irqlock);
- /* Drain DMA */
- dmaengine_synchronize(dcmi->dma_chan);
- spin_lock_irq(&dcmi->irqlock);
-
- /* Get DMA status and residue size */
- status = dmaengine_tx_status(dcmi->dma_chan, dcmi->dma_cookie, &state);
if (status != DMA_ERROR && state.residue < buf->size) {
+ bytes_used = buf->size - state.residue;
+
+ if (state.residue && dcmi->mdma_chan) {
+ dmaengine_tx_status(dcmi->dma_chan, dcmi->dma_cookie, &state_dma);
+ /* Getting full size residue equal to no residue */
+ if (state_dma.residue == dcmi->sram_buf_size)
+ state_dma.residue = 0;
+ bytes_used -= state_dma.residue;
+ }
+
/* Return buffer to V4L2 with received data size */
- dcmi_buffer_done(dcmi, buf, buf->size - state.residue, 0);
+ dcmi_buffer_done(dcmi, buf, bytes_used, 0);
} else {
dcmi->errors_count++;
dev_err(dcmi->dev, "%s: DMA error. status: 0x%x, residue: %d\n",
/* Abort DMA operation */
dmaengine_terminate_async(dcmi->dma_chan);
+ if (dcmi->mdma_chan)
+ dmaengine_terminate_async(dcmi->mdma_chan);
}
static irqreturn_t dcmi_irq_thread(int irq, void *arg)
dcmi->errors_count++;
dmaengine_terminate_async(dcmi->dma_chan);
+ if (dcmi->mdma_chan)
+ dmaengine_terminate_async(dcmi->mdma_chan);
if (dcmi_restart_capture(dcmi))
dev_err(dcmi->dev, "%s: Cannot restart capture\n", __func__);
spin_unlock_irq(&dcmi->irqlock);
-
return IRQ_HANDLED;
}
+
if (dcmi->misr & IT_ERR)
dcmi->errors_count++;
vb2_set_plane_payload(vb, 0, size);
if (!buf->prepared) {
+ u32 max_size = dcmi->dma_max_burst;
+ unsigned int dma_nents;
+
/* Get memory addresses */
buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0);
- if (buf->size > dcmi->dma_max_burst)
- num_sgs = DIV_ROUND_UP(buf->size, dcmi->dma_max_burst);
+ if (dcmi->mdma_chan)
+ max_size = dcmi->sram_buf_size / 2;
- ret = sg_alloc_table(&buf->sgt, num_sgs, GFP_ATOMIC);
+ if (buf->size > max_size)
+ num_sgs = DIV_ROUND_UP(buf->size, max_size);
+
+ /*
+ * If we use MDMA chaining, DMA is used in cyclic mode and the scatterlist
+ * maximum size is thus 2
+ */
+ dma_nents = num_sgs;
+ if (dcmi->mdma_chan)
+ dma_nents = min_t(unsigned int, num_sgs, 2);
+
+ ret = sg_alloc_table(&buf->sgt, dma_nents, GFP_ATOMIC);
if (ret) {
- dev_err(dcmi->dev, "sg table alloc failed\n");
+ dev_err(dcmi->dev, "sg table alloc failed for DMA\n");
return ret;
}
+ if (dcmi->mdma_chan) {
+ ret = sg_alloc_table(&buf->sgt_mdma, num_sgs, GFP_ATOMIC);
+ if (ret) {
+ dev_err(dcmi->dev, "sg table alloc failed for MDMA\n");
+ return ret;
+ }
+ }
+
dma_buf = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
dev_dbg(dcmi->dev, "buffer[%d] phy=%pad size=%zu\n",
vb->index, &dma_buf, buf->size);
- for_each_sg(buf->sgt.sgl, sg, num_sgs, i) {
- size_t bytes = min_t(size_t, size, dcmi->dma_max_burst);
+ for_each_sg(buf->sgt.sgl, sg, dma_nents, i) {
+ size_t bytes = min_t(size_t, size, max_size);
+
+ if (!dcmi->mdma_chan) {
+ sg_dma_address(sg) = dma_buf;
+ dma_buf += bytes;
+ } else {
+ /* Targets the beginning = first half of the sram_buf */
+ sg_dma_address(sg) = dcmi->sram_dma_buf;
+ /*
+ * Targets the second half of the sram_bubf
+ * for odd indexes of the item of the sg_list
+ */
+ if (i & 1)
+ sg->dma_address += (dcmi->sram_buf_size / 2);
+ }
+ /*
+ * In case of DMA-MDMA chaining, since the DMA is working in cyclic mode,
+ * we need to provide two linked-list node of same size in order to have
+ * a correct residue value computed.
+ */
+ if (!dcmi->mdma_chan)
+ sg_dma_len(sg) = bytes;
+ else
+ sg_dma_len(sg) = dcmi->sram_buf_size / 2;
- sg_dma_address(sg) = dma_buf;
- sg_dma_len(sg) = bytes;
- dma_buf += bytes;
size -= bytes;
}
DMA_PREP_INTERRUPT);
if (!buf->dma_desc) {
dev_err(dcmi->dev, "%s: DMA dmaengine_prep_slave_sg failed\n", __func__);
+ if (dcmi->mdma_chan)
+ sg_free_table(&buf->sgt_mdma);
sg_free_table(&buf->sgt);
return -EIO;
}
if (ret) {
dev_err(dcmi->dev, "%s: DMA dmaengine_desc_set_reuse failed\n", __func__);
dmaengine_desc_free(buf->dma_desc);
+ if (dcmi->mdma_chan)
+ sg_free_table(&buf->sgt_mdma);
sg_free_table(&buf->sgt);
return -EIO;
}
+ if (dcmi->mdma_chan) {
+ size = dcmi->fmt.fmt.pix.sizeimage;
+ for_each_sg(buf->sgt_mdma.sgl, sg, num_sgs, i) {
+ size_t bytes = min_t(size_t, size, max_size);
+
+ sg_dma_address(sg) = dma_buf;
+ sg_dma_len(sg) = bytes;
+ dma_buf += bytes;
+ size -= bytes;
+ }
+
+ buf->mdma_desc = dmaengine_prep_slave_sg(dcmi->mdma_chan, buf->sgt_mdma.sgl,
+ buf->sgt_mdma.nents,
+ DMA_DEV_TO_MEM,
+ DMA_PREP_INTERRUPT);
+ if (!buf->mdma_desc) {
+ dev_err(dcmi->dev, "%s: failed dmaengine_prep_slave_sg for MDMA\n",
+ __func__);
+ dmaengine_desc_free(buf->dma_desc);
+ sg_free_table(&buf->sgt_mdma);
+ sg_free_table(&buf->sgt);
+ return -EIO;
+ }
+
+ ret = dmaengine_desc_set_reuse(buf->mdma_desc);
+ if (ret) {
+ dev_err(dcmi->dev, "%s: failed to set reuse for mdma desc\n",
+ __func__);
+ dmaengine_desc_free(buf->mdma_desc);
+ dmaengine_desc_free(buf->dma_desc);
+ sg_free_table(&buf->sgt_mdma);
+ sg_free_table(&buf->sgt);
+ return -EIO;
+ }
+ }
+
buf->prepared = true;
vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size);
if (!buf->prepared)
return;
+ if (dcmi->mdma_chan) {
+ ret = dmaengine_desc_free(buf->mdma_desc);
+ if (ret)
+ dev_err(dcmi->dev, "%s: Failed to free the mdma descriptor (0x%x)\n",
+ __func__, ret);
+ sg_free_table(&buf->sgt_mdma);
+ }
+
ret = dmaengine_desc_free(buf->dma_desc);
if (ret)
dev_err(dcmi->dev, "%s: Failed to free the mdma descriptor (0x%x)\n",
/* Stop all pending DMA operations */
dmaengine_terminate_sync(dcmi->dma_chan);
+ if (dcmi->mdma_chan)
+ dmaengine_terminate_sync(dcmi->mdma_chan);
pm_runtime_put(dcmi->dev);
struct v4l2_fwnode_endpoint ep = { .bus_type = 0 };
struct stm32_dcmi *dcmi;
struct vb2_queue *q;
- struct dma_chan *chan;
+ struct dma_chan *chan, *mdma_chan;
struct dma_slave_caps caps;
- struct dma_slave_config dma_config;
+ struct dma_slave_config dma_config, mdma_config;
struct clk *mclk;
int ret = 0;
return dev_err_probe(&pdev->dev, PTR_ERR(chan),
"Failed to request DMA channel\n");
- dcmi->dma_max_burst = UINT_MAX;
- ret = dma_get_slave_caps(chan, &caps);
- if (!ret && caps.max_sg_burst)
- dcmi->dma_max_burst = caps.max_sg_burst * DMA_SLAVE_BUSWIDTH_4_BYTES;
+ mdma_chan = dma_request_chan(&pdev->dev, "mdma_tx");
+ if (IS_ERR(mdma_chan)) {
+ ret = PTR_ERR(mdma_chan);
+ if (ret != -ENODEV)
+ return dev_err_probe(&pdev->dev, ret, "Failed to request MDMA channel\n");
+ mdma_chan = NULL;
+ }
+ /* Configure the DMA channel */
memset(&dma_config, 0, sizeof(dma_config));
dma_config.src_addr = (dma_addr_t)dcmi->res->start + DCMI_DR;
dma_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ if (mdma_chan)
+ dma_config.peripheral_size = 1; /* Indicates chaining */
/* Configure DMA channel */
ret = dmaengine_slave_config(chan, &dma_config);
goto err_dma_slave_config;
}
+ /* If we want MDMA, we also need a sram pool */
+ if (mdma_chan) {
+ dcmi->sram_pool = of_gen_pool_get(pdev->dev.of_node, "sram", 0);
+ if (!dcmi->sram_pool) {
+ dev_info(&pdev->dev, "No SRAM pool, can't use MDMA chaining\n");
+ goto err_dma_slave_config;
+ }
+
+ dev_info(&pdev->dev, "SRAM pool: %zu KiB for DMA/MDMA chaining\n",
+ gen_pool_size(dcmi->sram_pool) / 1024);
+
+ dcmi->sram_buf_size = gen_pool_size(dcmi->sram_pool);
+ dcmi->sram_buf = gen_pool_dma_zalloc(dcmi->sram_pool, dcmi->sram_buf_size,
+ &dcmi->sram_dma_buf);
+ if (!dcmi->sram_buf) {
+ dev_err(dcmi->dev, "Failed to allocate from SRAM\n");
+ goto err_dma_slave_config;
+ }
+
+ /* Configure the MDMA Channel */
+ memset(&mdma_config, 0, sizeof(mdma_config));
+ mdma_config.direction = DMA_DEV_TO_MEM;
+ mdma_config.src_addr = dcmi->sram_dma_buf;
+ mdma_config.peripheral_size = dma_config.peripheral_size;
+ mdma_config.peripheral_config = dma_config.peripheral_config;
+ ret = dmaengine_slave_config(mdma_chan, &mdma_config);
+ if (ret < 0) {
+ dev_err(dcmi->dev, "%s: MDMA channel config failed (%d)\n",
+ __func__, ret);
+ goto err_mdma_slave_config;
+ }
+ }
+
+ dcmi->dma_max_burst = UINT_MAX;
+ /* In case of using DMA-MDMA chaining we consider the maximum infini */
+ if (!mdma_chan) {
+ ret = dma_get_slave_caps(chan, &caps);
+ if (!ret && caps.max_sg_burst)
+ dcmi->dma_max_burst = caps.max_sg_burst * DMA_SLAVE_BUSWIDTH_4_BYTES;
+ }
+
spin_lock_init(&dcmi->irqlock);
mutex_init(&dcmi->lock);
init_completion(&dcmi->complete);
dcmi->mclk = mclk;
dcmi->state = STOPPED;
dcmi->dma_chan = chan;
+ dcmi->mdma_chan = mdma_chan;
q = &dcmi->queue;
v4l2_device_unregister(&dcmi->v4l2_dev);
err_media_device_cleanup:
media_device_cleanup(&dcmi->mdev);
+err_mdma_slave_config:
+ if (dcmi->mdma_chan)
+ gen_pool_free(dcmi->sram_pool, (unsigned long)dcmi->sram_buf, dcmi->sram_buf_size);
err_dma_slave_config:
dma_release_channel(dcmi->dma_chan);
+ if (dcmi->mdma_chan)
+ dma_release_channel(mdma_chan);
return ret;
}
media_device_cleanup(&dcmi->mdev);
dma_release_channel(dcmi->dma_chan);
+ if (dcmi->mdma_chan) {
+ gen_pool_free(dcmi->sram_pool, (unsigned long)dcmi->sram_buf, dcmi->sram_buf_size);
+ dma_release_channel(dcmi->mdma_chan);
+ }
}
static int dcmi_runtime_suspend(struct device *dev)