This series improve stability of the capture by fixing the handling of the overrun which was leading to captured frame corruption. Locking within the driver is also simplified and the way DMA is handled is reworked allowing to avoid having a specific handling for the JPEG data.
Performances of capture can now be increased via the usage of a DMA->MDMA chaining which allows for capture of higher resolution / framerate.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- Alain Volmat (12): media: stm32: dcmi: Switch from __maybe_unused to pm_sleep_ptr() media: stm32: dcmi: perform dmaengine_slave_config at probe media: stm32: dcmi: only create dma descriptor once at buf_prepare media: stm32: dcmi: stop the dma transfer on overrun media: stm32: dcmi: rework spin_lock calls media: stm32: dcmi: perform all dma handling within irq_thread media: stm32: dcmi: use dmaengine_terminate_async in irq context media: stm32: dcmi: continuous mode capture in JPEG dt-bindings: media: st: dcmi: add DMA-MDMA chaining properties media: stm32: dcmi: addition of DMA-MDMA chaining support ARM: dts: stm32: add sram node within stm32mp151.dtsi ARM: dts: stm32: enable DCMI DMA-MDMA chaining on stm32mp157c-ev1.dts
.../devicetree/bindings/media/st,stm32-dcmi.yaml | 13 +- arch/arm/boot/dts/st/stm32mp151.dtsi | 8 + arch/arm/boot/dts/st/stm32mp157c-ev1.dts | 15 + drivers/media/platform/st/stm32/stm32-dcmi.c | 470 ++++++++++++++------- 4 files changed, 341 insertions(+), 165 deletions(-) --- base-commit: f7231cff1f3ff8259bef02dc4999bc132abf29cf change-id: 20251213-stm32-dcmi-dma-chaining-9ea1da83007d
Best regards,
Perform the dma channel configuration at probe time right after the channel allocation since this is fixed for the whole lifetime of the driver.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- drivers/media/platform/st/stm32/stm32-dcmi.c | 32 +++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-)
diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index 07b67eb5b9dd..27b283474096 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -301,23 +301,6 @@ static int dcmi_start_dma(struct stm32_dcmi *dcmi, struct dcmi_buf *buf) { struct dma_async_tx_descriptor *desc = NULL; - struct dma_slave_config config; - int ret; - - memset(&config, 0, sizeof(config)); - - config.src_addr = (dma_addr_t)dcmi->res->start + DCMI_DR; - config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - config.dst_maxburst = 4; - - /* Configure DMA channel */ - ret = dmaengine_slave_config(dcmi->dma_chan, &config); - if (ret < 0) { - dev_err(dcmi->dev, "%s: DMA channel config failed (%d)\n", - __func__, ret); - return ret; - }
/* * Avoid call of dmaengine_terminate_sync() between @@ -1888,6 +1871,7 @@ static int dcmi_probe(struct platform_device *pdev) struct vb2_queue *q; struct dma_chan *chan; struct dma_slave_caps caps; + struct dma_slave_config dma_config; struct clk *mclk; int ret = 0;
@@ -1954,6 +1938,19 @@ static int dcmi_probe(struct platform_device *pdev) if (!ret && caps.max_sg_burst) dcmi->dma_max_burst = caps.max_sg_burst * DMA_SLAVE_BUSWIDTH_4_BYTES;
+ 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; + + /* Configure DMA channel */ + ret = dmaengine_slave_config(chan, &dma_config); + if (ret < 0) { + dev_err(dcmi->dev, "%s: DMA channel config failed (%d)\n", + __func__, ret); + goto err_dma_slave_config; + } + spin_lock_init(&dcmi->irqlock); mutex_init(&dcmi->lock); mutex_init(&dcmi->dma_lock); @@ -2072,6 +2069,7 @@ static int dcmi_probe(struct platform_device *pdev) v4l2_device_unregister(&dcmi->v4l2_dev); err_media_device_cleanup: media_device_cleanup(&dcmi->mdev); +err_dma_slave_config: dma_release_channel(dcmi->dma_chan);
return ret;
Perform the dmaengine prep_slave_sg call within buf_prepare and mark the descriptor as reusable in order to avoid having to redo this at every start of the dma. This also allow to remove the mutex used by the driver to protect dma descriptors related piece of codes.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- drivers/media/platform/st/stm32/stm32-dcmi.c | 78 ++++++++++++++++------------ 1 file changed, 45 insertions(+), 33 deletions(-)
diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index 27b283474096..d8ef06bd7506 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -113,6 +113,7 @@ struct dcmi_buf { struct vb2_v4l2_buffer vb; bool prepared; struct sg_table sgt; + struct dma_async_tx_descriptor *dma_desc; size_t size; struct list_head list; }; @@ -163,9 +164,6 @@ struct stm32_dcmi { int overrun_count; int buffers_count;
- /* Ensure DMA operations atomicity */ - struct mutex dma_lock; - struct media_device mdev; struct media_pad vid_cap_pad; struct media_pipeline pipeline; @@ -300,39 +298,13 @@ static void dcmi_dma_callback(void *param) static int dcmi_start_dma(struct stm32_dcmi *dcmi, struct dcmi_buf *buf) { - struct dma_async_tx_descriptor *desc = NULL; - - /* - * Avoid call of dmaengine_terminate_sync() between - * dmaengine_prep_slave_single() and dmaengine_submit() - * by locking the whole DMA submission sequence - */ - mutex_lock(&dcmi->dma_lock); - - /* Prepare a DMA transaction */ - desc = dmaengine_prep_slave_sg(dcmi->dma_chan, buf->sgt.sgl, buf->sgt.nents, - DMA_DEV_TO_MEM, - DMA_PREP_INTERRUPT); - if (!desc) { - dev_err(dcmi->dev, "%s: DMA dmaengine_prep_slave_sg failed\n", __func__); - mutex_unlock(&dcmi->dma_lock); - return -EINVAL; - } - - /* Set completion callback routine for notification */ - desc->callback = dcmi_dma_callback; - desc->callback_param = dcmi; - /* Push current DMA transaction in the pending queue */ - dcmi->dma_cookie = dmaengine_submit(desc); + 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__); - mutex_unlock(&dcmi->dma_lock); return -ENXIO; }
- mutex_unlock(&dcmi->dma_lock); - dma_async_issue_pending(dcmi->dma_chan);
return 0; @@ -547,6 +519,31 @@ static int dcmi_buf_prepare(struct vb2_buffer *vb) size -= bytes; }
+ /* Prepare a DMA transaction */ + buf->dma_desc = dmaengine_prep_slave_sg(dcmi->dma_chan, + buf->sgt.sgl, + buf->sgt.nents, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!buf->dma_desc) { + dev_err(dcmi->dev, "%s: DMA dmaengine_prep_slave_sg failed\n", __func__); + sg_free_table(&buf->sgt); + return -EIO; + } + + /* Set completion callback routine for notification */ + buf->dma_desc->callback = dcmi_dma_callback; + buf->dma_desc->callback_param = dcmi; + + /* Mark the descriptor as reusable to avoid having to prepare it */ + ret = dmaengine_desc_set_reuse(buf->dma_desc); + if (ret) { + dev_err(dcmi->dev, "%s: DMA dmaengine_desc_set_reuse failed\n", __func__); + dmaengine_desc_free(buf->dma_desc); + sg_free_table(&buf->sgt); + return -EIO; + } + buf->prepared = true;
vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size); @@ -555,6 +552,23 @@ static int dcmi_buf_prepare(struct vb2_buffer *vb) return 0; }
+static void dcmi_buf_cleanup(struct vb2_buffer *vb) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb); + int ret; + + if (!buf->prepared) + return; + + ret = dmaengine_desc_free(buf->dma_desc); + if (ret) + dev_err(dcmi->dev, "%s: Failed to free the mdma descriptor (0x%x)\n", + __func__, ret); + sg_free_table(&buf->sgt); +} + static void dcmi_buf_queue(struct vb2_buffer *vb) { struct stm32_dcmi *dcmi = vb2_get_drv_priv(vb->vb2_queue); @@ -859,9 +873,7 @@ static void dcmi_stop_streaming(struct vb2_queue *vq) spin_unlock_irq(&dcmi->irqlock);
/* Stop all pending DMA operations */ - mutex_lock(&dcmi->dma_lock); dmaengine_terminate_sync(dcmi->dma_chan); - mutex_unlock(&dcmi->dma_lock);
pm_runtime_put(dcmi->dev);
@@ -878,6 +890,7 @@ static const struct vb2_ops dcmi_video_qops = { .queue_setup = dcmi_queue_setup, .buf_init = dcmi_buf_init, .buf_prepare = dcmi_buf_prepare, + .buf_cleanup = dcmi_buf_cleanup, .buf_queue = dcmi_buf_queue, .start_streaming = dcmi_start_streaming, .stop_streaming = dcmi_stop_streaming, @@ -1953,7 +1966,6 @@ static int dcmi_probe(struct platform_device *pdev)
spin_lock_init(&dcmi->irqlock); mutex_init(&dcmi->lock); - mutex_init(&dcmi->dma_lock); init_completion(&dcmi->complete); INIT_LIST_HEAD(&dcmi->buffers);
Letting the compiler remove these functions when the kernel is built without CONFIG_PM_SLEEP support is simpler and less heavier for builds than the use of __maybe_unused attributes.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- drivers/media/platform/st/stm32/stm32-dcmi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index 13762861b769..07b67eb5b9dd 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -2092,7 +2092,7 @@ static void dcmi_remove(struct platform_device *pdev) dma_release_channel(dcmi->dma_chan); }
-static __maybe_unused int dcmi_runtime_suspend(struct device *dev) +static int dcmi_runtime_suspend(struct device *dev) { struct stm32_dcmi *dcmi = dev_get_drvdata(dev);
@@ -2101,7 +2101,7 @@ static __maybe_unused int dcmi_runtime_suspend(struct device *dev) return 0; }
-static __maybe_unused int dcmi_runtime_resume(struct device *dev) +static int dcmi_runtime_resume(struct device *dev) { struct stm32_dcmi *dcmi = dev_get_drvdata(dev); int ret; @@ -2113,7 +2113,7 @@ static __maybe_unused int dcmi_runtime_resume(struct device *dev) return ret; }
-static __maybe_unused int dcmi_suspend(struct device *dev) +static int dcmi_suspend(struct device *dev) { /* disable clock */ pm_runtime_force_suspend(dev); @@ -2124,7 +2124,7 @@ static __maybe_unused int dcmi_suspend(struct device *dev) return 0; }
-static __maybe_unused int dcmi_resume(struct device *dev) +static int dcmi_resume(struct device *dev) { /* restore pinctl default state */ pinctrl_pm_select_default_state(dev); @@ -2147,7 +2147,7 @@ static struct platform_driver stm32_dcmi_driver = { .driver = { .name = DRV_NAME, .of_match_table = of_match_ptr(stm32_dcmi_of_match), - .pm = &dcmi_pm_ops, + .pm = pm_sleep_ptr(&dcmi_pm_ops), }, };
Hi Alain,
kernel test robot noticed the following build warnings:
[auto build test WARNING on atorgue-stm32/stm32-next] [also build test WARNING on robh/for-next linus/master v6.19-rc1 next-20251219] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Alain-Volmat/media-stm32-dcmi... base: https://git.kernel.org/pub/scm/linux/kernel/git/atorgue/stm32.git stm32-next patch link: https://lore.kernel.org/r/20251218-stm32-dcmi-dma-chaining-v1-1-39948ca6cbf6... patch subject: [PATCH 01/12] media: stm32: dcmi: Switch from __maybe_unused to pm_sleep_ptr() config: arc-allyesconfig (https://download.01.org/0day-ci/archive/20251221/202512210044.xNNW6QJZ-lkp@i...) compiler: arc-linux-gcc (GCC) 15.1.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251221/202512210044.xNNW6QJZ-lkp@i...)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202512210044.xNNW6QJZ-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/media/platform/st/stm32/stm32-dcmi.c:2127:12: warning: 'dcmi_resume' defined but not used [-Wunused-function]
2127 | static int dcmi_resume(struct device *dev) | ^~~~~~~~~~~
drivers/media/platform/st/stm32/stm32-dcmi.c:2116:12: warning: 'dcmi_suspend' defined but not used [-Wunused-function]
2116 | static int dcmi_suspend(struct device *dev) | ^~~~~~~~~~~~
vim +/dcmi_resume +2127 drivers/media/platform/st/stm32/stm32-dcmi.c
2115
2116 static int dcmi_suspend(struct device *dev)
2117 { 2118 /* disable clock */ 2119 pm_runtime_force_suspend(dev); 2120 2121 /* change pinctrl state */ 2122 pinctrl_pm_select_sleep_state(dev); 2123 2124 return 0; 2125 } 2126
2127 static int dcmi_resume(struct device *dev)
2128 { 2129 /* restore pinctl default state */ 2130 pinctrl_pm_select_default_state(dev); 2131 2132 /* clock enable */ 2133 pm_runtime_force_resume(dev); 2134 2135 return 0; 2136 } 2137
Ensure to stop the dma transfer whenever receiving a overrun to avoid having a buffer partially filled with a frame and partially with the next frame.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- drivers/media/platform/st/stm32/stm32-dcmi.c | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index d8ef06bd7506..fa33acc34eab 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -402,9 +402,21 @@ static irqreturn_t dcmi_irq_thread(int irq, void *arg) spin_lock_irq(&dcmi->irqlock);
if (dcmi->misr & IT_OVR) { + /* Disable capture */ + reg_clear(dcmi->regs, DCMI_CR, CR_CAPTURE); + dcmi->overrun_count++; + if (dcmi->overrun_count > OVERRUN_ERROR_THRESHOLD) dcmi->errors_count++; + + spin_unlock_irq(&dcmi->irqlock); + dmaengine_terminate_sync(dcmi->dma_chan); + + if (dcmi_restart_capture(dcmi)) + dev_err(dcmi->dev, "%s: Cannot restart capture\n", __func__); + + return IRQ_HANDLED; } if (dcmi->misr & IT_ERR) dcmi->errors_count++;
Move all the type of frame handling within the dcmi_irq_thread handler and do not rely on dma_callback as previously.
This simplifies the code by having only a single path for both compressed and uncompressed data while also making the system more reactive since irq_handler have more chances to be called faster than the dma completion callback. Indeed, in case of the dma completion callback, this run as a tasklet created by the dma framework upon getting an interrupt from the dma and run at a lower priority level than other irq handlers.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- drivers/media/platform/st/stm32/stm32-dcmi.c | 97 ++++++---------------------- 1 file changed, 18 insertions(+), 79 deletions(-)
diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index 1015f2d88f54..06b66095844b 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -227,8 +227,9 @@ static int dcmi_restart_capture(struct stm32_dcmi *dcmi) { struct dcmi_buf *buf;
+ /* Nothing to do if we are not running */ if (dcmi->state != RUNNING) - return -EINVAL; + return 0;
/* Restart a new DMA transfer with next buffer */ if (list_empty(&dcmi->buffers)) { @@ -242,52 +243,6 @@ static int dcmi_restart_capture(struct stm32_dcmi *dcmi) return dcmi_start_capture(dcmi, buf); }
-static void dcmi_dma_callback(void *param) -{ - struct stm32_dcmi *dcmi = (struct stm32_dcmi *)param; - struct dma_tx_state state; - enum dma_status status; - struct dcmi_buf *buf = dcmi->active; - - spin_lock_irq(&dcmi->irqlock); - - /* Check DMA status */ - status = dmaengine_tx_status(dcmi->dma_chan, dcmi->dma_cookie, &state); - - switch (status) { - case DMA_IN_PROGRESS: - dev_dbg(dcmi->dev, "%s: Received DMA_IN_PROGRESS\n", __func__); - break; - case DMA_PAUSED: - dev_err(dcmi->dev, "%s: Received DMA_PAUSED\n", __func__); - break; - case DMA_ERROR: - dev_err(dcmi->dev, "%s: Received DMA_ERROR\n", __func__); - - /* Return buffer to V4L2 in error state */ - dcmi_buffer_done(dcmi, buf, 0, -EIO); - break; - case DMA_COMPLETE: - dev_dbg(dcmi->dev, "%s: Received DMA_COMPLETE\n", __func__); - - /* Return buffer to V4L2 */ - dcmi_buffer_done(dcmi, buf, buf->size, 0); - - spin_unlock_irq(&dcmi->irqlock); - - /* Restart capture */ - if (dcmi_restart_capture(dcmi)) - dev_err(dcmi->dev, "%s: Cannot restart capture on DMA complete\n", - __func__); - return; - default: - dev_err(dcmi->dev, "%s: Received unknown status\n", __func__); - break; - } - - spin_unlock_irq(&dcmi->irqlock); -} - static int dcmi_start_dma(struct stm32_dcmi *dcmi, struct dcmi_buf *buf) { @@ -344,7 +299,7 @@ static void dcmi_set_crop(struct stm32_dcmi *dcmi) reg_set(dcmi->regs, DCMI_CR, CR_CROP); }
-static void dcmi_process_jpeg(struct stm32_dcmi *dcmi) +static void dcmi_process_frame(struct stm32_dcmi *dcmi) { struct dma_tx_state state; enum dma_status status; @@ -354,13 +309,11 @@ static void dcmi_process_jpeg(struct stm32_dcmi *dcmi) return;
/* - * Because of variable JPEG buffer size sent by sensor, - * DMA transfer never completes due to transfer size never reached. - * In order to ensure that all the JPEG data are transferred - * in active buffer memory, DMA is drained. - * Then DMA tx status gives the amount of data transferred - * to memory, which is then returned to V4L2 through the active - * buffer payload. + * 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. */
spin_unlock_irq(&dcmi->irqlock); @@ -368,16 +321,16 @@ static void dcmi_process_jpeg(struct stm32_dcmi *dcmi) dmaengine_synchronize(dcmi->dma_chan); spin_lock_irq(&dcmi->irqlock);
- /* Get DMA residue to get JPEG size */ + /* 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) { - /* Return JPEG buffer to V4L2 with received JPEG buffer size */ + /* Return buffer to V4L2 with received data size */ dcmi_buffer_done(dcmi, buf, buf->size - state.residue, 0); } else { dcmi->errors_count++; - dev_err(dcmi->dev, "%s: Cannot get JPEG size from DMA\n", - __func__); - /* Return JPEG buffer to V4L2 in ERROR state */ + dev_err(dcmi->dev, "%s: DMA error. status: 0x%x, residue: %d\n", + __func__, status, state.residue); + /* Return buffer to V4L2 in ERROR state */ dcmi_buffer_done(dcmi, buf, 0, -EIO); }
@@ -385,11 +338,6 @@ static void dcmi_process_jpeg(struct stm32_dcmi *dcmi) /* Abort DMA operation */ dmaengine_terminate_sync(dcmi->dma_chan); spin_lock_irq(&dcmi->irqlock); - - /* Restart capture */ - if (dcmi_restart_capture(dcmi)) - dev_err(dcmi->dev, "%s: Cannot restart capture on JPEG received\n", - __func__); }
static irqreturn_t dcmi_irq_thread(int irq, void *arg) @@ -420,12 +368,10 @@ static irqreturn_t dcmi_irq_thread(int irq, void *arg) if (dcmi->misr & IT_ERR) dcmi->errors_count++;
- if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG && - dcmi->misr & IT_FRAME) { - /* JPEG received */ - dcmi_process_jpeg(dcmi); - spin_unlock_irq(&dcmi->irqlock); - return IRQ_HANDLED; + if (dcmi->misr & IT_FRAME) { + dcmi_process_frame(dcmi); + if (dcmi_restart_capture(dcmi)) + dev_err(dcmi->dev, "%s: Cannot restart capture\n", __func__); }
spin_unlock_irq(&dcmi->irqlock); @@ -542,10 +488,6 @@ static int dcmi_buf_prepare(struct vb2_buffer *vb) return -EIO; }
- /* Set completion callback routine for notification */ - buf->dma_desc->callback = dcmi_dma_callback; - buf->dma_desc->callback_param = dcmi; - /* Mark the descriptor as reusable to avoid having to prepare it */ ret = dmaengine_desc_set_reuse(buf->dma_desc); if (ret) { @@ -818,10 +760,7 @@ static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count) }
/* Enable interruptions */ - if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG) - reg_set(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR); - else - reg_set(dcmi->regs, DCMI_IER, IT_OVR | IT_ERR); + reg_set(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR);
spin_unlock_irq(&dcmi->irqlock);
Introduce the sram node in order to be used by drivers requiring SRAM memory space.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- arch/arm/boot/dts/st/stm32mp151.dtsi | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/arch/arm/boot/dts/st/stm32mp151.dtsi b/arch/arm/boot/dts/st/stm32mp151.dtsi index b1b568dfd126..85cb0f16ca73 100644 --- a/arch/arm/boot/dts/st/stm32mp151.dtsi +++ b/arch/arm/boot/dts/st/stm32mp151.dtsi @@ -123,6 +123,14 @@ soc { interrupt-parent = <&intc>; ranges;
+ sram4: sram@10050000 { + compatible = "mmio-sram"; + reg = <0x10050000 0x10000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0x10050000 0x10000>; + }; + ipcc: mailbox@4c001000 { compatible = "st,stm32mp1-ipcc"; #mbox-cells = <1>;
Add properties update and new sram property necessary in order to enable the DMA-MDMA chaining.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml index 34147127192f..ccaa2d0a2669 100644 --- a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml +++ b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml @@ -27,11 +27,14 @@ properties: - const: mclk
dmas: - maxItems: 1 + minItems: 1 + maxItems: 2
dma-names: items: - const: tx + - const: mdma_tx + minItems: 1
resets: maxItems: 1 @@ -40,6 +43,14 @@ properties: minItems: 1 maxItems: 2
+ sram: + $ref: /schemas/types.yaml#/definitions/phandle + description: + phandles to a reserved SRAM region which is used as temporary + storage memory between DMA and MDMA engines. + The region should be defined as child nodes of the AHB SRAM node + as per the generic bindings in Documentation/devicetree/bindings/sram/sram.yaml + port: $ref: /schemas/graph.yaml#/$defs/port-base unevaluatedProperties: false
On Thu, Dec 18, 2025 at 07:44:49PM +0100, Alain Volmat wrote:
Add properties update and new sram property necessary in order to enable the DMA-MDMA chaining.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com
Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml index 34147127192f..ccaa2d0a2669 100644 --- a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml +++ b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml @@ -27,11 +27,14 @@ properties: - const: mclk dmas:
- maxItems: 1
- minItems: 1
- maxItems: 2
dma-names: items: - const: tx
- const: mdma_tx- minItems: 1
resets: maxItems: 1 @@ -40,6 +43,14 @@ properties: minItems: 1 maxItems: 2
- sram:
- $ref: /schemas/types.yaml#/definitions/phandle
- description:
phandles to a reserved SRAM region which is used as temporary
phandle to...
storage memory between DMA and MDMA engines.
The region should be defined as child nodes of the AHB SRAM nodeas per the generic bindings in Documentation/devicetree/bindings/sram/sram.yaml
Drop this sentence.
Rob
Rework of the spin_lock calls in preparation of the rework of the data handling of the driver. Keep it straight forward with basically spin_lock protection around everything except dmaengine calls that might sleep (ex: synchronize / terminate)
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- drivers/media/platform/st/stm32/stm32-dcmi.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-)
diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index fa33acc34eab..1015f2d88f54 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -227,25 +227,18 @@ static int dcmi_restart_capture(struct stm32_dcmi *dcmi) { struct dcmi_buf *buf;
- spin_lock_irq(&dcmi->irqlock); - - if (dcmi->state != RUNNING) { - spin_unlock_irq(&dcmi->irqlock); + if (dcmi->state != RUNNING) return -EINVAL; - }
/* Restart a new DMA transfer with next buffer */ if (list_empty(&dcmi->buffers)) { dev_dbg(dcmi->dev, "Capture restart is deferred to next buffer queueing\n"); dcmi->state = WAIT_FOR_BUFFER; - spin_unlock_irq(&dcmi->irqlock); return 0; } buf = list_entry(dcmi->buffers.next, struct dcmi_buf, list); dcmi->active = buf;
- spin_unlock_irq(&dcmi->irqlock); - return dcmi_start_capture(dcmi, buf); }
@@ -370,8 +363,10 @@ static void dcmi_process_jpeg(struct stm32_dcmi *dcmi) * buffer payload. */
+ spin_unlock_irq(&dcmi->irqlock); /* Drain DMA */ dmaengine_synchronize(dcmi->dma_chan); + spin_lock_irq(&dcmi->irqlock);
/* Get DMA residue to get JPEG size */ status = dmaengine_tx_status(dcmi->dma_chan, dcmi->dma_cookie, &state); @@ -386,8 +381,10 @@ static void dcmi_process_jpeg(struct stm32_dcmi *dcmi) dcmi_buffer_done(dcmi, buf, 0, -EIO); }
+ spin_unlock_irq(&dcmi->irqlock); /* Abort DMA operation */ dmaengine_terminate_sync(dcmi->dma_chan); + spin_lock_irq(&dcmi->irqlock);
/* Restart capture */ if (dcmi_restart_capture(dcmi)) @@ -413,8 +410,10 @@ static irqreturn_t dcmi_irq_thread(int irq, void *arg) spin_unlock_irq(&dcmi->irqlock); dmaengine_terminate_sync(dcmi->dma_chan);
+ spin_lock_irq(&dcmi->irqlock); if (dcmi_restart_capture(dcmi)) dev_err(dcmi->dev, "%s: Cannot restart capture\n", __func__); + spin_unlock_irq(&dcmi->irqlock);
return IRQ_HANDLED; } @@ -424,8 +423,8 @@ static irqreturn_t dcmi_irq_thread(int irq, void *arg) if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG && dcmi->misr & IT_FRAME) { /* JPEG received */ - spin_unlock_irq(&dcmi->irqlock); dcmi_process_jpeg(dcmi); + spin_unlock_irq(&dcmi->irqlock); return IRQ_HANDLED; }
@@ -599,11 +598,9 @@ static void dcmi_buf_queue(struct vb2_buffer *vb) dev_dbg(dcmi->dev, "Starting capture on buffer[%d] queued\n", buf->vb.vb2_buf.index);
- spin_unlock_irq(&dcmi->irqlock); if (dcmi_start_capture(dcmi, buf)) dev_err(dcmi->dev, "%s: Cannot restart capture on overflow or error\n", __func__); - return; }
spin_unlock_irq(&dcmi->irqlock); @@ -812,11 +809,11 @@ static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count)
dev_dbg(dcmi->dev, "Start streaming, starting capture\n");
- spin_unlock_irq(&dcmi->irqlock); ret = dcmi_start_capture(dcmi, buf); if (ret) { dev_err(dcmi->dev, "%s: Start streaming failed, cannot start capture\n", __func__); + spin_unlock_irq(&dcmi->irqlock); goto err_pipeline_stop; }
@@ -826,6 +823,8 @@ static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count) else reg_set(dcmi->regs, DCMI_IER, IT_OVR | IT_ERR);
+ spin_unlock_irq(&dcmi->irqlock); + return 0;
err_pipeline_stop:
Whenever receiving an OVERRUN event or an end of frame, the driver stops currently ongoing DMA transfer since the DCMI stops sending data to dma. Not doing this would lead to having DMA & DCMI no more synchronized in term of expected data to be copied. Since this is done in irq handler context, it is not possible to make any call that would lead to scheduling hence dmaengine_terminate_sync are not possible. Since the dcmi driver is NOT using dma callbacks, it is possible thus to call instead dmaengine_terminate_async (aka without synchronize) and call again right after a new dmaengine_submit to setup again a new transfer. And since this is now a dmaengine_submit_async, there is no need to release the spinlock around calls to the dmaengine_submit_async.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- drivers/media/platform/st/stm32/stm32-dcmi.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index 06b66095844b..25e95ba2ca84 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -334,10 +334,8 @@ static void dcmi_process_frame(struct stm32_dcmi *dcmi) dcmi_buffer_done(dcmi, buf, 0, -EIO); }
- spin_unlock_irq(&dcmi->irqlock); /* Abort DMA operation */ - dmaengine_terminate_sync(dcmi->dma_chan); - spin_lock_irq(&dcmi->irqlock); + dmaengine_terminate_async(dcmi->dma_chan); }
static irqreturn_t dcmi_irq_thread(int irq, void *arg) @@ -355,10 +353,8 @@ static irqreturn_t dcmi_irq_thread(int irq, void *arg) if (dcmi->overrun_count > OVERRUN_ERROR_THRESHOLD) dcmi->errors_count++;
- spin_unlock_irq(&dcmi->irqlock); - dmaengine_terminate_sync(dcmi->dma_chan); + dmaengine_terminate_async(dcmi->dma_chan);
- spin_lock_irq(&dcmi->irqlock); if (dcmi_restart_capture(dcmi)) dev_err(dcmi->dev, "%s: Cannot restart capture\n", __func__); spin_unlock_irq(&dcmi->irqlock);
Add possibility to rely on an additional MDMA channel and chain the DMA and MDMA channels allowing to achieve faster capture. Indeed, on the MP15 platform, the MDMA engine has an higher bandwidth to the DDR than the DMA engine. Relying on that it is possible to capture frames from the DCMI IP into the DDR by using two channels as follow:
DCMI -> (DMA) -> SRAM -> (MDMA) -> DDR
The DMA is able by himself to trigger a MDMA request hence, once properly configured, the DCMI IP can simply trigger the DMA in order to have the data pushed up to the DDR (via the SRAM and a MDMA channel).
This behavior is detailed in the document Documentation/arch/arm/stm32/stm32-dma-mdma-chaining.rst
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- drivers/media/platform/st/stm32/stm32-dcmi.c | 250 +++++++++++++++++++++++---- 1 file changed, 220 insertions(+), 30 deletions(-)
diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index 4c0f8ed0f87e..e4a9f96a989b 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -15,6 +15,7 @@ #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> @@ -113,7 +114,9 @@ struct dcmi_buf { 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; }; @@ -159,6 +162,15 @@ struct stm32_dcmi { 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; @@ -247,12 +259,22 @@ static int dcmi_start_dma(struct stm32_dcmi *dcmi, 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; @@ -301,7 +323,9 @@ static void dcmi_set_crop(struct stm32_dcmi *dcmi)
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;
@@ -309,23 +333,36 @@ static void dcmi_process_frame(struct stm32_dcmi *dcmi) 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", @@ -336,6 +373,8 @@ static void dcmi_process_frame(struct stm32_dcmi *dcmi)
/* 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) @@ -354,13 +393,15 @@ 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++;
@@ -447,28 +488,70 @@ static int dcmi_buf_prepare(struct vb2_buffer *vb) 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; }
@@ -480,6 +563,8 @@ static int dcmi_buf_prepare(struct vb2_buffer *vb) 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; } @@ -489,10 +574,48 @@ static int dcmi_buf_prepare(struct vb2_buffer *vb) 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); @@ -511,6 +634,14 @@ static void dcmi_buf_cleanup(struct vb2_buffer *vb) 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", @@ -816,6 +947,8 @@ static void dcmi_stop_streaming(struct vb2_queue *vq)
/* 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);
@@ -1824,9 +1957,9 @@ static int dcmi_probe(struct platform_device *pdev) 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;
@@ -1888,15 +2021,21 @@ static int dcmi_probe(struct platform_device *pdev) 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); @@ -1906,6 +2045,47 @@ static int dcmi_probe(struct platform_device *pdev) 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); @@ -1915,6 +2095,7 @@ static int dcmi_probe(struct platform_device *pdev) dcmi->mclk = mclk; dcmi->state = STOPPED; dcmi->dma_chan = chan; + dcmi->mdma_chan = mdma_chan;
q = &dcmi->queue;
@@ -2023,8 +2204,13 @@ static int dcmi_probe(struct platform_device *pdev) 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; } @@ -2042,6 +2228,10 @@ static void dcmi_remove(struct platform_device *pdev) 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)
Enable the DMA-MDMA chaining for the dcmi (camera capture) in order to be able to achieve higher resolution.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- arch/arm/boot/dts/st/stm32mp157c-ev1.dts | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
diff --git a/arch/arm/boot/dts/st/stm32mp157c-ev1.dts b/arch/arm/boot/dts/st/stm32mp157c-ev1.dts index 8f99c30f1af1..68b536b0c6ee 100644 --- a/arch/arm/boot/dts/st/stm32mp157c-ev1.dts +++ b/arch/arm/boot/dts/st/stm32mp157c-ev1.dts @@ -86,6 +86,14 @@ &dcmi { pinctrl-names = "default", "sleep"; pinctrl-0 = <&dcmi_pins_a>; pinctrl-1 = <&dcmi_sleep_pins_a>; + /* + * Enable DMA-MDMA chaining by adding a SRAM pool and + * a MDMA channel + */ + sram = <&dcmi_pool>; + + dmas = <&dmamux1 75 0x400 0x01>, <&mdma1 0 0x3 0x1200000a 0 0>; + dma-names = "tx", "mdma_tx";
port { dcmi_0: endpoint { @@ -301,6 +309,13 @@ &spi1 { status = "disabled"; };
+&sram4 { + dcmi_pool: dcmi-sram@0 { + reg = <0x0 0x8000>; + pool; + }; +}; + &timers2 { /* spare dmas for other usage (un-delete to enable pwm capture) */ /delete-property/dmas;
Overall cleanup done allows to now have the JPEG handling done in the same way as other formats in continuous mode, allowing to achieve a faster framerate in all resolutions.
Signed-off-by: Alain Volmat alain.volmat@foss.st.com --- drivers/media/platform/st/stm32/stm32-dcmi.c | 4 ---- 1 file changed, 4 deletions(-)
diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index 25e95ba2ca84..4c0f8ed0f87e 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -717,10 +717,6 @@ static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count) if (dcmi->do_crop) dcmi_set_crop(dcmi);
- /* Enable jpeg capture */ - if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG) - reg_set(dcmi->regs, DCMI_CR, CR_CM);/* Snapshot mode */ - /* Enable dcmi */ reg_set(dcmi->regs, DCMI_CR, CR_ENABLE);
linaro-mm-sig@lists.linaro.org