* [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes
@ 2026-05-26 8:46 Claudiu Beznea
2026-05-26 8:46 ` [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up Claudiu Beznea
` (17 more replies)
0 siblings, 18 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:46 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Hi,
This series addresses issues identified in the DMA engine and RZ SSI
drivers.
As described in the patch "dmaengine: sh: rz-dmac: Set the Link End (LE)
bit on the last descriptor", stress testing on the Renesas RZ/G2L SoC
showed that starting all available DMA channels could cause the system
to stall after several hours of operation. This issue was resolved by
setting the Link End bit on the last descriptor of a DMA transfer.
However, after applying that fix, the SSI audio driver began to suffer
from frequent overruns and underruns. This was caused by the way the SSI
driver emulated cyclic DMA transfers: at the start of playback/capture
it initially enqueued 4 DMA descriptors as single SG transfers, and upon
completion of each descriptor, a new one was enqueued. Since there was
no indication to the DMA hardware where the descriptor list ended
(though the LE bit), the DMA engine continued transferring until the
audio stream was stopped. From time to time, audio signal spikes were
observed in the recorded file with this approach.
To address these issue, cyclic DMA support was added to the DMA engine
driver, and the SSI audio driver was reworked to use this support via
the generic PCM dmaengine APIs.
Due to the behavior described above, no Fixes tags were added to the
patches in this series, and all patches should be merged through the
same tree.
In case this series will be merged this release cycle, as the audio
patches are acked, best would be to go though the DMA tree.
However, there might be merge conflict on the rz-ssi driver due to the
recently posted patch at [1].
Thank you,
Claudiu
[1] https://lore.kernel.org/all/875x4agb2x.wl-kuninori.morimoto.gx@renesas.com
Changes in v6:
- addressed sashiko review comments
- addressed Frank's review comments
- collected tags
Changes in v5:
- dropped patch "dmaengine: sh: rz-dmac: Do not disable the channel on error"
- added patch "dmaengine: sh: rz-dmac: Add runtime PM support"
Changes in v4:
- collected tags
- addressed review comments got from sashiko.dev. For this:
- added patches:
-- dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
-- dmaengine: sh: rz-dmac: Fix incorrect NULL check on list_first_entry()
Changes in v3:
- addressed review comments got from sashiko.dev. For this:
- added patches 1-9
- added patch "ASoC: renesas: rz-ssi: Add pause support"
- dropped patches:
-- dmaengine: sh: rz-dmac: Add enable status bit
-- dmaengine: sh: rz-dmac: Add pause status bit
Changes in v2:
- fixed typos in patch descriptions and patch titles
- updated "ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs"
to fix the PIO mode
- in patch "dmaengine: sh: rz-dmac: Add suspend to RAM support"
clear the RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED status bit for
channel w/o RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL
- per-patch updates can be found in individual patches changelog
- rebased on top of next-20260319
- updated the cover letter
Claudiu Beznea (18):
dmaengine: sh: rz-dmac: Move interrupt request after everything is set
up
dmaengine: sh: rz-dmac: Fix incorrect NULL check for
list_first_entry()
dmaengine: sh: rz-dmac: Use list_first_entry_or_null()
dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw()
dmaengine: sh: rz-dmac: Add helper to compute the lmdesc address
dmaengine: sh: rz-dmac: Save the start LM descriptor
dmaengine: sh: rz-dmac: Add helper to check if the channel is enabled
dmaengine: sh: rz-dmac: Add helper to check if the channel is paused
dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor
processing
dmaengine: sh: rz-dmac: Refactor pause/resume code
dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with
CHCTRL_SETEN
dmaengine: sh: rz-dmac: Add cyclic DMA support
dmaengine: sh: rz-dmac: Adjust rz_dmac_chan_get_residue() to return
error codes
dmaengine: sh: rz-dmac: Add runtime PM support
dmaengine: sh: rz-dmac: Add suspend to RAM support
ASoC: renesas: rz-ssi: Add pause support
ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs
dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last
descriptor
drivers/dma/sh/rz-dmac.c | 823 ++++++++++++++++++++++++++-----------
sound/soc/renesas/Kconfig | 1 +
sound/soc/renesas/rz-ssi.c | 399 +++++++-----------
3 files changed, 723 insertions(+), 500 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
@ 2026-05-26 8:46 ` Claudiu Beznea
2026-05-26 8:54 ` Biju Das
` (2 more replies)
2026-05-26 8:46 ` [PATCH v6 02/18] dmaengine: sh: rz-dmac: Fix incorrect NULL check for list_first_entry() Claudiu Beznea
` (16 subsequent siblings)
17 siblings, 3 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:46 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, stable, Frank Li,
John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Once the interrupt is requested, the interrupt handler may run immediately.
Since the IRQ handler can access channel->ch_base, which is initialized
only after requesting the IRQ, this may lead to invalid memory access.
Likewise, the IRQ thread may access uninitialized data (the ld_free,
ld_queue, and ld_active lists), which may also lead to issues.
Request the interrupts only after everything is set up. To keep the error
path simpler, use dmam_alloc_coherent() instead of dma_alloc_coherent().
Fixes: 5000d37042a6 ("dmaengine: sh: Add DMAC driver for RZ/G2L SoC")
Cc: stable@vger.kernel.org
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- collected tags
Changes in v5:
- none
Changes in v4:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 88 +++++++++++++++-------------------------
1 file changed, 33 insertions(+), 55 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 625ff29024de..9f206a33dcc6 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -981,25 +981,6 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
channel->index = index;
channel->mid_rid = -EINVAL;
- /* Request the channel interrupt. */
- scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index);
- irq = platform_get_irq_byname(pdev, pdev_irqname);
- if (irq < 0)
- return irq;
-
- irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u",
- dev_name(dmac->dev), index);
- if (!irqname)
- return -ENOMEM;
-
- ret = devm_request_threaded_irq(dmac->dev, irq, rz_dmac_irq_handler,
- rz_dmac_irq_handler_thread, 0,
- irqname, channel);
- if (ret) {
- dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", irq, ret);
- return ret;
- }
-
/* Set io base address for each channel */
if (index < 8) {
channel->ch_base = dmac->base + CHANNEL_0_7_OFFSET +
@@ -1012,9 +993,9 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
}
/* Allocate descriptors */
- lmdesc = dma_alloc_coherent(&pdev->dev,
- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
- &channel->lmdesc.base_dma, GFP_KERNEL);
+ lmdesc = dmam_alloc_coherent(&pdev->dev,
+ sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
+ &channel->lmdesc.base_dma, GFP_KERNEL);
if (!lmdesc) {
dev_err(&pdev->dev, "Can't allocate memory (lmdesc)\n");
return -ENOMEM;
@@ -1030,7 +1011,24 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
INIT_LIST_HEAD(&channel->ld_free);
INIT_LIST_HEAD(&channel->ld_active);
- return 0;
+ /* Request the channel interrupt. */
+ scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index);
+ irq = platform_get_irq_byname(pdev, pdev_irqname);
+ if (irq < 0)
+ return irq;
+
+ irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u",
+ dev_name(dmac->dev), index);
+ if (!irqname)
+ return -ENOMEM;
+
+ ret = devm_request_threaded_irq(dmac->dev, irq, rz_dmac_irq_handler,
+ rz_dmac_irq_handler_thread, 0,
+ irqname, channel);
+ if (ret)
+ dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", irq, ret);
+
+ return ret;
}
static void rz_dmac_put_device(void *_dev)
@@ -1099,7 +1097,6 @@ static int rz_dmac_probe(struct platform_device *pdev)
const char *irqname = "error";
struct dma_device *engine;
struct rz_dmac *dmac;
- int channel_num;
int ret;
int irq;
u8 i;
@@ -1132,18 +1129,6 @@ static int rz_dmac_probe(struct platform_device *pdev)
return PTR_ERR(dmac->ext_base);
}
- /* Register interrupt handler for error */
- irq = platform_get_irq_byname_optional(pdev, irqname);
- if (irq > 0) {
- ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0,
- irqname, NULL);
- if (ret) {
- dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n",
- irq, ret);
- return ret;
- }
- }
-
/* Initialize the channels. */
INIT_LIST_HEAD(&dmac->engine.channels);
@@ -1169,6 +1154,18 @@ static int rz_dmac_probe(struct platform_device *pdev)
goto err;
}
+ /* Register interrupt handler for error */
+ irq = platform_get_irq_byname_optional(pdev, irqname);
+ if (irq > 0) {
+ ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0,
+ irqname, NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n",
+ irq, ret);
+ goto err;
+ }
+ }
+
/* Register the DMAC as a DMA provider for DT. */
ret = of_dma_controller_register(pdev->dev.of_node, rz_dmac_of_xlate,
NULL);
@@ -1210,16 +1207,6 @@ static int rz_dmac_probe(struct platform_device *pdev)
dma_register_err:
of_dma_controller_free(pdev->dev.of_node);
err:
- channel_num = i ? i - 1 : 0;
- for (i = 0; i < channel_num; i++) {
- struct rz_dmac_chan *channel = &dmac->channels[i];
-
- dma_free_coherent(&pdev->dev,
- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
- channel->lmdesc.base,
- channel->lmdesc.base_dma);
- }
-
reset_control_assert(dmac->rstc);
err_pm_runtime_put:
pm_runtime_put(&pdev->dev);
@@ -1232,18 +1219,9 @@ static int rz_dmac_probe(struct platform_device *pdev)
static void rz_dmac_remove(struct platform_device *pdev)
{
struct rz_dmac *dmac = platform_get_drvdata(pdev);
- unsigned int i;
dma_async_device_unregister(&dmac->engine);
of_dma_controller_free(pdev->dev.of_node);
- for (i = 0; i < dmac->n_channels; i++) {
- struct rz_dmac_chan *channel = &dmac->channels[i];
-
- dma_free_coherent(&pdev->dev,
- sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
- channel->lmdesc.base,
- channel->lmdesc.base_dma);
- }
reset_control_assert(dmac->rstc);
pm_runtime_put(&pdev->dev);
pm_runtime_disable(&pdev->dev);
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 02/18] dmaengine: sh: rz-dmac: Fix incorrect NULL check for list_first_entry()
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
2026-05-26 8:46 ` [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up Claudiu Beznea
@ 2026-05-26 8:46 ` Claudiu Beznea
2026-05-26 9:03 ` sashiko-bot
2026-05-28 13:45 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 03/18] dmaengine: sh: rz-dmac: Use list_first_entry_or_null() Claudiu Beznea
` (15 subsequent siblings)
17 siblings, 2 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:46 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, stable,
John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
list_first_entry() does not return NULL when the list is empty,
making the existing NULL check invalid. Use list_first_entry_or_null()
instead.
Fixes: 21323b118c16 ("dmaengine: sh: rz-dmac: Add device_tx_status() callback")
Cc: stable@vger.kernel.org
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- collected tags
- updated the patch title and description to reflect better the changes
Changes in v5:
- none
Changes in v4:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 9f206a33dcc6..6d80cb668957 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -723,8 +723,8 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
u32 crla, crtb, i;
/* Get current processing virtual descriptor */
- current_desc = list_first_entry(&channel->ld_active,
- struct rz_dmac_desc, node);
+ current_desc = list_first_entry_or_null(&channel->ld_active,
+ struct rz_dmac_desc, node);
if (!current_desc)
return 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 03/18] dmaengine: sh: rz-dmac: Use list_first_entry_or_null()
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
2026-05-26 8:46 ` [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up Claudiu Beznea
2026-05-26 8:46 ` [PATCH v6 02/18] dmaengine: sh: rz-dmac: Fix incorrect NULL check for list_first_entry() Claudiu Beznea
@ 2026-05-26 8:46 ` Claudiu Beznea
2026-05-28 13:45 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 04/18] dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw() Claudiu Beznea
` (14 subsequent siblings)
17 siblings, 1 reply; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:46 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, Frank Li,
John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Use list_first_entry_or_null() instead of open-coding it with a
list_empty() check and list_first_entry(). This simplifies the code.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- collected tags
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 6d80cb668957..1717b407ab9e 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -503,11 +503,10 @@ rz_dmac_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
__func__, channel->index, &src, &dest, len);
scoped_guard(spinlock_irqsave, &channel->vc.lock) {
- if (list_empty(&channel->ld_free))
+ desc = list_first_entry_or_null(&channel->ld_free, struct rz_dmac_desc, node);
+ if (!desc)
return NULL;
- desc = list_first_entry(&channel->ld_free, struct rz_dmac_desc, node);
-
desc->type = RZ_DMAC_DESC_MEMCPY;
desc->src = src;
desc->dest = dest;
@@ -533,11 +532,10 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
int i = 0;
scoped_guard(spinlock_irqsave, &channel->vc.lock) {
- if (list_empty(&channel->ld_free))
+ desc = list_first_entry_or_null(&channel->ld_free, struct rz_dmac_desc, node);
+ if (!desc)
return NULL;
- desc = list_first_entry(&channel->ld_free, struct rz_dmac_desc, node);
-
for_each_sg(sgl, sg, sg_len, i)
dma_length += sg_dma_len(sg);
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 04/18] dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw()
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (2 preceding siblings ...)
2026-05-26 8:46 ` [PATCH v6 03/18] dmaengine: sh: rz-dmac: Use list_first_entry_or_null() Claudiu Beznea
@ 2026-05-26 8:46 ` Claudiu Beznea
2026-05-26 9:15 ` sashiko-bot
2026-05-28 13:46 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 05/18] dmaengine: sh: rz-dmac: Add helper to compute the lmdesc address Claudiu Beznea
` (13 subsequent siblings)
17 siblings, 2 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:46 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, Frank Li,
John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Use rz_dmac_disable_hw() instead of open coding it. This unifies the
code and prepares it for the addition of suspend to RAM and cyclic DMA.
The rz_dmac_disable_hw() from rz_dmac_chan_probe() was moved after
vchan_init() as it initializes the channel->vc.chan.device used in
rz_dmac_disable_hw().
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- fixed typo in patch description
- collected tags
Changes in v5:
- none
Changes in v4:
- in rz_dmac_chan_probe(): moved rz_dmac_disable_hw() after the
vchan_init(&channel->vc, &dmac->engine) call as this is the one which
initializes data structures used by the debug code from
rz_dmac_disable_hw(); updated the patch description to reflect this
Changes in v3:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 1717b407ab9e..40ddf534c094 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -873,7 +873,7 @@ static void rz_dmac_irq_handle_channel(struct rz_dmac_chan *channel)
channel->index, chstat);
scoped_guard(spinlock_irqsave, &channel->vc.lock)
- rz_dmac_ch_writel(channel, CHCTRL_DEFAULT, CHCTRL, 1);
+ rz_dmac_disable_hw(channel);
return;
}
@@ -1000,15 +1000,15 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
}
rz_lmdesc_setup(channel, lmdesc);
- /* Initialize register for each channel */
- rz_dmac_ch_writel(channel, CHCTRL_DEFAULT, CHCTRL, 1);
-
channel->vc.desc_free = rz_dmac_virt_desc_free;
vchan_init(&channel->vc, &dmac->engine);
INIT_LIST_HEAD(&channel->ld_queue);
INIT_LIST_HEAD(&channel->ld_free);
INIT_LIST_HEAD(&channel->ld_active);
+ /* Initialize register for each channel */
+ rz_dmac_disable_hw(channel);
+
/* Request the channel interrupt. */
scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index);
irq = platform_get_irq_byname(pdev, pdev_irqname);
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 05/18] dmaengine: sh: rz-dmac: Add helper to compute the lmdesc address
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (3 preceding siblings ...)
2026-05-26 8:46 ` [PATCH v6 04/18] dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw() Claudiu Beznea
@ 2026-05-26 8:46 ` Claudiu Beznea
2026-05-28 13:47 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 06/18] dmaengine: sh: rz-dmac: Save the start LM descriptor Claudiu Beznea
` (12 subsequent siblings)
17 siblings, 1 reply; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:46 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, Frank Li,
John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Add a the rz_dmac_lmdesc_addr() helper function to compute the lmdesc
address, to make the code easier to understand. The helper will be used in
subsequent patches.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- updated patch description
- collected tags
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 40ddf534c094..c48858b68dee 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -259,6 +259,12 @@ static void rz_lmdesc_setup(struct rz_dmac_chan *channel,
* Descriptors preparation
*/
+static u32 rz_dmac_lmdesc_addr(struct rz_dmac_chan *channel, struct rz_lmdesc *lmdesc)
+{
+ return channel->lmdesc.base_dma +
+ (sizeof(struct rz_lmdesc) * (lmdesc - channel->lmdesc.base));
+}
+
static void rz_dmac_lmdesc_recycle(struct rz_dmac_chan *channel)
{
struct rz_lmdesc *lmdesc = channel->lmdesc.head;
@@ -284,9 +290,7 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel)
rz_dmac_lmdesc_recycle(channel);
- nxla = channel->lmdesc.base_dma +
- (sizeof(struct rz_lmdesc) * (channel->lmdesc.head -
- channel->lmdesc.base));
+ nxla = rz_dmac_lmdesc_addr(channel, channel->lmdesc.head);
chstat = rz_dmac_ch_readl(channel, CHSTAT, 1);
if (!(chstat & CHSTAT_EN)) {
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 06/18] dmaengine: sh: rz-dmac: Save the start LM descriptor
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (4 preceding siblings ...)
2026-05-26 8:46 ` [PATCH v6 05/18] dmaengine: sh: rz-dmac: Add helper to compute the lmdesc address Claudiu Beznea
@ 2026-05-26 8:46 ` Claudiu Beznea
2026-05-26 9:41 ` sashiko-bot
2026-05-28 13:47 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 07/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is enabled Claudiu Beznea
` (11 subsequent siblings)
17 siblings, 2 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:46 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Save the start LM descriptor to avoid starting from the beginning of the
channel's LM descriptor list in rz_dmac_calculate_residue_bytes_in_vd().
This avoids unnecessary iterations.
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- updated patch description to describe better the changes
- collected tags
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index c48858b68dee..d3926ecd63ac 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -58,6 +58,7 @@ struct rz_dmac_desc {
/* For slave sg */
struct scatterlist *sg;
unsigned int sgcount;
+ struct rz_lmdesc *start_lmdesc;
};
#define to_rz_dmac_desc(d) container_of(d, struct rz_dmac_desc, vd)
@@ -343,6 +344,8 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel)
struct rz_dmac_desc *d = channel->desc;
u32 chcfg = CHCFG_MEM_COPY;
+ d->start_lmdesc = lmdesc;
+
/* prepare descriptor */
lmdesc->sa = d->src;
lmdesc->da = d->dest;
@@ -377,6 +380,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
}
lmdesc = channel->lmdesc.tail;
+ d->start_lmdesc = lmdesc;
for (i = 0, sg = sgl; i < sg_len; i++, sg = sg_next(sg)) {
if (d->direction == DMA_DEV_TO_MEM) {
@@ -693,9 +697,10 @@ rz_dmac_get_next_lmdesc(struct rz_lmdesc *base, struct rz_lmdesc *lmdesc)
return next;
}
-static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel, u32 crla)
+static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
+ struct rz_dmac_desc *desc, u32 crla)
{
- struct rz_lmdesc *lmdesc = channel->lmdesc.head;
+ struct rz_lmdesc *lmdesc = desc->start_lmdesc;
struct dma_chan *chan = &channel->vc.chan;
struct rz_dmac *dmac = to_rz_dmac(chan->device);
u32 residue = 0, i = 0;
@@ -794,7 +799,7 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
* Calculate number of bytes transferred in processing virtual descriptor.
* One virtual descriptor can have many lmdesc.
*/
- return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, crla);
+ return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, current_desc, crla);
}
static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 07/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is enabled
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (5 preceding siblings ...)
2026-05-26 8:46 ` [PATCH v6 06/18] dmaengine: sh: rz-dmac: Save the start LM descriptor Claudiu Beznea
@ 2026-05-26 8:46 ` Claudiu Beznea
2026-05-28 13:48 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 08/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is paused Claudiu Beznea
` (10 subsequent siblings)
17 siblings, 1 reply; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:46 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, Frank Li,
John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Add the rz_dmac_chan_is_enabled() helper to check if a channel is
enabled. This helper will be reused in subsequent patches.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- updated the patch description to describe better the changes
- collected tags
- s/chan/channel in rz_dmac_chan_is_enabled() to follow the naming convention
accross the driver for the variable of type struct rz_dmac_chan
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index d3926ecd63ac..76bac11c217c 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -279,6 +279,13 @@ static void rz_dmac_lmdesc_recycle(struct rz_dmac_chan *channel)
channel->lmdesc.head = lmdesc;
}
+static bool rz_dmac_chan_is_enabled(struct rz_dmac_chan *channel)
+{
+ u32 val = rz_dmac_ch_readl(channel, CHSTAT, 1);
+
+ return !!(val & CHSTAT_EN);
+}
+
static void rz_dmac_enable_hw(struct rz_dmac_chan *channel)
{
struct dma_chan *chan = &channel->vc.chan;
@@ -840,8 +847,7 @@ static int rz_dmac_device_pause(struct dma_chan *chan)
guard(spinlock_irqsave)(&channel->vc.lock);
- val = rz_dmac_ch_readl(channel, CHSTAT, 1);
- if (!(val & CHSTAT_EN))
+ if (!rz_dmac_chan_is_enabled(channel))
return 0;
rz_dmac_ch_writel(channel, CHCTRL_SETSUS, CHCTRL, 1);
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 08/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is paused
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (6 preceding siblings ...)
2026-05-26 8:46 ` [PATCH v6 07/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is enabled Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-28 13:48 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 09/18] dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor processing Claudiu Beznea
` (9 subsequent siblings)
17 siblings, 1 reply; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, Frank Li,
John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Add the rz_dmac_chan_is_paused() helper to check if the channel is paused.
This helper will be reused in subsequent patches.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- updated patch description to reflect better the changes
- collected tags
- s/chan/channel in rz_dmac_chan_is_paused() to follow the naming convention
accross the driver for the variable of type struct rz_dmac_chan
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 76bac11c217c..217657513fa7 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -286,6 +286,13 @@ static bool rz_dmac_chan_is_enabled(struct rz_dmac_chan *channel)
return !!(val & CHSTAT_EN);
}
+static bool rz_dmac_chan_is_paused(struct rz_dmac_chan *channel)
+{
+ u32 val = rz_dmac_ch_readl(channel, CHSTAT, 1);
+
+ return !!(val & CHSTAT_SUS);
+}
+
static void rz_dmac_enable_hw(struct rz_dmac_chan *channel)
{
struct dma_chan *chan = &channel->vc.chan;
@@ -822,12 +829,9 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
return status;
scoped_guard(spinlock_irqsave, &channel->vc.lock) {
- u32 val;
-
residue = rz_dmac_chan_get_residue(channel, cookie);
- val = rz_dmac_ch_readl(channel, CHSTAT, 1);
- if (val & CHSTAT_SUS)
+ if (rz_dmac_chan_is_paused(channel))
status = DMA_PAUSED;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 09/18] dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor processing
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (7 preceding siblings ...)
2026-05-26 8:47 ` [PATCH v6 08/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is paused Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-26 9:28 ` sashiko-bot
2026-05-28 13:49 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 10/18] dmaengine: sh: rz-dmac: Refactor pause/resume code Claudiu Beznea
` (8 subsequent siblings)
17 siblings, 2 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
The driver used a mix of virt-dma APIs and driver specific logic to
process descriptors. It maintained three internal queues: ld_free,
ld_queue, and ld_active as follows:
- ld_free: stores the descriptors pre-allocated at probe time
- ld_queue: stores descriptors after they are taken from ld_free and
prepared. At the same time, vchan_tx_prep() queues them to
vc->desc_allocated. The vc->desc_allocated list is then checked in
rz_dmac_issue_pending() and rz_dmac_irq_handler_thread() before
starting a new transfer via rz_dmac_xfer_desc(). In turn,
rz_dmac_xfer_desc() grabs the next descriptor from vc->desc_issued and
submits it for transfer
- ld_active: stores the descriptors currently being transferred
The interrupt handler moved a completed descriptor to ld_free before
invoking its completion callback. Once returned to ld_free, the
descriptor can be reused to prepare a new transfer. In theory, this
means the descriptor could be re-prepared before its completion
callback is called.
Commit fully back the driver by the virt-dma APIs. With this, only ld_free
need to be kept to track how many free descriptors are available. This
is now done as follows:
- the prepare stage removes the first descriptor from the ld_free and
prepares it
- the completion calls for it vc->desc_free() (rz_dmac_virt_desc_free())
which re-adds the descriptor at the end of ld_free
With this, the critical areas in prepare callbacks were minimized to only
getting the descriptor from the ld_free list.
Introduce struct rz_dmac_chan::desc to keep track of the currently
transferred descriptor. It is cleared in rz_dmac_terminate_all(),
referenced from rz_dmac_issue_pending() to determine whether a new transfer
can be started, and from rz_dmac_irq_handler_thread() once a descriptor has
completed. Finally, the rz_dmac_device_synchronize() was updated with
vchan_synchronize() call to ensure the terminated descriptor is freed and
the tasklet is killed.
With this, residue computation is also simplified, as it can now be
handled entirely through the virt-dma APIs.
The spin_lock/unlock operations from rz_dmac_irq_handler_thread() were
replaced by guard as the final code after rework is simpler this way.
As subsequent commits will set the Link End bit on the last descriptor
of a transfer, rz_dmac_enable_hw() is also adjusted as part of the full
conversion to virt-dma APIs. It no longer checks the channel enable
status itself; instead, its callers verify whether the channel is
enabled and whether the previous transfer has completed before starting
a new one.
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- updated patch description as suggested in the review process
- collected tags
Changes in v5:
- none
Changes in v4:
- in rz_dmac_tx_status(): return DMA_PAUSED if the channel is paused;
call rz_dmac_chan_get_residue() only if status is not complete
Changes in v3:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 233 +++++++++++++++------------------------
1 file changed, 86 insertions(+), 147 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 217657513fa7..1f884ec101f8 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -79,8 +79,6 @@ struct rz_dmac_chan {
int mid_rid;
struct list_head ld_free;
- struct list_head ld_queue;
- struct list_head ld_active;
struct {
struct rz_lmdesc *base;
@@ -299,7 +297,6 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel)
struct rz_dmac *dmac = to_rz_dmac(chan->device);
u32 nxla;
u32 chctrl;
- u32 chstat;
dev_dbg(dmac->dev, "%s channel %d\n", __func__, channel->index);
@@ -307,14 +304,11 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel)
nxla = rz_dmac_lmdesc_addr(channel, channel->lmdesc.head);
- chstat = rz_dmac_ch_readl(channel, CHSTAT, 1);
- if (!(chstat & CHSTAT_EN)) {
- chctrl = (channel->chctrl | CHCTRL_SETEN);
- rz_dmac_ch_writel(channel, nxla, NXLA, 1);
- rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1);
- rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1);
- rz_dmac_ch_writel(channel, chctrl, CHCTRL, 1);
- }
+ chctrl = (channel->chctrl | CHCTRL_SETEN);
+ rz_dmac_ch_writel(channel, nxla, NXLA, 1);
+ rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1);
+ rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1);
+ rz_dmac_ch_writel(channel, chctrl, CHCTRL, 1);
}
static void rz_dmac_disable_hw(struct rz_dmac_chan *channel)
@@ -426,18 +420,20 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
channel->chctrl = CHCTRL_SETEN;
}
-static int rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
+static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
{
- struct rz_dmac_desc *d = chan->desc;
struct virt_dma_desc *vd;
vd = vchan_next_desc(&chan->vc);
- if (!vd)
- return 0;
+ if (!vd) {
+ chan->desc = NULL;
+ return;
+ }
list_del(&vd->node);
+ chan->desc = to_rz_dmac_desc(vd);
- switch (d->type) {
+ switch (chan->desc->type) {
case RZ_DMAC_DESC_MEMCPY:
rz_dmac_prepare_desc_for_memcpy(chan);
break;
@@ -445,14 +441,9 @@ static int rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
case RZ_DMAC_DESC_SLAVE_SG:
rz_dmac_prepare_descs_for_slave_sg(chan);
break;
-
- default:
- return -EINVAL;
}
rz_dmac_enable_hw(chan);
-
- return 0;
}
/*
@@ -494,8 +485,6 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
rz_lmdesc_setup(channel, channel->lmdesc.base);
rz_dmac_disable_hw(channel);
- list_splice_tail_init(&channel->ld_active, &channel->ld_free);
- list_splice_tail_init(&channel->ld_queue, &channel->ld_free);
if (channel->mid_rid >= 0) {
clear_bit(channel->mid_rid, dmac->modules);
@@ -504,13 +493,19 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
spin_unlock_irqrestore(&channel->vc.lock, flags);
+ vchan_free_chan_resources(&channel->vc);
+
+ spin_lock_irqsave(&channel->vc.lock, flags);
+
list_for_each_entry_safe(desc, _desc, &channel->ld_free, node) {
+ list_del(&desc->node);
kfree(desc);
channel->descs_allocated--;
}
INIT_LIST_HEAD(&channel->ld_free);
- vchan_free_chan_resources(&channel->vc);
+
+ spin_unlock_irqrestore(&channel->vc.lock, flags);
}
static struct dma_async_tx_descriptor *
@@ -529,15 +524,15 @@ rz_dmac_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
if (!desc)
return NULL;
- desc->type = RZ_DMAC_DESC_MEMCPY;
- desc->src = src;
- desc->dest = dest;
- desc->len = len;
- desc->direction = DMA_MEM_TO_MEM;
-
- list_move_tail(channel->ld_free.next, &channel->ld_queue);
+ list_del(&desc->node);
}
+ desc->type = RZ_DMAC_DESC_MEMCPY;
+ desc->src = src;
+ desc->dest = dest;
+ desc->len = len;
+ desc->direction = DMA_MEM_TO_MEM;
+
return vchan_tx_prep(&channel->vc, &desc->vd, flags);
}
@@ -558,22 +553,22 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
if (!desc)
return NULL;
- for_each_sg(sgl, sg, sg_len, i)
- dma_length += sg_dma_len(sg);
+ list_del(&desc->node);
+ }
- desc->type = RZ_DMAC_DESC_SLAVE_SG;
- desc->sg = sgl;
- desc->sgcount = sg_len;
- desc->len = dma_length;
- desc->direction = direction;
+ for_each_sg(sgl, sg, sg_len, i)
+ dma_length += sg_dma_len(sg);
- if (direction == DMA_DEV_TO_MEM)
- desc->src = channel->src_per_address;
- else
- desc->dest = channel->dst_per_address;
+ desc->type = RZ_DMAC_DESC_SLAVE_SG;
+ desc->sg = sgl;
+ desc->sgcount = sg_len;
+ desc->len = dma_length;
+ desc->direction = direction;
- list_move_tail(channel->ld_free.next, &channel->ld_queue);
- }
+ if (direction == DMA_DEV_TO_MEM)
+ desc->src = channel->src_per_address;
+ else
+ desc->dest = channel->dst_per_address;
return vchan_tx_prep(&channel->vc, &desc->vd, flags);
}
@@ -588,8 +583,11 @@ static int rz_dmac_terminate_all(struct dma_chan *chan)
rz_dmac_disable_hw(channel);
rz_lmdesc_setup(channel, channel->lmdesc.base);
- list_splice_tail_init(&channel->ld_active, &channel->ld_free);
- list_splice_tail_init(&channel->ld_queue, &channel->ld_free);
+ if (channel->desc) {
+ vchan_terminate_vdesc(&channel->desc->vd);
+ channel->desc = NULL;
+ }
+
vchan_get_all_descriptors(&channel->vc, &head);
spin_unlock_irqrestore(&channel->vc.lock, flags);
vchan_dma_desc_free_list(&channel->vc, &head);
@@ -600,25 +598,16 @@ static int rz_dmac_terminate_all(struct dma_chan *chan)
static void rz_dmac_issue_pending(struct dma_chan *chan)
{
struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
- struct rz_dmac *dmac = to_rz_dmac(chan->device);
- struct rz_dmac_desc *desc;
unsigned long flags;
spin_lock_irqsave(&channel->vc.lock, flags);
- if (!list_empty(&channel->ld_queue)) {
- desc = list_first_entry(&channel->ld_queue,
- struct rz_dmac_desc, node);
- channel->desc = desc;
- if (vchan_issue_pending(&channel->vc)) {
- if (rz_dmac_xfer_desc(channel) < 0)
- dev_warn(dmac->dev, "ch: %d couldn't issue DMA xfer\n",
- channel->index);
- else
- list_move_tail(channel->ld_queue.next,
- &channel->ld_active);
- }
- }
+ /*
+ * Issue the descriptor. If another transfer is already in progress, the
+ * issued descriptor will be handled after the current transfer finishes.
+ */
+ if (vchan_issue_pending(&channel->vc) && !channel->desc)
+ rz_dmac_xfer_desc(channel);
spin_unlock_irqrestore(&channel->vc.lock, flags);
}
@@ -676,13 +665,13 @@ static int rz_dmac_config(struct dma_chan *chan,
static void rz_dmac_virt_desc_free(struct virt_dma_desc *vd)
{
- /*
- * Place holder
- * Descriptor allocation is done during alloc_chan_resources and
- * get freed during free_chan_resources.
- * list is used to manage the descriptors and avoid any memory
- * allocation/free during DMA read/write.
- */
+ struct rz_dmac_chan *channel = to_rz_dmac_chan(vd->tx.chan);
+ struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
+ struct rz_dmac_desc *desc = to_rz_dmac_desc(vd);
+
+ guard(spinlock_irqsave)(&vc->lock);
+
+ list_add_tail(&desc->node, &channel->ld_free);
}
static void rz_dmac_device_synchronize(struct dma_chan *chan)
@@ -692,6 +681,8 @@ static void rz_dmac_device_synchronize(struct dma_chan *chan)
u32 chstat;
int ret;
+ vchan_synchronize(&channel->vc);
+
ret = read_poll_timeout(rz_dmac_ch_readl, chstat, !(chstat & CHSTAT_EN),
100, 100000, false, channel, CHSTAT, 1);
if (ret < 0)
@@ -739,58 +730,22 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
dma_cookie_t cookie)
{
- struct rz_dmac_desc *current_desc, *desc;
- enum dma_status status;
+ struct rz_dmac_desc *desc = NULL;
+ struct virt_dma_desc *vd;
u32 crla, crtb, i;
- /* Get current processing virtual descriptor */
- current_desc = list_first_entry_or_null(&channel->ld_active,
- struct rz_dmac_desc, node);
- if (!current_desc)
- return 0;
-
- /*
- * If the cookie corresponds to a descriptor that has been completed
- * there is no residue. The same check has already been performed by the
- * caller but without holding the channel lock, so the descriptor could
- * now be complete.
- */
- status = dma_cookie_status(&channel->vc.chan, cookie, NULL);
- if (status == DMA_COMPLETE)
- return 0;
-
- /*
- * If the cookie doesn't correspond to the currently processing virtual
- * descriptor then the descriptor hasn't been processed yet, and the
- * residue is equal to the full descriptor size. Also, a client driver
- * is possible to call this function before rz_dmac_irq_handler_thread()
- * runs. In this case, the running descriptor will be the next
- * descriptor, and will appear in the done list. So, if the argument
- * cookie matches the done list's cookie, we can assume the residue is
- * zero.
- */
- if (cookie != current_desc->vd.tx.cookie) {
- list_for_each_entry(desc, &channel->ld_free, node) {
- if (cookie == desc->vd.tx.cookie)
- return 0;
- }
-
- list_for_each_entry(desc, &channel->ld_queue, node) {
- if (cookie == desc->vd.tx.cookie)
- return desc->len;
- }
-
- list_for_each_entry(desc, &channel->ld_active, node) {
- if (cookie == desc->vd.tx.cookie)
- return desc->len;
- }
+ vd = vchan_find_desc(&channel->vc, cookie);
+ if (vd) {
+ /* Descriptor has been issued but not yet processed. */
+ desc = to_rz_dmac_desc(vd);
+ return desc->len;
+ } else if (channel->desc && channel->desc->vd.tx.cookie == cookie) {
+ /* Descriptor is currently processed. */
+ desc = channel->desc;
+ }
- /*
- * No descriptor found for the cookie, there's thus no residue.
- * This shouldn't happen if the calling driver passes a correct
- * cookie value.
- */
- WARN(1, "No descriptor for cookie!");
+ if (!desc) {
+ /* Descriptor was not found. May be already completed by now. */
return 0;
}
@@ -813,7 +768,7 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
* Calculate number of bytes transferred in processing virtual descriptor.
* One virtual descriptor can have many lmdesc.
*/
- return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, current_desc, crla);
+ return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, desc, crla);
}
static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
@@ -824,21 +779,17 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
enum dma_status status;
u32 residue;
- status = dma_cookie_status(chan, cookie, txstate);
- if (status == DMA_COMPLETE || !txstate)
- return status;
-
scoped_guard(spinlock_irqsave, &channel->vc.lock) {
+ status = dma_cookie_status(chan, cookie, txstate);
+ if (status == DMA_COMPLETE || !txstate)
+ return status;
+
residue = rz_dmac_chan_get_residue(channel, cookie);
- if (rz_dmac_chan_is_paused(channel))
+ if (status == DMA_IN_PROGRESS && rz_dmac_chan_is_paused(channel))
status = DMA_PAUSED;
}
- /* if there's no residue and no paused, the cookie is complete */
- if (!residue && status != DMA_PAUSED)
- return DMA_COMPLETE;
-
dma_set_residue(txstate, residue);
return status;
@@ -918,28 +869,18 @@ static irqreturn_t rz_dmac_irq_handler(int irq, void *dev_id)
static irqreturn_t rz_dmac_irq_handler_thread(int irq, void *dev_id)
{
struct rz_dmac_chan *channel = dev_id;
- struct rz_dmac_desc *desc = NULL;
- unsigned long flags;
+ struct rz_dmac_desc *desc;
- spin_lock_irqsave(&channel->vc.lock, flags);
+ guard(spinlock_irqsave)(&channel->vc.lock);
- if (list_empty(&channel->ld_active)) {
- /* Someone might have called terminate all */
- goto out;
- }
+ desc = channel->desc;
+ if (!desc)
+ return IRQ_HANDLED;
- desc = list_first_entry(&channel->ld_active, struct rz_dmac_desc, node);
vchan_cookie_complete(&desc->vd);
- list_move_tail(channel->ld_active.next, &channel->ld_free);
- if (!list_empty(&channel->ld_queue)) {
- desc = list_first_entry(&channel->ld_queue, struct rz_dmac_desc,
- node);
- channel->desc = desc;
- if (rz_dmac_xfer_desc(channel) == 0)
- list_move_tail(channel->ld_queue.next, &channel->ld_active);
- }
-out:
- spin_unlock_irqrestore(&channel->vc.lock, flags);
+ channel->desc = NULL;
+
+ rz_dmac_xfer_desc(channel);
return IRQ_HANDLED;
}
@@ -1021,9 +962,7 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
channel->vc.desc_free = rz_dmac_virt_desc_free;
vchan_init(&channel->vc, &dmac->engine);
- INIT_LIST_HEAD(&channel->ld_queue);
INIT_LIST_HEAD(&channel->ld_free);
- INIT_LIST_HEAD(&channel->ld_active);
/* Initialize register for each channel */
rz_dmac_disable_hw(channel);
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 10/18] dmaengine: sh: rz-dmac: Refactor pause/resume code
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (8 preceding siblings ...)
2026-05-26 8:47 ` [PATCH v6 09/18] dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor processing Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-26 9:28 ` sashiko-bot
2026-05-28 13:50 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 11/18] dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with CHCTRL_SETEN Claudiu Beznea
` (7 subsequent siblings)
17 siblings, 2 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Subsequent patches will add suspend/resume and cyclic DMA support to the
rz-dmac driver. This support needs to work on SoCs where power to most
components (including DMA) is turned off during system suspend. For this,
some channels (for example cyclic ones) may need to be paused and resumed
manually by the DMA driver during system suspend/resume.
Refactor the pause/resume support so the same code can be reused in the
system suspend/resume path.
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- collected tags
Changes in v5:
- none
Changes in v4:
- reset channel->status in rz_dmac_free_chan_resources() and
rz_dmac_terminate_all()
Changes in v3:
- none, this patch new new
drivers/dma/sh/rz-dmac.c | 73 ++++++++++++++++++++++++++++++++++------
1 file changed, 62 insertions(+), 11 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 1f884ec101f8..557364443a5f 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -18,6 +18,7 @@
#include <linux/irqchip/irq-renesas-rzv2h.h>
#include <linux/irqchip/irq-renesas-rzt2h.h>
#include <linux/list.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_dma.h>
@@ -63,6 +64,14 @@ struct rz_dmac_desc {
#define to_rz_dmac_desc(d) container_of(d, struct rz_dmac_desc, vd)
+/**
+ * enum rz_dmac_chan_status: RZ DMAC channel status
+ * @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks
+ */
+enum rz_dmac_chan_status {
+ RZ_DMAC_CHAN_STATUS_PAUSED,
+};
+
struct rz_dmac_chan {
struct virt_dma_chan vc;
void __iomem *ch_base;
@@ -74,6 +83,8 @@ struct rz_dmac_chan {
dma_addr_t src_per_address;
dma_addr_t dst_per_address;
+ unsigned long status;
+
u32 chcfg;
u32 chctrl;
int mid_rid;
@@ -491,6 +502,8 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
channel->mid_rid = -EINVAL;
}
+ channel->status = 0;
+
spin_unlock_irqrestore(&channel->vc.lock, flags);
vchan_free_chan_resources(&channel->vc);
@@ -589,6 +602,9 @@ static int rz_dmac_terminate_all(struct dma_chan *chan)
}
vchan_get_all_descriptors(&channel->vc, &head);
+
+ channel->status = 0;
+
spin_unlock_irqrestore(&channel->vc.lock, flags);
vchan_dma_desc_free_list(&channel->vc, &head);
@@ -795,35 +811,70 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
return status;
}
-static int rz_dmac_device_pause(struct dma_chan *chan)
+static int rz_dmac_device_pause_set(struct rz_dmac_chan *channel,
+ unsigned long set_bitmask)
{
- struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
+ int ret = 0;
u32 val;
- guard(spinlock_irqsave)(&channel->vc.lock);
+ lockdep_assert_held(&channel->vc.lock);
if (!rz_dmac_chan_is_enabled(channel))
return 0;
+ if (rz_dmac_chan_is_paused(channel))
+ goto set_bit;
+
rz_dmac_ch_writel(channel, CHCTRL_SETSUS, CHCTRL, 1);
- return read_poll_timeout_atomic(rz_dmac_ch_readl, val,
- (val & CHSTAT_SUS), 1, 1024,
- false, channel, CHSTAT, 1);
+ ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val,
+ (val & CHSTAT_SUS), 1, 1024, false,
+ channel, CHSTAT, 1);
+
+set_bit:
+ channel->status |= set_bitmask;
+
+ return ret;
}
-static int rz_dmac_device_resume(struct dma_chan *chan)
+static int rz_dmac_device_pause(struct dma_chan *chan)
{
struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
- u32 val;
guard(spinlock_irqsave)(&channel->vc.lock);
+ return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED));
+}
+
+static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel,
+ unsigned long clear_bitmask)
+{
+ int ret = 0;
+ u32 val;
+
+ lockdep_assert_held(&channel->vc.lock);
+
/* Do not check CHSTAT_SUS but rely on HW capabilities. */
rz_dmac_ch_writel(channel, CHCTRL_CLRSUS, CHCTRL, 1);
- return read_poll_timeout_atomic(rz_dmac_ch_readl, val,
- !(val & CHSTAT_SUS), 1, 1024,
- false, channel, CHSTAT, 1);
+ ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val,
+ !(val & CHSTAT_SUS), 1, 1024, false,
+ channel, CHSTAT, 1);
+
+ channel->status &= ~clear_bitmask;
+
+ return ret;
+}
+
+static int rz_dmac_device_resume(struct dma_chan *chan)
+{
+ struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
+
+ guard(spinlock_irqsave)(&channel->vc.lock);
+
+ if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED)))
+ return 0;
+
+ return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED));
}
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 11/18] dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with CHCTRL_SETEN
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (9 preceding siblings ...)
2026-05-26 8:47 ` [PATCH v6 10/18] dmaengine: sh: rz-dmac: Refactor pause/resume code Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-26 9:11 ` sashiko-bot
2026-05-28 13:50 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 12/18] dmaengine: sh: rz-dmac: Add cyclic DMA support Claudiu Beznea
` (6 subsequent siblings)
17 siblings, 2 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, Frank Li,
John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
The CHCTRL_SETEN bit is explicitly set in rz_dmac_enable_hw(). Updating
struct rz_dmac_chan::chctrl with this bit in
rz_dmac_prepare_desc_for_memcpy() and rz_dmac_prepare_descs_for_slave_sg()
is unnecessary in the current code base. Moreover, it conflicts with the
configuration sequence that will be used for cyclic DMA channels during
suspend to RAM. Cyclic DMA support will be introduced in subsequent
commits.
This is a preparatory commit for cyclic DMA suspend to RAM support.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- collected tags
Changes in v5:
- none
Changes in v4:
- set channel->chctrl = 0 in rz_dmac_prepare_descs_for_slave_sg()
Changes in v3:
- none
Changes in v2:
- fixed typos in patch title and patch description
drivers/dma/sh/rz-dmac.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 557364443a5f..c9c00650ddd5 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -377,7 +377,7 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel)
rz_dmac_set_dma_req_no(dmac, channel->index, dmac->info->default_dma_req_no);
channel->chcfg = chcfg;
- channel->chctrl = CHCTRL_STG | CHCTRL_SETEN;
+ channel->chctrl = CHCTRL_STG;
}
static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
@@ -428,7 +428,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid);
- channel->chctrl = CHCTRL_SETEN;
+ channel->chctrl = 0;
}
static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 12/18] dmaengine: sh: rz-dmac: Add cyclic DMA support
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (10 preceding siblings ...)
2026-05-26 8:47 ` [PATCH v6 11/18] dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with CHCTRL_SETEN Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-26 9:31 ` sashiko-bot
2026-05-28 13:51 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 13/18] dmaengine: sh: rz-dmac: Adjust rz_dmac_chan_get_residue() to return error codes Claudiu Beznea
` (5 subsequent siblings)
17 siblings, 2 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Add cyclic DMA support to the RZ DMAC driver. A per-channel status bit is
introduced to mark cyclic channels and is set during the DMA prepare
callback. The IRQ handler checks this status bit and calls
vchan_cyclic_callback() accordingly.
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- collected tags
Changes in v5:
- none
Changes in v4:
- drop the nxla update logic in rz_dmac_lmdesc_recycle() as this is
not needed for any kind of transfers
- drop the update of channel->status = 0 from rz_dmac_free_chan_resources()
and rz_dmac_terminate_all() as this was moved in patch 09/17
Changes in v3:
- updated rz_dmac_lmdesc_recycle() to restore the lmdesc->nxla
- in rz_dmac_prepare_descs_for_cyclic() update directly the
desc->start_lmdesc with the descriptor pointer insted of the
descriptor address
- used rz_dmac_lmdesc_addr() to compute the descritor address
- set channel->status = 0 in rz_dmac_free_chan_resources()
- in rz_dmac_prep_dma_cyclic() check for invalid periods or buffer len
and limit the critical area protected by spinlock
- set channel->status = 0 in rz_dmac_terminate_all()
- updated rz_dmac_calculate_residue_bytes_in_vd() to use
rz_dmac_lmdesc_addr()
- dropped goto in rz_dmac_irq_handler_thread() as it is not needed
anymore; dropped also the local variable desc
Changes in v2:
- none
drivers/dma/sh/rz-dmac.c | 136 +++++++++++++++++++++++++++++++++++++--
1 file changed, 130 insertions(+), 6 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index c9c00650ddd5..8fd8a4bd9cc9 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -35,6 +35,7 @@
enum rz_dmac_prep_type {
RZ_DMAC_DESC_MEMCPY,
RZ_DMAC_DESC_SLAVE_SG,
+ RZ_DMAC_DESC_CYCLIC,
};
struct rz_lmdesc {
@@ -67,9 +68,11 @@ struct rz_dmac_desc {
/**
* enum rz_dmac_chan_status: RZ DMAC channel status
* @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks
+ * @RZ_DMAC_CHAN_STATUS_CYCLIC: Channel is cyclic
*/
enum rz_dmac_chan_status {
RZ_DMAC_CHAN_STATUS_PAUSED,
+ RZ_DMAC_CHAN_STATUS_CYCLIC,
};
struct rz_dmac_chan {
@@ -191,6 +194,7 @@ struct rz_dmac {
/* LINK MODE DESCRIPTOR */
#define HEADER_LV BIT(0)
+#define HEADER_WBD BIT(2)
#define RZ_DMAC_MAX_CHAN_DESCRIPTORS 16
#define RZ_DMAC_MAX_CHANNELS 16
@@ -431,6 +435,57 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
channel->chctrl = 0;
}
+static void rz_dmac_prepare_descs_for_cyclic(struct rz_dmac_chan *channel)
+{
+ struct dma_chan *chan = &channel->vc.chan;
+ struct rz_dmac *dmac = to_rz_dmac(chan->device);
+ struct rz_dmac_desc *d = channel->desc;
+ size_t period_len = d->sgcount;
+ struct rz_lmdesc *lmdesc;
+ size_t buf_len = d->len;
+ size_t periods = buf_len / period_len;
+
+ lockdep_assert_held(&channel->vc.lock);
+
+ channel->chcfg |= CHCFG_SEL(channel->index) | CHCFG_DMS;
+
+ if (d->direction == DMA_DEV_TO_MEM) {
+ channel->chcfg |= CHCFG_SAD;
+ channel->chcfg &= ~CHCFG_REQD;
+ } else {
+ channel->chcfg |= CHCFG_DAD | CHCFG_REQD;
+ }
+
+ lmdesc = channel->lmdesc.tail;
+ d->start_lmdesc = lmdesc;
+
+ for (size_t i = 0; i < periods; i++) {
+ if (d->direction == DMA_DEV_TO_MEM) {
+ lmdesc->sa = d->src;
+ lmdesc->da = d->dest + (i * period_len);
+ } else {
+ lmdesc->sa = d->src + (i * period_len);
+ lmdesc->da = d->dest;
+ }
+
+ lmdesc->tb = period_len;
+ lmdesc->chitvl = 0;
+ lmdesc->chext = 0;
+ lmdesc->chcfg = channel->chcfg;
+ lmdesc->header = HEADER_LV | HEADER_WBD;
+
+ if (i == periods - 1)
+ lmdesc->nxla = rz_dmac_lmdesc_addr(channel, d->start_lmdesc);
+
+ if (++lmdesc >= (channel->lmdesc.base + DMAC_NR_LMDESC))
+ lmdesc = channel->lmdesc.base;
+ }
+
+ channel->lmdesc.tail = lmdesc;
+
+ rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid);
+}
+
static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
{
struct virt_dma_desc *vd;
@@ -452,6 +507,10 @@ static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
case RZ_DMAC_DESC_SLAVE_SG:
rz_dmac_prepare_descs_for_slave_sg(chan);
break;
+
+ case RZ_DMAC_DESC_CYCLIC:
+ rz_dmac_prepare_descs_for_cyclic(chan);
+ break;
}
rz_dmac_enable_hw(chan);
@@ -586,6 +645,55 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
return vchan_tx_prep(&channel->vc, &desc->vd, flags);
}
+static struct dma_async_tx_descriptor *
+rz_dmac_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr,
+ size_t buf_len, size_t period_len,
+ enum dma_transfer_direction direction,
+ unsigned long flags)
+{
+ struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
+ struct rz_dmac_desc *desc;
+ size_t periods;
+
+ if (!is_slave_direction(direction))
+ return NULL;
+
+ if (!period_len || !buf_len)
+ return NULL;
+
+ periods = buf_len / period_len;
+ if (!periods || periods > DMAC_NR_LMDESC)
+ return NULL;
+
+ scoped_guard(spinlock_irqsave, &channel->vc.lock) {
+ if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC))
+ return NULL;
+
+ desc = list_first_entry_or_null(&channel->ld_free, struct rz_dmac_desc, node);
+ if (!desc)
+ return NULL;
+
+ list_del(&desc->node);
+
+ channel->status |= BIT(RZ_DMAC_CHAN_STATUS_CYCLIC);
+ }
+
+ desc->type = RZ_DMAC_DESC_CYCLIC;
+ desc->sgcount = period_len;
+ desc->len = buf_len;
+ desc->direction = direction;
+
+ if (direction == DMA_DEV_TO_MEM) {
+ desc->src = channel->src_per_address;
+ desc->dest = buf_addr;
+ } else {
+ desc->src = buf_addr;
+ desc->dest = channel->dst_per_address;
+ }
+
+ return vchan_tx_prep(&channel->vc, &desc->vd, flags);
+}
+
static int rz_dmac_terminate_all(struct dma_chan *chan)
{
struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
@@ -733,9 +841,18 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
}
/* Calculate residue from next lmdesc to end of virtual desc */
- while (lmdesc->chcfg & CHCFG_DEM) {
- residue += lmdesc->tb;
- lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
+ if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) {
+ u32 start_lmdesc_addr = rz_dmac_lmdesc_addr(channel, desc->start_lmdesc);
+
+ while (lmdesc->nxla != start_lmdesc_addr) {
+ residue += lmdesc->tb;
+ lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
+ }
+ } else {
+ while (lmdesc->chcfg & CHCFG_DEM) {
+ residue += lmdesc->tb;
+ lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
+ }
}
dev_dbg(dmac->dev, "%s: VD residue is %u\n", __func__, residue);
@@ -928,10 +1045,14 @@ static irqreturn_t rz_dmac_irq_handler_thread(int irq, void *dev_id)
if (!desc)
return IRQ_HANDLED;
- vchan_cookie_complete(&desc->vd);
- channel->desc = NULL;
+ if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) {
+ vchan_cyclic_callback(&desc->vd);
+ } else {
+ vchan_cookie_complete(&desc->vd);
+ channel->desc = NULL;
- rz_dmac_xfer_desc(channel);
+ rz_dmac_xfer_desc(channel);
+ }
return IRQ_HANDLED;
}
@@ -1183,6 +1304,8 @@ static int rz_dmac_probe(struct platform_device *pdev)
engine = &dmac->engine;
dma_cap_set(DMA_SLAVE, engine->cap_mask);
dma_cap_set(DMA_MEMCPY, engine->cap_mask);
+ dma_cap_set(DMA_CYCLIC, engine->cap_mask);
+ engine->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
engine->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_0_7_COMMON_BASE + DCTRL);
rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_8_15_COMMON_BASE + DCTRL);
@@ -1194,6 +1317,7 @@ static int rz_dmac_probe(struct platform_device *pdev)
engine->device_tx_status = rz_dmac_tx_status;
engine->device_prep_slave_sg = rz_dmac_prep_slave_sg;
engine->device_prep_dma_memcpy = rz_dmac_prep_dma_memcpy;
+ engine->device_prep_dma_cyclic = rz_dmac_prep_dma_cyclic;
engine->device_config = rz_dmac_config;
engine->device_terminate_all = rz_dmac_terminate_all;
engine->device_issue_pending = rz_dmac_issue_pending;
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 13/18] dmaengine: sh: rz-dmac: Adjust rz_dmac_chan_get_residue() to return error codes
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (11 preceding siblings ...)
2026-05-26 8:47 ` [PATCH v6 12/18] dmaengine: sh: rz-dmac: Add cyclic DMA support Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-28 13:51 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 14/18] dmaengine: sh: rz-dmac: Add runtime PM support Claudiu Beznea
` (4 subsequent siblings)
17 siblings, 1 reply; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Adjust rz_dmac_chan_get_residue() to return error codes on failure and
provide the residue to callers through the residue parameter. This
prepares the code for the addition of runtime PM support.
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 8fd8a4bd9cc9..93394b9934c8 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -860,8 +860,8 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
return residue;
}
-static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
- dma_cookie_t cookie)
+static int rz_dmac_chan_get_residue(struct device *dev, struct rz_dmac_chan *channel,
+ dma_cookie_t cookie, u32 *residue)
{
struct rz_dmac_desc *desc = NULL;
struct virt_dma_desc *vd;
@@ -871,7 +871,8 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
if (vd) {
/* Descriptor has been issued but not yet processed. */
desc = to_rz_dmac_desc(vd);
- return desc->len;
+ *residue = desc->len;
+ return 0;
} else if (channel->desc && channel->desc->vd.tx.cookie == cookie) {
/* Descriptor is currently processed. */
desc = channel->desc;
@@ -879,6 +880,7 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
if (!desc) {
/* Descriptor was not found. May be already completed by now. */
+ *residue = 0;
return 0;
}
@@ -901,7 +903,9 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
* Calculate number of bytes transferred in processing virtual descriptor.
* One virtual descriptor can have many lmdesc.
*/
- return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, desc, crla);
+ *residue = crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, desc, crla);
+
+ return 0;
}
static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
@@ -909,15 +913,20 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
struct dma_tx_state *txstate)
{
struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
+ struct rz_dmac *dmac = to_rz_dmac(chan->device);
enum dma_status status;
u32 residue;
scoped_guard(spinlock_irqsave, &channel->vc.lock) {
+ int ret;
+
status = dma_cookie_status(chan, cookie, txstate);
if (status == DMA_COMPLETE || !txstate)
return status;
- residue = rz_dmac_chan_get_residue(channel, cookie);
+ ret = rz_dmac_chan_get_residue(dmac->dev, channel, cookie, &residue);
+ if (ret)
+ return DMA_ERROR;
if (status == DMA_IN_PROGRESS && rz_dmac_chan_is_paused(channel))
status = DMA_PAUSED;
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 14/18] dmaengine: sh: rz-dmac: Add runtime PM support
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (12 preceding siblings ...)
2026-05-26 8:47 ` [PATCH v6 13/18] dmaengine: sh: rz-dmac: Adjust rz_dmac_chan_get_residue() to return error codes Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-26 9:57 ` sashiko-bot
2026-05-28 13:52 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 15/18] dmaengine: sh: rz-dmac: Add suspend to RAM support Claudiu Beznea
` (3 subsequent siblings)
17 siblings, 2 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Protect the driver exposed APIs with runtime PM suspend/resume calls
before accessing HW registers. As the current driver leaves runtime PM
enabled in probe, the purpose of the changes in this patch is to avoid
accessing HW registers after a failed system suspend leaves the runtime
PM state of the device improperly reinitialized.
In that case, the driver remains bound to the device, the APIs are still
exposed, and any access to HW registers without runtime resuming the
device may lead to synchronous aborts.
To avoid leaking resources in case of runtime PM failures, save the error
code returned by PM_RUNTIME_ACQUIRE_ERR() in rz_dmac_terminate_all() and
return it only at the end of the function to allow the cleanup code to
run. A similar approach is used in rz_dmac_free_chan_resources().
Because some exposed APIs (e.g. ->device_terminate_all()) may be called
from atomic context according to the documentation, mark the DMA device as
pm_runtime_irq_safe().
This patch prepares the driver for suspend-to-RAM support.
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- updated patch description
- collected tags
- in rz_dmac_free_chan_resources() and rz_dmac_terminate_all() don't touch
the HW registers if runtime resume failed but allow freeing resources
as suggested by sashiko; along with it added debug messages in case the
RPM resume failed
- dropped the runtime resume from rz_dmac_xfer_desc() and move it instead
in rz_dmac_issue_pending() only to avoid calling rpm resume code in
interrupt path as, if we are in the interrupt path the device is sanely
in runtime resume state
- moved the RPM resume code in from rz_dmac_tx_status to
rz_dmac_chan_get_residue(), as close as possible to the HW registers read
to avoid RPM resume in case the residue could be returned w/o interracting
with the HW
- updated patch description
Changes in v5:
- none, this patch is new
drivers/dma/sh/rz-dmac.c | 60 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 57 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 93394b9934c8..bd4ca8e939f1 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -549,12 +549,22 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
struct rz_dmac *dmac = to_rz_dmac(chan->device);
struct rz_dmac_desc *desc, *_desc;
unsigned long flags;
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret) {
+ dev_err(dmac->dev, "RPM resume failed for channel %s, ret=%d\n!",
+ dma_chan_name(chan), ret);
+ }
spin_lock_irqsave(&channel->vc.lock, flags);
rz_lmdesc_setup(channel, channel->lmdesc.base);
- rz_dmac_disable_hw(channel);
+ /* Skip touching HW if RPM resume failed. Let the cleanup do its jobs. */
+ if (!ret)
+ rz_dmac_disable_hw(channel);
if (channel->mid_rid >= 0) {
clear_bit(channel->mid_rid, dmac->modules);
@@ -697,11 +707,22 @@ rz_dmac_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr,
static int rz_dmac_terminate_all(struct dma_chan *chan)
{
struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
+ struct rz_dmac *dmac = to_rz_dmac(chan->device);
unsigned long flags;
LIST_HEAD(head);
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret) {
+ dev_err(dmac->dev, "RPM resume failed for channel %s, ret=%d\n!",
+ dma_chan_name(chan), ret);
+ }
spin_lock_irqsave(&channel->vc.lock, flags);
- rz_dmac_disable_hw(channel);
+ /* Don't return if RPM failed. Let the cleanup do its jobs. */
+ if (!ret)
+ rz_dmac_disable_hw(channel);
rz_lmdesc_setup(channel, channel->lmdesc.base);
if (channel->desc) {
@@ -716,13 +737,20 @@ static int rz_dmac_terminate_all(struct dma_chan *chan)
spin_unlock_irqrestore(&channel->vc.lock, flags);
vchan_dma_desc_free_list(&channel->vc, &head);
- return 0;
+ return ret;
}
static void rz_dmac_issue_pending(struct dma_chan *chan)
{
struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
+ struct rz_dmac *dmac = to_rz_dmac(chan->device);
unsigned long flags;
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return;
spin_lock_irqsave(&channel->vc.lock, flags);
@@ -807,6 +835,11 @@ static void rz_dmac_device_synchronize(struct dma_chan *chan)
vchan_synchronize(&channel->vc);
+ PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return;
+
ret = read_poll_timeout(rz_dmac_ch_readl, chstat, !(chstat & CHSTAT_EN),
100, 100000, false, channel, CHSTAT, 1);
if (ret < 0)
@@ -866,6 +899,7 @@ static int rz_dmac_chan_get_residue(struct device *dev, struct rz_dmac_chan *cha
struct rz_dmac_desc *desc = NULL;
struct virt_dma_desc *vd;
u32 crla, crtb, i;
+ int ret;
vd = vchan_find_desc(&channel->vc, cookie);
if (vd) {
@@ -884,6 +918,11 @@ static int rz_dmac_chan_get_residue(struct device *dev, struct rz_dmac_chan *cha
return 0;
}
+ PM_RUNTIME_ACQUIRE_IF_ENABLED(dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
/*
* We need to read two registers. Make sure the hardware does not move
* to next lmdesc while reading the current lmdesc. Trying it 3 times
@@ -965,6 +1004,13 @@ static int rz_dmac_device_pause_set(struct rz_dmac_chan *channel,
static int rz_dmac_device_pause(struct dma_chan *chan)
{
struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
+ struct rz_dmac *dmac = to_rz_dmac(chan->device);
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
guard(spinlock_irqsave)(&channel->vc.lock);
@@ -994,6 +1040,13 @@ static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel,
static int rz_dmac_device_resume(struct dma_chan *chan)
{
struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
+ struct rz_dmac *dmac = to_rz_dmac(chan->device);
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
guard(spinlock_irqsave)(&channel->vc.lock);
@@ -1274,6 +1327,7 @@ static int rz_dmac_probe(struct platform_device *pdev)
return dev_err_probe(&pdev->dev, PTR_ERR(dmac->rstc),
"failed to get resets\n");
+ pm_runtime_irq_safe(&pdev->dev);
pm_runtime_enable(&pdev->dev);
ret = pm_runtime_resume_and_get(&pdev->dev);
if (ret < 0) {
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 15/18] dmaengine: sh: rz-dmac: Add suspend to RAM support
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (13 preceding siblings ...)
2026-05-26 8:47 ` [PATCH v6 14/18] dmaengine: sh: rz-dmac: Add runtime PM support Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-26 9:43 ` sashiko-bot
2026-05-28 14:38 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 16/18] ASoC: renesas: rz-ssi: Add pause support Claudiu Beznea
` (2 subsequent siblings)
17 siblings, 2 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
The Renesas RZ/G3S SoC supports a power saving mode in which power to most
of the SoC components is turned off, including the DMA IP. Add suspend to
RAM support to save and restore the DMA IP registers.
Cyclic DMA channels require special handling. Since they can be paused and
resumed during system suspend/resume, the driver restores additional
registers for these channels during the system resume phase. If a channel
was not explicitly paused during suspend, the driver ensures that it is
paused and resumed as part of the system suspend/resume flow.
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- collected tags
- dropped rz_dmac_suspend_prepare() as I found issues with it and updated the
patch description
- with it used DEFINE_SIMPLE_DEV_PM_OPS() for PM ops
- used pm_ptr() instead of pm_sleep_ptr()
Changes in v5:
- runtime PM enable in rz_dmac_suspend_prepare() and rz_dmac_suspend_recover()
- initialize ret in rz_dmac_suspend()
- in suspend/resume APIs changed the order b/w runtime PM and reset calls
to follow the sequence present in remove and probe
- in rz_dmac_suspend(): take into account the error code returned by
pm_runtime_put_sync()
- in rz_dmac_resume(): use "return errors ? : 0;" instead of
"return errors ? : ret;"
Changes in v4:
- in rz_dmac_device_synchronize() kept the read_poll_timeout() as
this doesn't fail anymore with the proper status return from
->device_tx_status() API in case the channel is paused; with it
the patch description was updated
- keep the cleanup path in rz_dmac_suspend() simpler to avoid
confusion when using guard()
- used SYSTEM_SLEEP_PM_OPS() as there is no need for having the
suspend/resume callbacks being called in NOIRQ phase
Changes in v3:
- dropped RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED
- dropped read_poll_timeout() from rz_dmac_device_synchronze() as
with audio drivers this times out all the time on suspend because
the audio DMA is already paused when the rz_dmac_device_synchronize()
is called; updated the commit description to describe this change
- call rz_dmac_device_pause_internal() only if RZ_DMAC_CHAN_STATUS_PAUSED
bit is not set or the device is enabled in HW
- updated rz_dmac_device_resume_set() to have it simpler and cover
the cases when it is called with the channel enabled or paused;
updated the comment describing the covered use cases
- call rz_dmac_device_resume_internal() only if
RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL bit is set
- in rz_dmac_chan_is_enabled() return -EAGAIN only if the channel is
enabled in HW
- in rz_dmac_suspend_recover() drop the update of
RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED as this is not available anymore
- in rz_dmac_suspend() call rz_dmac_device_pause_internal() unconditionally
as the logic is now handled inside the called function; also, do not
ignore anymore the failure of internal suspend and abort the suspend
instead
- report channel internal resume failures in rz_dmac_resume()
- use rz_dmac_disable_hw() instead of open coding it in rz_dmac_resume()
- call rz_dmac_device_resume_internal() uncoditionally as the skip
logic is now handled in the function itself
- use NOIRQ_SYSTEM_SLEEP_PM_OPS()
- didn't collect Tommaso's Tb tag as the series was changed a lot since
v2
Changes in v2:
- fixed typos in patch description
- in rz_dmac_suspend_prepare(): return -EAGAIN based on the value returned
by vchan_issue_pending()
- in rz_dmac_suspend_recover(): clear RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED for
non cyclic channels
- in rz_dmac_resume(): call rz_dmac_set_dma_req_no() only for cyclic channels
drivers/dma/sh/rz-dmac.c | 180 +++++++++++++++++++++++++++++++++++++--
1 file changed, 175 insertions(+), 5 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index bd4ca8e939f1..2a7124e4aea3 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -69,10 +69,12 @@ struct rz_dmac_desc {
* enum rz_dmac_chan_status: RZ DMAC channel status
* @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks
* @RZ_DMAC_CHAN_STATUS_CYCLIC: Channel is cyclic
+ * @RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL: Channel is paused through driver internal logic
*/
enum rz_dmac_chan_status {
RZ_DMAC_CHAN_STATUS_PAUSED,
RZ_DMAC_CHAN_STATUS_CYCLIC,
+ RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL,
};
struct rz_dmac_chan {
@@ -92,6 +94,10 @@ struct rz_dmac_chan {
u32 chctrl;
int mid_rid;
+ struct {
+ u32 nxla;
+ } pm_state;
+
struct list_head ld_free;
struct {
@@ -1017,20 +1023,57 @@ static int rz_dmac_device_pause(struct dma_chan *chan)
return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED));
}
+static int rz_dmac_device_pause_internal(struct rz_dmac_chan *channel)
+{
+ lockdep_assert_held(&channel->vc.lock);
+
+ /* Skip channels explicitly paused by consummers or disabled. */
+ if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED) ||
+ !rz_dmac_chan_is_enabled(channel))
+ return 0;
+
+ return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL));
+}
+
static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel,
unsigned long clear_bitmask)
{
- int ret = 0;
u32 val;
+ int ret;
lockdep_assert_held(&channel->vc.lock);
- /* Do not check CHSTAT_SUS but rely on HW capabilities. */
+ /*
+ * We can be:
+ *
+ * 1/ after the channel was paused by a consummer and now it
+ * needs to be resummed
+ * 2/ after the channel was paused internally (as a result of
+ * a system suspend with power loss or not)
+ * 3/ after the channel was paused by a consummer, the system
+ * went through a system suspend (with power loss or not)
+ * and the consummer wants to resume the channel
+ *
+ * To cover all the above cases we set both CLRSUS and SETEN.
+ *
+ * In case 1/ setting SETEN while the channel is still enabled
+ * is harmless for the controller.
+ *
+ * In case 2/ the channel is disabled when calling this function
+ * and setting CLRSUS is harmless for the controller as the
+ * channel is disabled anyway.
+ *
+ * In case 3/ the channel is disabled/enabled if the system
+ * went though a suspend with power loss/or not and setting
+ * CLRSUS/SETEN is harmless for the controller as the channel
+ * is enabled/disabled anyway.
+ */
+
+ rz_dmac_ch_writel(channel, CHCTRL_CLRSUS | CHCTRL_SETEN, CHCTRL, 1);
- rz_dmac_ch_writel(channel, CHCTRL_CLRSUS, CHCTRL, 1);
ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val,
- !(val & CHSTAT_SUS), 1, 1024, false,
- channel, CHSTAT, 1);
+ ((val & (CHSTAT_SUS | CHSTAT_EN)) == CHSTAT_EN),
+ 1, 1024, false, channel, CHSTAT, 1);
channel->status &= ~clear_bitmask;
@@ -1056,6 +1099,16 @@ static int rz_dmac_device_resume(struct dma_chan *chan)
return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED));
}
+static int rz_dmac_device_resume_internal(struct rz_dmac_chan *channel)
+{
+ lockdep_assert_held(&channel->vc.lock);
+
+ if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL)))
+ return 0;
+
+ return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL));
+}
+
/*
* -----------------------------------------------------------------------------
* IRQ handling
@@ -1421,6 +1474,122 @@ static void rz_dmac_remove(struct platform_device *pdev)
pm_runtime_disable(&pdev->dev);
}
+static void rz_dmac_suspend_recover(struct rz_dmac *dmac)
+{
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return;
+
+ for (unsigned int i = 0; i < dmac->n_channels; i++) {
+ struct rz_dmac_chan *channel = &dmac->channels[i];
+
+ guard(spinlock_irqsave)(&channel->vc.lock);
+
+ if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
+ continue;
+
+ rz_dmac_device_resume_internal(channel);
+ }
+}
+
+static int rz_dmac_suspend(struct device *dev)
+{
+ struct rz_dmac *dmac = dev_get_drvdata(dev);
+ int ret = 0;
+
+ for (unsigned int i = 0; i < dmac->n_channels; i++) {
+ struct rz_dmac_chan *channel = &dmac->channels[i];
+
+ guard(spinlock_irqsave)(&channel->vc.lock);
+
+ if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
+ continue;
+
+ ret = rz_dmac_device_pause_internal(channel);
+ if (ret) {
+ dev_err(dev, "Failed to suspend channel %s\n",
+ dma_chan_name(&channel->vc.chan));
+ break;
+ }
+
+ channel->pm_state.nxla = rz_dmac_ch_readl(channel, NXLA, 1);
+ }
+
+ if (ret)
+ goto suspend_recover;
+
+ ret = reset_control_assert(dmac->rstc);
+ if (ret)
+ goto suspend_recover;
+
+ ret = pm_runtime_put_sync(dev);
+ if (ret < 0)
+ goto reset_deassert;
+
+ return 0;
+
+reset_deassert:
+ reset_control_deassert(dmac->rstc);
+suspend_recover:
+ rz_dmac_suspend_recover(dmac);
+ return ret;
+}
+
+static int rz_dmac_resume(struct device *dev)
+{
+ struct rz_dmac *dmac = dev_get_drvdata(dev);
+ int errors = 0, ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = reset_control_deassert(dmac->rstc);
+ if (ret) {
+ /*
+ * Do not put runtime PM here and keep the same state as in
+ * probe. As subsequent suspend/resume cycles may follow, leave
+ * the runtime PM as is, here, to avoid imbalances.
+ */
+ return ret;
+ }
+
+ rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_0_7_COMMON_BASE + DCTRL);
+ rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_8_15_COMMON_BASE + DCTRL);
+
+ for (unsigned int i = 0; i < dmac->n_channels; i++) {
+ struct rz_dmac_chan *channel = &dmac->channels[i];
+
+ guard(spinlock_irqsave)(&channel->vc.lock);
+
+ rz_dmac_disable_hw(&dmac->channels[i]);
+
+ if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
+ continue;
+
+ rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid);
+
+ rz_dmac_ch_writel(channel, channel->pm_state.nxla, NXLA, 1);
+ rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1);
+ rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1);
+ rz_dmac_ch_writel(channel, channel->chctrl, CHCTRL, 1);
+
+ ret = rz_dmac_device_resume_internal(channel);
+ if (ret) {
+ errors = ret;
+ dev_err(dev, "Failed to resume channel %s, ret=%d\n",
+ dma_chan_name(&channel->vc.chan), ret);
+ }
+ }
+
+ return errors ? : 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(rz_dmac_pm_ops, rz_dmac_suspend, rz_dmac_resume);
+
static const struct rz_dmac_info rz_dmac_v2h_info = {
.icu_register_dma_req = rzv2h_icu_register_dma_req,
.default_dma_req_no = RZV2H_ICU_DMAC_REQ_NO_DEFAULT,
@@ -1447,6 +1616,7 @@ static struct platform_driver rz_dmac_driver = {
.driver = {
.name = "rz-dmac",
.of_match_table = of_rz_dmac_match,
+ .pm = pm_ptr(&rz_dmac_pm_ops),
},
.probe = rz_dmac_probe,
.remove = rz_dmac_remove,
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 16/18] ASoC: renesas: rz-ssi: Add pause support
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (14 preceding siblings ...)
2026-05-26 8:47 ` [PATCH v6 15/18] dmaengine: sh: rz-dmac: Add suspend to RAM support Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-26 9:46 ` sashiko-bot
2026-05-26 8:47 ` [PATCH v6 17/18] ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs Claudiu Beznea
2026-05-26 8:47 ` [PATCH v6 18/18] dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last descriptor Claudiu Beznea
17 siblings, 1 reply; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Add pause support as a preparatory step to switch to PCM dmaengine APIs.
Acked-by: Mark Brown <broonie@kernel.org>
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- collected tags
Changes in v5:
- none
Changes in v4:
- collected tags
Changes in v3:
- none, this patch is new
sound/soc/renesas/rz-ssi.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c
index 71e434cfe07b..d4e1dded3a9c 100644
--- a/sound/soc/renesas/rz-ssi.c
+++ b/sound/soc/renesas/rz-ssi.c
@@ -847,6 +847,7 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ret = rz_ssi_trigger_resume(ssi, strm);
if (ret)
return ret;
@@ -888,6 +889,7 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
rz_ssi_stop(ssi, strm);
break;
@@ -1054,7 +1056,8 @@ static const struct snd_pcm_hardware rz_ssi_pcm_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
- SNDRV_PCM_INFO_RESUME,
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_PAUSE,
.buffer_bytes_max = PREALLOC_BUFFER,
.period_bytes_min = 32,
.period_bytes_max = 8192,
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 17/18] ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (15 preceding siblings ...)
2026-05-26 8:47 ` [PATCH v6 16/18] ASoC: renesas: rz-ssi: Add pause support Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-26 10:00 ` sashiko-bot
2026-05-26 8:47 ` [PATCH v6 18/18] dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last descriptor Claudiu Beznea
17 siblings, 1 reply; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
On Renesas RZ/G2L and RZ/G3S SoCs (where this was tested), captured audio
files occasionally contained random spikes when viewed with a tool such
as Audacity. These spikes were also audible as popping noises.
Using cyclic DMA resolves this issue. The driver was reworked to use the
existing support provided by the generic PCM dmaengine APIs. In addition
to eliminating the random spikes, the following issues were addressed:
- blank periods at the beginning of recorded files, which occurred
intermittently, are no longer present
- no overruns or underruns were observed when continuously recording
short audio files (e.g. 5 seconds long) in a loop
- concurrency issues in the SSI driver when enqueuing DMA requests were
eliminated; previously, DMA requests could be prepared and submitted
both from the DMA completion callback and the interrupt handler, which
led to crashes after several hours of testing
- the SSI driver logic is simplified
- the number of generated interrupts is reduced by approximately 250%
In the SSI platform driver probe function, the following changes were
made:
- the driver-specific DMA configuration was removed in favor of the
generic PCM dmaengine APIs. As a result, explicit cleanup goto labels
are no longer required and the driver remove callback was dropped,
since resource management is now handled via devres helpers
- special handling was added for IP variants operating in half-duplex
mode, where the DMA channel name in the device tree is "rt"; this DMA
channel name is taken into account and passed to the generic PCM
dmaengine configuration data
All code previously responsible for preparing and completing DMA
transfers was removed, as this functionality is now handled entirely by
the generic PCM dmaengine APIs.
Since DMA channels must be paused and resumed during recovery paths
(overruns and underruns reported by the hardware), the DMA channel
references are stored in rz_ssi_hw_params().
The logic in rz_ssi_is_dma_enabled() was updated to reflect that the
driver no longer manages DMA transfers directly.
To avoid software reported underruns (e.g. when running aplay during
consecutive suspend/resume cycles, or when the CPU is nearly 100%
loaded), rz_ssi_pcm_hardware.buffer_bytes_max was increased to 192K.
At the same time, rz_ssi_pcm_hardware.period_bytes_max was set to 48K
to reduce interrupt overhead.
Finally, rz_ssi_stream_is_play() was removed, as it had only a single
remaining user after this rework, and its logic was inlined at the call
site.
Acked-by: Mark Brown <broonie@kernel.org>
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- collected tags
- in rz_ssi_interrupt() added extra validity checkes for playback/capture
substream and the ssi->dmas[] as suggested by sashiko
- still preserved the Mark's Ack
Changes in v5:
- in rz_ssi_interrupt(): check if playback and capture dmas are the same
to avoid calling dmaengine_pause() twice, on the same DMA channel
- in rz_ssi_shutdown(): set ssi->dmas[i] = NULL
- still preserved the Mark's Ack
Changes in v4:
- collected tags
- in rz_ssi_interrupt() checked the dma channel is valid before
calling dmaengine_pause(); at the same time initialized the
rz_ssi->dmas[] with NULL in case the DMA is not available in
rz_ssi_dai_hw_params()
- set rz_ssi_dmaengine_pcm_conf.prealloc_buffer_size
- dinamically allocate the object of type snd_dmaengine_pcm_config passed
to devm_snd_dmaengine_pcm_register() to avoid issues when the driver
is instantiated for more than one HW instance
- I considered keeping the ack was still OK; Mark, please let me know if
you consider otherwise
Changes in v3:
- s/CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM/SND_SOC_GENERIC_DMAENGINE_PCM
in Kconfig
- in rz_ssi_clk_setup(): drop the update of dma_dai->maxburst
- in rz_ssi_interrupt(): pause the DMA channels in case of HW over/underruns
- add different open APIs for rz_ssi_soc_component_pio and
rz_ssi_soc_component_dma
- set rz_ssi_pcm_hardware to rz_ssi_dmaengine_pcm_conf.pcm_hardware
and updated the buffer_bytes_max to avoid underruns detected by
applications just before suspending; along with it updated
period_bytes_max for lower interrupt overhead; updated the patch
description for this; with it updated the snd_pcm_set_managed_buffer_all()
arguments to use the rz_ssi_pcm_hardware
- added back rz_ssi_soc_component_pio.pcm_new instantiation as the
PIO mode was broken w/o it
- use specific rz_ssi_soc_component_dma.open implementation for DMA
- updated rz_ssi_dmaengine_pcm_conf.chan_names[].{tx, rx} either if
there is about full or half duplex instantiation and move the flags
variable local to the code block that uses it
- check devm_snd_dmaengine_pcm_register() for defer errors
Changes in v2:
- fixed typos in patch description
- select CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM for rz-ssi driver
- in rz_ssi_dai_hw_params() check if DMA is enabled before calling
snd_dmaengine_pcm_get_chan() to avoid failures for PIO mode
- do not drop rz_ssi_pcm_pointer() and rz_ssi_pcm_new() as these
are necessary for PIO mode
- added 2 struct snd_soc_component_driver, one for PIO mode, one for
DMA and updated probe() to register the proper
snd_soc_component_driver based on the working mode
sound/soc/renesas/Kconfig | 1 +
sound/soc/renesas/rz-ssi.c | 394 +++++++++++++------------------------
2 files changed, 136 insertions(+), 259 deletions(-)
diff --git a/sound/soc/renesas/Kconfig b/sound/soc/renesas/Kconfig
index 11c2027c88a7..6520217e7407 100644
--- a/sound/soc/renesas/Kconfig
+++ b/sound/soc/renesas/Kconfig
@@ -56,6 +56,7 @@ config SND_SOC_MSIOF
config SND_SOC_RZ
tristate "RZ/G2L series SSIF-2 support"
depends on ARCH_RZG2L || COMPILE_TEST
+ select SND_SOC_GENERIC_DMAENGINE_PCM
help
This option enables RZ/G2L SSIF-2 sound support.
diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c
index d4e1dded3a9c..9fe8a639c47c 100644
--- a/sound/soc/renesas/rz-ssi.c
+++ b/sound/soc/renesas/rz-ssi.c
@@ -13,6 +13,8 @@
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
@@ -87,8 +89,6 @@ struct rz_ssi_stream {
struct rz_ssi_priv *priv;
struct snd_pcm_substream *substream;
int fifo_sample_size; /* sample capacity of SSI FIFO */
- int dma_buffer_pos; /* The address for the next DMA descriptor */
- int completed_dma_buf_pos; /* The address of the last completed DMA descriptor. */
int period_counter; /* for keeping track of periods transferred */
int buffer_pos; /* current frame position in the buffer */
int running; /* 0=stopped, 1=running */
@@ -96,8 +96,6 @@ struct rz_ssi_stream {
int uerr_num;
int oerr_num;
- struct dma_chan *dma_ch;
-
int (*transfer)(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm);
};
@@ -108,7 +106,6 @@ struct rz_ssi_priv {
struct clk *sfr_clk;
struct clk *clk;
- phys_addr_t phys;
int irq_int;
int irq_tx;
int irq_rx;
@@ -148,9 +145,10 @@ struct rz_ssi_priv {
unsigned int sample_width;
unsigned int sample_bits;
} hw_params_cache;
-};
-static void rz_ssi_dma_complete(void *data);
+ struct snd_dmaengine_dai_dma_data dma_dais[SNDRV_PCM_STREAM_LAST + 1];
+ struct dma_chan *dmas[SNDRV_PCM_STREAM_LAST + 1];
+};
static void rz_ssi_reg_writel(struct rz_ssi_priv *priv, uint reg, u32 data)
{
@@ -172,11 +170,6 @@ static void rz_ssi_reg_mask_setl(struct rz_ssi_priv *priv, uint reg,
writel(val, (priv->base + reg));
}
-static inline bool rz_ssi_stream_is_play(struct snd_pcm_substream *substream)
-{
- return substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
-}
-
static inline struct rz_ssi_stream *
rz_ssi_stream_get(struct rz_ssi_priv *ssi, struct snd_pcm_substream *substream)
{
@@ -185,7 +178,7 @@ rz_ssi_stream_get(struct rz_ssi_priv *ssi, struct snd_pcm_substream *substream)
static inline bool rz_ssi_is_dma_enabled(struct rz_ssi_priv *ssi)
{
- return (ssi->playback.dma_ch && (ssi->dma_rt || ssi->capture.dma_ch));
+ return !ssi->playback.transfer && !ssi->capture.transfer;
}
static void rz_ssi_set_substream(struct rz_ssi_stream *strm,
@@ -215,8 +208,6 @@ static void rz_ssi_stream_init(struct rz_ssi_stream *strm,
struct snd_pcm_substream *substream)
{
rz_ssi_set_substream(strm, substream);
- strm->dma_buffer_pos = 0;
- strm->completed_dma_buf_pos = 0;
strm->period_counter = 0;
strm->buffer_pos = 0;
@@ -242,12 +233,13 @@ static void rz_ssi_stream_quit(struct rz_ssi_priv *ssi,
dev_info(dev, "underrun = %d\n", strm->uerr_num);
}
-static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, unsigned int rate,
- unsigned int channels)
+static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, struct snd_pcm_substream *substream,
+ unsigned int rate, unsigned int channels)
{
static u8 ckdv[] = { 1, 2, 4, 8, 16, 32, 64, 128, 6, 12, 24, 48, 96 };
unsigned int channel_bits = 32; /* System Word Length */
unsigned long bclk_rate = rate * channels * channel_bits;
+ struct snd_dmaengine_dai_dma_data *dma_dai;
unsigned int div;
unsigned int i;
u32 ssicr = 0;
@@ -290,6 +282,8 @@ static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, unsigned int rate,
return -EINVAL;
}
+ dma_dai = &ssi->dma_dais[substream->stream];
+
/*
* DWL: Data Word Length = {16, 24, 32} bits
* SWL: System Word Length = 32 bits
@@ -298,12 +292,15 @@ static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, unsigned int rate,
switch (ssi->hw_params_cache.sample_width) {
case 16:
ssicr |= SSICR_DWL(1);
+ dma_dai->addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
break;
case 24:
ssicr |= SSICR_DWL(5) | SSICR_PDTA;
+ dma_dai->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
break;
case 32:
ssicr |= SSICR_DWL(6);
+ dma_dai->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
break;
default:
dev_err(ssi->dev, "Not support %u data width",
@@ -344,7 +341,7 @@ static void rz_ssi_set_idle(struct rz_ssi_priv *ssi)
static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
{
- bool is_play = rz_ssi_stream_is_play(strm->substream);
+ bool is_play = strm->substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
bool is_full_duplex;
u32 ssicr, ssifcr;
@@ -423,14 +420,6 @@ static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
/* Disable TX/RX */
rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TEN | SSICR_REN, 0);
- /* Cancel all remaining DMA transactions */
- if (rz_ssi_is_dma_enabled(ssi)) {
- if (ssi->playback.dma_ch)
- dmaengine_terminate_async(ssi->playback.dma_ch);
- if (ssi->capture.dma_ch)
- dmaengine_terminate_async(ssi->capture.dma_ch);
- }
-
rz_ssi_set_idle(ssi);
return 0;
@@ -458,10 +447,6 @@ static void rz_ssi_pointer_update(struct rz_ssi_stream *strm, int frames)
snd_pcm_period_elapsed(strm->substream);
strm->period_counter = current_period;
}
-
- strm->completed_dma_buf_pos += runtime->period_size;
- if (strm->completed_dma_buf_pos >= runtime->buffer_size)
- strm->completed_dma_buf_pos = 0;
}
static int rz_ssi_pio_recv(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
@@ -606,12 +591,6 @@ static irqreturn_t rz_ssi_interrupt(int irq, void *data)
if (irq == ssi->irq_int) { /* error or idle */
bool is_stopped = !!(ssisr & (SSISR_RUIRQ | SSISR_ROIRQ |
SSISR_TUIRQ | SSISR_TOIRQ));
- int i, count;
-
- if (rz_ssi_is_dma_enabled(ssi))
- count = 4;
- else
- count = 1;
if (ssi->capture.substream && is_stopped) {
if (ssisr & SSISR_RUIRQ)
@@ -631,19 +610,41 @@ static irqreturn_t rz_ssi_interrupt(int irq, void *data)
rz_ssi_stop(ssi, strm_playback);
}
+ if (!rz_ssi_is_stream_running(&ssi->playback) &&
+ !rz_ssi_is_stream_running(&ssi->capture) &&
+ rz_ssi_is_dma_enabled(ssi) && is_stopped) {
+ if (ssi->playback.substream &&
+ ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK])
+ dmaengine_pause(ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK]);
+ if (ssi->capture.substream &&
+ ssi->dmas[SNDRV_PCM_STREAM_CAPTURE] &&
+ /* Avoid calling pause twice in case of half duplex. */
+ ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK] !=
+ ssi->dmas[SNDRV_PCM_STREAM_CAPTURE])
+ dmaengine_pause(ssi->dmas[SNDRV_PCM_STREAM_CAPTURE]);
+ }
+
/* Clear all flags */
rz_ssi_reg_mask_setl(ssi, SSISR, SSISR_TOIRQ | SSISR_TUIRQ |
SSISR_ROIRQ | SSISR_RUIRQ, 0);
/* Add/remove more data */
if (ssi->capture.substream && is_stopped) {
- for (i = 0; i < count; i++)
+ if (rz_ssi_is_dma_enabled(ssi)) {
+ if (ssi->dmas[SNDRV_PCM_STREAM_CAPTURE])
+ dmaengine_resume(ssi->dmas[SNDRV_PCM_STREAM_CAPTURE]);
+ } else {
strm_capture->transfer(ssi, strm_capture);
+ }
}
if (ssi->playback.substream && is_stopped) {
- for (i = 0; i < count; i++)
+ if (rz_ssi_is_dma_enabled(ssi)) {
+ if (ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK])
+ dmaengine_resume(ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK]);
+ } else {
strm_playback->transfer(ssi, strm_playback);
+ }
}
/* Resume */
@@ -679,153 +680,11 @@ static irqreturn_t rz_ssi_interrupt(int irq, void *data)
return IRQ_HANDLED;
}
-static int rz_ssi_dma_slave_config(struct rz_ssi_priv *ssi,
- struct dma_chan *dma_ch, bool is_play)
-{
- struct dma_slave_config cfg;
-
- memset(&cfg, 0, sizeof(cfg));
-
- cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
- cfg.dst_addr = ssi->phys + SSIFTDR;
- cfg.src_addr = ssi->phys + SSIFRDR;
- if (ssi->hw_params_cache.sample_width == 16) {
- cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
- cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
- } else {
- cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
- cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
- }
-
- return dmaengine_slave_config(dma_ch, &cfg);
-}
-
-static int rz_ssi_dma_transfer(struct rz_ssi_priv *ssi,
- struct rz_ssi_stream *strm)
-{
- struct snd_pcm_substream *substream = strm->substream;
- struct dma_async_tx_descriptor *desc;
- struct snd_pcm_runtime *runtime;
- enum dma_transfer_direction dir;
- u32 dma_paddr, dma_size;
- int amount;
-
- if (!rz_ssi_stream_is_valid(ssi, strm))
- return -EINVAL;
-
- runtime = substream->runtime;
- if (runtime->state == SNDRV_PCM_STATE_DRAINING)
- /*
- * Stream is ending, so do not queue up any more DMA
- * transfers otherwise we play partial sound clips
- * because we can't shut off the DMA quick enough.
- */
- return 0;
-
- dir = rz_ssi_stream_is_play(substream) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
-
- /* Always transfer 1 period */
- amount = runtime->period_size;
-
- /* DMA physical address and size */
- dma_paddr = runtime->dma_addr + frames_to_bytes(runtime,
- strm->dma_buffer_pos);
- dma_size = frames_to_bytes(runtime, amount);
- desc = dmaengine_prep_slave_single(strm->dma_ch, dma_paddr, dma_size,
- dir,
- DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
- if (!desc) {
- dev_err(ssi->dev, "dmaengine_prep_slave_single() fail\n");
- return -ENOMEM;
- }
-
- desc->callback = rz_ssi_dma_complete;
- desc->callback_param = strm;
-
- if (dmaengine_submit(desc) < 0) {
- dev_err(ssi->dev, "dmaengine_submit() fail\n");
- return -EIO;
- }
-
- /* Update DMA pointer */
- strm->dma_buffer_pos += amount;
- if (strm->dma_buffer_pos >= runtime->buffer_size)
- strm->dma_buffer_pos = 0;
-
- /* Start DMA */
- dma_async_issue_pending(strm->dma_ch);
-
- return 0;
-}
-
-static void rz_ssi_dma_complete(void *data)
-{
- struct rz_ssi_stream *strm = (struct rz_ssi_stream *)data;
-
- if (!strm->running || !strm->substream || !strm->substream->runtime)
- return;
-
- /* Note that next DMA transaction has probably already started */
- rz_ssi_pointer_update(strm, strm->substream->runtime->period_size);
-
- /* Queue up another DMA transaction */
- rz_ssi_dma_transfer(strm->priv, strm);
-}
-
-static void rz_ssi_release_dma_channels(struct rz_ssi_priv *ssi)
-{
- if (ssi->playback.dma_ch) {
- dma_release_channel(ssi->playback.dma_ch);
- ssi->playback.dma_ch = NULL;
- if (ssi->dma_rt)
- ssi->dma_rt = false;
- }
-
- if (ssi->capture.dma_ch) {
- dma_release_channel(ssi->capture.dma_ch);
- ssi->capture.dma_ch = NULL;
- }
-}
-
-static int rz_ssi_dma_request(struct rz_ssi_priv *ssi, struct device *dev)
-{
- ssi->playback.dma_ch = dma_request_chan(dev, "tx");
- if (IS_ERR(ssi->playback.dma_ch))
- ssi->playback.dma_ch = NULL;
-
- ssi->capture.dma_ch = dma_request_chan(dev, "rx");
- if (IS_ERR(ssi->capture.dma_ch))
- ssi->capture.dma_ch = NULL;
-
- if (!ssi->playback.dma_ch && !ssi->capture.dma_ch) {
- ssi->playback.dma_ch = dma_request_chan(dev, "rt");
- if (IS_ERR(ssi->playback.dma_ch)) {
- ssi->playback.dma_ch = NULL;
- goto no_dma;
- }
-
- ssi->dma_rt = true;
- }
-
- if (!rz_ssi_is_dma_enabled(ssi))
- goto no_dma;
-
- return 0;
-
-no_dma:
- rz_ssi_release_dma_channels(ssi);
-
- return -ENODEV;
-}
-
static int rz_ssi_trigger_resume(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
{
struct snd_pcm_substream *substream = strm->substream;
- struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
- strm->dma_buffer_pos = strm->completed_dma_buf_pos + runtime->period_size;
-
if (rz_ssi_is_stream_running(&ssi->playback) ||
rz_ssi_is_stream_running(&ssi->capture))
return 0;
@@ -834,7 +693,7 @@ static int rz_ssi_trigger_resume(struct rz_ssi_priv *ssi, struct rz_ssi_stream *
if (ret)
return ret;
- return rz_ssi_clk_setup(ssi, ssi->hw_params_cache.rate,
+ return rz_ssi_clk_setup(ssi, substream, ssi->hw_params_cache.rate,
ssi->hw_params_cache.channels);
}
@@ -843,7 +702,7 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
{
struct rz_ssi_priv *ssi = snd_soc_dai_get_drvdata(dai);
struct rz_ssi_stream *strm = rz_ssi_stream_get(ssi, substream);
- int ret = 0, i, num_transfer = 1;
+ int ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
@@ -858,28 +717,7 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
if (cmd == SNDRV_PCM_TRIGGER_START)
rz_ssi_stream_init(strm, substream);
- if (rz_ssi_is_dma_enabled(ssi)) {
- bool is_playback = rz_ssi_stream_is_play(substream);
-
- if (ssi->dma_rt)
- ret = rz_ssi_dma_slave_config(ssi, ssi->playback.dma_ch,
- is_playback);
- else
- ret = rz_ssi_dma_slave_config(ssi, strm->dma_ch,
- is_playback);
-
- /* Fallback to pio */
- if (ret < 0) {
- ssi->playback.transfer = rz_ssi_pio_send;
- ssi->capture.transfer = rz_ssi_pio_recv;
- rz_ssi_release_dma_channels(ssi);
- } else {
- /* For DMA, queue up multiple DMA descriptors */
- num_transfer = 4;
- }
- }
-
- for (i = 0; i < num_transfer; i++) {
+ if (!rz_ssi_is_dma_enabled(ssi)) {
ret = strm->transfer(ssi, strm);
if (ret)
return ret;
@@ -975,6 +813,8 @@ static void rz_ssi_shutdown(struct snd_pcm_substream *substream,
ssi->dup.tx_active = false;
else
ssi->dup.rx_active = false;
+
+ ssi->dmas[substream->stream] = NULL;
}
static bool rz_ssi_is_valid_hw_params(struct rz_ssi_priv *ssi, unsigned int rate,
@@ -1026,6 +866,12 @@ static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream,
return -EINVAL;
}
+ /* Save the DMA channels for recovery. */
+ if (rz_ssi_is_dma_enabled(ssi))
+ ssi->dmas[substream->stream] = snd_dmaengine_pcm_get_chan(substream);
+ else
+ ssi->dmas[substream->stream] = NULL;
+
if (rz_ssi_is_stream_running(&ssi->playback) ||
rz_ssi_is_stream_running(&ssi->capture)) {
if (rz_ssi_is_valid_hw_params(ssi, rate, channels, sample_width, sample_bits))
@@ -1041,10 +887,21 @@ static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream,
if (ret)
return ret;
- return rz_ssi_clk_setup(ssi, rate, channels);
+ return rz_ssi_clk_setup(ssi, substream, rate, channels);
+}
+
+static int rz_ssi_dai_probe(struct snd_soc_dai *dai)
+{
+ struct rz_ssi_priv *ssi = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_init_dma_data(dai, &ssi->dma_dais[SNDRV_PCM_STREAM_PLAYBACK],
+ &ssi->dma_dais[SNDRV_PCM_STREAM_CAPTURE]);
+
+ return 0;
}
static const struct snd_soc_dai_ops rz_ssi_dai_ops = {
+ .probe = rz_ssi_dai_probe,
.startup = rz_ssi_startup,
.shutdown = rz_ssi_shutdown,
.trigger = rz_ssi_dai_trigger,
@@ -1058,9 +915,9 @@ static const struct snd_pcm_hardware rz_ssi_pcm_hardware = {
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_PAUSE,
- .buffer_bytes_max = PREALLOC_BUFFER,
+ .buffer_bytes_max = 192 * 1024,
.period_bytes_min = 32,
- .period_bytes_max = 8192,
+ .period_bytes_max = 48 * 1024,
.channels_min = SSI_CHAN_MIN,
.channels_max = SSI_CHAN_MAX,
.periods_min = 1,
@@ -1068,8 +925,8 @@ static const struct snd_pcm_hardware rz_ssi_pcm_hardware = {
.fifo_size = 32 * 2,
};
-static int rz_ssi_pcm_open(struct snd_soc_component *component,
- struct snd_pcm_substream *substream)
+static int rz_ssi_pcm_open_pio(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
{
snd_soc_set_runtime_hwparams(substream, &rz_ssi_pcm_hardware);
@@ -1077,6 +934,13 @@ static int rz_ssi_pcm_open(struct snd_soc_component *component,
SNDRV_PCM_HW_PARAM_PERIODS);
}
+static int rz_ssi_pcm_open_dma(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+}
+
static snd_pcm_uframes_t rz_ssi_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
@@ -1093,7 +957,8 @@ static int rz_ssi_pcm_new(struct snd_soc_component *component,
{
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
rtd->card->snd_card->dev,
- PREALLOC_BUFFER, PREALLOC_BUFFER_MAX);
+ rz_ssi_pcm_hardware.buffer_bytes_max,
+ rz_ssi_pcm_hardware.buffer_bytes_max);
return 0;
}
@@ -1116,16 +981,30 @@ static struct snd_soc_dai_driver rz_ssi_soc_dai[] = {
},
};
-static const struct snd_soc_component_driver rz_ssi_soc_component = {
+static const struct snd_soc_component_driver rz_ssi_soc_component_pio = {
.name = "rz-ssi",
- .open = rz_ssi_pcm_open,
+ .open = rz_ssi_pcm_open_pio,
.pointer = rz_ssi_pcm_pointer,
.pcm_new = rz_ssi_pcm_new,
.legacy_dai_naming = 1,
};
+static const struct snd_soc_component_driver rz_ssi_soc_component_dma = {
+ .name = "rz-ssi",
+ .open = rz_ssi_pcm_open_dma,
+ .legacy_dai_naming = 1,
+};
+
+static const struct snd_dmaengine_pcm_config rz_ssi_dmaengine_pcm_conf = {
+ .pcm_hardware = &rz_ssi_pcm_hardware,
+ .prealloc_buffer_size = 192 * 1024,
+ .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+};
+
static int rz_ssi_probe(struct platform_device *pdev)
{
+ const struct snd_soc_component_driver *component_driver;
+ struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct rz_ssi_priv *ssi;
struct clk *audio_clk;
@@ -1141,7 +1020,6 @@ static int rz_ssi_probe(struct platform_device *pdev)
if (IS_ERR(ssi->base))
return PTR_ERR(ssi->base);
- ssi->phys = res->start;
ssi->clk = devm_clk_get(dev, "ssi");
if (IS_ERR(ssi->clk))
return PTR_ERR(ssi->clk);
@@ -1165,16 +1043,43 @@ static int rz_ssi_probe(struct platform_device *pdev)
ssi->audio_mck = ssi->audio_clk_1 ? ssi->audio_clk_1 : ssi->audio_clk_2;
- /* Detect DMA support */
- ret = rz_ssi_dma_request(ssi, dev);
- if (ret < 0) {
+ ssi->dma_dais[SNDRV_PCM_STREAM_PLAYBACK].addr = (dma_addr_t)res->start + SSIFTDR;
+ ssi->dma_dais[SNDRV_PCM_STREAM_CAPTURE].addr = (dma_addr_t)res->start + SSIFRDR;
+
+ if (of_property_present(np, "dma-names")) {
+ struct snd_dmaengine_pcm_config *config;
+ unsigned int flags = 0;
+
+ config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+
+ config->pcm_hardware = rz_ssi_dmaengine_pcm_conf.pcm_hardware;
+ config->prealloc_buffer_size = rz_ssi_dmaengine_pcm_conf.prealloc_buffer_size;
+ config->prepare_slave_config = rz_ssi_dmaengine_pcm_conf.prepare_slave_config;
+
+ if (of_property_match_string(np, "dma-names", "rt") == 0) {
+ flags = SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX;
+ config->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = "rt";
+ } else {
+ config->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = "tx";
+ config->chan_names[SNDRV_PCM_STREAM_CAPTURE] = "rx";
+ }
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, config, flags);
+ } else {
+ ret = -ENODEV;
+ }
+
+ if (ret == -EPROBE_DEFER) {
+ return ret;
+ } else if (ret) {
dev_warn(dev, "DMA not available, using PIO\n");
ssi->playback.transfer = rz_ssi_pio_send;
ssi->capture.transfer = rz_ssi_pio_recv;
+ component_driver = &rz_ssi_soc_component_pio;
} else {
- dev_info(dev, "DMA enabled");
- ssi->playback.transfer = rz_ssi_dma_transfer;
- ssi->capture.transfer = rz_ssi_dma_transfer;
+ dev_info(dev, "DMA enabled\n");
+ component_driver = &rz_ssi_soc_component_dma;
}
ssi->playback.priv = ssi;
@@ -1185,17 +1090,13 @@ static int rz_ssi_probe(struct platform_device *pdev)
/* Error Interrupt */
ssi->irq_int = platform_get_irq_byname(pdev, "int_req");
- if (ssi->irq_int < 0) {
- ret = ssi->irq_int;
- goto err_release_dma_chs;
- }
+ if (ssi->irq_int < 0)
+ return ssi->irq_int;
ret = devm_request_irq(dev, ssi->irq_int, rz_ssi_interrupt,
0, dev_name(dev), ssi);
- if (ret < 0) {
- dev_err_probe(dev, ret, "irq request error (int_req)\n");
- goto err_release_dma_chs;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "irq request error (int_req)\n");
if (!rz_ssi_is_dma_enabled(ssi)) {
/* Tx and Rx interrupts (pio only) */
@@ -1236,43 +1137,19 @@ static int rz_ssi_probe(struct platform_device *pdev)
}
ssi->rstc = devm_reset_control_get_exclusive(dev, NULL);
- if (IS_ERR(ssi->rstc)) {
- ret = PTR_ERR(ssi->rstc);
- goto err_release_dma_chs;
- }
+ if (IS_ERR(ssi->rstc))
+ return dev_err_probe(dev, PTR_ERR(ssi->rstc), "Failed to get reset\n");
/* Default 0 for power saving. Can be overridden via sysfs. */
pm_runtime_set_autosuspend_delay(dev, 0);
pm_runtime_use_autosuspend(dev);
ret = devm_pm_runtime_enable(dev);
- if (ret < 0) {
- dev_err(dev, "Failed to enable runtime PM!\n");
- goto err_release_dma_chs;
- }
-
- ret = devm_snd_soc_register_component(dev, &rz_ssi_soc_component,
- rz_ssi_soc_dai,
- ARRAY_SIZE(rz_ssi_soc_dai));
- if (ret < 0) {
- dev_err(dev, "failed to register snd component\n");
- goto err_release_dma_chs;
- }
-
- return 0;
-
-err_release_dma_chs:
- rz_ssi_release_dma_channels(ssi);
-
- return ret;
-}
-
-static void rz_ssi_remove(struct platform_device *pdev)
-{
- struct rz_ssi_priv *ssi = dev_get_drvdata(&pdev->dev);
-
- rz_ssi_release_dma_channels(ssi);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to enable runtime PM!\n");
- reset_control_assert(ssi->rstc);
+ return devm_snd_soc_register_component(dev, component_driver,
+ rz_ssi_soc_dai,
+ ARRAY_SIZE(rz_ssi_soc_dai));
}
static const struct of_device_id rz_ssi_of_match[] = {
@@ -1307,7 +1184,6 @@ static struct platform_driver rz_ssi_driver = {
.pm = pm_ptr(&rz_ssi_pm_ops),
},
.probe = rz_ssi_probe,
- .remove = rz_ssi_remove,
};
module_platform_driver(rz_ssi_driver);
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v6 18/18] dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last descriptor
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
` (16 preceding siblings ...)
2026-05-26 8:47 ` [PATCH v6 17/18] ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs Claudiu Beznea
@ 2026-05-26 8:47 ` Claudiu Beznea
2026-05-26 9:43 ` sashiko-bot
2026-05-28 14:39 ` Tommaso Merciai
17 siblings, 2 replies; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 8:47 UTC (permalink / raw)
To: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur
Cc: claudiu.beznea, claudiu.beznea, dmaengine, linux-kernel,
linux-sound, linux-renesas-soc, Claudiu Beznea, John Madieu
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
On an RZ/G2L-based system, it has been observed that when the DMA channels
for all enabled IPs are active (TX and RX for one serial IP, TX and RX for
one audio IP, and TX and RX for one SPI IP), shortly after all of them are
started, the system can become irrecoverably blocked. In one debug session
the system did not block, and the DMA HW registers were inspected. It was
found that the DER (Descriptor Error) bit in the CHSTAT register for one of
the SPI DMA channels was set.
According to the RZ/G2L HW Manual, Rev. 1.30, chapter 14.4.7 Channel
Status Register n/nS (CHSTAT_n/nS), description of the DER bit, the DER
bit is set when the LV (Link Valid) value loaded with a descriptor in link
mode is 0. This means that the DMA engine has loaded an invalid
descriptor (as defined in Table 14.14, Header Area, of the same manual).
The same chapter states that when a descriptor error occurs, the transfer
is stopped, but no DMA error interrupt is generated.
Set the LE bit on the last descriptor of a transfer. This informs the DMA
engine that this is the final descriptor for the transfer.
Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v6:
- collected tags
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- none
Changes in v2:
- none
drivers/dma/sh/rz-dmac.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
index 2a7124e4aea3..f1174d25da84 100644
--- a/drivers/dma/sh/rz-dmac.c
+++ b/drivers/dma/sh/rz-dmac.c
@@ -200,6 +200,7 @@ struct rz_dmac {
/* LINK MODE DESCRIPTOR */
#define HEADER_LV BIT(0)
+#define HEADER_LE BIT(1)
#define HEADER_WBD BIT(2)
#define RZ_DMAC_MAX_CHAN_DESCRIPTORS 16
@@ -382,7 +383,7 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel)
lmdesc->chcfg = chcfg;
lmdesc->chitvl = 0;
lmdesc->chext = 0;
- lmdesc->header = HEADER_LV;
+ lmdesc->header = HEADER_LV | HEADER_LE;
rz_dmac_set_dma_req_no(dmac, channel->index, dmac->info->default_dma_req_no);
@@ -425,7 +426,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
lmdesc->chext = 0;
if (i == (sg_len - 1)) {
lmdesc->chcfg = (channel->chcfg & ~CHCFG_DEM);
- lmdesc->header = HEADER_LV;
+ lmdesc->header = HEADER_LV | HEADER_LE;
} else {
lmdesc->chcfg = channel->chcfg;
lmdesc->header = HEADER_LV;
--
2.43.0
^ permalink raw reply related [flat|nested] 53+ messages in thread
* RE: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
2026-05-26 8:46 ` [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up Claudiu Beznea
@ 2026-05-26 8:54 ` Biju Das
2026-05-26 9:45 ` Claudiu Beznea
2026-05-26 9:20 ` sashiko-bot
2026-05-28 13:44 ` Tommaso Merciai
2 siblings, 1 reply; 53+ messages in thread
From: Biju Das @ 2026-05-26 8:54 UTC (permalink / raw)
To: Claudiu Beznea, vkoul@kernel.org, Frank.Li@kernel.org,
lgirdwood@gmail.com, broonie@kernel.org, perex@perex.cz,
tiwai@suse.com, Prabhakar Mahadev Lad, p.zabel@pengutronix.de,
geert+renesas@glider.be, Kuninori Morimoto, Long Luu
Cc: Claudiu.Beznea, dmaengine@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org,
linux-renesas-soc@vger.kernel.org, Claudiu Beznea,
stable@vger.kernel.org, Frank Li, John Madieu
Hi Claudiu,
> -----Original Message-----
> From: Claudiu Beznea <claudiu.beznea@kernel.org>
> Sent: 26 May 2026 09:47
> Subject: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
>
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Once the interrupt is requested, the interrupt handler may run immediately.
Do you mean spurious interrupt?
After DMA driver probe only, consumer device can access the DMA handle
right? or am I missing something here?
Cheers,
Biju
Cheers,
Biju
> Since the IRQ handler can access channel->ch_base, which is initialized only after requesting the IRQ,
> this may lead to invalid memory access.
> Likewise, the IRQ thread may access uninitialized data (the ld_free, ld_queue, and ld_active lists),
> which may also lead to issues.
>
> Request the interrupts only after everything is set up. To keep the error path simpler, use
> dmam_alloc_coherent() instead of dma_alloc_coherent().
>
> Fixes: 5000d37042a6 ("dmaengine: sh: Add DMAC driver for RZ/G2L SoC")
> Cc: stable@vger.kernel.org
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 88 +++++++++++++++-------------------------
> 1 file changed, 33 insertions(+), 55 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 625ff29024de..9f206a33dcc6
> 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -981,25 +981,6 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
> channel->index = index;
> channel->mid_rid = -EINVAL;
>
> - /* Request the channel interrupt. */
> - scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index);
> - irq = platform_get_irq_byname(pdev, pdev_irqname);
> - if (irq < 0)
> - return irq;
> -
> - irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u",
> - dev_name(dmac->dev), index);
> - if (!irqname)
> - return -ENOMEM;
> -
> - ret = devm_request_threaded_irq(dmac->dev, irq, rz_dmac_irq_handler,
> - rz_dmac_irq_handler_thread, 0,
> - irqname, channel);
> - if (ret) {
> - dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", irq, ret);
> - return ret;
> - }
> -
> /* Set io base address for each channel */
> if (index < 8) {
> channel->ch_base = dmac->base + CHANNEL_0_7_OFFSET + @@ -1012,9 +993,9 @@ static int
> rz_dmac_chan_probe(struct rz_dmac *dmac,
> }
>
> /* Allocate descriptors */
> - lmdesc = dma_alloc_coherent(&pdev->dev,
> - sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
> - &channel->lmdesc.base_dma, GFP_KERNEL);
> + lmdesc = dmam_alloc_coherent(&pdev->dev,
> + sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
> + &channel->lmdesc.base_dma, GFP_KERNEL);
> if (!lmdesc) {
> dev_err(&pdev->dev, "Can't allocate memory (lmdesc)\n");
> return -ENOMEM;
> @@ -1030,7 +1011,24 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
> INIT_LIST_HEAD(&channel->ld_free);
> INIT_LIST_HEAD(&channel->ld_active);
>
> - return 0;
> + /* Request the channel interrupt. */
> + scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index);
> + irq = platform_get_irq_byname(pdev, pdev_irqname);
> + if (irq < 0)
> + return irq;
> +
> + irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u",
> + dev_name(dmac->dev), index);
> + if (!irqname)
> + return -ENOMEM;
> +
> + ret = devm_request_threaded_irq(dmac->dev, irq, rz_dmac_irq_handler,
> + rz_dmac_irq_handler_thread, 0,
> + irqname, channel);
> + if (ret)
> + dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", irq, ret);
> +
> + return ret;
> }
>
> static void rz_dmac_put_device(void *_dev) @@ -1099,7 +1097,6 @@ static int rz_dmac_probe(struct
> platform_device *pdev)
> const char *irqname = "error";
> struct dma_device *engine;
> struct rz_dmac *dmac;
> - int channel_num;
> int ret;
> int irq;
> u8 i;
> @@ -1132,18 +1129,6 @@ static int rz_dmac_probe(struct platform_device *pdev)
> return PTR_ERR(dmac->ext_base);
> }
>
> - /* Register interrupt handler for error */
> - irq = platform_get_irq_byname_optional(pdev, irqname);
> - if (irq > 0) {
> - ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0,
> - irqname, NULL);
> - if (ret) {
> - dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n",
> - irq, ret);
> - return ret;
> - }
> - }
> -
> /* Initialize the channels. */
> INIT_LIST_HEAD(&dmac->engine.channels);
>
> @@ -1169,6 +1154,18 @@ static int rz_dmac_probe(struct platform_device *pdev)
> goto err;
> }
>
> + /* Register interrupt handler for error */
> + irq = platform_get_irq_byname_optional(pdev, irqname);
> + if (irq > 0) {
> + ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0,
> + irqname, NULL);
> + if (ret) {
> + dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n",
> + irq, ret);
> + goto err;
> + }
> + }
> +
> /* Register the DMAC as a DMA provider for DT. */
> ret = of_dma_controller_register(pdev->dev.of_node, rz_dmac_of_xlate,
> NULL);
> @@ -1210,16 +1207,6 @@ static int rz_dmac_probe(struct platform_device *pdev)
> dma_register_err:
> of_dma_controller_free(pdev->dev.of_node);
> err:
> - channel_num = i ? i - 1 : 0;
> - for (i = 0; i < channel_num; i++) {
> - struct rz_dmac_chan *channel = &dmac->channels[i];
> -
> - dma_free_coherent(&pdev->dev,
> - sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
> - channel->lmdesc.base,
> - channel->lmdesc.base_dma);
> - }
> -
> reset_control_assert(dmac->rstc);
> err_pm_runtime_put:
> pm_runtime_put(&pdev->dev);
> @@ -1232,18 +1219,9 @@ static int rz_dmac_probe(struct platform_device *pdev) static void
> rz_dmac_remove(struct platform_device *pdev) {
> struct rz_dmac *dmac = platform_get_drvdata(pdev);
> - unsigned int i;
>
> dma_async_device_unregister(&dmac->engine);
> of_dma_controller_free(pdev->dev.of_node);
> - for (i = 0; i < dmac->n_channels; i++) {
> - struct rz_dmac_chan *channel = &dmac->channels[i];
> -
> - dma_free_coherent(&pdev->dev,
> - sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
> - channel->lmdesc.base,
> - channel->lmdesc.base_dma);
> - }
> reset_control_assert(dmac->rstc);
> pm_runtime_put(&pdev->dev);
> pm_runtime_disable(&pdev->dev);
> --
> 2.43.0
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 02/18] dmaengine: sh: rz-dmac: Fix incorrect NULL check for list_first_entry()
2026-05-26 8:46 ` [PATCH v6 02/18] dmaengine: sh: rz-dmac: Fix incorrect NULL check for list_first_entry() Claudiu Beznea
@ 2026-05-26 9:03 ` sashiko-bot
2026-05-28 13:45 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:03 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: dmaengine, vkoul, Frank.Li, wsa+renesas
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Premature early return in `rz_dmac_chan_get_residue()` incorrectly reports pending transfers as complete.
--
commit fea7825b62a6663b4eee5a60ada5a7e2123e0506
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
dmaengine: sh: rz-dmac: Fix incorrect NULL check for list_first_entry()
This commit replaces list_first_entry() with list_first_entry_or_null() in
rz_dmac_chan_get_residue() to fix an invalid NULL check. It addresses the fact
that list_first_entry() does not return NULL when a list is empty.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 9f206a33dcc6b..6d80cb668957f 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -723,8 +723,8 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
> u32 crla, crtb, i;
>
> /* Get current processing virtual descriptor */
> - current_desc = list_first_entry(&channel->ld_active,
> - struct rz_dmac_desc, node);
> + current_desc = list_first_entry_or_null(&channel->ld_active,
> + struct rz_dmac_desc, node);
> if (!current_desc)
> return 0;
[Severity: High]
Does returning 0 here prematurely report pending transfers as complete?
When ld_active is empty, such as for a transfer that has been prepared and
queued but not yet started by the hardware, list_first_entry_or_null()
returns NULL, causing an immediate return of 0.
Before this patch, list_first_entry() on an empty list evaluated to a non-NULL
pointer, allowing the code to bypass this early return and successfully fall
back to checking ld_queue to find the pending descriptor.
By returning 0 early here, rz_dmac_tx_status() will override the transfer
status to DMA_COMPLETE since the residue is 0. Will this cause a DMA client
to falsely assume memory has been transferred before the hardware begins the
operation?
Note that this issue appears to be fixed later in the series by commit
d09437d0a9b5a ("dmaengine: sh: rz-dmac: Use virt-dma APIs for channel
descriptor processing"), which refactors descriptor tracking.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=2
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 11/18] dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with CHCTRL_SETEN
2026-05-26 8:47 ` [PATCH v6 11/18] dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with CHCTRL_SETEN Claudiu Beznea
@ 2026-05-26 9:11 ` sashiko-bot
2026-05-28 13:50 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:11 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: Frank.Li, dmaengine, vkoul, wsa+renesas
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The scatter-gather length (sg_len) is not validated against the hardware descriptor ring buffer size (DMAC_NR_LMDESC), leading to ring buffer self-overwriting and data corruption.
--
commit b5b7f66ce5e55b5fb6774aa2e02322dedba11acd
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with CHCTRL_SETEN
This commit stops updating channel->chctrl with CHCTRL_SETEN during
descriptor preparation. The bit is already explicitly set when enabling
the hardware, and removing it here prevents conflicts with the upcoming
cyclic DMA suspend to RAM configuration sequence.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 557364443a5fd..c9c00650ddd5d 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
[ ... ]
> @@ -428,7 +428,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
>
> rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid);
>
> - channel->chctrl = CHCTRL_SETEN;
> + channel->chctrl = 0;
[Severity: High]
This is a pre-existing issue, but while looking at this function, does the
driver validate sg_len against the hardware descriptor capacity?
Looking at the earlier loop in rz_dmac_prepare_descs_for_slave_sg():
for (i = 0, sg = sgl; i < sg_len; i++, sg = sg_next(sg)) {
...
if (++lmdesc >= (channel->lmdesc.base + DMAC_NR_LMDESC))
lmdesc = channel->lmdesc.base;
}
If a client submits a scatterlist with more than DMAC_NR_LMDESC (64)
elements, the lmdesc pointer wraps around and overwrites the initial
descriptors of the current transfer.
Since desc->start_lmdesc points to the beginning of the buffer, could
this cause the hardware to execute the corrupted descriptor (containing
the 65th segment's data) and skip the first 64 segments?
If CHCFG_DEM is cleared in the overwritten descriptor, could this lead
to silent data corruption by prematurely terminating the transfer while
the driver still marks it as completed?
I noticed that subsequent patches in this series (like the one adding
cyclic DMA support) correctly add a length check for cyclic transfers,
but it appears slave SG lists remain unchecked.
> }
>
> static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=11
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 04/18] dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw()
2026-05-26 8:46 ` [PATCH v6 04/18] dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw() Claudiu Beznea
@ 2026-05-26 9:15 ` sashiko-bot
2026-05-28 13:46 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:15 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: Frank.Li, vkoul, dmaengine, wsa+renesas
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The IRQ handler is managed via `devm_request_threaded_irq` but not explicitly disabled before suspending `pm_runtime` and asserting reset in `rz_dmac_remove`.
--
commit e012424755ae0df83a2b4aaef3c18ec6973ad51c
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw()
This commit replaces an open-coded register write with a call to
rz_dmac_disable_hw() to unify the code. It also moves the hardware disable
sequence after vchan_init() during channel probe.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 1717b407ab9e9..40ddf534c0948 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
[ ... ]
> @@ -1000,15 +1000,15 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
> }
> rz_lmdesc_setup(channel, lmdesc);
>
> - /* Initialize register for each channel */
> - rz_dmac_ch_writel(channel, CHCTRL_DEFAULT, CHCTRL, 1);
> -
> channel->vc.desc_free = rz_dmac_virt_desc_free;
> vchan_init(&channel->vc, &dmac->engine);
> INIT_LIST_HEAD(&channel->ld_queue);
> INIT_LIST_HEAD(&channel->ld_free);
> INIT_LIST_HEAD(&channel->ld_active);
>
> + /* Initialize register for each channel */
> + rz_dmac_disable_hw(channel);
> +
> /* Request the channel interrupt. */
> scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index);
> irq = platform_get_irq_byname(pdev, pdev_irqname);
[Severity: High]
This is a pre-existing issue, but since we are looking at the channel
initialization here, could the use of devres for the IRQ handler lead to a
crash during module removal?
In rz_dmac_remove(), the driver asserts the hardware reset via
reset_control_assert() and disables device power via pm_runtime_put() and
pm_runtime_disable(). Because the interrupts for the DMA channels are managed
using devm_request_threaded_irq() just below this block, they are not freed
and synchronized until the devres cleanup runs after rz_dmac_remove() returns.
If an interrupt is already in flight on another CPU or pending in the
interrupt controller just before the reset is asserted, would the interrupt
handler execute and attempt to read device registers?
Accessing registers of a device that is in reset or powered down might cause a
synchronous external abort, leading to a kernel panic during module unbind.
Should the driver explicitly free or disable the IRQs before turning off the
hardware in the remove path?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=4
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
2026-05-26 8:46 ` [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up Claudiu Beznea
2026-05-26 8:54 ` Biju Das
@ 2026-05-26 9:20 ` sashiko-bot
2026-05-28 13:44 ` Tommaso Merciai
2 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:20 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: dmaengine, vkoul, Frank.Li, wsa+renesas
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] Incorrect use of asynchronous pm_runtime_put() immediately followed by pm_runtime_disable() in the removal and error paths.
- [High] Pending vchan tasklets are not killed or synchronized during device removal or probe failure, leading to a potential Use-After-Free.
- [High] The DMAERR error interrupt handler unconditionally returns IRQ_HANDLED without acknowledging or clearing the hardware error status.
- [High] Devres-managed IRQs remain active while the hardware is reset and suspended during teardown, risking synchronous bus faults.
--
commit de8a17ea38f0c9f4203f815d7c5b435480aba456
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
This patch moves the channel interrupt request to the end of the channel
probe, and uses dmam_alloc_coherent() to simplify the error paths. This
fixes a potential issue where the interrupt handler might run before the
channel base address is initialized.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 625ff29024de0..9f206a33dcc6b 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
[ ... ]
> @@ -1169,6 +1154,18 @@ static int rz_dmac_probe(struct platform_device *pdev)
> goto err;
> }
>
> + /* Register interrupt handler for error */
> + irq = platform_get_irq_byname_optional(pdev, irqname);
> + if (irq > 0) {
> + ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0,
> + irqname, NULL);
[Severity: High]
This isn't a bug introduced by this patch, but does this error interrupt
handler cause an interrupt storm?
The global error interrupt is requested with dev_id set to NULL. When the
error interrupt triggers, rz_dmac_irq_handler() receives this NULL pointer,
bypasses the channel handling logic, and unconditionally returns IRQ_HANDLED
without interacting with any hardware registers.
Returning IRQ_HANDLED defeats the kernel's spurious interrupt detector, and
because SoC error interrupts are typically level-triggered, failing to clear
the source condition at the hardware level can cause an unrecoverable
interrupt storm that locks up the CPU.
> + if (ret) {
> + dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n",
> + irq, ret);
> + goto err;
> + }
> + }
> +
[ ... ]
> @@ -1210,16 +1207,6 @@ static int rz_dmac_probe(struct platform_device *pdev)
> dma_register_err:
> of_dma_controller_free(pdev->dev.of_node);
> err:
> reset_control_assert(dmac->rstc);
> err_pm_runtime_put:
> pm_runtime_put(&pdev->dev);
> pm_runtime_disable(&pdev->dev);
[Severity: High]
This is a pre-existing issue, but does using the asynchronous
pm_runtime_put() here prevent the device from entering a lower power state?
Calling pm_runtime_put() immediately followed by pm_runtime_disable()
cancels the pending suspend operation via __pm_runtime_barrier(). This
leaves the device permanently in an active (RPM_ACTIVE) power state after a
probe failure. Could this use the synchronous pm_runtime_put_sync() instead
to ensure the hardware is properly suspended before teardown?
[Severity: High]
This is a pre-existing issue, but could there be a use-after-free here if a
tasklet is still pending?
The driver initializes a tasklet for each channel via vchan_init(), yet
never calls tasklet_kill() in the probe error path or rz_dmac_remove().
Because the dmac structure is managed by devres, its memory is freed shortly
after remove or probe failure returns. If a pending interrupt schedules the
tasklet during teardown, the tasklet will execute after its backing memory
has been freed.
[ ... ]
> @@ -1232,18 +1219,9 @@ static int rz_dmac_probe(struct platform_device *pdev)
> static void rz_dmac_remove(struct platform_device *pdev)
> {
> struct rz_dmac *dmac = platform_get_drvdata(pdev);
>
> dma_async_device_unregister(&dmac->engine);
> of_dma_controller_free(pdev->dev.of_node);
> reset_control_assert(dmac->rstc);
> pm_runtime_put(&pdev->dev);
> pm_runtime_disable(&pdev->dev);
[Severity: High]
This isn't a bug introduced by this patch, but does this teardown sequence
risk synchronous bus faults?
The driver synchronously asserts hardware reset (reset_control_assert) and
initiates suspend before devres unregisters the IRQs. If a pending or
spurious interrupt fires during this window, the handler will attempt to
read the CHSTAT register while the IP is in reset or unclocked.
Since the IRQ handler lacks a pm_runtime_get_if_active() check, accessing
unclocked or reset hardware registers could trigger a synchronous external
abort or bus fault, leading to a kernel panic.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=1
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 10/18] dmaengine: sh: rz-dmac: Refactor pause/resume code
2026-05-26 8:47 ` [PATCH v6 10/18] dmaengine: sh: rz-dmac: Refactor pause/resume code Claudiu Beznea
@ 2026-05-26 9:28 ` sashiko-bot
2026-05-28 13:50 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:28 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: dmaengine, wsa+renesas, Frank.Li, vkoul
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Software status tracking becomes inconsistent if hardware pause/resume operations time out.
- [High] Unconditional hardware resume ignores other active pause requests.
--
commit 9f2ce07d8501ca3cb4ac77194345402583e5ba4b
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
dmaengine: sh: rz-dmac: Refactor pause/resume code
This refactors the pause/resume support in the rz-dmac driver.
It introduces a bitmask-based status field to allow reusing the pause
and resume code for system suspend/resume and cyclic DMA operations.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 1f884ec101f80..557364443a5fd 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
[ ... ]
> @@ -795,35 +811,70 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
[ ... ]
> +static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel,
> + unsigned long clear_bitmask)
> +{
> + int ret = 0;
> + u32 val;
> +
> + lockdep_assert_held(&channel->vc.lock);
> +
> + /* Do not check CHSTAT_SUS but rely on HW capabilities. */
> +
> + rz_dmac_ch_writel(channel, CHCTRL_CLRSUS, CHCTRL, 1);
[Severity: High]
Does this unconditional hardware resume ignore other active pause requests?
The bitmask-based status tracking appears to be designed to handle multiple
independent pause conditions. However, writing CHCTRL_CLRSUS happens
regardless of the remaining state.
If a channel is paused for multiple reasons, clearing one condition will
immediately unpause the hardware while other constraints are still active.
Could this regression lead to unexpected DMA execution and data corruption?
> + ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val,
> + !(val & CHSTAT_SUS), 1, 1024, false,
> + channel, CHSTAT, 1);
> +
> + channel->status &= ~clear_bitmask;
[Severity: High]
Should channel->status be cleared if the hardware resume operation times out?
If read_poll_timeout_atomic returns an error like -ETIMEDOUT, the status
bitmask is still unconditionally cleared.
If a caller later attempts to retry the resume operation, it will exit early
because the status bit is already gone, leaving the hardware permanently
suspended while the software stack thinks it is active. Does this create a
regression where the channel can no longer be resumed?
> +
> + return ret;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=10
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 09/18] dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor processing
2026-05-26 8:47 ` [PATCH v6 09/18] dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor processing Claudiu Beznea
@ 2026-05-26 9:28 ` sashiko-bot
2026-05-28 13:49 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:28 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: dmaengine, Frank.Li, wsa+renesas, vkoul
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Missing cleanup of the active descriptor (`channel->desc`) in `rz_dmac_free_chan_resources` causes permanent memory leaks and channel breakage.
- [High] Premature tasklet synchronization in `rz_dmac_device_synchronize` violates DMA synchronization guarantees by allowing callbacks to execute after the function returns.
--
commit d09437d0a9b5a01f2fe83f477e60abbcf577d287
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor processing
This commit updates the driver to fully rely on the virt-dma APIs for
descriptor management. It replaces custom active and queue lists with a
single channel->desc pointer to track the current descriptor and relies
on virt-dma to handle the rest.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 217657513fa76..1f884ec101f80 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
[ ... ]
> @@ -494,8 +485,6 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
> rz_lmdesc_setup(channel, channel->lmdesc.base);
>
> rz_dmac_disable_hw(channel);
> - list_splice_tail_init(&channel->ld_active, &channel->ld_free);
> - list_splice_tail_init(&channel->ld_queue, &channel->ld_free);
>
> if (channel->mid_rid >= 0) {
> clear_bit(channel->mid_rid, dmac->modules);
> @@ -504,13 +493,19 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
>
> spin_unlock_irqrestore(&channel->vc.lock, flags);
>
> + vchan_free_chan_resources(&channel->vc);
> +
> + spin_lock_irqsave(&channel->vc.lock, flags);
> +
> list_for_each_entry_safe(desc, _desc, &channel->ld_free, node) {
> + list_del(&desc->node);
> kfree(desc);
> channel->descs_allocated--;
> }
>
> INIT_LIST_HEAD(&channel->ld_free);
> - vchan_free_chan_resources(&channel->vc);
> +
> + spin_unlock_irqrestore(&channel->vc.lock, flags);
> }
[Severity: High]
Does this code leak the active descriptor? If channel->desc is currently set,
vchan_free_chan_resources only clears virt-dma's internal lists, leaving
the memory allocated for channel->desc dangling.
Could this also cause the channel to be permanently broken for future clients?
If channel->desc remains non-NULL, a subsequent request for the channel
might cause rz_dmac_issue_pending to see channel->desc != NULL and
silently refuse to start new transfers. Unlike rz_dmac_terminate_all,
this function does not appear to terminate and clear channel->desc.
[ ... ]
> @@ -692,6 +681,8 @@ static void rz_dmac_device_synchronize(struct dma_chan *chan)
> u32 chstat;
> int ret;
>
> + vchan_synchronize(&channel->vc);
> +
> ret = read_poll_timeout(rz_dmac_ch_readl, chstat, !(chstat & CHSTAT_EN),
> 100, 100000, false, channel, CHSTAT, 1);
> if (ret < 0)
[Severity: High]
Does calling vchan_synchronize here allow client completion callbacks to
run after this function returns?
If the hardware is still actively transferring, vchan_synchronize will
return immediately. Then, the polling loop waits for the hardware to stop.
Once the hardware finishes, it raises an interrupt, and the IRQ handler
calls vchan_cookie_complete, which schedules the virt-dma tasklet again.
This seems like it could expose clients to use-after-free bugs if they rely
on the DMA synchronization guarantee to safely free DMA buffers after
calling dmaengine_synchronize.
Should vchan_synchronize be called after the hardware polling loop finishes?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=9
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 12/18] dmaengine: sh: rz-dmac: Add cyclic DMA support
2026-05-26 8:47 ` [PATCH v6 12/18] dmaengine: sh: rz-dmac: Add cyclic DMA support Claudiu Beznea
@ 2026-05-26 9:31 ` sashiko-bot
2026-05-28 13:51 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:31 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: dmaengine, Frank.Li, wsa+renesas, vkoul
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Using a global channel status flag to track cyclic transfer state causes race conditions, incorrect handling of concurrent transfers, and state leaks.
- [High] The calculation of residue for cyclic transfers evaluates to erratic, mathematically incorrect values.
--
commit 0cfb7df401a4633fd8fdbdc75fec6ad11e0803e3
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
dmaengine: sh: rz-dmac: Add cyclic DMA support
Add cyclic DMA support to the RZ DMAC driver. A per-channel status bit is
introduced to mark cyclic channels and is set during the DMA prepare
callback. The IRQ handler checks this status bit and calls
vchan_cyclic_callback() accordingly.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index c9c00650ddd5d..8fd8a4bd9cc90 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
[ ... ]
> @@ -733,9 +841,18 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
> }
>
> /* Calculate residue from next lmdesc to end of virtual desc */
> - while (lmdesc->chcfg & CHCFG_DEM) {
> - residue += lmdesc->tb;
> - lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
> + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) {
> + u32 start_lmdesc_addr = rz_dmac_lmdesc_addr(channel, desc->start_lmdesc);
> +
> + while (lmdesc->nxla != start_lmdesc_addr) {
> + residue += lmdesc->tb;
> + lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
> + }
[Severity: High]
Does this loop calculate the remaining bytes correctly for cyclic
transfers?
The earlier loop leaves lmdesc pointing to the descriptor preceding the
currently executing one. If the loop here sums lmdesc->tb from this
preceding descriptor up to (but excluding) the last descriptor, won't the
returned residue values be cyclically shifted by one period?
For example, in a 3-period buffer (A, B, C), if the hardware is executing
period B, summing the transfer bytes of A and B would return
crtb + 2 * period_len instead of the correct crtb + period_len. Could this
cause audio cyclic pointers to jump erratically backwards and forwards?
> + } else {
> + while (lmdesc->chcfg & CHCFG_DEM) {
> + residue += lmdesc->tb;
> + lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
> + }
> }
[ ... ]
> @@ -928,10 +1045,14 @@ static irqreturn_t rz_dmac_irq_handler_thread(int irq, void *dev_id)
> if (!desc)
> return IRQ_HANDLED;
>
> - vchan_cookie_complete(&desc->vd);
> - channel->desc = NULL;
> + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) {
> + vchan_cyclic_callback(&desc->vd);
[Severity: High]
Should this check desc->type == RZ_DMAC_DESC_CYCLIC instead of the global
channel status?
If a cyclic transfer is prepared while a non-cyclic transfer is currently
executing, the global channel status will be updated immediately. When the
active non-cyclic transfer finishes, won't this logic incorrectly call
vchan_cyclic_callback() for the non-cyclic descriptor?
This could skip vchan_cookie_complete() and rz_dmac_xfer_desc(), which
would leak the descriptor and stall the DMA channel.
> + } else {
> + vchan_cookie_complete(&desc->vd);
> + channel->desc = NULL;
>
> - rz_dmac_xfer_desc(channel);
> + rz_dmac_xfer_desc(channel);
> + }
>
> return IRQ_HANDLED;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=12
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 06/18] dmaengine: sh: rz-dmac: Save the start LM descriptor
2026-05-26 8:46 ` [PATCH v6 06/18] dmaengine: sh: rz-dmac: Save the start LM descriptor Claudiu Beznea
@ 2026-05-26 9:41 ` sashiko-bot
2026-05-28 13:47 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:41 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: Frank.Li, vkoul, dmaengine, wsa+renesas
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] The newly introduced `start_lmdesc` field can be dereferenced while `NULL` in `rz_dmac_calculate_residue_bytes_in_vd()`.
Pre-existing issues:
- [High] Multiple MEMCPY transfers overwrite each other's hardware descriptors causing data corruption.
- [High] Missing `dma_wmb()` memory barrier before setting the Link Valid (`HEADER_LV`) bit in DMA descriptors.
- [High] Silent ring buffer overflow when preparing slave SG descriptors if the scatterlist exceeds 64 entries.
--
commit 1282aed7089327e34eb2eeee4b69973599e70406
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
dmaengine: sh: rz-dmac: Save the start LM descriptor
This commit saves the start LM descriptor in the channel's descriptor
structure to avoid unnecessary iterations from the beginning of the list
when calculating residue bytes.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index c48858b68dee6..d3926ecd63ac1 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
[ ... ]
> @@ -343,6 +344,8 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel)
> struct rz_dmac_desc *d = channel->desc;
> u32 chcfg = CHCFG_MEM_COPY;
>
> + d->start_lmdesc = lmdesc;
> +
> /* prepare descriptor */
> lmdesc->sa = d->src;
> lmdesc->da = d->dest;
[Severity: High]
This isn't a bug introduced by this patch, but does
rz_dmac_prepare_desc_for_memcpy() overwrite hardware descriptors of an
already active memcpy transfer?
The transfer is configured at channel->lmdesc.tail, but the tail pointer is
never advanced (unlike in the slave_sg path). If rz_dmac_issue_pending()
is called while a memcpy transfer is already active, the new transfer is
prepared into the exact same channel->lmdesc.tail slot currently being read
by the active DMA hardware.
Can this cause memory corruption and unpredictable hardware behavior?
I note this is fixed later in the series in commit d09437d0a9b5a
("dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor
processing"), which adds a !channel->desc check.
[ ... ]
> @@ -377,6 +380,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
> }
>
> lmdesc = channel->lmdesc.tail;
> + d->start_lmdesc = lmdesc;
>
> for (i = 0, sg = sgl; i < sg_len; i++, sg = sg_next(sg)) {
> if (d->direction == DMA_DEV_TO_MEM) {
[Severity: High]
This isn't a bug introduced by this patch, but does this loop lack a
bounds check for sg_len?
The driver iterates over sg_len without checking if it exceeds the hardware
ring buffer size (DMAC_NR_LMDESC, which is 64).
If a client submits a scatterlist with more than 64 entries, the loop will
wrap around and overwrite the beginning of the very same transfer it is
currently preparing.
Could this result in a corrupted DMA ring buffer and an infinite hardware
loop?
[Severity: High]
This is a pre-existing issue, but does setting the Link Valid (HEADER_LV)
bit require a dma_wmb() barrier?
The DMA descriptors (lmdesc) are allocated in coherent memory. The driver
writes the payload (sa, da, tb, chcfg) and then sets the HEADER_LV bit to
indicate to the hardware that the descriptor is ready:
rz_dmac_prepare_descs_for_slave_sg() {
...
lmdesc->tb = sg_dma_len(sg);
lmdesc->chitvl = 0;
lmdesc->chext = 0;
if (i == (sg_len - 1)) {
lmdesc->chcfg = (channel->chcfg & ~CHCFG_DEM);
lmdesc->header = HEADER_LV;
...
}
Because writes to Normal Non-Cacheable memory can be reordered by the CPU
write buffer, is a dma_wmb() barrier strictly required before setting
lmdesc->header = HEADER_LV?
Without it, if the hardware is executing the chain and polling for the
next descriptor, it might see the HEADER_LV bit set before the payload
writes have completed.
[ ... ]
> @@ -693,9 +697,10 @@ rz_dmac_get_next_lmdesc(struct rz_lmdesc *base, struct rz_lmdesc *lmdesc)
> return next;
> }
>
> -static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel, u32 crla)
> +static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
> + struct rz_dmac_desc *desc, u32 crla)
> {
> - struct rz_lmdesc *lmdesc = channel->lmdesc.head;
> + struct rz_lmdesc *lmdesc = desc->start_lmdesc;
[Severity: High]
Can desc->start_lmdesc be NULL here and cause a kernel panic when
accessing lmdesc->nxla?
Because struct rz_dmac_desc is allocated via kzalloc_obj(), start_lmdesc
is initially NULL and only assigned when the descriptor is prepared by the
hardware.
Due to the driver's internal queue management, if a descriptor is prepared
but not yet submitted, an unrelated IRQ or issue_pending call can move it
from ld_queue to ld_active without processing it.
If rz_dmac_chan_get_residue() is then called and matches this descriptor's
cookie, it will unconditionally dereference the NULL pointer.
I note this is addressed later in the series by commit d09437d0a9b5a
("dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor
processing"), which completely removes ld_queue and ld_active.
> struct dma_chan *chan = &channel->vc.chan;
> struct rz_dmac *dmac = to_rz_dmac(chan->device);
> u32 residue = 0, i = 0;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=6
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 18/18] dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last descriptor
2026-05-26 8:47 ` [PATCH v6 18/18] dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last descriptor Claudiu Beznea
@ 2026-05-26 9:43 ` sashiko-bot
2026-05-28 14:39 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:43 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: vkoul, Frank.Li, dmaengine, wsa+renesas
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing validation for `sg_len` in `rz_dmac_prep_slave_sg` and `periods` in `rz_dmac_prep_dma_cyclic` leads to either an infinite loop (kernel hard lockup) or descriptor ring buffer overflow and silent data corruption.
- [High] The DMA residue calculation logic in `rz_dmac_calculate_residue_bytes_in_vd` is broken due to incorrect descriptor matching and off-by-one errors, resulting in bogus residue reports.
--
commit 1936bc22a913ae590a18e0828fe08cbdd4e5a588
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last descriptor
This patch sets the Link End (LE) bit on the last descriptor of a transfer
to inform the DMA engine that it is the final descriptor. This prevents the
DMA engine from loading an invalid descriptor and raising a descriptor error.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 2a7124e4aea3a..f1174d25da849 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
[ ... ]
> @@ -425,7 +426,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
> lmdesc->chext = 0;
> if (i == (sg_len - 1)) {
> lmdesc->chcfg = (channel->chcfg & ~CHCFG_DEM);
[Severity: High]
This is a pre-existing issue, but is the DMA residue calculation broken?
The logic to calculate remaining bytes in
rz_dmac_calculate_residue_bytes_in_vd() appears to have several issues:
1) The search loop while (lmdesc->nxla != crla) erroneously matches the next
link address against the current link address (crla), causing lmdesc to
incorrectly resolve to the descriptor preceding the currently executing one.
2) The subsequent sum adds the total bytes (lmdesc->tb) of this
already-completed preceding descriptor, and then double-counts the current
descriptor (whose remaining bytes are already added via crtb).
3) Because CHCFG_DEM is intentionally cleared on the final descriptor here,
the accumulation loop while (lmdesc->chcfg & CHCFG_DEM) terminates early
and fails to include the byte count of the last descriptor.
Could this impact subsystems like ALSA audio and serial UART that rely on
precise DMA residue?
> - lmdesc->header = HEADER_LV;
> + lmdesc->header = HEADER_LV | HEADER_LE;
[Severity: High]
This is a pre-existing issue, but does the lack of validation for sg_len and
periods cause problems?
If dma_map_sg() returns 0 and the client passes sg_len == 0, the
initialization loop is skipped entirely, leaving all descriptor headers as 0.
When rz_dmac_enable_hw() is called, rz_dmac_lmdesc_recycle() enters an
infinite loop because no descriptor has the HEADER_LV bit set:
while (!(lmdesc->header & HEADER_LV))
This could cause a kernel hard lockup with spinlocks held.
Additionally, if a client submits a transfer with more than 64 segments
(sg_len or periods > DMAC_NR_LMDESC), the fixed-size ring buffer overflows
and wraps around.
Because the last segment will overwrite the beginning of the transfer
with the HEADER_LE bit added by this patch, the hardware will only execute
the corrupted tail of the scatterlist and immediately stop.
Could this lead to silent data corruption?
> } else {
> lmdesc->chcfg = channel->chcfg;
> lmdesc->header = HEADER_LV;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=18
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 15/18] dmaengine: sh: rz-dmac: Add suspend to RAM support
2026-05-26 8:47 ` [PATCH v6 15/18] dmaengine: sh: rz-dmac: Add suspend to RAM support Claudiu Beznea
@ 2026-05-26 9:43 ` sashiko-bot
2026-05-28 14:38 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:43 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: vkoul, wsa+renesas, Frank.Li, dmaengine
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] The error recovery path in `rz_dmac_suspend()` fails to restore essential hardware registers before re-enabling cyclic channels.
- [High] Error paths in system suspend and resume callbacks fail to properly manage the runtime PM usage counter, causing an imbalance that leads to underflows.
- [High] The IRQ handler accesses hardware registers without verifying the device power state, risking a crash if an interrupt fires during or after system suspend.
- [Medium] The `rz_dmac_suspend()` function incorrectly mixes scope-based `guard()` locking with `goto`-based error handling.
--
commit 2944b998cf54bad86273dc3ee963623d49e8fffa
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
dmaengine: sh: rz-dmac: Add suspend to RAM support
This patch adds suspend to RAM support for the Renesas RZ/G3S SoC DMA IP to
save and restore DMA IP registers. It adds specific handling for cyclic
channels by pausing and resuming them during system suspend/resume.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index bd4ca8e939f17..2a7124e4aea3a 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
[ ... ]
> @@ -1421,6 +1474,122 @@ static void rz_dmac_remove(struct platform_device *pdev)
[ ... ]
> +static void rz_dmac_suspend_recover(struct rz_dmac *dmac)
> +{
> + int ret;
> +
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret)
> + return;
> +
> + for (unsigned int i = 0; i < dmac->n_channels; i++) {
> + struct rz_dmac_chan *channel = &dmac->channels[i];
> +
> + guard(spinlock_irqsave)(&channel->vc.lock);
> +
> + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
> + continue;
> +
> + rz_dmac_device_resume_internal(channel);
[Severity: Critical]
Does this error recovery path fail to restore essential hardware registers
before re-enabling cyclic channels?
If pm_runtime_put_sync() fails in rz_dmac_suspend(), the driver jumps to
reset_deassert, calls reset_control_deassert(), and then executes this
function. However, the prior reset_control_assert() wiped the hardware state.
This function simply calls rz_dmac_device_resume_internal(), which writes
CHCTRL_SETEN to start the channel. Since essential configuration registers
like NXLA, CHCFG, and DCTRL are never restored, the hardware could attempt to
execute DMA transfers with zeroed configurations, potentially causing wild
memory accesses or silent data corruption.
> + }
> +}
> +
> +static int rz_dmac_suspend(struct device *dev)
> +{
> + struct rz_dmac *dmac = dev_get_drvdata(dev);
> + int ret = 0;
> +
> + for (unsigned int i = 0; i < dmac->n_channels; i++) {
> + struct rz_dmac_chan *channel = &dmac->channels[i];
> +
> + guard(spinlock_irqsave)(&channel->vc.lock);
[Severity: Medium]
Does this function mix guard() based locking with goto based error handling?
According to the subsystem cleanup guidelines, a single function must never
mix goto statements with __free() or guard() declarations. This function uses
guard(spinlock_irqsave)(&channel->vc.lock) inside the loop, while
goto suspend_recover and goto reset_deassert are used for error handling
outside the loop.
> +
> + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
> + continue;
> +
> + ret = rz_dmac_device_pause_internal(channel);
> + if (ret) {
> + dev_err(dev, "Failed to suspend channel %s\n",
> + dma_chan_name(&channel->vc.chan));
> + break;
> + }
> +
> + channel->pm_state.nxla = rz_dmac_ch_readl(channel, NXLA, 1);
> + }
> +
> + if (ret)
> + goto suspend_recover;
> +
> + ret = reset_control_assert(dmac->rstc);
[Severity: High]
Does this logic risk a regression if an interrupt fires during or after system
suspend?
This driver implements system suspend using SIMPLE_DEV_PM_OPS, so this
function runs during the normal suspend phase where device interrupts are
still enabled. This function asserts the hardware reset and powers down the
DMA controller.
If a delayed interrupt fires concurrently on another CPU,
rz_dmac_irq_handle_channel() will execute and directly read the CHSTAT
register. Since the driver neither disables the IRQ, calls synchronize_irq(),
nor verifies the power state via pm_runtime_get_if_active(), the handler will
read from powered-off or reset hardware.
> + if (ret)
> + goto suspend_recover;
> +
> + ret = pm_runtime_put_sync(dev);
> + if (ret < 0)
> + goto reset_deassert;
[Severity: High]
Does this error path fail to properly manage the runtime PM usage counter?
pm_runtime_put_sync() unconditionally decrements the usage counter. If it
fails, the function jumps to reset_deassert and aborts system suspend without
restoring the usage counter via pm_runtime_get_noresume().
Because the system suspend is aborted, the resume callback will be skipped,
leaving the usage counter permanently decremented. Subsequent suspends could
underflow the counter and break runtime PM functionality.
> +
> + return 0;
> +
> +reset_deassert:
> + reset_control_deassert(dmac->rstc);
> +suspend_recover:
> + rz_dmac_suspend_recover(dmac);
> + return ret;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=15
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
2026-05-26 8:54 ` Biju Das
@ 2026-05-26 9:45 ` Claudiu Beznea
2026-05-26 9:51 ` Biju Das
0 siblings, 1 reply; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 9:45 UTC (permalink / raw)
To: Biju Das, vkoul@kernel.org, Frank.Li@kernel.org,
lgirdwood@gmail.com, broonie@kernel.org, perex@perex.cz,
tiwai@suse.com, Prabhakar Mahadev Lad, p.zabel@pengutronix.de,
geert+renesas@glider.be, Kuninori Morimoto, Long Luu
Cc: Claudiu.Beznea, dmaengine@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org,
linux-renesas-soc@vger.kernel.org, Claudiu Beznea,
stable@vger.kernel.org, Frank Li, John Madieu
On 5/26/26 11:54, Biju Das wrote:
> Hi Claudiu,
>
>> -----Original Message-----
>> From: Claudiu Beznea <claudiu.beznea@kernel.org>
>> Sent: 26 May 2026 09:47
>> Subject: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
>>
>> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>>
>> Once the interrupt is requested, the interrupt handler may run immediately.
>
> Do you mean spurious interrupt?
>
> After DMA driver probe only, consumer device can access the DMA handle
> right? or am I missing something here?
In theory there could be pending interrupts not yet served (e.g. due to the
previous usage of the controller, HW behavior, etc). Those could trigger the
execution of the IRQ handler once the interrupt is requested.
--
Thank you,
Claudiu
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 16/18] ASoC: renesas: rz-ssi: Add pause support
2026-05-26 8:47 ` [PATCH v6 16/18] ASoC: renesas: rz-ssi: Add pause support Claudiu Beznea
@ 2026-05-26 9:46 ` sashiko-bot
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:46 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: wsa+renesas, vkoul, dmaengine, Frank.Li
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The pause/resume implementation terminates the DMA channel and recalculates pointers to period boundaries, violating ALSA's lossless pause semantics and causing permanent audio data loss.
- [High] In full-duplex mode, PAUSE_PUSH fails to pause the hardware or DMA, leading to hardware starvation, auto-restart loops, and DMA queue explosion on PAUSE_RELEASE.
--
commit 7d75d5203513e23a4244ea8798db8bc41af3f70c
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
ASoC: renesas: rz-ssi: Add pause support
This commit adds ALSA pause support (PAUSE_PUSH and PAUSE_RELEASE) to the
rz-ssi driver as a preparatory step for switching to PCM dmaengine APIs.
> diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c
> index 71e434cfe07b9..d4e1dded3a9c1 100644
> --- a/sound/soc/renesas/rz-ssi.c
> +++ b/sound/soc/renesas/rz-ssi.c
[ ... ]
> @@ -888,6 +889,7 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
> break;
>
> case SNDRV_PCM_TRIGGER_SUSPEND:
> + case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> rz_ssi_stop(ssi, strm);
> break;
[Severity: High]
When PAUSE_PUSH is issued in half-duplex mode, rz_ssi_stop() calls
dmaengine_terminate_async(), which discards all in-flight DMA progress.
On PAUSE_RELEASE, rz_ssi_trigger_resume() recalculates the DMA pointer:
static int rz_ssi_trigger_resume(struct rz_ssi_priv *ssi,
struct rz_ssi_stream *strm)
{
...
strm->dma_buffer_pos = strm->completed_dma_buf_pos +
runtime->period_size;
...
}
Does this effectively snap to the next period boundary and permanently skip
the partially transferred period, violating ALSA's lossless pause semantics?
Additionally, calling dmaengine_terminate_async() without
dmaengine_synchronize() before submitting new descriptors can expose race
conditions. I notice this is fixed later in the series in commit
7b32dcd4a1c5c ("ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs").
[Severity: High]
During full-duplex operation, if PAUSE_PUSH is issued to one stream while
the other runs, rz_ssi_stop() returns early without disabling the hardware
or terminating the DMA channel:
static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
{
strm->running = 0;
if (rz_ssi_is_stream_running(&ssi->playback) ||
rz_ssi_is_stream_running(&ssi->capture))
return 0;
...
}
Because the stream is marked as stopped but the hardware keeps running,
the DMA callbacks will stop queuing new descriptors. Will this starve the
hardware FIFO and trigger an under/overrun interrupt?
If the interrupt handler rz_ssi_interrupt() detects the error, will it
force the stream back into a running state via rz_ssi_start() because
strm->substream is not cleared during a pause?
When PAUSE_RELEASE is subsequently issued, the driver falls through to
SNDRV_PCM_TRIGGER_START and queues 4 additional descriptors onto the
already-running DMA channel. Can this corrupt the state and risk queue
exhaustion?
I see this is also fixed later in the series in commit 7b32dcd4a1c5c
("ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs").
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=16
^ permalink raw reply [flat|nested] 53+ messages in thread
* RE: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
2026-05-26 9:45 ` Claudiu Beznea
@ 2026-05-26 9:51 ` Biju Das
2026-05-26 10:25 ` Claudiu Beznea
0 siblings, 1 reply; 53+ messages in thread
From: Biju Das @ 2026-05-26 9:51 UTC (permalink / raw)
To: Claudiu Beznea, vkoul@kernel.org, Frank.Li@kernel.org,
lgirdwood@gmail.com, broonie@kernel.org, perex@perex.cz,
tiwai@suse.com, Prabhakar Mahadev Lad, p.zabel@pengutronix.de,
geert+renesas@glider.be, Kuninori Morimoto, Long Luu
Cc: Claudiu.Beznea, dmaengine@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org,
linux-renesas-soc@vger.kernel.org, Claudiu Beznea,
stable@vger.kernel.org, Frank Li, John Madieu
Hi Claudiu,
> -----Original Message-----
> From: Claudiu Beznea <claudiu.beznea@kernel.org>
> Sent: 26 May 2026 10:46
> Subject: Re: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set
> up
>
>
>
> On 5/26/26 11:54, Biju Das wrote:
> > Hi Claudiu,
> >
> >> -----Original Message-----
> >> From: Claudiu Beznea <claudiu.beznea@kernel.org>
> >> Sent: 26 May 2026 09:47
> >> Subject: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt
> >> request after everything is set up
> >>
> >> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> >>
> >> Once the interrupt is requested, the interrupt handler may run immediately.
> >
> > Do you mean spurious interrupt?
> >
> > After DMA driver probe only, consumer device can access the DMA handle
> > right? or am I missing something here?
>
> In theory there could be pending interrupts not yet served (e.g. due to the previous usage of the
> controller, HW behavior, etc). Those could trigger the execution of the IRQ handler once the interrupt
> is requested.
You mean DMA consumers configured by bootloader and linux probing the DMA driver can
trigger IRQ?
Cheers,
Biju
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 14/18] dmaengine: sh: rz-dmac: Add runtime PM support
2026-05-26 8:47 ` [PATCH v6 14/18] dmaengine: sh: rz-dmac: Add runtime PM support Claudiu Beznea
@ 2026-05-26 9:57 ` sashiko-bot
2026-05-28 13:52 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 9:57 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: vkoul, Frank.Li, wsa+renesas, dmaengine
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
New issues:
- [High] Early return on PM resume failure in rz_dmac_issue_pending() leaves descriptors in the submitted list, causing incorrect residue reporting.
- [Low] Misplaced exclamation mark in error message formatting (\n!).
Pre-existing issues:
- [High] Asynchronous pm_runtime_put() immediately followed by pm_runtime_disable() leaves hardware in an incorrect state.
- [High] IRQ handlers access hardware registers without verifying that the device is runtime active.
- [High] Race condition between devm-managed IRQs and device removal leads to hardware register access while the device is in reset or suspended.
--
commit 1bdb517f551fc047d57449a7201275740a491091
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Subject: dmaengine: sh: rz-dmac: Add runtime PM support
This patch protects exposed APIs with runtime PM calls before accessing
hardware registers. It also marks the DMA device as pm_runtime_irq_safe()
to support APIs called from atomic context. Error handling paths in
termination and cleanup functions are updated to skip hardware access if
RPM resume fails.
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 93394b9934c89..bd4ca8e939f17 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -549,12 +549,22 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
> struct rz_dmac *dmac = to_rz_dmac(chan->device);
> struct rz_dmac_desc *desc, *_desc;
> unsigned long flags;
> + int ret;
> +
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret) {
> + dev_err(dmac->dev, "RPM resume failed for channel %s, ret=%d\n!",
> + dma_chan_name(chan), ret);
> + }
[Severity: Low]
Does this error message format string place the exclamation mark correctly?
The "\n!" sequence places the exclamation mark at the beginning of the next
line instead of the end of the sentence, breaking the intended log output.
[ ... ]
> @@ -716,13 +737,20 @@ static int rz_dmac_terminate_all(struct dma_chan *chan)
> spin_unlock_irqrestore(&channel->vc.lock, flags);
> vchan_dma_desc_free_list(&channel->vc, &head);
>
> - return 0;
> + return ret;
> }
>
> static void rz_dmac_issue_pending(struct dma_chan *chan)
> {
> struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> + struct rz_dmac *dmac = to_rz_dmac(chan->device);
> unsigned long flags;
> + int ret;
> +
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret)
> + return;
[Severity: High]
If this returns early on failure, does it leave newly submitted descriptors
stuck in the vc.desc_submitted list?
If vchan_issue_pending() is skipped, descriptors are never moved to the
vc.desc_issued list. Later, if a client calls dmaengine_tx_status(),
rz_dmac_chan_get_residue() will use vchan_find_desc() which only checks
the desc_issued list:
drivers/dma/virt-dma.c:vchan_find_desc() {
list_for_each_entry(vd, &vc->desc_issued, node)
if (vd->tx.cookie == cookie)
return vd;
}
This will fail to find the pending descriptor, causing
rz_dmac_chan_get_residue() to incorrectly return 0 for the residue.
This could mislead the client into thinking the transfer has fully completed
while the status remains DMA_IN_PROGRESS. Should vchan_issue_pending()
still be called to update the software state correctly?
[ ... ]
> @@ -1274,6 +1327,7 @@ static int rz_dmac_probe(struct platform_device *pdev)
> return dev_err_probe(&pdev->dev, PTR_ERR(dmac->rstc),
> "failed to get resets\n");
>
> + pm_runtime_irq_safe(&pdev->dev);
[Severity: High]
This is a pre-existing issue, but does adding pm_runtime_irq_safe() expose
the interrupt handlers to accessing powered-down hardware?
The IRQ handler rz_dmac_irq_handle_channel() directly reads hardware
registers like rz_dmac_ch_readl().
If a spurious or delayed interrupt occurs while the device is runtime
suspended (e.g., after a failed system suspend or during driver teardown),
it will access hardware without verifying it is powered, resulting in a
kernel crash.
Should IRQ handlers use pm_runtime_get_if_active() to verify the device is
powered before touching registers?
[Severity: High]
This is also a pre-existing issue, but is there a race condition between
the devm-managed IRQs and device removal?
The driver requests channel IRQs using devm_request_threaded_irq(). During
module removal in rz_dmac_remove(), it asserts the hardware reset and
suspends the device:
drivers/dma/sh/rz-dmac.c:rz_dmac_remove() {
...
reset_control_assert(dmac->rstc);
pm_runtime_put(&pdev->dev);
pm_runtime_disable(&pdev->dev);
}
Because devm-managed resources are freed only after the remove() function
returns, an in-flight interrupt can execute concurrently with or immediately
after the reset assertion and suspension.
The interrupt handler will attempt to read hardware registers while the
controller is held in reset or suspended, leading to a system hang or
synchronous abort.
Should synchronize_irq() be called, or the IRQs manually freed, before
asserting the hardware reset and disabling runtime PM?
[Severity: High]
This isn't a bug introduced by this patch, but does the error handling in
rz_dmac_probe() and rz_dmac_remove() leave the hardware in an incorrect state?
In rz_dmac_probe() error paths and rz_dmac_remove(), the driver calls
pm_runtime_put() immediately followed by pm_runtime_disable():
drivers/dma/sh/rz-dmac.c:rz_dmac_probe() {
...
err_pm_runtime_put:
pm_runtime_put(&pdev->dev);
err_pm_disable:
pm_runtime_disable(&pdev->dev);
}
Calling pm_runtime_put() queues an async idle notification, but
pm_runtime_disable() invokes __pm_runtime_barrier() which cancels any
pending asynchronous requests. This causes the device to never enter the
runtime suspended state, leaving hardware resources powered on.
Should pm_runtime_put_sync() be used instead?
> pm_runtime_enable(&pdev->dev);
> ret = pm_runtime_resume_and_get(&pdev->dev);
> if (ret < 0) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=14
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 17/18] ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs
2026-05-26 8:47 ` [PATCH v6 17/18] ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs Claudiu Beznea
@ 2026-05-26 10:00 ` sashiko-bot
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-05-26 10:00 UTC (permalink / raw)
To: Claudiu Beznea; +Cc: Frank.Li, wsa+renesas, vkoul, dmaengine
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Removal of the `.remove` callback leaves the hardware un-reset and active if the driver is unbound.
Pre-existing issues:
- [Critical] In PIO mode, if both streams share `irq_rt`, concurrent playback and capture causes an infinite interrupt storm.
- [High] TOCTOU race on `strm->substream` leading to a NULL pointer dereference in the interrupt handler.
- [High] Hardware error recovery blindly forces paused streams to start, causing interrupt storms.
--
commit 7b32dcd4a1c5c9a1cf65850718c1a5545f3b08a5
Author: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs
This commit reworks the Renesas RZ-SSI driver to use the generic PCM
dmaengine APIs instead of managing DMA directly. It replaces explicit DMA
resource cleanup with devres helpers, removes the driver remove callback,
and increases buffer sizes to prevent software underruns.
> diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c
> index d4e1dded3a9c1..9fe8a639c47c1 100644
> --- a/sound/soc/renesas/rz-ssi.c
> +++ b/sound/soc/renesas/rz-ssi.c
[ ... ]
> @@ -606,12 +591,6 @@ static irqreturn_t rz_ssi_interrupt(int irq, void *data)
> if (irq == ssi->irq_int) { /* error or idle */
> bool is_stopped = !!(ssisr & (SSISR_RUIRQ | SSISR_ROIRQ |
> SSISR_TUIRQ | SSISR_TOIRQ));
[Severity: High]
This is a pre-existing issue, but does this error recovery logic blindly
force paused streams to start?
The is_stopped flag evaluates to true if ANY stream encounters an error.
If one stream is in a paused state (where its substream pointer is still
non-NULL), an error on the opposite stream will spontaneously restart the
paused stream here, which could lead to immediate underruns/overruns and an
interrupt storm.
> - int i, count;
> -
> - if (rz_ssi_is_dma_enabled(ssi))
> - count = 4;
> - else
> - count = 1;
>
> if (ssi->capture.substream && is_stopped) {
> if (ssisr & SSISR_RUIRQ)
[ ... ]
> @@ -631,19 +610,41 @@ static irqreturn_t rz_ssi_interrupt(int irq, void *data)
[ ... ]
> /* Add/remove more data */
> if (ssi->capture.substream && is_stopped) {
> - for (i = 0; i < count; i++)
> + if (rz_ssi_is_dma_enabled(ssi)) {
> + if (ssi->dmas[SNDRV_PCM_STREAM_CAPTURE])
> + dmaengine_resume(ssi->dmas[SNDRV_PCM_STREAM_CAPTURE]);
> + } else {
> strm_capture->transfer(ssi, strm_capture);
> + }
> }
>
> if (ssi->playback.substream && is_stopped) {
> - for (i = 0; i < count; i++)
> + if (rz_ssi_is_dma_enabled(ssi)) {
> + if (ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK])
> + dmaengine_resume(ssi->dmas[SNDRV_PCM_STREAM_PLAYBACK]);
> + } else {
> strm_playback->transfer(ssi, strm_playback);
> + }
> }
>
> /* Resume */
[Severity: High]
This is a pre-existing issue, but is there a TOCTOU race here that could
cause a NULL pointer dereference?
When stopping a stream, rz_ssi_dai_trigger() calls rz_ssi_stop() (which masks
interrupts at the device level but lacks synchronize_irq()) and then calls
rz_ssi_stream_quit(), which sets strm->substream = NULL.
If the interrupt handler is already running concurrently on another CPU, it
could pass the lockless check on the stream's substream pointer, but then
rz_ssi_start() may fetch strm->substream again from memory after it was
nulled by trigger(STOP).
[ ... ]
> @@ -679,153 +680,11 @@ static irqreturn_t rz_ssi_interrupt(int irq, void *data)
> return IRQ_HANDLED;
> }
[Severity: Critical]
This is a pre-existing issue, but does the irq_rt block in rz_ssi_interrupt()
cause an infinite interrupt storm in PIO mode when both streams are active
and share irq_rt?
It checks if the playback substream is valid and services ONLY the playback
stream:
if (irq == ssi->irq_rt) {
if (ssi->playback.substream) {
strm_playback->transfer(ssi, &ssi->playback);
} else {
strm_capture->transfer(ssi, &ssi->capture);
rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0);
}
}
If both playback and capture are open concurrently, any capture interrupt
(RX FIFO full) will be ignored.
The RX FIFO is never drained, the interrupt condition remains asserted,
and returning IRQ_HANDLED causes an immediate re-entry.
[ ... ]
> @@ -1236,43 +1137,19 @@ static int rz_ssi_probe(struct platform_device *pdev)
[ ... ]
> -static void rz_ssi_remove(struct platform_device *pdev)
> -{
> - struct rz_ssi_priv *ssi = dev_get_drvdata(&pdev->dev);
> -
> - rz_ssi_release_dma_channels(ssi);
>
> - reset_control_assert(ssi->rstc);
> + return devm_snd_soc_register_component(dev, component_driver,
> + rz_ssi_soc_dai,
> + ARRAY_SIZE(rz_ssi_soc_dai));
> }
[Severity: High]
Does removing the remove callback leave the hardware un-reset and active
if the driver is unbound?
The driver uses devm_reset_control_get_exclusive(), which only registers a
cleanup action to drop the reference (via reset_control_put()), but does not
assert the hardware reset line.
Without the explicit reset_control_assert() call that was removed here,
unloading the driver while the device is active will skip resetting the
hardware, potentially causing a resource leak or instability.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526084710.3491480-1-claudiu.beznea@kernel.org?part=17
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
2026-05-26 9:51 ` Biju Das
@ 2026-05-26 10:25 ` Claudiu Beznea
2026-05-26 10:39 ` Biju Das
0 siblings, 1 reply; 53+ messages in thread
From: Claudiu Beznea @ 2026-05-26 10:25 UTC (permalink / raw)
To: Biju Das, vkoul@kernel.org, Frank.Li@kernel.org,
lgirdwood@gmail.com, broonie@kernel.org, perex@perex.cz,
tiwai@suse.com, Prabhakar Mahadev Lad, p.zabel@pengutronix.de,
geert+renesas@glider.be, Kuninori Morimoto, Long Luu
Cc: Claudiu.Beznea, dmaengine@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org,
linux-renesas-soc@vger.kernel.org, Claudiu Beznea,
stable@vger.kernel.org, Frank Li, John Madieu
On 5/26/26 12:51, Biju Das wrote:
> Hi Claudiu,
>
>> -----Original Message-----
>> From: Claudiu Beznea <claudiu.beznea@kernel.org>
>> Sent: 26 May 2026 10:46
>> Subject: Re: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set
>> up
>>
>>
>>
>> On 5/26/26 11:54, Biju Das wrote:
>>> Hi Claudiu,
>>>
>>>> -----Original Message-----
>>>> From: Claudiu Beznea <claudiu.beznea@kernel.org>
>>>> Sent: 26 May 2026 09:47
>>>> Subject: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt
>>>> request after everything is set up
>>>>
>>>> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>>>>
>>>> Once the interrupt is requested, the interrupt handler may run immediately.
>>>
>>> Do you mean spurious interrupt?
>>>
>>> After DMA driver probe only, consumer device can access the DMA handle
>>> right? or am I missing something here?
>>
>> In theory there could be pending interrupts not yet served (e.g. due to the previous usage of the
>> controller, HW behavior, etc). Those could trigger the execution of the IRQ handler once the interrupt
>> is requested.
>
> You mean DMA consumers configured by bootloader and linux probing the DMA driver can
> trigger IRQ?
DMA used by bootloaders may be a valid scenario, even though may not currently
be used in the setups this IP is used.
Please check the documentation of request_threaded_irq():
https://elixir.bootlin.com/linux/v7.1-rc4/source/kernel/irq/manage.c#L2089
"* ... From the point this call is made your handler function
* may be invoked. Since your handler function must clear any interrupt the
* board raises, you must take care both to initialise your hardware and to
* set up the interrupt handler in the right order"
--
Thank you,
Claudiu
^ permalink raw reply [flat|nested] 53+ messages in thread
* RE: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
2026-05-26 10:25 ` Claudiu Beznea
@ 2026-05-26 10:39 ` Biju Das
0 siblings, 0 replies; 53+ messages in thread
From: Biju Das @ 2026-05-26 10:39 UTC (permalink / raw)
To: Claudiu Beznea, vkoul@kernel.org, Frank.Li@kernel.org,
lgirdwood@gmail.com, broonie@kernel.org, perex@perex.cz,
tiwai@suse.com, Prabhakar Mahadev Lad, p.zabel@pengutronix.de,
geert+renesas@glider.be, Kuninori Morimoto, Long Luu
Cc: Claudiu.Beznea, dmaengine@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org,
linux-renesas-soc@vger.kernel.org, Claudiu Beznea,
stable@vger.kernel.org, Frank Li, John Madieu
> -----Original Message-----
> From: Claudiu Beznea <claudiu.beznea@kernel.org>
> Sent: 26 May 2026 11:26
> Subject: Re: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set
> up
>
>
>
> On 5/26/26 12:51, Biju Das wrote:
> > Hi Claudiu,
> >
> >> -----Original Message-----
> >> From: Claudiu Beznea <claudiu.beznea@kernel.org>
> >> Sent: 26 May 2026 10:46
> >> Subject: Re: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt
> >> request after everything is set up
> >>
> >>
> >>
> >> On 5/26/26 11:54, Biju Das wrote:
> >>> Hi Claudiu,
> >>>
> >>>> -----Original Message-----
> >>>> From: Claudiu Beznea <claudiu.beznea@kernel.org>
> >>>> Sent: 26 May 2026 09:47
> >>>> Subject: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt
> >>>> request after everything is set up
> >>>>
> >>>> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> >>>>
> >>>> Once the interrupt is requested, the interrupt handler may run immediately.
> >>>
> >>> Do you mean spurious interrupt?
> >>>
> >>> After DMA driver probe only, consumer device can access the DMA
> >>> handle right? or am I missing something here?
> >>
> >> In theory there could be pending interrupts not yet served (e.g. due
> >> to the previous usage of the controller, HW behavior, etc). Those
> >> could trigger the execution of the IRQ handler once the interrupt is requested.
> >
> > You mean DMA consumers configured by bootloader and linux probing the
> > DMA driver can trigger IRQ?
> DMA used by bootloaders may be a valid scenario, even though may not currently be used in the setups
> this IP is used.
>
> Please check the documentation of request_threaded_irq():
> https://elixir.bootlin.com/linux/v7.1-rc4/source/kernel/irq/manage.c#L2089
>
> "* ... From the point this call is made your handler function
> * may be invoked. Since your handler function must clear any interrupt the
> * board raises, you must take care both to initialise your hardware and to
> * set up the interrupt handler in the right order"
OK. Then it make sense as the bootloader/board may have some register
configured for triggering IRQ and it will lead to crash..
Cheers,
Biju
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up
2026-05-26 8:46 ` [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up Claudiu Beznea
2026-05-26 8:54 ` Biju Das
2026-05-26 9:20 ` sashiko-bot
@ 2026-05-28 13:44 ` Tommaso Merciai
2 siblings, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:44 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
stable, Frank Li, John Madieu
Hi Claudiu,
Thanks for your patch.
On Tue, May 26, 2026 at 11:46:53AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Once the interrupt is requested, the interrupt handler may run immediately.
> Since the IRQ handler can access channel->ch_base, which is initialized
> only after requesting the IRQ, this may lead to invalid memory access.
> Likewise, the IRQ thread may access uninitialized data (the ld_free,
> ld_queue, and ld_active lists), which may also lead to issues.
>
> Request the interrupts only after everything is set up. To keep the error
> path simpler, use dmam_alloc_coherent() instead of dma_alloc_coherent().
>
Tested on RZ/G3E using dma along with rspi0
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Thanks, Tommaso
> Fixes: 5000d37042a6 ("dmaengine: sh: Add DMAC driver for RZ/G2L SoC")
> Cc: stable@vger.kernel.org
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 88 +++++++++++++++-------------------------
> 1 file changed, 33 insertions(+), 55 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 625ff29024de..9f206a33dcc6 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -981,25 +981,6 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
> channel->index = index;
> channel->mid_rid = -EINVAL;
>
> - /* Request the channel interrupt. */
> - scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index);
> - irq = platform_get_irq_byname(pdev, pdev_irqname);
> - if (irq < 0)
> - return irq;
> -
> - irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u",
> - dev_name(dmac->dev), index);
> - if (!irqname)
> - return -ENOMEM;
> -
> - ret = devm_request_threaded_irq(dmac->dev, irq, rz_dmac_irq_handler,
> - rz_dmac_irq_handler_thread, 0,
> - irqname, channel);
> - if (ret) {
> - dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", irq, ret);
> - return ret;
> - }
> -
> /* Set io base address for each channel */
> if (index < 8) {
> channel->ch_base = dmac->base + CHANNEL_0_7_OFFSET +
> @@ -1012,9 +993,9 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
> }
>
> /* Allocate descriptors */
> - lmdesc = dma_alloc_coherent(&pdev->dev,
> - sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
> - &channel->lmdesc.base_dma, GFP_KERNEL);
> + lmdesc = dmam_alloc_coherent(&pdev->dev,
> + sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
> + &channel->lmdesc.base_dma, GFP_KERNEL);
> if (!lmdesc) {
> dev_err(&pdev->dev, "Can't allocate memory (lmdesc)\n");
> return -ENOMEM;
> @@ -1030,7 +1011,24 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
> INIT_LIST_HEAD(&channel->ld_free);
> INIT_LIST_HEAD(&channel->ld_active);
>
> - return 0;
> + /* Request the channel interrupt. */
> + scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index);
> + irq = platform_get_irq_byname(pdev, pdev_irqname);
> + if (irq < 0)
> + return irq;
> +
> + irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u",
> + dev_name(dmac->dev), index);
> + if (!irqname)
> + return -ENOMEM;
> +
> + ret = devm_request_threaded_irq(dmac->dev, irq, rz_dmac_irq_handler,
> + rz_dmac_irq_handler_thread, 0,
> + irqname, channel);
> + if (ret)
> + dev_err(dmac->dev, "failed to request IRQ %u (%d)\n", irq, ret);
> +
> + return ret;
> }
>
> static void rz_dmac_put_device(void *_dev)
> @@ -1099,7 +1097,6 @@ static int rz_dmac_probe(struct platform_device *pdev)
> const char *irqname = "error";
> struct dma_device *engine;
> struct rz_dmac *dmac;
> - int channel_num;
> int ret;
> int irq;
> u8 i;
> @@ -1132,18 +1129,6 @@ static int rz_dmac_probe(struct platform_device *pdev)
> return PTR_ERR(dmac->ext_base);
> }
>
> - /* Register interrupt handler for error */
> - irq = platform_get_irq_byname_optional(pdev, irqname);
> - if (irq > 0) {
> - ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0,
> - irqname, NULL);
> - if (ret) {
> - dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n",
> - irq, ret);
> - return ret;
> - }
> - }
> -
> /* Initialize the channels. */
> INIT_LIST_HEAD(&dmac->engine.channels);
>
> @@ -1169,6 +1154,18 @@ static int rz_dmac_probe(struct platform_device *pdev)
> goto err;
> }
>
> + /* Register interrupt handler for error */
> + irq = platform_get_irq_byname_optional(pdev, irqname);
> + if (irq > 0) {
> + ret = devm_request_irq(&pdev->dev, irq, rz_dmac_irq_handler, 0,
> + irqname, NULL);
> + if (ret) {
> + dev_err(&pdev->dev, "failed to request IRQ %u (%d)\n",
> + irq, ret);
> + goto err;
> + }
> + }
> +
> /* Register the DMAC as a DMA provider for DT. */
> ret = of_dma_controller_register(pdev->dev.of_node, rz_dmac_of_xlate,
> NULL);
> @@ -1210,16 +1207,6 @@ static int rz_dmac_probe(struct platform_device *pdev)
> dma_register_err:
> of_dma_controller_free(pdev->dev.of_node);
> err:
> - channel_num = i ? i - 1 : 0;
> - for (i = 0; i < channel_num; i++) {
> - struct rz_dmac_chan *channel = &dmac->channels[i];
> -
> - dma_free_coherent(&pdev->dev,
> - sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
> - channel->lmdesc.base,
> - channel->lmdesc.base_dma);
> - }
> -
> reset_control_assert(dmac->rstc);
> err_pm_runtime_put:
> pm_runtime_put(&pdev->dev);
> @@ -1232,18 +1219,9 @@ static int rz_dmac_probe(struct platform_device *pdev)
> static void rz_dmac_remove(struct platform_device *pdev)
> {
> struct rz_dmac *dmac = platform_get_drvdata(pdev);
> - unsigned int i;
>
> dma_async_device_unregister(&dmac->engine);
> of_dma_controller_free(pdev->dev.of_node);
> - for (i = 0; i < dmac->n_channels; i++) {
> - struct rz_dmac_chan *channel = &dmac->channels[i];
> -
> - dma_free_coherent(&pdev->dev,
> - sizeof(struct rz_lmdesc) * DMAC_NR_LMDESC,
> - channel->lmdesc.base,
> - channel->lmdesc.base_dma);
> - }
> reset_control_assert(dmac->rstc);
> pm_runtime_put(&pdev->dev);
> pm_runtime_disable(&pdev->dev);
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 02/18] dmaengine: sh: rz-dmac: Fix incorrect NULL check for list_first_entry()
2026-05-26 8:46 ` [PATCH v6 02/18] dmaengine: sh: rz-dmac: Fix incorrect NULL check for list_first_entry() Claudiu Beznea
2026-05-26 9:03 ` sashiko-bot
@ 2026-05-28 13:45 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:45 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
stable, John Madieu
On Tue, May 26, 2026 at 11:46:54AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> list_first_entry() does not return NULL when the list is empty,
> making the existing NULL check invalid. Use list_first_entry_or_null()
> instead.
>
Same.
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Kind Regards,
Tommaso
> Fixes: 21323b118c16 ("dmaengine: sh: rz-dmac: Add device_tx_status() callback")
> Cc: stable@vger.kernel.org
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - collected tags
> - updated the patch title and description to reflect better the changes
>
> Changes in v5:
> - none
>
> Changes in v4:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 9f206a33dcc6..6d80cb668957 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -723,8 +723,8 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
> u32 crla, crtb, i;
>
> /* Get current processing virtual descriptor */
> - current_desc = list_first_entry(&channel->ld_active,
> - struct rz_dmac_desc, node);
> + current_desc = list_first_entry_or_null(&channel->ld_active,
> + struct rz_dmac_desc, node);
> if (!current_desc)
> return 0;
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 03/18] dmaengine: sh: rz-dmac: Use list_first_entry_or_null()
2026-05-26 8:46 ` [PATCH v6 03/18] dmaengine: sh: rz-dmac: Use list_first_entry_or_null() Claudiu Beznea
@ 2026-05-28 13:45 ` Tommaso Merciai
0 siblings, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:45 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
Frank Li, John Madieu
On Tue, May 26, 2026 at 11:46:55AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Use list_first_entry_or_null() instead of open-coding it with a
> list_empty() check and list_first_entry(). This simplifies the code.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - none
>
> Changes in v3:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 10 ++++------
> 1 file changed, 4 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 6d80cb668957..1717b407ab9e 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -503,11 +503,10 @@ rz_dmac_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
> __func__, channel->index, &src, &dest, len);
>
> scoped_guard(spinlock_irqsave, &channel->vc.lock) {
> - if (list_empty(&channel->ld_free))
> + desc = list_first_entry_or_null(&channel->ld_free, struct rz_dmac_desc, node);
> + if (!desc)
> return NULL;
>
> - desc = list_first_entry(&channel->ld_free, struct rz_dmac_desc, node);
> -
> desc->type = RZ_DMAC_DESC_MEMCPY;
> desc->src = src;
> desc->dest = dest;
> @@ -533,11 +532,10 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
> int i = 0;
>
> scoped_guard(spinlock_irqsave, &channel->vc.lock) {
> - if (list_empty(&channel->ld_free))
> + desc = list_first_entry_or_null(&channel->ld_free, struct rz_dmac_desc, node);
> + if (!desc)
> return NULL;
>
> - desc = list_first_entry(&channel->ld_free, struct rz_dmac_desc, node);
> -
> for_each_sg(sgl, sg, sg_len, i)
> dma_length += sg_dma_len(sg);
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 04/18] dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw()
2026-05-26 8:46 ` [PATCH v6 04/18] dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw() Claudiu Beznea
2026-05-26 9:15 ` sashiko-bot
@ 2026-05-28 13:46 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:46 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
Frank Li, John Madieu
On Tue, May 26, 2026 at 11:46:56AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Use rz_dmac_disable_hw() instead of open coding it. This unifies the
> code and prepares it for the addition of suspend to RAM and cyclic DMA.
>
> The rz_dmac_disable_hw() from rz_dmac_chan_probe() was moved after
> vchan_init() as it initializes the channel->vc.chan.device used in
> rz_dmac_disable_hw().
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - fixed typo in patch description
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - in rz_dmac_chan_probe(): moved rz_dmac_disable_hw() after the
> vchan_init(&channel->vc, &dmac->engine) call as this is the one which
> initializes data structures used by the debug code from
> rz_dmac_disable_hw(); updated the patch description to reflect this
>
> Changes in v3:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 1717b407ab9e..40ddf534c094 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -873,7 +873,7 @@ static void rz_dmac_irq_handle_channel(struct rz_dmac_chan *channel)
> channel->index, chstat);
>
> scoped_guard(spinlock_irqsave, &channel->vc.lock)
> - rz_dmac_ch_writel(channel, CHCTRL_DEFAULT, CHCTRL, 1);
> + rz_dmac_disable_hw(channel);
> return;
> }
>
> @@ -1000,15 +1000,15 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
> }
> rz_lmdesc_setup(channel, lmdesc);
>
> - /* Initialize register for each channel */
> - rz_dmac_ch_writel(channel, CHCTRL_DEFAULT, CHCTRL, 1);
> -
> channel->vc.desc_free = rz_dmac_virt_desc_free;
> vchan_init(&channel->vc, &dmac->engine);
> INIT_LIST_HEAD(&channel->ld_queue);
> INIT_LIST_HEAD(&channel->ld_free);
> INIT_LIST_HEAD(&channel->ld_active);
>
> + /* Initialize register for each channel */
> + rz_dmac_disable_hw(channel);
> +
> /* Request the channel interrupt. */
> scnprintf(pdev_irqname, sizeof(pdev_irqname), "ch%u", index);
> irq = platform_get_irq_byname(pdev, pdev_irqname);
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 05/18] dmaengine: sh: rz-dmac: Add helper to compute the lmdesc address
2026-05-26 8:46 ` [PATCH v6 05/18] dmaengine: sh: rz-dmac: Add helper to compute the lmdesc address Claudiu Beznea
@ 2026-05-28 13:47 ` Tommaso Merciai
0 siblings, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:47 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
Frank Li, John Madieu
On Tue, May 26, 2026 at 11:46:57AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Add a the rz_dmac_lmdesc_addr() helper function to compute the lmdesc
> address, to make the code easier to understand. The helper will be used in
> subsequent patches.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Kind Regards,
Tommaso
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - updated patch description
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - none
>
> Changes in v3:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 10 +++++++---
> 1 file changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 40ddf534c094..c48858b68dee 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -259,6 +259,12 @@ static void rz_lmdesc_setup(struct rz_dmac_chan *channel,
> * Descriptors preparation
> */
>
> +static u32 rz_dmac_lmdesc_addr(struct rz_dmac_chan *channel, struct rz_lmdesc *lmdesc)
> +{
> + return channel->lmdesc.base_dma +
> + (sizeof(struct rz_lmdesc) * (lmdesc - channel->lmdesc.base));
> +}
> +
> static void rz_dmac_lmdesc_recycle(struct rz_dmac_chan *channel)
> {
> struct rz_lmdesc *lmdesc = channel->lmdesc.head;
> @@ -284,9 +290,7 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel)
>
> rz_dmac_lmdesc_recycle(channel);
>
> - nxla = channel->lmdesc.base_dma +
> - (sizeof(struct rz_lmdesc) * (channel->lmdesc.head -
> - channel->lmdesc.base));
> + nxla = rz_dmac_lmdesc_addr(channel, channel->lmdesc.head);
>
> chstat = rz_dmac_ch_readl(channel, CHSTAT, 1);
> if (!(chstat & CHSTAT_EN)) {
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 06/18] dmaengine: sh: rz-dmac: Save the start LM descriptor
2026-05-26 8:46 ` [PATCH v6 06/18] dmaengine: sh: rz-dmac: Save the start LM descriptor Claudiu Beznea
2026-05-26 9:41 ` sashiko-bot
@ 2026-05-28 13:47 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:47 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
John Madieu
On Tue, May 26, 2026 at 11:46:58AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Save the start LM descriptor to avoid starting from the beginning of the
> channel's LM descriptor list in rz_dmac_calculate_residue_bytes_in_vd().
> This avoids unnecessary iterations.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - updated patch description to describe better the changes
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - none
>
> Changes in v3:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 11 ++++++++---
> 1 file changed, 8 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index c48858b68dee..d3926ecd63ac 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -58,6 +58,7 @@ struct rz_dmac_desc {
> /* For slave sg */
> struct scatterlist *sg;
> unsigned int sgcount;
> + struct rz_lmdesc *start_lmdesc;
> };
>
> #define to_rz_dmac_desc(d) container_of(d, struct rz_dmac_desc, vd)
> @@ -343,6 +344,8 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel)
> struct rz_dmac_desc *d = channel->desc;
> u32 chcfg = CHCFG_MEM_COPY;
>
> + d->start_lmdesc = lmdesc;
> +
> /* prepare descriptor */
> lmdesc->sa = d->src;
> lmdesc->da = d->dest;
> @@ -377,6 +380,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
> }
>
> lmdesc = channel->lmdesc.tail;
> + d->start_lmdesc = lmdesc;
>
> for (i = 0, sg = sgl; i < sg_len; i++, sg = sg_next(sg)) {
> if (d->direction == DMA_DEV_TO_MEM) {
> @@ -693,9 +697,10 @@ rz_dmac_get_next_lmdesc(struct rz_lmdesc *base, struct rz_lmdesc *lmdesc)
> return next;
> }
>
> -static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel, u32 crla)
> +static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
> + struct rz_dmac_desc *desc, u32 crla)
> {
> - struct rz_lmdesc *lmdesc = channel->lmdesc.head;
> + struct rz_lmdesc *lmdesc = desc->start_lmdesc;
> struct dma_chan *chan = &channel->vc.chan;
> struct rz_dmac *dmac = to_rz_dmac(chan->device);
> u32 residue = 0, i = 0;
> @@ -794,7 +799,7 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
> * Calculate number of bytes transferred in processing virtual descriptor.
> * One virtual descriptor can have many lmdesc.
> */
> - return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, crla);
> + return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, current_desc, crla);
> }
>
> static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 07/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is enabled
2026-05-26 8:46 ` [PATCH v6 07/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is enabled Claudiu Beznea
@ 2026-05-28 13:48 ` Tommaso Merciai
0 siblings, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:48 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
Frank Li, John Madieu
On Tue, May 26, 2026 at 11:46:59AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Add the rz_dmac_chan_is_enabled() helper to check if a channel is
> enabled. This helper will be reused in subsequent patches.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - updated the patch description to describe better the changes
> - collected tags
> - s/chan/channel in rz_dmac_chan_is_enabled() to follow the naming convention
> accross the driver for the variable of type struct rz_dmac_chan
>
> Changes in v5:
> - none
>
> Changes in v4:
> - none
>
> Changes in v3:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 10 ++++++++--
> 1 file changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index d3926ecd63ac..76bac11c217c 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -279,6 +279,13 @@ static void rz_dmac_lmdesc_recycle(struct rz_dmac_chan *channel)
> channel->lmdesc.head = lmdesc;
> }
>
> +static bool rz_dmac_chan_is_enabled(struct rz_dmac_chan *channel)
> +{
> + u32 val = rz_dmac_ch_readl(channel, CHSTAT, 1);
> +
> + return !!(val & CHSTAT_EN);
> +}
> +
> static void rz_dmac_enable_hw(struct rz_dmac_chan *channel)
> {
> struct dma_chan *chan = &channel->vc.chan;
> @@ -840,8 +847,7 @@ static int rz_dmac_device_pause(struct dma_chan *chan)
>
> guard(spinlock_irqsave)(&channel->vc.lock);
>
> - val = rz_dmac_ch_readl(channel, CHSTAT, 1);
> - if (!(val & CHSTAT_EN))
> + if (!rz_dmac_chan_is_enabled(channel))
> return 0;
>
> rz_dmac_ch_writel(channel, CHCTRL_SETSUS, CHCTRL, 1);
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 08/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is paused
2026-05-26 8:47 ` [PATCH v6 08/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is paused Claudiu Beznea
@ 2026-05-28 13:48 ` Tommaso Merciai
0 siblings, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:48 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
Frank Li, John Madieu
On Tue, May 26, 2026 at 11:47:00AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Add the rz_dmac_chan_is_paused() helper to check if the channel is paused.
> This helper will be reused in subsequent patches.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - updated patch description to reflect better the changes
> - collected tags
> - s/chan/channel in rz_dmac_chan_is_paused() to follow the naming convention
> accross the driver for the variable of type struct rz_dmac_chan
>
> Changes in v5:
> - none
>
> Changes in v4:
> - none
>
> Changes in v3:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 12 ++++++++----
> 1 file changed, 8 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 76bac11c217c..217657513fa7 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -286,6 +286,13 @@ static bool rz_dmac_chan_is_enabled(struct rz_dmac_chan *channel)
> return !!(val & CHSTAT_EN);
> }
>
> +static bool rz_dmac_chan_is_paused(struct rz_dmac_chan *channel)
> +{
> + u32 val = rz_dmac_ch_readl(channel, CHSTAT, 1);
> +
> + return !!(val & CHSTAT_SUS);
> +}
> +
> static void rz_dmac_enable_hw(struct rz_dmac_chan *channel)
> {
> struct dma_chan *chan = &channel->vc.chan;
> @@ -822,12 +829,9 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
> return status;
>
> scoped_guard(spinlock_irqsave, &channel->vc.lock) {
> - u32 val;
> -
> residue = rz_dmac_chan_get_residue(channel, cookie);
>
> - val = rz_dmac_ch_readl(channel, CHSTAT, 1);
> - if (val & CHSTAT_SUS)
> + if (rz_dmac_chan_is_paused(channel))
> status = DMA_PAUSED;
> }
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 09/18] dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor processing
2026-05-26 8:47 ` [PATCH v6 09/18] dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor processing Claudiu Beznea
2026-05-26 9:28 ` sashiko-bot
@ 2026-05-28 13:49 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:49 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
John Madieu
On Tue, May 26, 2026 at 11:47:01AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> The driver used a mix of virt-dma APIs and driver specific logic to
> process descriptors. It maintained three internal queues: ld_free,
> ld_queue, and ld_active as follows:
> - ld_free: stores the descriptors pre-allocated at probe time
> - ld_queue: stores descriptors after they are taken from ld_free and
> prepared. At the same time, vchan_tx_prep() queues them to
> vc->desc_allocated. The vc->desc_allocated list is then checked in
> rz_dmac_issue_pending() and rz_dmac_irq_handler_thread() before
> starting a new transfer via rz_dmac_xfer_desc(). In turn,
> rz_dmac_xfer_desc() grabs the next descriptor from vc->desc_issued and
> submits it for transfer
> - ld_active: stores the descriptors currently being transferred
>
> The interrupt handler moved a completed descriptor to ld_free before
> invoking its completion callback. Once returned to ld_free, the
> descriptor can be reused to prepare a new transfer. In theory, this
> means the descriptor could be re-prepared before its completion
> callback is called.
>
> Commit fully back the driver by the virt-dma APIs. With this, only ld_free
> need to be kept to track how many free descriptors are available. This
> is now done as follows:
> - the prepare stage removes the first descriptor from the ld_free and
> prepares it
> - the completion calls for it vc->desc_free() (rz_dmac_virt_desc_free())
> which re-adds the descriptor at the end of ld_free
>
> With this, the critical areas in prepare callbacks were minimized to only
> getting the descriptor from the ld_free list.
>
> Introduce struct rz_dmac_chan::desc to keep track of the currently
> transferred descriptor. It is cleared in rz_dmac_terminate_all(),
> referenced from rz_dmac_issue_pending() to determine whether a new transfer
> can be started, and from rz_dmac_irq_handler_thread() once a descriptor has
> completed. Finally, the rz_dmac_device_synchronize() was updated with
> vchan_synchronize() call to ensure the terminated descriptor is freed and
> the tasklet is killed.
>
> With this, residue computation is also simplified, as it can now be
> handled entirely through the virt-dma APIs.
>
> The spin_lock/unlock operations from rz_dmac_irq_handler_thread() were
> replaced by guard as the final code after rework is simpler this way.
>
> As subsequent commits will set the Link End bit on the last descriptor
> of a transfer, rz_dmac_enable_hw() is also adjusted as part of the full
> conversion to virt-dma APIs. It no longer checks the channel enable
> status itself; instead, its callers verify whether the channel is
> enabled and whether the previous transfer has completed before starting
> a new one.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - updated patch description as suggested in the review process
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - in rz_dmac_tx_status(): return DMA_PAUSED if the channel is paused;
> call rz_dmac_chan_get_residue() only if status is not complete
>
> Changes in v3:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 233 +++++++++++++++------------------------
> 1 file changed, 86 insertions(+), 147 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 217657513fa7..1f884ec101f8 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -79,8 +79,6 @@ struct rz_dmac_chan {
> int mid_rid;
>
> struct list_head ld_free;
> - struct list_head ld_queue;
> - struct list_head ld_active;
>
> struct {
> struct rz_lmdesc *base;
> @@ -299,7 +297,6 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel)
> struct rz_dmac *dmac = to_rz_dmac(chan->device);
> u32 nxla;
> u32 chctrl;
> - u32 chstat;
>
> dev_dbg(dmac->dev, "%s channel %d\n", __func__, channel->index);
>
> @@ -307,14 +304,11 @@ static void rz_dmac_enable_hw(struct rz_dmac_chan *channel)
>
> nxla = rz_dmac_lmdesc_addr(channel, channel->lmdesc.head);
>
> - chstat = rz_dmac_ch_readl(channel, CHSTAT, 1);
> - if (!(chstat & CHSTAT_EN)) {
> - chctrl = (channel->chctrl | CHCTRL_SETEN);
> - rz_dmac_ch_writel(channel, nxla, NXLA, 1);
> - rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1);
> - rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1);
> - rz_dmac_ch_writel(channel, chctrl, CHCTRL, 1);
> - }
> + chctrl = (channel->chctrl | CHCTRL_SETEN);
> + rz_dmac_ch_writel(channel, nxla, NXLA, 1);
> + rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1);
> + rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1);
> + rz_dmac_ch_writel(channel, chctrl, CHCTRL, 1);
> }
>
> static void rz_dmac_disable_hw(struct rz_dmac_chan *channel)
> @@ -426,18 +420,20 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
> channel->chctrl = CHCTRL_SETEN;
> }
>
> -static int rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
> +static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
> {
> - struct rz_dmac_desc *d = chan->desc;
> struct virt_dma_desc *vd;
>
> vd = vchan_next_desc(&chan->vc);
> - if (!vd)
> - return 0;
> + if (!vd) {
> + chan->desc = NULL;
> + return;
> + }
>
> list_del(&vd->node);
> + chan->desc = to_rz_dmac_desc(vd);
>
> - switch (d->type) {
> + switch (chan->desc->type) {
> case RZ_DMAC_DESC_MEMCPY:
> rz_dmac_prepare_desc_for_memcpy(chan);
> break;
> @@ -445,14 +441,9 @@ static int rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
> case RZ_DMAC_DESC_SLAVE_SG:
> rz_dmac_prepare_descs_for_slave_sg(chan);
> break;
> -
> - default:
> - return -EINVAL;
> }
>
> rz_dmac_enable_hw(chan);
> -
> - return 0;
> }
>
> /*
> @@ -494,8 +485,6 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
> rz_lmdesc_setup(channel, channel->lmdesc.base);
>
> rz_dmac_disable_hw(channel);
> - list_splice_tail_init(&channel->ld_active, &channel->ld_free);
> - list_splice_tail_init(&channel->ld_queue, &channel->ld_free);
>
> if (channel->mid_rid >= 0) {
> clear_bit(channel->mid_rid, dmac->modules);
> @@ -504,13 +493,19 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
>
> spin_unlock_irqrestore(&channel->vc.lock, flags);
>
> + vchan_free_chan_resources(&channel->vc);
> +
> + spin_lock_irqsave(&channel->vc.lock, flags);
> +
> list_for_each_entry_safe(desc, _desc, &channel->ld_free, node) {
> + list_del(&desc->node);
> kfree(desc);
> channel->descs_allocated--;
> }
>
> INIT_LIST_HEAD(&channel->ld_free);
> - vchan_free_chan_resources(&channel->vc);
> +
> + spin_unlock_irqrestore(&channel->vc.lock, flags);
> }
>
> static struct dma_async_tx_descriptor *
> @@ -529,15 +524,15 @@ rz_dmac_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
> if (!desc)
> return NULL;
>
> - desc->type = RZ_DMAC_DESC_MEMCPY;
> - desc->src = src;
> - desc->dest = dest;
> - desc->len = len;
> - desc->direction = DMA_MEM_TO_MEM;
> -
> - list_move_tail(channel->ld_free.next, &channel->ld_queue);
> + list_del(&desc->node);
> }
>
> + desc->type = RZ_DMAC_DESC_MEMCPY;
> + desc->src = src;
> + desc->dest = dest;
> + desc->len = len;
> + desc->direction = DMA_MEM_TO_MEM;
> +
> return vchan_tx_prep(&channel->vc, &desc->vd, flags);
> }
>
> @@ -558,22 +553,22 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
> if (!desc)
> return NULL;
>
> - for_each_sg(sgl, sg, sg_len, i)
> - dma_length += sg_dma_len(sg);
> + list_del(&desc->node);
> + }
>
> - desc->type = RZ_DMAC_DESC_SLAVE_SG;
> - desc->sg = sgl;
> - desc->sgcount = sg_len;
> - desc->len = dma_length;
> - desc->direction = direction;
> + for_each_sg(sgl, sg, sg_len, i)
> + dma_length += sg_dma_len(sg);
>
> - if (direction == DMA_DEV_TO_MEM)
> - desc->src = channel->src_per_address;
> - else
> - desc->dest = channel->dst_per_address;
> + desc->type = RZ_DMAC_DESC_SLAVE_SG;
> + desc->sg = sgl;
> + desc->sgcount = sg_len;
> + desc->len = dma_length;
> + desc->direction = direction;
>
> - list_move_tail(channel->ld_free.next, &channel->ld_queue);
> - }
> + if (direction == DMA_DEV_TO_MEM)
> + desc->src = channel->src_per_address;
> + else
> + desc->dest = channel->dst_per_address;
>
> return vchan_tx_prep(&channel->vc, &desc->vd, flags);
> }
> @@ -588,8 +583,11 @@ static int rz_dmac_terminate_all(struct dma_chan *chan)
> rz_dmac_disable_hw(channel);
> rz_lmdesc_setup(channel, channel->lmdesc.base);
>
> - list_splice_tail_init(&channel->ld_active, &channel->ld_free);
> - list_splice_tail_init(&channel->ld_queue, &channel->ld_free);
> + if (channel->desc) {
> + vchan_terminate_vdesc(&channel->desc->vd);
> + channel->desc = NULL;
> + }
> +
> vchan_get_all_descriptors(&channel->vc, &head);
> spin_unlock_irqrestore(&channel->vc.lock, flags);
> vchan_dma_desc_free_list(&channel->vc, &head);
> @@ -600,25 +598,16 @@ static int rz_dmac_terminate_all(struct dma_chan *chan)
> static void rz_dmac_issue_pending(struct dma_chan *chan)
> {
> struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> - struct rz_dmac *dmac = to_rz_dmac(chan->device);
> - struct rz_dmac_desc *desc;
> unsigned long flags;
>
> spin_lock_irqsave(&channel->vc.lock, flags);
>
> - if (!list_empty(&channel->ld_queue)) {
> - desc = list_first_entry(&channel->ld_queue,
> - struct rz_dmac_desc, node);
> - channel->desc = desc;
> - if (vchan_issue_pending(&channel->vc)) {
> - if (rz_dmac_xfer_desc(channel) < 0)
> - dev_warn(dmac->dev, "ch: %d couldn't issue DMA xfer\n",
> - channel->index);
> - else
> - list_move_tail(channel->ld_queue.next,
> - &channel->ld_active);
> - }
> - }
> + /*
> + * Issue the descriptor. If another transfer is already in progress, the
> + * issued descriptor will be handled after the current transfer finishes.
> + */
> + if (vchan_issue_pending(&channel->vc) && !channel->desc)
> + rz_dmac_xfer_desc(channel);
>
> spin_unlock_irqrestore(&channel->vc.lock, flags);
> }
> @@ -676,13 +665,13 @@ static int rz_dmac_config(struct dma_chan *chan,
>
> static void rz_dmac_virt_desc_free(struct virt_dma_desc *vd)
> {
> - /*
> - * Place holder
> - * Descriptor allocation is done during alloc_chan_resources and
> - * get freed during free_chan_resources.
> - * list is used to manage the descriptors and avoid any memory
> - * allocation/free during DMA read/write.
> - */
> + struct rz_dmac_chan *channel = to_rz_dmac_chan(vd->tx.chan);
> + struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
> + struct rz_dmac_desc *desc = to_rz_dmac_desc(vd);
> +
> + guard(spinlock_irqsave)(&vc->lock);
> +
> + list_add_tail(&desc->node, &channel->ld_free);
> }
>
> static void rz_dmac_device_synchronize(struct dma_chan *chan)
> @@ -692,6 +681,8 @@ static void rz_dmac_device_synchronize(struct dma_chan *chan)
> u32 chstat;
> int ret;
>
> + vchan_synchronize(&channel->vc);
> +
> ret = read_poll_timeout(rz_dmac_ch_readl, chstat, !(chstat & CHSTAT_EN),
> 100, 100000, false, channel, CHSTAT, 1);
> if (ret < 0)
> @@ -739,58 +730,22 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
> static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
> dma_cookie_t cookie)
> {
> - struct rz_dmac_desc *current_desc, *desc;
> - enum dma_status status;
> + struct rz_dmac_desc *desc = NULL;
> + struct virt_dma_desc *vd;
> u32 crla, crtb, i;
>
> - /* Get current processing virtual descriptor */
> - current_desc = list_first_entry_or_null(&channel->ld_active,
> - struct rz_dmac_desc, node);
> - if (!current_desc)
> - return 0;
> -
> - /*
> - * If the cookie corresponds to a descriptor that has been completed
> - * there is no residue. The same check has already been performed by the
> - * caller but without holding the channel lock, so the descriptor could
> - * now be complete.
> - */
> - status = dma_cookie_status(&channel->vc.chan, cookie, NULL);
> - if (status == DMA_COMPLETE)
> - return 0;
> -
> - /*
> - * If the cookie doesn't correspond to the currently processing virtual
> - * descriptor then the descriptor hasn't been processed yet, and the
> - * residue is equal to the full descriptor size. Also, a client driver
> - * is possible to call this function before rz_dmac_irq_handler_thread()
> - * runs. In this case, the running descriptor will be the next
> - * descriptor, and will appear in the done list. So, if the argument
> - * cookie matches the done list's cookie, we can assume the residue is
> - * zero.
> - */
> - if (cookie != current_desc->vd.tx.cookie) {
> - list_for_each_entry(desc, &channel->ld_free, node) {
> - if (cookie == desc->vd.tx.cookie)
> - return 0;
> - }
> -
> - list_for_each_entry(desc, &channel->ld_queue, node) {
> - if (cookie == desc->vd.tx.cookie)
> - return desc->len;
> - }
> -
> - list_for_each_entry(desc, &channel->ld_active, node) {
> - if (cookie == desc->vd.tx.cookie)
> - return desc->len;
> - }
> + vd = vchan_find_desc(&channel->vc, cookie);
> + if (vd) {
> + /* Descriptor has been issued but not yet processed. */
> + desc = to_rz_dmac_desc(vd);
> + return desc->len;
> + } else if (channel->desc && channel->desc->vd.tx.cookie == cookie) {
> + /* Descriptor is currently processed. */
> + desc = channel->desc;
> + }
>
> - /*
> - * No descriptor found for the cookie, there's thus no residue.
> - * This shouldn't happen if the calling driver passes a correct
> - * cookie value.
> - */
> - WARN(1, "No descriptor for cookie!");
> + if (!desc) {
> + /* Descriptor was not found. May be already completed by now. */
> return 0;
> }
>
> @@ -813,7 +768,7 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
> * Calculate number of bytes transferred in processing virtual descriptor.
> * One virtual descriptor can have many lmdesc.
> */
> - return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, current_desc, crla);
> + return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, desc, crla);
> }
>
> static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
> @@ -824,21 +779,17 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
> enum dma_status status;
> u32 residue;
>
> - status = dma_cookie_status(chan, cookie, txstate);
> - if (status == DMA_COMPLETE || !txstate)
> - return status;
> -
> scoped_guard(spinlock_irqsave, &channel->vc.lock) {
> + status = dma_cookie_status(chan, cookie, txstate);
> + if (status == DMA_COMPLETE || !txstate)
> + return status;
> +
> residue = rz_dmac_chan_get_residue(channel, cookie);
>
> - if (rz_dmac_chan_is_paused(channel))
> + if (status == DMA_IN_PROGRESS && rz_dmac_chan_is_paused(channel))
> status = DMA_PAUSED;
> }
>
> - /* if there's no residue and no paused, the cookie is complete */
> - if (!residue && status != DMA_PAUSED)
> - return DMA_COMPLETE;
> -
> dma_set_residue(txstate, residue);
>
> return status;
> @@ -918,28 +869,18 @@ static irqreturn_t rz_dmac_irq_handler(int irq, void *dev_id)
> static irqreturn_t rz_dmac_irq_handler_thread(int irq, void *dev_id)
> {
> struct rz_dmac_chan *channel = dev_id;
> - struct rz_dmac_desc *desc = NULL;
> - unsigned long flags;
> + struct rz_dmac_desc *desc;
>
> - spin_lock_irqsave(&channel->vc.lock, flags);
> + guard(spinlock_irqsave)(&channel->vc.lock);
>
> - if (list_empty(&channel->ld_active)) {
> - /* Someone might have called terminate all */
> - goto out;
> - }
> + desc = channel->desc;
> + if (!desc)
> + return IRQ_HANDLED;
>
> - desc = list_first_entry(&channel->ld_active, struct rz_dmac_desc, node);
> vchan_cookie_complete(&desc->vd);
> - list_move_tail(channel->ld_active.next, &channel->ld_free);
> - if (!list_empty(&channel->ld_queue)) {
> - desc = list_first_entry(&channel->ld_queue, struct rz_dmac_desc,
> - node);
> - channel->desc = desc;
> - if (rz_dmac_xfer_desc(channel) == 0)
> - list_move_tail(channel->ld_queue.next, &channel->ld_active);
> - }
> -out:
> - spin_unlock_irqrestore(&channel->vc.lock, flags);
> + channel->desc = NULL;
> +
> + rz_dmac_xfer_desc(channel);
>
> return IRQ_HANDLED;
> }
> @@ -1021,9 +962,7 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac,
>
> channel->vc.desc_free = rz_dmac_virt_desc_free;
> vchan_init(&channel->vc, &dmac->engine);
> - INIT_LIST_HEAD(&channel->ld_queue);
> INIT_LIST_HEAD(&channel->ld_free);
> - INIT_LIST_HEAD(&channel->ld_active);
>
> /* Initialize register for each channel */
> rz_dmac_disable_hw(channel);
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 10/18] dmaengine: sh: rz-dmac: Refactor pause/resume code
2026-05-26 8:47 ` [PATCH v6 10/18] dmaengine: sh: rz-dmac: Refactor pause/resume code Claudiu Beznea
2026-05-26 9:28 ` sashiko-bot
@ 2026-05-28 13:50 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:50 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
John Madieu
On Tue, May 26, 2026 at 11:47:02AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Subsequent patches will add suspend/resume and cyclic DMA support to the
> rz-dmac driver. This support needs to work on SoCs where power to most
> components (including DMA) is turned off during system suspend. For this,
> some channels (for example cyclic ones) may need to be paused and resumed
> manually by the DMA driver during system suspend/resume.
>
> Refactor the pause/resume support so the same code can be reused in the
> system suspend/resume path.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - reset channel->status in rz_dmac_free_chan_resources() and
> rz_dmac_terminate_all()
>
> Changes in v3:
> - none, this patch new new
>
> drivers/dma/sh/rz-dmac.c | 73 ++++++++++++++++++++++++++++++++++------
> 1 file changed, 62 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 1f884ec101f8..557364443a5f 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -18,6 +18,7 @@
> #include <linux/irqchip/irq-renesas-rzv2h.h>
> #include <linux/irqchip/irq-renesas-rzt2h.h>
> #include <linux/list.h>
> +#include <linux/lockdep.h>
> #include <linux/module.h>
> #include <linux/of.h>
> #include <linux/of_dma.h>
> @@ -63,6 +64,14 @@ struct rz_dmac_desc {
>
> #define to_rz_dmac_desc(d) container_of(d, struct rz_dmac_desc, vd)
>
> +/**
> + * enum rz_dmac_chan_status: RZ DMAC channel status
> + * @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks
> + */
> +enum rz_dmac_chan_status {
> + RZ_DMAC_CHAN_STATUS_PAUSED,
> +};
> +
> struct rz_dmac_chan {
> struct virt_dma_chan vc;
> void __iomem *ch_base;
> @@ -74,6 +83,8 @@ struct rz_dmac_chan {
> dma_addr_t src_per_address;
> dma_addr_t dst_per_address;
>
> + unsigned long status;
> +
> u32 chcfg;
> u32 chctrl;
> int mid_rid;
> @@ -491,6 +502,8 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
> channel->mid_rid = -EINVAL;
> }
>
> + channel->status = 0;
> +
> spin_unlock_irqrestore(&channel->vc.lock, flags);
>
> vchan_free_chan_resources(&channel->vc);
> @@ -589,6 +602,9 @@ static int rz_dmac_terminate_all(struct dma_chan *chan)
> }
>
> vchan_get_all_descriptors(&channel->vc, &head);
> +
> + channel->status = 0;
> +
> spin_unlock_irqrestore(&channel->vc.lock, flags);
> vchan_dma_desc_free_list(&channel->vc, &head);
>
> @@ -795,35 +811,70 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
> return status;
> }
>
> -static int rz_dmac_device_pause(struct dma_chan *chan)
> +static int rz_dmac_device_pause_set(struct rz_dmac_chan *channel,
> + unsigned long set_bitmask)
> {
> - struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> + int ret = 0;
> u32 val;
>
> - guard(spinlock_irqsave)(&channel->vc.lock);
> + lockdep_assert_held(&channel->vc.lock);
>
> if (!rz_dmac_chan_is_enabled(channel))
> return 0;
>
> + if (rz_dmac_chan_is_paused(channel))
> + goto set_bit;
> +
> rz_dmac_ch_writel(channel, CHCTRL_SETSUS, CHCTRL, 1);
> - return read_poll_timeout_atomic(rz_dmac_ch_readl, val,
> - (val & CHSTAT_SUS), 1, 1024,
> - false, channel, CHSTAT, 1);
> + ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val,
> + (val & CHSTAT_SUS), 1, 1024, false,
> + channel, CHSTAT, 1);
> +
> +set_bit:
> + channel->status |= set_bitmask;
> +
> + return ret;
> }
>
> -static int rz_dmac_device_resume(struct dma_chan *chan)
> +static int rz_dmac_device_pause(struct dma_chan *chan)
> {
> struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> - u32 val;
>
> guard(spinlock_irqsave)(&channel->vc.lock);
>
> + return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED));
> +}
> +
> +static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel,
> + unsigned long clear_bitmask)
> +{
> + int ret = 0;
> + u32 val;
> +
> + lockdep_assert_held(&channel->vc.lock);
> +
> /* Do not check CHSTAT_SUS but rely on HW capabilities. */
>
> rz_dmac_ch_writel(channel, CHCTRL_CLRSUS, CHCTRL, 1);
> - return read_poll_timeout_atomic(rz_dmac_ch_readl, val,
> - !(val & CHSTAT_SUS), 1, 1024,
> - false, channel, CHSTAT, 1);
> + ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val,
> + !(val & CHSTAT_SUS), 1, 1024, false,
> + channel, CHSTAT, 1);
> +
> + channel->status &= ~clear_bitmask;
> +
> + return ret;
> +}
> +
> +static int rz_dmac_device_resume(struct dma_chan *chan)
> +{
> + struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> +
> + guard(spinlock_irqsave)(&channel->vc.lock);
> +
> + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED)))
> + return 0;
> +
> + return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED));
> }
>
> /*
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 11/18] dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with CHCTRL_SETEN
2026-05-26 8:47 ` [PATCH v6 11/18] dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with CHCTRL_SETEN Claudiu Beznea
2026-05-26 9:11 ` sashiko-bot
@ 2026-05-28 13:50 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:50 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
Frank Li, John Madieu
On Tue, May 26, 2026 at 11:47:03AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> The CHCTRL_SETEN bit is explicitly set in rz_dmac_enable_hw(). Updating
> struct rz_dmac_chan::chctrl with this bit in
> rz_dmac_prepare_desc_for_memcpy() and rz_dmac_prepare_descs_for_slave_sg()
> is unnecessary in the current code base. Moreover, it conflicts with the
> configuration sequence that will be used for cyclic DMA channels during
> suspend to RAM. Cyclic DMA support will be introduced in subsequent
> commits.
>
> This is a preparatory commit for cyclic DMA suspend to RAM support.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - set channel->chctrl = 0 in rz_dmac_prepare_descs_for_slave_sg()
>
> Changes in v3:
> - none
>
> Changes in v2:
> - fixed typos in patch title and patch description
>
> drivers/dma/sh/rz-dmac.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 557364443a5f..c9c00650ddd5 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -377,7 +377,7 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel)
> rz_dmac_set_dma_req_no(dmac, channel->index, dmac->info->default_dma_req_no);
>
> channel->chcfg = chcfg;
> - channel->chctrl = CHCTRL_STG | CHCTRL_SETEN;
> + channel->chctrl = CHCTRL_STG;
> }
>
> static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
> @@ -428,7 +428,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
>
> rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid);
>
> - channel->chctrl = CHCTRL_SETEN;
> + channel->chctrl = 0;
> }
>
> static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 12/18] dmaengine: sh: rz-dmac: Add cyclic DMA support
2026-05-26 8:47 ` [PATCH v6 12/18] dmaengine: sh: rz-dmac: Add cyclic DMA support Claudiu Beznea
2026-05-26 9:31 ` sashiko-bot
@ 2026-05-28 13:51 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:51 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
John Madieu
On Tue, May 26, 2026 at 11:47:04AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Add cyclic DMA support to the RZ DMAC driver. A per-channel status bit is
> introduced to mark cyclic channels and is set during the DMA prepare
> callback. The IRQ handler checks this status bit and calls
> vchan_cyclic_callback() accordingly.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - drop the nxla update logic in rz_dmac_lmdesc_recycle() as this is
> not needed for any kind of transfers
> - drop the update of channel->status = 0 from rz_dmac_free_chan_resources()
> and rz_dmac_terminate_all() as this was moved in patch 09/17
>
> Changes in v3:
> - updated rz_dmac_lmdesc_recycle() to restore the lmdesc->nxla
> - in rz_dmac_prepare_descs_for_cyclic() update directly the
> desc->start_lmdesc with the descriptor pointer insted of the
> descriptor address
> - used rz_dmac_lmdesc_addr() to compute the descritor address
> - set channel->status = 0 in rz_dmac_free_chan_resources()
> - in rz_dmac_prep_dma_cyclic() check for invalid periods or buffer len
> and limit the critical area protected by spinlock
> - set channel->status = 0 in rz_dmac_terminate_all()
> - updated rz_dmac_calculate_residue_bytes_in_vd() to use
> rz_dmac_lmdesc_addr()
> - dropped goto in rz_dmac_irq_handler_thread() as it is not needed
> anymore; dropped also the local variable desc
>
> Changes in v2:
> - none
>
> drivers/dma/sh/rz-dmac.c | 136 +++++++++++++++++++++++++++++++++++++--
> 1 file changed, 130 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index c9c00650ddd5..8fd8a4bd9cc9 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -35,6 +35,7 @@
> enum rz_dmac_prep_type {
> RZ_DMAC_DESC_MEMCPY,
> RZ_DMAC_DESC_SLAVE_SG,
> + RZ_DMAC_DESC_CYCLIC,
> };
>
> struct rz_lmdesc {
> @@ -67,9 +68,11 @@ struct rz_dmac_desc {
> /**
> * enum rz_dmac_chan_status: RZ DMAC channel status
> * @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks
> + * @RZ_DMAC_CHAN_STATUS_CYCLIC: Channel is cyclic
> */
> enum rz_dmac_chan_status {
> RZ_DMAC_CHAN_STATUS_PAUSED,
> + RZ_DMAC_CHAN_STATUS_CYCLIC,
> };
>
> struct rz_dmac_chan {
> @@ -191,6 +194,7 @@ struct rz_dmac {
>
> /* LINK MODE DESCRIPTOR */
> #define HEADER_LV BIT(0)
> +#define HEADER_WBD BIT(2)
>
> #define RZ_DMAC_MAX_CHAN_DESCRIPTORS 16
> #define RZ_DMAC_MAX_CHANNELS 16
> @@ -431,6 +435,57 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
> channel->chctrl = 0;
> }
>
> +static void rz_dmac_prepare_descs_for_cyclic(struct rz_dmac_chan *channel)
> +{
> + struct dma_chan *chan = &channel->vc.chan;
> + struct rz_dmac *dmac = to_rz_dmac(chan->device);
> + struct rz_dmac_desc *d = channel->desc;
> + size_t period_len = d->sgcount;
> + struct rz_lmdesc *lmdesc;
> + size_t buf_len = d->len;
> + size_t periods = buf_len / period_len;
> +
> + lockdep_assert_held(&channel->vc.lock);
> +
> + channel->chcfg |= CHCFG_SEL(channel->index) | CHCFG_DMS;
> +
> + if (d->direction == DMA_DEV_TO_MEM) {
> + channel->chcfg |= CHCFG_SAD;
> + channel->chcfg &= ~CHCFG_REQD;
> + } else {
> + channel->chcfg |= CHCFG_DAD | CHCFG_REQD;
> + }
> +
> + lmdesc = channel->lmdesc.tail;
> + d->start_lmdesc = lmdesc;
> +
> + for (size_t i = 0; i < periods; i++) {
> + if (d->direction == DMA_DEV_TO_MEM) {
> + lmdesc->sa = d->src;
> + lmdesc->da = d->dest + (i * period_len);
> + } else {
> + lmdesc->sa = d->src + (i * period_len);
> + lmdesc->da = d->dest;
> + }
> +
> + lmdesc->tb = period_len;
> + lmdesc->chitvl = 0;
> + lmdesc->chext = 0;
> + lmdesc->chcfg = channel->chcfg;
> + lmdesc->header = HEADER_LV | HEADER_WBD;
> +
> + if (i == periods - 1)
> + lmdesc->nxla = rz_dmac_lmdesc_addr(channel, d->start_lmdesc);
> +
> + if (++lmdesc >= (channel->lmdesc.base + DMAC_NR_LMDESC))
> + lmdesc = channel->lmdesc.base;
> + }
> +
> + channel->lmdesc.tail = lmdesc;
> +
> + rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid);
> +}
> +
> static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
> {
> struct virt_dma_desc *vd;
> @@ -452,6 +507,10 @@ static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
> case RZ_DMAC_DESC_SLAVE_SG:
> rz_dmac_prepare_descs_for_slave_sg(chan);
> break;
> +
> + case RZ_DMAC_DESC_CYCLIC:
> + rz_dmac_prepare_descs_for_cyclic(chan);
> + break;
> }
>
> rz_dmac_enable_hw(chan);
> @@ -586,6 +645,55 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
> return vchan_tx_prep(&channel->vc, &desc->vd, flags);
> }
>
> +static struct dma_async_tx_descriptor *
> +rz_dmac_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr,
> + size_t buf_len, size_t period_len,
> + enum dma_transfer_direction direction,
> + unsigned long flags)
> +{
> + struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> + struct rz_dmac_desc *desc;
> + size_t periods;
> +
> + if (!is_slave_direction(direction))
> + return NULL;
> +
> + if (!period_len || !buf_len)
> + return NULL;
> +
> + periods = buf_len / period_len;
> + if (!periods || periods > DMAC_NR_LMDESC)
> + return NULL;
> +
> + scoped_guard(spinlock_irqsave, &channel->vc.lock) {
> + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC))
> + return NULL;
> +
> + desc = list_first_entry_or_null(&channel->ld_free, struct rz_dmac_desc, node);
> + if (!desc)
> + return NULL;
> +
> + list_del(&desc->node);
> +
> + channel->status |= BIT(RZ_DMAC_CHAN_STATUS_CYCLIC);
> + }
> +
> + desc->type = RZ_DMAC_DESC_CYCLIC;
> + desc->sgcount = period_len;
> + desc->len = buf_len;
> + desc->direction = direction;
> +
> + if (direction == DMA_DEV_TO_MEM) {
> + desc->src = channel->src_per_address;
> + desc->dest = buf_addr;
> + } else {
> + desc->src = buf_addr;
> + desc->dest = channel->dst_per_address;
> + }
> +
> + return vchan_tx_prep(&channel->vc, &desc->vd, flags);
> +}
> +
> static int rz_dmac_terminate_all(struct dma_chan *chan)
> {
> struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> @@ -733,9 +841,18 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
> }
>
> /* Calculate residue from next lmdesc to end of virtual desc */
> - while (lmdesc->chcfg & CHCFG_DEM) {
> - residue += lmdesc->tb;
> - lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
> + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) {
> + u32 start_lmdesc_addr = rz_dmac_lmdesc_addr(channel, desc->start_lmdesc);
> +
> + while (lmdesc->nxla != start_lmdesc_addr) {
> + residue += lmdesc->tb;
> + lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
> + }
> + } else {
> + while (lmdesc->chcfg & CHCFG_DEM) {
> + residue += lmdesc->tb;
> + lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
> + }
> }
>
> dev_dbg(dmac->dev, "%s: VD residue is %u\n", __func__, residue);
> @@ -928,10 +1045,14 @@ static irqreturn_t rz_dmac_irq_handler_thread(int irq, void *dev_id)
> if (!desc)
> return IRQ_HANDLED;
>
> - vchan_cookie_complete(&desc->vd);
> - channel->desc = NULL;
> + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) {
> + vchan_cyclic_callback(&desc->vd);
> + } else {
> + vchan_cookie_complete(&desc->vd);
> + channel->desc = NULL;
>
> - rz_dmac_xfer_desc(channel);
> + rz_dmac_xfer_desc(channel);
> + }
>
> return IRQ_HANDLED;
> }
> @@ -1183,6 +1304,8 @@ static int rz_dmac_probe(struct platform_device *pdev)
> engine = &dmac->engine;
> dma_cap_set(DMA_SLAVE, engine->cap_mask);
> dma_cap_set(DMA_MEMCPY, engine->cap_mask);
> + dma_cap_set(DMA_CYCLIC, engine->cap_mask);
> + engine->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> engine->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
> rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_0_7_COMMON_BASE + DCTRL);
> rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_8_15_COMMON_BASE + DCTRL);
> @@ -1194,6 +1317,7 @@ static int rz_dmac_probe(struct platform_device *pdev)
> engine->device_tx_status = rz_dmac_tx_status;
> engine->device_prep_slave_sg = rz_dmac_prep_slave_sg;
> engine->device_prep_dma_memcpy = rz_dmac_prep_dma_memcpy;
> + engine->device_prep_dma_cyclic = rz_dmac_prep_dma_cyclic;
> engine->device_config = rz_dmac_config;
> engine->device_terminate_all = rz_dmac_terminate_all;
> engine->device_issue_pending = rz_dmac_issue_pending;
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 13/18] dmaengine: sh: rz-dmac: Adjust rz_dmac_chan_get_residue() to return error codes
2026-05-26 8:47 ` [PATCH v6 13/18] dmaengine: sh: rz-dmac: Adjust rz_dmac_chan_get_residue() to return error codes Claudiu Beznea
@ 2026-05-28 13:51 ` Tommaso Merciai
0 siblings, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:51 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea
On Tue, May 26, 2026 at 11:47:05AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Adjust rz_dmac_chan_get_residue() to return error codes on failure and
> provide the residue to callers through the residue parameter. This
> prepares the code for the addition of runtime PM support.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 19 ++++++++++++++-----
> 1 file changed, 14 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 8fd8a4bd9cc9..93394b9934c8 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -860,8 +860,8 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
> return residue;
> }
>
> -static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
> - dma_cookie_t cookie)
> +static int rz_dmac_chan_get_residue(struct device *dev, struct rz_dmac_chan *channel,
> + dma_cookie_t cookie, u32 *residue)
> {
> struct rz_dmac_desc *desc = NULL;
> struct virt_dma_desc *vd;
> @@ -871,7 +871,8 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
> if (vd) {
> /* Descriptor has been issued but not yet processed. */
> desc = to_rz_dmac_desc(vd);
> - return desc->len;
> + *residue = desc->len;
> + return 0;
> } else if (channel->desc && channel->desc->vd.tx.cookie == cookie) {
> /* Descriptor is currently processed. */
> desc = channel->desc;
> @@ -879,6 +880,7 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
>
> if (!desc) {
> /* Descriptor was not found. May be already completed by now. */
> + *residue = 0;
> return 0;
> }
>
> @@ -901,7 +903,9 @@ static u32 rz_dmac_chan_get_residue(struct rz_dmac_chan *channel,
> * Calculate number of bytes transferred in processing virtual descriptor.
> * One virtual descriptor can have many lmdesc.
> */
> - return crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, desc, crla);
> + *residue = crtb + rz_dmac_calculate_residue_bytes_in_vd(channel, desc, crla);
> +
> + return 0;
> }
>
> static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
> @@ -909,15 +913,20 @@ static enum dma_status rz_dmac_tx_status(struct dma_chan *chan,
> struct dma_tx_state *txstate)
> {
> struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> + struct rz_dmac *dmac = to_rz_dmac(chan->device);
> enum dma_status status;
> u32 residue;
>
> scoped_guard(spinlock_irqsave, &channel->vc.lock) {
> + int ret;
> +
> status = dma_cookie_status(chan, cookie, txstate);
> if (status == DMA_COMPLETE || !txstate)
> return status;
>
> - residue = rz_dmac_chan_get_residue(channel, cookie);
> + ret = rz_dmac_chan_get_residue(dmac->dev, channel, cookie, &residue);
> + if (ret)
> + return DMA_ERROR;
>
> if (status == DMA_IN_PROGRESS && rz_dmac_chan_is_paused(channel))
> status = DMA_PAUSED;
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 14/18] dmaengine: sh: rz-dmac: Add runtime PM support
2026-05-26 8:47 ` [PATCH v6 14/18] dmaengine: sh: rz-dmac: Add runtime PM support Claudiu Beznea
2026-05-26 9:57 ` sashiko-bot
@ 2026-05-28 13:52 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 13:52 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
John Madieu
On Tue, May 26, 2026 at 11:47:06AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> Protect the driver exposed APIs with runtime PM suspend/resume calls
> before accessing HW registers. As the current driver leaves runtime PM
> enabled in probe, the purpose of the changes in this patch is to avoid
> accessing HW registers after a failed system suspend leaves the runtime
> PM state of the device improperly reinitialized.
>
> In that case, the driver remains bound to the device, the APIs are still
> exposed, and any access to HW registers without runtime resuming the
> device may lead to synchronous aborts.
>
> To avoid leaking resources in case of runtime PM failures, save the error
> code returned by PM_RUNTIME_ACQUIRE_ERR() in rz_dmac_terminate_all() and
> return it only at the end of the function to allow the cleanup code to
> run. A similar approach is used in rz_dmac_free_chan_resources().
>
> Because some exposed APIs (e.g. ->device_terminate_all()) may be called
> from atomic context according to the documentation, mark the DMA device as
> pm_runtime_irq_safe().
>
> This patch prepares the driver for suspend-to-RAM support.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - updated patch description
> - collected tags
> - in rz_dmac_free_chan_resources() and rz_dmac_terminate_all() don't touch
> the HW registers if runtime resume failed but allow freeing resources
> as suggested by sashiko; along with it added debug messages in case the
> RPM resume failed
> - dropped the runtime resume from rz_dmac_xfer_desc() and move it instead
> in rz_dmac_issue_pending() only to avoid calling rpm resume code in
> interrupt path as, if we are in the interrupt path the device is sanely
> in runtime resume state
> - moved the RPM resume code in from rz_dmac_tx_status to
> rz_dmac_chan_get_residue(), as close as possible to the HW registers read
> to avoid RPM resume in case the residue could be returned w/o interracting
> with the HW
> - updated patch description
>
> Changes in v5:
> - none, this patch is new
>
> drivers/dma/sh/rz-dmac.c | 60 ++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 57 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 93394b9934c8..bd4ca8e939f1 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -549,12 +549,22 @@ static void rz_dmac_free_chan_resources(struct dma_chan *chan)
> struct rz_dmac *dmac = to_rz_dmac(chan->device);
> struct rz_dmac_desc *desc, *_desc;
> unsigned long flags;
> + int ret;
> +
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret) {
> + dev_err(dmac->dev, "RPM resume failed for channel %s, ret=%d\n!",
> + dma_chan_name(chan), ret);
> + }
>
> spin_lock_irqsave(&channel->vc.lock, flags);
>
> rz_lmdesc_setup(channel, channel->lmdesc.base);
>
> - rz_dmac_disable_hw(channel);
> + /* Skip touching HW if RPM resume failed. Let the cleanup do its jobs. */
> + if (!ret)
> + rz_dmac_disable_hw(channel);
>
> if (channel->mid_rid >= 0) {
> clear_bit(channel->mid_rid, dmac->modules);
> @@ -697,11 +707,22 @@ rz_dmac_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr,
> static int rz_dmac_terminate_all(struct dma_chan *chan)
> {
> struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> + struct rz_dmac *dmac = to_rz_dmac(chan->device);
> unsigned long flags;
> LIST_HEAD(head);
> + int ret;
> +
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret) {
> + dev_err(dmac->dev, "RPM resume failed for channel %s, ret=%d\n!",
> + dma_chan_name(chan), ret);
> + }
>
> spin_lock_irqsave(&channel->vc.lock, flags);
> - rz_dmac_disable_hw(channel);
> + /* Don't return if RPM failed. Let the cleanup do its jobs. */
> + if (!ret)
> + rz_dmac_disable_hw(channel);
> rz_lmdesc_setup(channel, channel->lmdesc.base);
>
> if (channel->desc) {
> @@ -716,13 +737,20 @@ static int rz_dmac_terminate_all(struct dma_chan *chan)
> spin_unlock_irqrestore(&channel->vc.lock, flags);
> vchan_dma_desc_free_list(&channel->vc, &head);
>
> - return 0;
> + return ret;
> }
>
> static void rz_dmac_issue_pending(struct dma_chan *chan)
> {
> struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> + struct rz_dmac *dmac = to_rz_dmac(chan->device);
> unsigned long flags;
> + int ret;
> +
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret)
> + return;
>
> spin_lock_irqsave(&channel->vc.lock, flags);
>
> @@ -807,6 +835,11 @@ static void rz_dmac_device_synchronize(struct dma_chan *chan)
>
> vchan_synchronize(&channel->vc);
>
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret)
> + return;
> +
> ret = read_poll_timeout(rz_dmac_ch_readl, chstat, !(chstat & CHSTAT_EN),
> 100, 100000, false, channel, CHSTAT, 1);
> if (ret < 0)
> @@ -866,6 +899,7 @@ static int rz_dmac_chan_get_residue(struct device *dev, struct rz_dmac_chan *cha
> struct rz_dmac_desc *desc = NULL;
> struct virt_dma_desc *vd;
> u32 crla, crtb, i;
> + int ret;
>
> vd = vchan_find_desc(&channel->vc, cookie);
> if (vd) {
> @@ -884,6 +918,11 @@ static int rz_dmac_chan_get_residue(struct device *dev, struct rz_dmac_chan *cha
> return 0;
> }
>
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret)
> + return ret;
> +
> /*
> * We need to read two registers. Make sure the hardware does not move
> * to next lmdesc while reading the current lmdesc. Trying it 3 times
> @@ -965,6 +1004,13 @@ static int rz_dmac_device_pause_set(struct rz_dmac_chan *channel,
> static int rz_dmac_device_pause(struct dma_chan *chan)
> {
> struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> + struct rz_dmac *dmac = to_rz_dmac(chan->device);
> + int ret;
> +
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret)
> + return ret;
>
> guard(spinlock_irqsave)(&channel->vc.lock);
>
> @@ -994,6 +1040,13 @@ static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel,
> static int rz_dmac_device_resume(struct dma_chan *chan)
> {
> struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
> + struct rz_dmac *dmac = to_rz_dmac(chan->device);
> + int ret;
> +
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret)
> + return ret;
>
> guard(spinlock_irqsave)(&channel->vc.lock);
>
> @@ -1274,6 +1327,7 @@ static int rz_dmac_probe(struct platform_device *pdev)
> return dev_err_probe(&pdev->dev, PTR_ERR(dmac->rstc),
> "failed to get resets\n");
>
> + pm_runtime_irq_safe(&pdev->dev);
> pm_runtime_enable(&pdev->dev);
> ret = pm_runtime_resume_and_get(&pdev->dev);
> if (ret < 0) {
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 15/18] dmaengine: sh: rz-dmac: Add suspend to RAM support
2026-05-26 8:47 ` [PATCH v6 15/18] dmaengine: sh: rz-dmac: Add suspend to RAM support Claudiu Beznea
2026-05-26 9:43 ` sashiko-bot
@ 2026-05-28 14:38 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 14:38 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
John Madieu
On Tue, May 26, 2026 at 11:47:07AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> The Renesas RZ/G3S SoC supports a power saving mode in which power to most
> of the SoC components is turned off, including the DMA IP. Add suspend to
> RAM support to save and restore the DMA IP registers.
>
> Cyclic DMA channels require special handling. Since they can be paused and
> resumed during system suspend/resume, the driver restores additional
> registers for these channels during the system resume phase. If a channel
> was not explicitly paused during suspend, the driver ensures that it is
> paused and resumed as part of the system suspend/resume flow.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - collected tags
> - dropped rz_dmac_suspend_prepare() as I found issues with it and updated the
> patch description
> - with it used DEFINE_SIMPLE_DEV_PM_OPS() for PM ops
> - used pm_ptr() instead of pm_sleep_ptr()
>
> Changes in v5:
> - runtime PM enable in rz_dmac_suspend_prepare() and rz_dmac_suspend_recover()
> - initialize ret in rz_dmac_suspend()
> - in suspend/resume APIs changed the order b/w runtime PM and reset calls
> to follow the sequence present in remove and probe
> - in rz_dmac_suspend(): take into account the error code returned by
> pm_runtime_put_sync()
> - in rz_dmac_resume(): use "return errors ? : 0;" instead of
> "return errors ? : ret;"
>
> Changes in v4:
> - in rz_dmac_device_synchronize() kept the read_poll_timeout() as
> this doesn't fail anymore with the proper status return from
> ->device_tx_status() API in case the channel is paused; with it
> the patch description was updated
> - keep the cleanup path in rz_dmac_suspend() simpler to avoid
> confusion when using guard()
> - used SYSTEM_SLEEP_PM_OPS() as there is no need for having the
> suspend/resume callbacks being called in NOIRQ phase
>
> Changes in v3:
> - dropped RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED
> - dropped read_poll_timeout() from rz_dmac_device_synchronze() as
> with audio drivers this times out all the time on suspend because
> the audio DMA is already paused when the rz_dmac_device_synchronize()
> is called; updated the commit description to describe this change
> - call rz_dmac_device_pause_internal() only if RZ_DMAC_CHAN_STATUS_PAUSED
> bit is not set or the device is enabled in HW
> - updated rz_dmac_device_resume_set() to have it simpler and cover
> the cases when it is called with the channel enabled or paused;
> updated the comment describing the covered use cases
> - call rz_dmac_device_resume_internal() only if
> RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL bit is set
> - in rz_dmac_chan_is_enabled() return -EAGAIN only if the channel is
> enabled in HW
> - in rz_dmac_suspend_recover() drop the update of
> RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED as this is not available anymore
> - in rz_dmac_suspend() call rz_dmac_device_pause_internal() unconditionally
> as the logic is now handled inside the called function; also, do not
> ignore anymore the failure of internal suspend and abort the suspend
> instead
> - report channel internal resume failures in rz_dmac_resume()
> - use rz_dmac_disable_hw() instead of open coding it in rz_dmac_resume()
> - call rz_dmac_device_resume_internal() uncoditionally as the skip
> logic is now handled in the function itself
> - use NOIRQ_SYSTEM_SLEEP_PM_OPS()
> - didn't collect Tommaso's Tb tag as the series was changed a lot since
> v2
>
> Changes in v2:
> - fixed typos in patch description
> - in rz_dmac_suspend_prepare(): return -EAGAIN based on the value returned
> by vchan_issue_pending()
> - in rz_dmac_suspend_recover(): clear RZ_DMAC_CHAN_STATUS_SYS_SUSPENDED for
> non cyclic channels
> - in rz_dmac_resume(): call rz_dmac_set_dma_req_no() only for cyclic channels
>
> drivers/dma/sh/rz-dmac.c | 180 +++++++++++++++++++++++++++++++++++++--
> 1 file changed, 175 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index bd4ca8e939f1..2a7124e4aea3 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -69,10 +69,12 @@ struct rz_dmac_desc {
> * enum rz_dmac_chan_status: RZ DMAC channel status
> * @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks
> * @RZ_DMAC_CHAN_STATUS_CYCLIC: Channel is cyclic
> + * @RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL: Channel is paused through driver internal logic
> */
> enum rz_dmac_chan_status {
> RZ_DMAC_CHAN_STATUS_PAUSED,
> RZ_DMAC_CHAN_STATUS_CYCLIC,
> + RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL,
> };
>
> struct rz_dmac_chan {
> @@ -92,6 +94,10 @@ struct rz_dmac_chan {
> u32 chctrl;
> int mid_rid;
>
> + struct {
> + u32 nxla;
> + } pm_state;
> +
> struct list_head ld_free;
>
> struct {
> @@ -1017,20 +1023,57 @@ static int rz_dmac_device_pause(struct dma_chan *chan)
> return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED));
> }
>
> +static int rz_dmac_device_pause_internal(struct rz_dmac_chan *channel)
> +{
> + lockdep_assert_held(&channel->vc.lock);
> +
> + /* Skip channels explicitly paused by consummers or disabled. */
> + if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED) ||
> + !rz_dmac_chan_is_enabled(channel))
> + return 0;
> +
> + return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL));
> +}
> +
> static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel,
> unsigned long clear_bitmask)
> {
> - int ret = 0;
> u32 val;
> + int ret;
>
> lockdep_assert_held(&channel->vc.lock);
>
> - /* Do not check CHSTAT_SUS but rely on HW capabilities. */
> + /*
> + * We can be:
> + *
> + * 1/ after the channel was paused by a consummer and now it
> + * needs to be resummed
> + * 2/ after the channel was paused internally (as a result of
> + * a system suspend with power loss or not)
> + * 3/ after the channel was paused by a consummer, the system
> + * went through a system suspend (with power loss or not)
> + * and the consummer wants to resume the channel
> + *
> + * To cover all the above cases we set both CLRSUS and SETEN.
> + *
> + * In case 1/ setting SETEN while the channel is still enabled
> + * is harmless for the controller.
> + *
> + * In case 2/ the channel is disabled when calling this function
> + * and setting CLRSUS is harmless for the controller as the
> + * channel is disabled anyway.
> + *
> + * In case 3/ the channel is disabled/enabled if the system
> + * went though a suspend with power loss/or not and setting
> + * CLRSUS/SETEN is harmless for the controller as the channel
> + * is enabled/disabled anyway.
> + */
> +
> + rz_dmac_ch_writel(channel, CHCTRL_CLRSUS | CHCTRL_SETEN, CHCTRL, 1);
>
> - rz_dmac_ch_writel(channel, CHCTRL_CLRSUS, CHCTRL, 1);
> ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val,
> - !(val & CHSTAT_SUS), 1, 1024, false,
> - channel, CHSTAT, 1);
> + ((val & (CHSTAT_SUS | CHSTAT_EN)) == CHSTAT_EN),
> + 1, 1024, false, channel, CHSTAT, 1);
>
> channel->status &= ~clear_bitmask;
>
> @@ -1056,6 +1099,16 @@ static int rz_dmac_device_resume(struct dma_chan *chan)
> return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED));
> }
>
> +static int rz_dmac_device_resume_internal(struct rz_dmac_chan *channel)
> +{
> + lockdep_assert_held(&channel->vc.lock);
> +
> + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL)))
> + return 0;
> +
> + return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL));
> +}
> +
> /*
> * -----------------------------------------------------------------------------
> * IRQ handling
> @@ -1421,6 +1474,122 @@ static void rz_dmac_remove(struct platform_device *pdev)
> pm_runtime_disable(&pdev->dev);
> }
>
> +static void rz_dmac_suspend_recover(struct rz_dmac *dmac)
> +{
> + int ret;
> +
> + PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
> + ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> + if (ret)
> + return;
> +
> + for (unsigned int i = 0; i < dmac->n_channels; i++) {
> + struct rz_dmac_chan *channel = &dmac->channels[i];
> +
> + guard(spinlock_irqsave)(&channel->vc.lock);
> +
> + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
> + continue;
> +
> + rz_dmac_device_resume_internal(channel);
> + }
> +}
> +
> +static int rz_dmac_suspend(struct device *dev)
> +{
> + struct rz_dmac *dmac = dev_get_drvdata(dev);
> + int ret = 0;
> +
> + for (unsigned int i = 0; i < dmac->n_channels; i++) {
> + struct rz_dmac_chan *channel = &dmac->channels[i];
> +
> + guard(spinlock_irqsave)(&channel->vc.lock);
> +
> + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
> + continue;
> +
> + ret = rz_dmac_device_pause_internal(channel);
> + if (ret) {
> + dev_err(dev, "Failed to suspend channel %s\n",
> + dma_chan_name(&channel->vc.chan));
> + break;
> + }
> +
> + channel->pm_state.nxla = rz_dmac_ch_readl(channel, NXLA, 1);
> + }
> +
> + if (ret)
> + goto suspend_recover;
> +
> + ret = reset_control_assert(dmac->rstc);
> + if (ret)
> + goto suspend_recover;
> +
> + ret = pm_runtime_put_sync(dev);
> + if (ret < 0)
> + goto reset_deassert;
> +
> + return 0;
> +
> +reset_deassert:
> + reset_control_deassert(dmac->rstc);
> +suspend_recover:
> + rz_dmac_suspend_recover(dmac);
> + return ret;
> +}
> +
> +static int rz_dmac_resume(struct device *dev)
> +{
> + struct rz_dmac *dmac = dev_get_drvdata(dev);
> + int errors = 0, ret;
> +
> + ret = pm_runtime_resume_and_get(dev);
> + if (ret)
> + return ret;
> +
> + ret = reset_control_deassert(dmac->rstc);
> + if (ret) {
> + /*
> + * Do not put runtime PM here and keep the same state as in
> + * probe. As subsequent suspend/resume cycles may follow, leave
> + * the runtime PM as is, here, to avoid imbalances.
> + */
> + return ret;
> + }
> +
> + rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_0_7_COMMON_BASE + DCTRL);
> + rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_8_15_COMMON_BASE + DCTRL);
> +
> + for (unsigned int i = 0; i < dmac->n_channels; i++) {
> + struct rz_dmac_chan *channel = &dmac->channels[i];
> +
> + guard(spinlock_irqsave)(&channel->vc.lock);
> +
> + rz_dmac_disable_hw(&dmac->channels[i]);
> +
> + if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
> + continue;
> +
> + rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid);
> +
> + rz_dmac_ch_writel(channel, channel->pm_state.nxla, NXLA, 1);
> + rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1);
> + rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1);
> + rz_dmac_ch_writel(channel, channel->chctrl, CHCTRL, 1);
> +
> + ret = rz_dmac_device_resume_internal(channel);
> + if (ret) {
> + errors = ret;
> + dev_err(dev, "Failed to resume channel %s, ret=%d\n",
> + dma_chan_name(&channel->vc.chan), ret);
> + }
> + }
> +
> + return errors ? : 0;
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(rz_dmac_pm_ops, rz_dmac_suspend, rz_dmac_resume);
> +
> static const struct rz_dmac_info rz_dmac_v2h_info = {
> .icu_register_dma_req = rzv2h_icu_register_dma_req,
> .default_dma_req_no = RZV2H_ICU_DMAC_REQ_NO_DEFAULT,
> @@ -1447,6 +1616,7 @@ static struct platform_driver rz_dmac_driver = {
> .driver = {
> .name = "rz-dmac",
> .of_match_table = of_rz_dmac_match,
> + .pm = pm_ptr(&rz_dmac_pm_ops),
> },
> .probe = rz_dmac_probe,
> .remove = rz_dmac_remove,
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v6 18/18] dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last descriptor
2026-05-26 8:47 ` [PATCH v6 18/18] dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last descriptor Claudiu Beznea
2026-05-26 9:43 ` sashiko-bot
@ 2026-05-28 14:39 ` Tommaso Merciai
1 sibling, 0 replies; 53+ messages in thread
From: Tommaso Merciai @ 2026-05-28 14:39 UTC (permalink / raw)
To: Claudiu Beznea
Cc: vkoul, Frank.Li, lgirdwood, broonie, perex, tiwai, biju.das.jz,
prabhakar.mahadev-lad.rj, p.zabel, geert+renesas,
kuninori.morimoto.gx, long.luu.ur, claudiu.beznea, dmaengine,
linux-kernel, linux-sound, linux-renesas-soc, Claudiu Beznea,
John Madieu
On Tue, May 26, 2026 at 11:47:10AM +0300, Claudiu Beznea wrote:
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> On an RZ/G2L-based system, it has been observed that when the DMA channels
> for all enabled IPs are active (TX and RX for one serial IP, TX and RX for
> one audio IP, and TX and RX for one SPI IP), shortly after all of them are
> started, the system can become irrecoverably blocked. In one debug session
> the system did not block, and the DMA HW registers were inspected. It was
> found that the DER (Descriptor Error) bit in the CHSTAT register for one of
> the SPI DMA channels was set.
>
> According to the RZ/G2L HW Manual, Rev. 1.30, chapter 14.4.7 Channel
> Status Register n/nS (CHSTAT_n/nS), description of the DER bit, the DER
> bit is set when the LV (Link Valid) value loaded with a descriptor in link
> mode is 0. This means that the DMA engine has loaded an invalid
> descriptor (as defined in Table 14.14, Header Area, of the same manual).
>
> The same chapter states that when a descriptor error occurs, the transfer
> is stopped, but no DMA error interrupt is generated.
>
> Set the LE bit on the last descriptor of a transfer. This informs the DMA
> engine that this is the final descriptor for the transfer.
>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v6:
> - collected tags
>
> Changes in v5:
> - none
>
> Changes in v4:
> - none
>
> Changes in v3:
> - none
>
> Changes in v2:
> - none
>
> drivers/dma/sh/rz-dmac.c | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c
> index 2a7124e4aea3..f1174d25da84 100644
> --- a/drivers/dma/sh/rz-dmac.c
> +++ b/drivers/dma/sh/rz-dmac.c
> @@ -200,6 +200,7 @@ struct rz_dmac {
>
> /* LINK MODE DESCRIPTOR */
> #define HEADER_LV BIT(0)
> +#define HEADER_LE BIT(1)
> #define HEADER_WBD BIT(2)
>
> #define RZ_DMAC_MAX_CHAN_DESCRIPTORS 16
> @@ -382,7 +383,7 @@ static void rz_dmac_prepare_desc_for_memcpy(struct rz_dmac_chan *channel)
> lmdesc->chcfg = chcfg;
> lmdesc->chitvl = 0;
> lmdesc->chext = 0;
> - lmdesc->header = HEADER_LV;
> + lmdesc->header = HEADER_LV | HEADER_LE;
>
> rz_dmac_set_dma_req_no(dmac, channel->index, dmac->info->default_dma_req_no);
>
> @@ -425,7 +426,7 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
> lmdesc->chext = 0;
> if (i == (sg_len - 1)) {
> lmdesc->chcfg = (channel->chcfg & ~CHCFG_DEM);
> - lmdesc->header = HEADER_LV;
> + lmdesc->header = HEADER_LV | HEADER_LE;
> } else {
> lmdesc->chcfg = channel->chcfg;
> lmdesc->header = HEADER_LV;
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 53+ messages in thread
end of thread, other threads:[~2026-05-28 14:40 UTC | newest]
Thread overview: 53+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-26 8:46 [PATCH v6 00/18] Renesas: dmaengine and ASoC fixes Claudiu Beznea
2026-05-26 8:46 ` [PATCH v6 01/18] dmaengine: sh: rz-dmac: Move interrupt request after everything is set up Claudiu Beznea
2026-05-26 8:54 ` Biju Das
2026-05-26 9:45 ` Claudiu Beznea
2026-05-26 9:51 ` Biju Das
2026-05-26 10:25 ` Claudiu Beznea
2026-05-26 10:39 ` Biju Das
2026-05-26 9:20 ` sashiko-bot
2026-05-28 13:44 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 02/18] dmaengine: sh: rz-dmac: Fix incorrect NULL check for list_first_entry() Claudiu Beznea
2026-05-26 9:03 ` sashiko-bot
2026-05-28 13:45 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 03/18] dmaengine: sh: rz-dmac: Use list_first_entry_or_null() Claudiu Beznea
2026-05-28 13:45 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 04/18] dmaengine: sh: rz-dmac: Use rz_dmac_disable_hw() Claudiu Beznea
2026-05-26 9:15 ` sashiko-bot
2026-05-28 13:46 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 05/18] dmaengine: sh: rz-dmac: Add helper to compute the lmdesc address Claudiu Beznea
2026-05-28 13:47 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 06/18] dmaengine: sh: rz-dmac: Save the start LM descriptor Claudiu Beznea
2026-05-26 9:41 ` sashiko-bot
2026-05-28 13:47 ` Tommaso Merciai
2026-05-26 8:46 ` [PATCH v6 07/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is enabled Claudiu Beznea
2026-05-28 13:48 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 08/18] dmaengine: sh: rz-dmac: Add helper to check if the channel is paused Claudiu Beznea
2026-05-28 13:48 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 09/18] dmaengine: sh: rz-dmac: Use virt-dma APIs for channel descriptor processing Claudiu Beznea
2026-05-26 9:28 ` sashiko-bot
2026-05-28 13:49 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 10/18] dmaengine: sh: rz-dmac: Refactor pause/resume code Claudiu Beznea
2026-05-26 9:28 ` sashiko-bot
2026-05-28 13:50 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 11/18] dmaengine: sh: rz-dmac: Drop the update of channel->chctrl with CHCTRL_SETEN Claudiu Beznea
2026-05-26 9:11 ` sashiko-bot
2026-05-28 13:50 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 12/18] dmaengine: sh: rz-dmac: Add cyclic DMA support Claudiu Beznea
2026-05-26 9:31 ` sashiko-bot
2026-05-28 13:51 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 13/18] dmaengine: sh: rz-dmac: Adjust rz_dmac_chan_get_residue() to return error codes Claudiu Beznea
2026-05-28 13:51 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 14/18] dmaengine: sh: rz-dmac: Add runtime PM support Claudiu Beznea
2026-05-26 9:57 ` sashiko-bot
2026-05-28 13:52 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 15/18] dmaengine: sh: rz-dmac: Add suspend to RAM support Claudiu Beznea
2026-05-26 9:43 ` sashiko-bot
2026-05-28 14:38 ` Tommaso Merciai
2026-05-26 8:47 ` [PATCH v6 16/18] ASoC: renesas: rz-ssi: Add pause support Claudiu Beznea
2026-05-26 9:46 ` sashiko-bot
2026-05-26 8:47 ` [PATCH v6 17/18] ASoC: renesas: rz-ssi: Use generic PCM dmaengine APIs Claudiu Beznea
2026-05-26 10:00 ` sashiko-bot
2026-05-26 8:47 ` [PATCH v6 18/18] dmaengine: sh: rz-dmac: Set the Link End (LE) bit on the last descriptor Claudiu Beznea
2026-05-26 9:43 ` sashiko-bot
2026-05-28 14:39 ` Tommaso Merciai
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox