linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250
@ 2025-08-23 15:39 Jisheng Zhang
  2025-08-23 15:39 ` [PATCH 01/14] dmaengine: dma350: Fix CH_CTRL_USESRCTRIGIN definition Jisheng Zhang
                   ` (13 more replies)
  0 siblings, 14 replies; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:39 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

This series firstly does some cleanups, then add support of
device_prep_slave_sg and device_prep_dma_cyclic to DMA-350. 

Lastly, add support for ARM DMA-250 IP. Compared with DMA-350, DMA-250
is a simplified version. They share many common parts, but they do
have difference. Add DMA-250 support by handling their difference by
using different device_prep_slave_sg, device_prep_dma_cyclic and
device_prep_dma_memcpy. DMA-250 doesn't support device_prep_dma_memset.


Jisheng Zhang (14):
  dmaengine: dma350: Fix CH_CTRL_USESRCTRIGIN definition
  dmaengine: dma350: Add missing dch->coherent setting
  dmaengine: dma350: Check vchan_next_desc() return value
  dmaengine: dma350: Check dma_cookie_status() ret code and txstate
  dmaengine: dma350: Register the DMA controller to DT DMA helpers
  dmaengine: dma350: Use dmaenginem_async_device_register
  dmaengine: dma350: Remove redundant err msg if platform_get_irq()
    fails
  dt-bindings: dma: dma350: Document interrupt-names
  dmaengine: dma350: Support dma-channel-mask
  dmaengine: dma350: Alloc command[] from dma pool
  dmaengine: dma350: Support device_prep_slave_sg
  dmaengine: dma350: Support device_prep_dma_cyclic
  dt-bindings: dma: dma350: Support ARM DMA-250
  dmaengine: dma350: Support ARM DMA-250

 .../devicetree/bindings/dma/arm,dma-350.yaml  |   6 +
 drivers/dma/arm-dma350.c                      | 909 ++++++++++++++++--
 2 files changed, 852 insertions(+), 63 deletions(-)

-- 
2.50.0



^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 01/14] dmaengine: dma350: Fix CH_CTRL_USESRCTRIGIN definition
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
@ 2025-08-23 15:39 ` Jisheng Zhang
  2025-08-29 10:44   ` Robin Murphy
  2025-08-23 15:39 ` [PATCH 02/14] dmaengine: dma350: Add missing dch->coherent setting Jisheng Zhang
                   ` (12 subsequent siblings)
  13 siblings, 1 reply; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:39 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

Per the arm-dma350 TRM, The CH_CTRL_USESRCTRIGIN is BIT(25).

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 9efe2ca7d5ec..bf3962f00650 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -58,7 +58,7 @@
 
 #define CH_CTRL			0x0c
 #define CH_CTRL_USEDESTRIGIN	BIT(26)
-#define CH_CTRL_USESRCTRIGIN	BIT(26)
+#define CH_CTRL_USESRCTRIGIN	BIT(25)
 #define CH_CTRL_DONETYPE	GENMASK(23, 21)
 #define CH_CTRL_REGRELOADTYPE	GENMASK(20, 18)
 #define CH_CTRL_XTYPE		GENMASK(11, 9)
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 02/14] dmaengine: dma350: Add missing dch->coherent setting
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
  2025-08-23 15:39 ` [PATCH 01/14] dmaengine: dma350: Fix CH_CTRL_USESRCTRIGIN definition Jisheng Zhang
@ 2025-08-23 15:39 ` Jisheng Zhang
  2025-08-29 10:52   ` Robin Murphy
  2025-08-23 15:39 ` [PATCH 03/14] dmaengine: dma350: Check vchan_next_desc() return value Jisheng Zhang
                   ` (11 subsequent siblings)
  13 siblings, 1 reply; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:39 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

The dch->coherent setting is missing.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index bf3962f00650..24cbadc5f076 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -587,6 +587,7 @@ static int d350_probe(struct platform_device *pdev)
 	for (int i = 0; i < nchan; i++) {
 		struct d350_chan *dch = &dmac->channels[i];
 
+		dch->coherent = coherent;
 		dch->base = base + DMACH(i);
 		writel_relaxed(CH_CMD_CLEAR, dch->base + CH_CMD);
 
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 03/14] dmaengine: dma350: Check vchan_next_desc() return value
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
  2025-08-23 15:39 ` [PATCH 01/14] dmaengine: dma350: Fix CH_CTRL_USESRCTRIGIN definition Jisheng Zhang
  2025-08-23 15:39 ` [PATCH 02/14] dmaengine: dma350: Add missing dch->coherent setting Jisheng Zhang
@ 2025-08-23 15:39 ` Jisheng Zhang
  2025-08-29 10:02   ` Robin Murphy
  2025-08-23 15:39 ` [PATCH 04/14] dmaengine: dma350: Check dma_cookie_status() ret code and txstate Jisheng Zhang
                   ` (10 subsequent siblings)
  13 siblings, 1 reply; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:39 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

vchan_next_desc() may return NULL, check its return value.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 24cbadc5f076..96350d15ed85 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -399,11 +399,14 @@ static enum dma_status d350_tx_status(struct dma_chan *chan, dma_cookie_t cookie
 static void d350_start_next(struct d350_chan *dch)
 {
 	u32 hdr, *reg;
+	struct virt_dma_desc *vd;
 
-	dch->desc = to_d350_desc(vchan_next_desc(&dch->vc));
-	if (!dch->desc)
+	vd = vchan_next_desc(&dch->vc);
+	if (!vd)
 		return;
 
+	dch->desc = to_d350_desc(vd);
+
 	list_del(&dch->desc->vd.node);
 	dch->status = DMA_IN_PROGRESS;
 	dch->cookie = dch->desc->vd.tx.cookie;
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 04/14] dmaengine: dma350: Check dma_cookie_status() ret code and txstate
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (2 preceding siblings ...)
  2025-08-23 15:39 ` [PATCH 03/14] dmaengine: dma350: Check vchan_next_desc() return value Jisheng Zhang
@ 2025-08-23 15:39 ` Jisheng Zhang
  2025-08-29 10:42   ` Robin Murphy
  2025-08-23 15:40 ` [PATCH 05/14] dmaengine: dma350: Register the DMA controller to DT DMA helpers Jisheng Zhang
                   ` (9 subsequent siblings)
  13 siblings, 1 reply; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:39 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

If dma_cookie_status() returns DMA_COMPLETE, we can return immediately.

From another side, the txstate is an optional parameter used to get a
struct with auxilary transfer status information. When not provided
the call to device_tx_status() should return the status of the dma
cookie. Return the status of dma cookie when the txstate optional
parameter is not provided.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 96350d15ed85..17af9bb2a18f 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -377,6 +377,8 @@ static enum dma_status d350_tx_status(struct dma_chan *chan, dma_cookie_t cookie
 	u32 residue = 0;
 
 	status = dma_cookie_status(chan, cookie, state);
+	if (status == DMA_COMPLETE || !state)
+		return status;
 
 	spin_lock_irqsave(&dch->vc.lock, flags);
 	if (cookie == dch->cookie) {
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 05/14] dmaengine: dma350: Register the DMA controller to DT DMA helpers
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (3 preceding siblings ...)
  2025-08-23 15:39 ` [PATCH 04/14] dmaengine: dma350: Check dma_cookie_status() ret code and txstate Jisheng Zhang
@ 2025-08-23 15:40 ` Jisheng Zhang
  2025-08-29 20:37   ` Robin Murphy
  2025-08-23 15:40 ` [PATCH 06/14] dmaengine: dma350: Use dmaenginem_async_device_register Jisheng Zhang
                   ` (8 subsequent siblings)
  13 siblings, 1 reply; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:40 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

Register the DMA controller to DT DMA helpers so that we convert a DT
phandle to a dma_chan structure.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 17af9bb2a18f..6a9f81f941b0 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -7,6 +7,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/io.h>
 #include <linux/of.h>
+#include <linux/of_dma.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 
@@ -635,7 +636,7 @@ static int d350_probe(struct platform_device *pdev)
 	if (ret)
 		return dev_err_probe(dev, ret, "Failed to register DMA device\n");
 
-	return 0;
+	return of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id, &dmac->dma);
 }
 
 static void d350_remove(struct platform_device *pdev)
@@ -643,6 +644,7 @@ static void d350_remove(struct platform_device *pdev)
 	struct d350 *dmac = platform_get_drvdata(pdev);
 
 	dma_async_device_unregister(&dmac->dma);
+	of_dma_controller_free(pdev->dev.of_node);
 }
 
 static const struct of_device_id d350_of_match[] __maybe_unused = {
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 06/14] dmaengine: dma350: Use dmaenginem_async_device_register
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (4 preceding siblings ...)
  2025-08-23 15:40 ` [PATCH 05/14] dmaengine: dma350: Register the DMA controller to DT DMA helpers Jisheng Zhang
@ 2025-08-23 15:40 ` Jisheng Zhang
  2025-08-23 15:40 ` [PATCH 07/14] dmaengine: dma350: Remove redundant err msg if platform_get_irq() fails Jisheng Zhang
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:40 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

Use managed API dmaenginem_async_device_register() to simplify code
path.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 6a9f81f941b0..36c8bfa67d70 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -632,7 +632,7 @@ static int d350_probe(struct platform_device *pdev)
 
 	platform_set_drvdata(pdev, dmac);
 
-	ret = dma_async_device_register(&dmac->dma);
+	ret = dmaenginem_async_device_register(&dmac->dma);
 	if (ret)
 		return dev_err_probe(dev, ret, "Failed to register DMA device\n");
 
@@ -641,9 +641,6 @@ static int d350_probe(struct platform_device *pdev)
 
 static void d350_remove(struct platform_device *pdev)
 {
-	struct d350 *dmac = platform_get_drvdata(pdev);
-
-	dma_async_device_unregister(&dmac->dma);
 	of_dma_controller_free(pdev->dev.of_node);
 }
 
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 07/14] dmaengine: dma350: Remove redundant err msg if platform_get_irq() fails
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (5 preceding siblings ...)
  2025-08-23 15:40 ` [PATCH 06/14] dmaengine: dma350: Use dmaenginem_async_device_register Jisheng Zhang
@ 2025-08-23 15:40 ` Jisheng Zhang
  2025-08-23 15:40 ` [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names Jisheng Zhang
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:40 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

The platform_get_irq() prints an error message if finding the IRQ
fails.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 36c8bfa67d70..6a6d1c2a3ee6 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -604,8 +604,7 @@ static int d350_probe(struct platform_device *pdev)
 		}
 		dch->irq = platform_get_irq(pdev, i);
 		if (dch->irq < 0)
-			return dev_err_probe(dev, dch->irq,
-					     "Failed to get IRQ for channel %d\n", i);
+			return dch->irq;
 
 		dch->has_wrap = FIELD_GET(CH_CFG_HAS_WRAP, reg);
 		dch->has_trig = FIELD_GET(CH_CFG_HAS_TRIGIN, reg) &
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (6 preceding siblings ...)
  2025-08-23 15:40 ` [PATCH 07/14] dmaengine: dma350: Remove redundant err msg if platform_get_irq() fails Jisheng Zhang
@ 2025-08-23 15:40 ` Jisheng Zhang
  2025-08-23 16:09   ` Krzysztof Kozlowski
  2025-08-29 11:08   ` Robin Murphy
  2025-08-23 15:40 ` [PATCH 09/14] dmaengine: dma350: Support dma-channel-mask Jisheng Zhang
                   ` (5 subsequent siblings)
  13 siblings, 2 replies; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:40 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

Currently, the dma350 driver assumes all channels are available to
linux, this may not be true on some platforms, so it's possible no
irq(s) for the unavailable channel(s). What's more, the available
channels may not be continuous. To handle this case, we'd better
get the irq of each channel by name.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
index 429f682f15d8..94752516e51a 100644
--- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
+++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
@@ -32,6 +32,10 @@ properties:
       - description: Channel 6 interrupt
       - description: Channel 7 interrupt
 
+  interrupt-names:
+    minItems: 1
+    maxItems: 8
+
   "#dma-cells":
     const: 1
     description: The cell is the trigger input number
@@ -40,5 +44,6 @@ required:
   - compatible
   - reg
   - interrupts
+  - interrupt-names
 
 unevaluatedProperties: false
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 09/14] dmaengine: dma350: Support dma-channel-mask
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (7 preceding siblings ...)
  2025-08-23 15:40 ` [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names Jisheng Zhang
@ 2025-08-23 15:40 ` Jisheng Zhang
  2025-08-23 16:10   ` Krzysztof Kozlowski
  2025-08-24  7:01   ` kernel test robot
  2025-08-23 15:40 ` [PATCH 10/14] dmaengine: dma350: Alloc command[] from dma pool Jisheng Zhang
                   ` (4 subsequent siblings)
  13 siblings, 2 replies; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:40 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

Not all channels are available to kernel, we need to support
dma-channel-mask.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 6a6d1c2a3ee6..72067518799e 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -534,7 +534,7 @@ static int d350_probe(struct platform_device *pdev)
 	struct device *dev = &pdev->dev;
 	struct d350 *dmac;
 	void __iomem *base;
-	u32 reg;
+	u32 reg, dma_chan_mask;
 	int ret, nchan, dw, aw, r, p;
 	bool coherent, memset;
 
@@ -563,6 +563,15 @@ static int d350_probe(struct platform_device *pdev)
 
 	dmac->nchan = nchan;
 
+	/* Enable all channels by default */
+	dma_chan_mask = nchan - 1;
+
+	ret = of_property_read_u32(dev->of_node, "dma-channel-mask", &dma_chan_mask);
+	if (ret < 0 && (ret != -EINVAL)) {
+		dev_err(&pdev->dev, "dma-channel-mask is not complete.\n");
+		return ret;
+	}
+
 	reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG1);
 	dmac->nreq = FIELD_GET(DMA_CFG_NUM_TRIGGER_IN, reg);
 
@@ -592,6 +601,11 @@ static int d350_probe(struct platform_device *pdev)
 	memset = true;
 	for (int i = 0; i < nchan; i++) {
 		struct d350_chan *dch = &dmac->channels[i];
+		char ch_irqname[8];
+
+		/* skip for reserved channels */
+		if (!test_bit(i, (unsigned long *)&dma_chan_mask))
+			continue;
 
 		dch->coherent = coherent;
 		dch->base = base + DMACH(i);
@@ -602,7 +616,9 @@ static int d350_probe(struct platform_device *pdev)
 			dev_warn(dev, "No command link support on channel %d\n", i);
 			continue;
 		}
-		dch->irq = platform_get_irq(pdev, i);
+
+		snprintf(ch_irqname, sizeof(ch_irqname), "ch%d", i);
+		dch->irq = platform_get_irq_byname(pdev, ch_irqname);
 		if (dch->irq < 0)
 			return dch->irq;
 
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 10/14] dmaengine: dma350: Alloc command[] from dma pool
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (8 preceding siblings ...)
  2025-08-23 15:40 ` [PATCH 09/14] dmaengine: dma350: Support dma-channel-mask Jisheng Zhang
@ 2025-08-23 15:40 ` Jisheng Zhang
  2025-08-29 12:56   ` Robin Murphy
  2025-09-01 10:27   ` Dan Carpenter
  2025-08-23 15:40 ` [PATCH 11/14] dmaengine: dma350: Support device_prep_slave_sg Jisheng Zhang
                   ` (3 subsequent siblings)
  13 siblings, 2 replies; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:40 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

Currently, the command[] is allocated with kzalloc(), but dma350 may be
used on dma-non-coherent platforms, to prepare the support of peripheral
and scatter-gather chaining on both dma-coherent and dma-non-coherent
platforms, let's alloc them from dma pool.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 143 +++++++++++++++++++++++++++++++--------
 1 file changed, 113 insertions(+), 30 deletions(-)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 72067518799e..3d26a1f020df 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -4,6 +4,7 @@
 
 #include <linux/bitfield.h>
 #include <linux/dmaengine.h>
+#include <linux/dmapool.h>
 #include <linux/dma-mapping.h>
 #include <linux/io.h>
 #include <linux/of.h>
@@ -143,6 +144,7 @@
 #define LINK_LINKADDR		BIT(30)
 #define LINK_LINKADDRHI		BIT(31)
 
+#define D350_MAX_CMDS		16
 
 enum ch_ctrl_donetype {
 	CH_CTRL_DONETYPE_NONE = 0,
@@ -169,18 +171,25 @@ enum ch_cfg_memattr {
 	MEMATTR_WB = 0xff
 };
 
-struct d350_desc {
-	struct virt_dma_desc vd;
-	u32 command[16];
+struct d350_sg {
+	u32 *command;
+	dma_addr_t phys;
 	u16 xsize;
 	u16 xsizehi;
 	u8 tsz;
 };
 
+struct d350_desc {
+	struct virt_dma_desc vd;
+	u32 sglen;
+	struct d350_sg sg[] __counted_by(sglen);
+};
+
 struct d350_chan {
 	struct virt_dma_chan vc;
 	struct d350_desc *desc;
 	void __iomem *base;
+	struct dma_pool *cmd_pool;
 	int irq;
 	enum dma_status status;
 	dma_cookie_t cookie;
@@ -210,7 +219,14 @@ static inline struct d350_desc *to_d350_desc(struct virt_dma_desc *vd)
 
 static void d350_desc_free(struct virt_dma_desc *vd)
 {
-	kfree(to_d350_desc(vd));
+	struct d350_chan *dch = to_d350_chan(vd->tx.chan);
+	struct d350_desc *desc = to_d350_desc(vd);
+	int i;
+
+	for (i = 0; i < desc->sglen; i++)
+		dma_pool_free(dch->cmd_pool, desc->sg[i].command, desc->sg[i].phys);
+
+	kfree(desc);
 }
 
 static struct dma_async_tx_descriptor *d350_prep_memcpy(struct dma_chan *chan,
@@ -218,22 +234,32 @@ static struct dma_async_tx_descriptor *d350_prep_memcpy(struct dma_chan *chan,
 {
 	struct d350_chan *dch = to_d350_chan(chan);
 	struct d350_desc *desc;
+	struct d350_sg *sg;
+	dma_addr_t phys;
 	u32 *cmd;
 
-	desc = kzalloc(sizeof(*desc), GFP_NOWAIT);
+	desc = kzalloc(struct_size(desc, sg, 1), GFP_NOWAIT);
 	if (!desc)
 		return NULL;
 
-	desc->tsz = __ffs(len | dest | src | (1 << dch->tsz));
-	desc->xsize = lower_16_bits(len >> desc->tsz);
-	desc->xsizehi = upper_16_bits(len >> desc->tsz);
+	sg = &desc->sg[0];
+	sg->command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
+	if (unlikely(!sg->command)) {
+		kfree(desc);
+		return NULL;
+	}
+	sg->phys = phys;
+
+	sg->tsz = __ffs(len | dest | src | (1 << dch->tsz));
+	sg->xsize = lower_16_bits(len >> sg->tsz);
+	sg->xsizehi = upper_16_bits(len >> sg->tsz);
 
-	cmd = desc->command;
+	cmd = sg->command;
 	cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
 		 LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_SRCTRANSCFG |
 		 LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR;
 
-	cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, desc->tsz) |
+	cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, sg->tsz) |
 		 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE) |
 		 FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD);
 
@@ -241,13 +267,15 @@ static struct dma_async_tx_descriptor *d350_prep_memcpy(struct dma_chan *chan,
 	cmd[3] = upper_32_bits(src);
 	cmd[4] = lower_32_bits(dest);
 	cmd[5] = upper_32_bits(dest);
-	cmd[6] = FIELD_PREP(CH_XY_SRC, desc->xsize) | FIELD_PREP(CH_XY_DES, desc->xsize);
-	cmd[7] = FIELD_PREP(CH_XY_SRC, desc->xsizehi) | FIELD_PREP(CH_XY_DES, desc->xsizehi);
+	cmd[6] = FIELD_PREP(CH_XY_SRC, sg->xsize) | FIELD_PREP(CH_XY_DES, sg->xsize);
+	cmd[7] = FIELD_PREP(CH_XY_SRC, sg->xsizehi) | FIELD_PREP(CH_XY_DES, sg->xsizehi);
 	cmd[8] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
 	cmd[9] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
 	cmd[10] = FIELD_PREP(CH_XY_SRC, 1) | FIELD_PREP(CH_XY_DES, 1);
 	cmd[11] = 0;
 
+	mb();
+
 	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
 }
 
@@ -256,34 +284,46 @@ static struct dma_async_tx_descriptor *d350_prep_memset(struct dma_chan *chan,
 {
 	struct d350_chan *dch = to_d350_chan(chan);
 	struct d350_desc *desc;
+	struct d350_sg *sg;
+	dma_addr_t phys;
 	u32 *cmd;
 
-	desc = kzalloc(sizeof(*desc), GFP_NOWAIT);
+	desc = kzalloc(struct_size(desc, sg, 1), GFP_NOWAIT);
 	if (!desc)
 		return NULL;
 
-	desc->tsz = __ffs(len | dest | (1 << dch->tsz));
-	desc->xsize = lower_16_bits(len >> desc->tsz);
-	desc->xsizehi = upper_16_bits(len >> desc->tsz);
+	sg = &desc->sg[0];
+	sg->command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
+	if (unlikely(!sg->command)) {
+		kfree(desc);
+		return NULL;
+	}
+	sg->phys = phys;
+
+	sg->tsz = __ffs(len | dest | (1 << dch->tsz));
+	sg->xsize = lower_16_bits(len >> sg->tsz);
+	sg->xsizehi = upper_16_bits(len >> sg->tsz);
 
-	cmd = desc->command;
+	cmd = sg->command;
 	cmd[0] = LINK_CTRL | LINK_DESADDR | LINK_DESADDRHI |
 		 LINK_XSIZE | LINK_XSIZEHI | LINK_DESTRANSCFG |
 		 LINK_XADDRINC | LINK_FILLVAL | LINK_LINKADDR;
 
-	cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, desc->tsz) |
+	cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, sg->tsz) |
 		 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_FILL) |
 		 FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD);
 
 	cmd[2] = lower_32_bits(dest);
 	cmd[3] = upper_32_bits(dest);
-	cmd[4] = FIELD_PREP(CH_XY_DES, desc->xsize);
-	cmd[5] = FIELD_PREP(CH_XY_DES, desc->xsizehi);
+	cmd[4] = FIELD_PREP(CH_XY_DES, sg->xsize);
+	cmd[5] = FIELD_PREP(CH_XY_DES, sg->xsizehi);
 	cmd[6] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
 	cmd[7] = FIELD_PREP(CH_XY_DES, 1);
 	cmd[8] = (u8)value * 0x01010101;
 	cmd[9] = 0;
 
+	mb();
+
 	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
 }
 
@@ -319,8 +359,9 @@ static int d350_resume(struct dma_chan *chan)
 
 static u32 d350_get_residue(struct d350_chan *dch)
 {
-	u32 res, xsize, xsizehi, hi_new;
-	int retries = 3; /* 1st time unlucky, 2nd improbable, 3rd just broken */
+	u32 res, xsize, xsizehi, linkaddr, linkaddrhi, hi_new;
+	int i, sgcur, retries = 3; /* 1st time unlucky, 2nd improbable, 3rd just broken */
+	struct d350_desc *desc = dch->desc;
 
 	hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
 	do {
@@ -329,10 +370,26 @@ static u32 d350_get_residue(struct d350_chan *dch)
 		hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
 	} while (xsizehi != hi_new && --retries);
 
+	hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
+	do {
+		linkaddrhi = hi_new;
+		linkaddr = readl_relaxed(dch->base + CH_LINKADDR);
+		hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
+	} while (linkaddrhi != hi_new && --retries);
+
+	for (i = 0; i < desc->sglen; i++) {
+		if (desc->sg[i].phys == (((u64)linkaddrhi << 32) | (linkaddr & ~CH_LINKADDR_EN)))
+			sgcur = i;
+	}
+
 	res = FIELD_GET(CH_XY_DES, xsize);
 	res |= FIELD_GET(CH_XY_DES, xsizehi) << 16;
+	res <<= desc->sg[sgcur].tsz;
+
+	for (i = sgcur + 1; i < desc->sglen; i++)
+		res += (((u32)desc->sg[i].xsizehi << 16 | desc->sg[i].xsize) << desc->sg[i].tsz);
 
-	return res << dch->desc->tsz;
+	return res;
 }
 
 static int d350_terminate_all(struct dma_chan *chan)
@@ -365,7 +422,13 @@ static void d350_synchronize(struct dma_chan *chan)
 
 static u32 d350_desc_bytes(struct d350_desc *desc)
 {
-	return ((u32)desc->xsizehi << 16 | desc->xsize) << desc->tsz;
+	int i;
+	u32 bytes = 0;
+
+	for (i = 0; i < desc->sglen; i++)
+		bytes += (((u32)desc->sg[i].xsizehi << 16 | desc->sg[i].xsize) << desc->sg[i].tsz);
+
+	return bytes;
 }
 
 static enum dma_status d350_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
@@ -415,8 +478,8 @@ static void d350_start_next(struct d350_chan *dch)
 	dch->cookie = dch->desc->vd.tx.cookie;
 	dch->residue = d350_desc_bytes(dch->desc);
 
-	hdr = dch->desc->command[0];
-	reg = &dch->desc->command[1];
+	hdr = dch->desc->sg[0].command[0];
+	reg = &dch->desc->sg[0].command[1];
 
 	if (hdr & LINK_INTREN)
 		writel_relaxed(*reg++, dch->base + CH_INTREN);
@@ -512,11 +575,29 @@ static irqreturn_t d350_irq(int irq, void *data)
 static int d350_alloc_chan_resources(struct dma_chan *chan)
 {
 	struct d350_chan *dch = to_d350_chan(chan);
-	int ret = request_irq(dch->irq, d350_irq, IRQF_SHARED,
-			      dev_name(&dch->vc.chan.dev->device), dch);
-	if (!ret)
-		writel_relaxed(CH_INTREN_DONE | CH_INTREN_ERR, dch->base + CH_INTREN);
+	int ret;
+
+	dch->cmd_pool = dma_pool_create(dma_chan_name(chan),
+					  chan->device->dev,
+					  D350_MAX_CMDS * sizeof(u32),
+					  sizeof(u32), 0);
+	if (!dch->cmd_pool) {
+		dev_err(chan->device->dev, "No memory for cmd pool\n");
+		return -ENOMEM;
+	}
+
+	ret = request_irq(dch->irq, d350_irq, 0,
+			  dev_name(&dch->vc.chan.dev->device), dch);
+	if (ret < 0)
+		goto err_irq;
+
+	writel_relaxed(CH_INTREN_DONE | CH_INTREN_ERR, dch->base + CH_INTREN);
+
+	return 0;
 
+err_irq:
+	dma_pool_destroy(dch->cmd_pool);
+	dch->cmd_pool = NULL;
 	return ret;
 }
 
@@ -527,6 +608,8 @@ static void d350_free_chan_resources(struct dma_chan *chan)
 	writel_relaxed(0, dch->base + CH_INTREN);
 	free_irq(dch->irq, dch);
 	vchan_free_chan_resources(&dch->vc);
+	dma_pool_destroy(dch->cmd_pool);
+	dch->cmd_pool = NULL;
 }
 
 static int d350_probe(struct platform_device *pdev)
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 11/14] dmaengine: dma350: Support device_prep_slave_sg
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (9 preceding siblings ...)
  2025-08-23 15:40 ` [PATCH 10/14] dmaengine: dma350: Alloc command[] from dma pool Jisheng Zhang
@ 2025-08-23 15:40 ` Jisheng Zhang
  2025-08-30  0:00   ` Robin Murphy
  2025-08-23 15:40 ` [PATCH 12/14] dmaengine: dma350: Support device_prep_dma_cyclic Jisheng Zhang
                   ` (2 subsequent siblings)
  13 siblings, 1 reply; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:40 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

Add device_prep_slave_sg() callback function so that DMA_MEM_TO_DEV
and DMA_DEV_TO_MEM operations in single mode can be supported.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 180 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 174 insertions(+), 6 deletions(-)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 3d26a1f020df..a285778264b9 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 // Copyright (C) 2024-2025 Arm Limited
+// Copyright (C) 2025 Synaptics Incorporated
 // Arm DMA-350 driver
 
 #include <linux/bitfield.h>
@@ -98,7 +99,23 @@
 
 #define CH_FILLVAL		0x38
 #define CH_SRCTRIGINCFG		0x4c
+#define CH_SRCTRIGINMODE	GENMASK(11, 10)
+#define CH_SRCTRIG_CMD		0
+#define CH_SRCTRIG_DMA_FC	2
+#define CH_SRCTRIG_PERIF_FC	3
+#define CH_SRCTRIGINTYPE	GENMASK(9, 8)
+#define CH_SRCTRIG_SW_REQ	0
+#define CH_SRCTRIG_HW_REQ	2
+#define CH_SRCTRIG_INTERN_REQ	3
 #define CH_DESTRIGINCFG		0x50
+#define CH_DESTRIGINMODE	GENMASK(11, 10)
+#define CH_DESTRIG_CMD		0
+#define CH_DESTRIG_DMA_FC	2
+#define CH_DESTRIG_PERIF_FC	3
+#define CH_DESTRIGINTYPE	GENMASK(9, 8)
+#define CH_DESTRIG_SW_REQ	0
+#define CH_DESTRIG_HW_REQ	2
+#define CH_DESTRIG_INTERN_REQ	3
 #define CH_LINKATTR		0x70
 #define CH_LINK_SHAREATTR	GENMASK(9, 8)
 #define CH_LINK_MEMATTR		GENMASK(7, 0)
@@ -190,11 +207,13 @@ struct d350_chan {
 	struct d350_desc *desc;
 	void __iomem *base;
 	struct dma_pool *cmd_pool;
+	struct dma_slave_config config;
 	int irq;
 	enum dma_status status;
 	dma_cookie_t cookie;
 	u32 residue;
 	u8 tsz;
+	u8 ch;
 	bool has_trig;
 	bool has_wrap;
 	bool coherent;
@@ -327,6 +346,144 @@ static struct dma_async_tx_descriptor *d350_prep_memset(struct dma_chan *chan,
 	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
 }
 
+static struct dma_async_tx_descriptor *
+d350_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
+		   unsigned int sg_len, enum dma_transfer_direction dir,
+		   unsigned long flags, void *context)
+{
+	struct d350_chan *dch = to_d350_chan(chan);
+	dma_addr_t src, dst, phys;
+	struct d350_desc *desc;
+	struct scatterlist *sg;
+	u32 len, trig, *cmd, *la_cmd, tsz;
+	struct d350_sg *dsg;
+	int i, j;
+
+	if (unlikely(!is_slave_direction(dir) || !sg_len))
+		return NULL;
+
+	desc = kzalloc(struct_size(desc, sg, sg_len), GFP_NOWAIT);
+	if (!desc)
+		return NULL;
+
+	desc->sglen = sg_len;
+
+	if (dir == DMA_MEM_TO_DEV)
+		tsz = __ffs(dch->config.dst_addr_width | (1 << dch->tsz));
+	else
+		tsz = __ffs(dch->config.src_addr_width | (1 << dch->tsz));
+
+	for_each_sg(sgl, sg, sg_len, i) {
+		desc->sg[i].command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
+		if (unlikely(!desc->sg[i].command))
+			goto err_cmd_alloc;
+
+		desc->sg[i].phys = phys;
+		dsg = &desc->sg[i];
+		len = sg_dma_len(sg);
+
+		if (dir == DMA_MEM_TO_DEV) {
+			src = sg_dma_address(sg);
+			dst = dch->config.dst_addr;
+			trig = CH_CTRL_USEDESTRIGIN;
+		} else {
+			src = dch->config.src_addr;
+			dst = sg_dma_address(sg);
+			trig = CH_CTRL_USESRCTRIGIN;
+		}
+		dsg->tsz = tsz;
+		dsg->xsize = lower_16_bits(len >> dsg->tsz);
+		dsg->xsizehi = upper_16_bits(len >> dsg->tsz);
+
+		cmd = dsg->command;
+		if (!i) {
+			cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
+				 LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_SRCTRANSCFG |
+				 LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR;
+
+			cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, dsg->tsz) | trig |
+				 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE);
+
+			cmd[2] = lower_32_bits(src);
+			cmd[3] = upper_32_bits(src);
+			cmd[4] = lower_32_bits(dst);
+			cmd[5] = upper_32_bits(dst);
+			cmd[6] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
+				 FIELD_PREP(CH_XY_DES, dsg->xsize);
+			cmd[7] = FIELD_PREP(CH_XY_SRC, dsg->xsizehi) |
+				 FIELD_PREP(CH_XY_DES, dsg->xsizehi);
+			if (dir == DMA_MEM_TO_DEV) {
+				cmd[0] |= LINK_DESTRIGINCFG;
+				cmd[8] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+				cmd[9] = TRANSCFG_DEVICE;
+				cmd[10] = FIELD_PREP(CH_XY_SRC, 1);
+				cmd[11] = FIELD_PREP(CH_DESTRIGINMODE, CH_DESTRIG_DMA_FC) |
+					  FIELD_PREP(CH_DESTRIGINTYPE, CH_DESTRIG_HW_REQ);
+			} else {
+				cmd[0] |= LINK_SRCTRIGINCFG;
+				cmd[8] = TRANSCFG_DEVICE;
+				cmd[9] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+				cmd[10] = FIELD_PREP(CH_XY_DES, 1);
+				cmd[11] = FIELD_PREP(CH_SRCTRIGINMODE, CH_SRCTRIG_DMA_FC) |
+					  FIELD_PREP(CH_SRCTRIGINTYPE, CH_SRCTRIG_HW_REQ);
+			}
+			la_cmd = &cmd[12];
+		} else {
+			*la_cmd = phys | CH_LINKADDR_EN;
+			if (i == sg_len - 1) {
+				cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
+					 LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_LINKADDR;
+				cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, dsg->tsz) | trig |
+					 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE);
+				cmd[2] = lower_32_bits(src);
+				cmd[3] = upper_32_bits(src);
+				cmd[4] = lower_32_bits(dst);
+				cmd[5] = upper_32_bits(dst);
+				cmd[6] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
+					 FIELD_PREP(CH_XY_DES, dsg->xsize);
+				cmd[7] = FIELD_PREP(CH_XY_SRC, dsg->xsizehi) |
+					 FIELD_PREP(CH_XY_DES, dsg->xsizehi);
+				la_cmd = &cmd[8];
+			} else {
+				cmd[0] = LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
+					 LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_LINKADDR;
+				cmd[1] = lower_32_bits(src);
+				cmd[2] = upper_32_bits(src);
+				cmd[3] = lower_32_bits(dst);
+				cmd[4] = upper_32_bits(dst);
+				cmd[5] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
+					 FIELD_PREP(CH_XY_DES, dsg->xsize);
+				cmd[6] = FIELD_PREP(CH_XY_SRC, dsg->xsizehi) |
+					 FIELD_PREP(CH_XY_DES, dsg->xsizehi);
+				la_cmd = &cmd[7];
+			}
+		}
+	}
+
+	/* the last command */
+	*la_cmd = 0;
+	desc->sg[sg_len - 1].command[1] |= FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD);
+
+	mb();
+
+	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
+
+err_cmd_alloc:
+	for (j = 0; j < i; j++)
+		dma_pool_free(dch->cmd_pool, desc->sg[j].command, desc->sg[j].phys);
+	kfree(desc);
+	return NULL;
+}
+
+static int d350_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
+{
+	struct d350_chan *dch = to_d350_chan(chan);
+
+	memcpy(&dch->config, config, sizeof(*config));
+
+	return 0;
+}
+
 static int d350_pause(struct dma_chan *chan)
 {
 	struct d350_chan *dch = to_d350_chan(chan);
@@ -558,8 +715,9 @@ static irqreturn_t d350_irq(int irq, void *data)
 	writel_relaxed(ch_status, dch->base + CH_STATUS);
 
 	spin_lock(&dch->vc.lock);
-	vchan_cookie_complete(vd);
 	if (ch_status & CH_STAT_INTR_DONE) {
+		vchan_cookie_complete(vd);
+		dch->desc = NULL;
 		dch->status = DMA_COMPLETE;
 		dch->residue = 0;
 		d350_start_next(dch);
@@ -617,7 +775,7 @@ static int d350_probe(struct platform_device *pdev)
 	struct device *dev = &pdev->dev;
 	struct d350 *dmac;
 	void __iomem *base;
-	u32 reg, dma_chan_mask;
+	u32 reg, dma_chan_mask, trig_bits = 0;
 	int ret, nchan, dw, aw, r, p;
 	bool coherent, memset;
 
@@ -637,13 +795,11 @@ static int d350_probe(struct platform_device *pdev)
 	dw = 1 << FIELD_GET(DMA_CFG_DATA_WIDTH, reg);
 	aw = FIELD_GET(DMA_CFG_ADDR_WIDTH, reg) + 1;
 
-	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(aw));
-	coherent = device_get_dma_attr(dev) == DEV_DMA_COHERENT;
-
 	dmac = devm_kzalloc(dev, struct_size(dmac, channels, nchan), GFP_KERNEL);
 	if (!dmac)
 		return -ENOMEM;
 
+	dmac->dma.dev = dev;
 	dmac->nchan = nchan;
 
 	/* Enable all channels by default */
@@ -655,12 +811,14 @@ static int d350_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(aw));
+	coherent = device_get_dma_attr(dev) == DEV_DMA_COHERENT;
+
 	reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG1);
 	dmac->nreq = FIELD_GET(DMA_CFG_NUM_TRIGGER_IN, reg);
 
 	dev_dbg(dev, "DMA-350 r%dp%d with %d channels, %d requests\n", r, p, dmac->nchan, dmac->nreq);
 
-	dmac->dma.dev = dev;
 	for (int i = min(dw, 16); i > 0; i /= 2) {
 		dmac->dma.src_addr_widths |= BIT(i);
 		dmac->dma.dst_addr_widths |= BIT(i);
@@ -692,6 +850,7 @@ static int d350_probe(struct platform_device *pdev)
 
 		dch->coherent = coherent;
 		dch->base = base + DMACH(i);
+		dch->ch = i;
 		writel_relaxed(CH_CMD_CLEAR, dch->base + CH_CMD);
 
 		reg = readl_relaxed(dch->base + CH_BUILDCFG1);
@@ -711,6 +870,7 @@ static int d350_probe(struct platform_device *pdev)
 
 		/* Fill is a special case of Wrap */
 		memset &= dch->has_wrap;
+		trig_bits |= dch->has_trig << dch->ch;
 
 		reg = readl_relaxed(dch->base + CH_BUILDCFG0);
 		dch->tsz = FIELD_GET(CH_CFG_DATA_WIDTH, reg);
@@ -723,6 +883,13 @@ static int d350_probe(struct platform_device *pdev)
 		vchan_init(&dch->vc, &dmac->dma);
 	}
 
+	if (trig_bits) {
+		dmac->dma.directions |= (BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV));
+		dma_cap_set(DMA_SLAVE, dmac->dma.cap_mask);
+		dmac->dma.device_config = d350_slave_config;
+		dmac->dma.device_prep_slave_sg = d350_prep_slave_sg;
+	}
+
 	if (memset) {
 		dma_cap_set(DMA_MEMSET, dmac->dma.cap_mask);
 		dmac->dma.device_prep_dma_memset = d350_prep_memset;
@@ -759,5 +926,6 @@ static struct platform_driver d350_driver = {
 module_platform_driver(d350_driver);
 
 MODULE_AUTHOR("Robin Murphy <robin.murphy@arm.com>");
+MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
 MODULE_DESCRIPTION("Arm DMA-350 driver");
 MODULE_LICENSE("GPL v2");
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 12/14] dmaengine: dma350: Support device_prep_dma_cyclic
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (10 preceding siblings ...)
  2025-08-23 15:40 ` [PATCH 11/14] dmaengine: dma350: Support device_prep_slave_sg Jisheng Zhang
@ 2025-08-23 15:40 ` Jisheng Zhang
  2025-08-23 15:40 ` [PATCH 13/14] dt-bindings: dma: dma350: Support ARM DMA-250 Jisheng Zhang
  2025-08-23 15:40 ` [PATCH 14/14] dmaengine: " Jisheng Zhang
  13 siblings, 0 replies; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:40 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

Add support for device_prep_dma_cyclic() callback function to benefit
DMA cyclic client, for example ALSA.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 118 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 113 insertions(+), 5 deletions(-)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index a285778264b9..5abb965c6687 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -212,8 +212,10 @@ struct d350_chan {
 	enum dma_status status;
 	dma_cookie_t cookie;
 	u32 residue;
+	u32 periods;
 	u8 tsz;
 	u8 ch;
+	bool cyclic;
 	bool has_trig;
 	bool has_wrap;
 	bool coherent;
@@ -475,6 +477,105 @@ d350_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
 	return NULL;
 }
 
+static struct dma_async_tx_descriptor *
+d350_prep_cyclic(struct dma_chan *chan, dma_addr_t buf_addr,
+		 size_t buf_len, size_t period_len, enum dma_transfer_direction dir,
+		 unsigned long flags)
+{
+	struct d350_chan *dch = to_d350_chan(chan);
+	u32 periods, trig, *cmd, tsz;
+	dma_addr_t src, dst, phys;
+	struct d350_desc *desc;
+	struct d350_sg *dsg;
+	int i, j;
+
+	if (unlikely(!is_slave_direction(dir) || !buf_len || !period_len))
+		return NULL;
+
+	periods = buf_len / period_len;
+
+	desc = kzalloc(struct_size(desc, sg, periods), GFP_NOWAIT);
+	if (!desc)
+		return NULL;
+
+	dch->cyclic = true;
+	desc->sglen = periods;
+
+	if (dir == DMA_MEM_TO_DEV)
+		tsz = __ffs(dch->config.dst_addr_width | (1 << dch->tsz));
+	else
+		tsz = __ffs(dch->config.src_addr_width | (1 << dch->tsz));
+
+	for (i = 0; i < periods; i++) {
+		desc->sg[i].command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
+		if (unlikely(!desc->sg[i].command))
+			goto err_cmd_alloc;
+
+		desc->sg[i].phys = phys;
+		dsg = &desc->sg[i];
+
+		if (dir == DMA_MEM_TO_DEV) {
+			src = buf_addr + i * period_len;
+			dst = dch->config.dst_addr;
+			trig = CH_CTRL_USEDESTRIGIN;
+		} else {
+			src = dch->config.src_addr;
+			dst = buf_addr + i * period_len;
+			trig = CH_CTRL_USESRCTRIGIN;
+		}
+		dsg->tsz = tsz;
+		dsg->xsize = lower_16_bits(period_len >> dsg->tsz);
+		dsg->xsizehi = upper_16_bits(period_len >> dsg->tsz);
+
+		cmd = dsg->command;
+		cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
+			 LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_SRCTRANSCFG |
+			 LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR;
+
+		cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, dsg->tsz) |
+			 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE) |
+			 FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD) | trig;
+
+		cmd[2] = lower_32_bits(src);
+		cmd[3] = upper_32_bits(src);
+		cmd[4] = lower_32_bits(dst);
+		cmd[5] = upper_32_bits(dst);
+		cmd[6] = FIELD_PREP(CH_XY_SRC, dsg->xsize) | FIELD_PREP(CH_XY_DES, dsg->xsize);
+		cmd[7] = FIELD_PREP(CH_XY_SRC, dsg->xsizehi) | FIELD_PREP(CH_XY_DES, dsg->xsizehi);
+		if (dir == DMA_MEM_TO_DEV) {
+			cmd[0] |= LINK_DESTRIGINCFG;
+			cmd[8] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+			cmd[9] = TRANSCFG_DEVICE;
+			cmd[10] = FIELD_PREP(CH_XY_SRC, 1);
+			cmd[11] = FIELD_PREP(CH_DESTRIGINMODE, CH_DESTRIG_DMA_FC) |
+				  FIELD_PREP(CH_DESTRIGINTYPE, CH_DESTRIG_HW_REQ);
+		} else {
+			cmd[0] |= LINK_SRCTRIGINCFG;
+			cmd[8] = TRANSCFG_DEVICE;
+			cmd[9] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+			cmd[10] = FIELD_PREP(CH_XY_DES, 1);
+			cmd[11] = FIELD_PREP(CH_SRCTRIGINMODE, CH_SRCTRIG_DMA_FC) |
+				  FIELD_PREP(CH_SRCTRIGINTYPE, CH_SRCTRIG_HW_REQ);
+		}
+
+		if (i)
+			desc->sg[i - 1].command[12] = phys | CH_LINKADDR_EN;
+	}
+
+	/* cyclic list */
+	desc->sg[periods - 1].command[12] = desc->sg[0].phys | CH_LINKADDR_EN;
+
+	mb();
+
+	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
+
+err_cmd_alloc:
+	for (j = 0; j < i; j++)
+		dma_pool_free(dch->cmd_pool, desc->sg[j].command, desc->sg[j].phys);
+	kfree(desc);
+	return NULL;
+}
+
 static int d350_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
 {
 	struct d350_chan *dch = to_d350_chan(chan);
@@ -565,6 +666,7 @@ static int d350_terminate_all(struct dma_chan *chan)
 	}
 	vchan_get_all_descriptors(&dch->vc, &list);
 	list_splice_tail(&list, &dch->vc.desc_terminated);
+	dch->cyclic = false;
 	spin_unlock_irqrestore(&dch->vc.lock, flags);
 
 	return 0;
@@ -716,11 +818,15 @@ static irqreturn_t d350_irq(int irq, void *data)
 
 	spin_lock(&dch->vc.lock);
 	if (ch_status & CH_STAT_INTR_DONE) {
-		vchan_cookie_complete(vd);
-		dch->desc = NULL;
-		dch->status = DMA_COMPLETE;
-		dch->residue = 0;
-		d350_start_next(dch);
+		if (dch->cyclic) {
+			vchan_cyclic_callback(vd);
+		} else {
+			vchan_cookie_complete(vd);
+			dch->desc = NULL;
+			dch->status = DMA_COMPLETE;
+			dch->residue = 0;
+			d350_start_next(dch);
+		}
 	} else {
 		dch->status = DMA_ERROR;
 		dch->residue = vd->tx_result.residue;
@@ -886,8 +992,10 @@ static int d350_probe(struct platform_device *pdev)
 	if (trig_bits) {
 		dmac->dma.directions |= (BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV));
 		dma_cap_set(DMA_SLAVE, dmac->dma.cap_mask);
+		dma_cap_set(DMA_CYCLIC, dmac->dma.cap_mask);
 		dmac->dma.device_config = d350_slave_config;
 		dmac->dma.device_prep_slave_sg = d350_prep_slave_sg;
+		dmac->dma.device_prep_dma_cyclic = d350_prep_cyclic;
 	}
 
 	if (memset) {
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 13/14] dt-bindings: dma: dma350: Support ARM DMA-250
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (11 preceding siblings ...)
  2025-08-23 15:40 ` [PATCH 12/14] dmaengine: dma350: Support device_prep_dma_cyclic Jisheng Zhang
@ 2025-08-23 15:40 ` Jisheng Zhang
  2025-08-23 16:11   ` Krzysztof Kozlowski
  2025-08-23 15:40 ` [PATCH 14/14] dmaengine: " Jisheng Zhang
  13 siblings, 1 reply; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:40 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

Compared with ARM DMA-350, DMA-250 is a simplified version, they share
many common parts, but they do have difference. The difference will be
handled in next driver patch, while let's add DMA-250 compatible string
to dt-binding now.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
index 94752516e51a..d49736b7de5e 100644
--- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
+++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
@@ -15,6 +15,7 @@ allOf:
 properties:
   compatible:
     const: arm,dma-350
+    const: arm,dma-250
 
   reg:
     items:
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 14/14] dmaengine: dma350: Support ARM DMA-250
  2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
                   ` (12 preceding siblings ...)
  2025-08-23 15:40 ` [PATCH 13/14] dt-bindings: dma: dma350: Support ARM DMA-250 Jisheng Zhang
@ 2025-08-23 15:40 ` Jisheng Zhang
  2025-08-23 16:13   ` Krzysztof Kozlowski
                     ` (2 more replies)
  13 siblings, 3 replies; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-23 15:40 UTC (permalink / raw)
  To: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

Compared with ARM DMA-350, DMA-250 is a simplified version. They share
many common parts, but they do have difference. Add DMA-250 support
by handling their difference by using different device_prep_slave_sg,
device_prep_dma_cyclic and device_prep_dma_memcpy. DMA-250 doesn't
support device_prep_dma_memset.

Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
 drivers/dma/arm-dma350.c | 444 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 424 insertions(+), 20 deletions(-)

diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 5abb965c6687..0ee807424b7e 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 // Copyright (C) 2024-2025 Arm Limited
 // Copyright (C) 2025 Synaptics Incorporated
-// Arm DMA-350 driver
+// Arm DMA-350/DMA-250 driver
 
 #include <linux/bitfield.h>
 #include <linux/dmaengine.h>
@@ -16,6 +16,10 @@
 #include "dmaengine.h"
 #include "virt-dma.h"
 
+#define DMANSECCTRL		0x0200
+
+#define NSEC_CNTXBASE		0x10
+
 #define DMAINFO			0x0f00
 
 #define DMA_BUILDCFG0		0xb0
@@ -26,12 +30,16 @@
 #define DMA_BUILDCFG1		0xb4
 #define DMA_CFG_NUM_TRIGGER_IN	GENMASK(8, 0)
 
+#define DMA_BUILDCFG2		0xb8
+#define DMA_CFG_HAS_TZ		BIT(8)
+
 #define IIDR			0xc8
 #define IIDR_PRODUCTID		GENMASK(31, 20)
 #define IIDR_VARIANT		GENMASK(19, 16)
 #define IIDR_REVISION		GENMASK(15, 12)
 #define IIDR_IMPLEMENTER	GENMASK(11, 0)
 
+#define PRODUCTID_DMA250	0x250
 #define PRODUCTID_DMA350	0x3a0
 #define IMPLEMENTER_ARM		0x43b
 
@@ -140,6 +148,7 @@
 #define CH_CFG_HAS_TRIGSEL	BIT(7)
 #define CH_CFG_HAS_TRIGIN	BIT(5)
 #define CH_CFG_HAS_WRAP		BIT(1)
+#define CH_CFG_HAS_XSIZEHI	BIT(0)
 
 
 #define LINK_REGCLEAR		BIT(0)
@@ -218,6 +227,7 @@ struct d350_chan {
 	bool cyclic;
 	bool has_trig;
 	bool has_wrap;
+	bool has_xsizehi;
 	bool coherent;
 };
 
@@ -225,6 +235,10 @@ struct d350 {
 	struct dma_device dma;
 	int nchan;
 	int nreq;
+	bool is_d250;
+	dma_addr_t cntx_mem_paddr;
+	void *cntx_mem;
+	u32 cntx_mem_size;
 	struct d350_chan channels[] __counted_by(nchan);
 };
 
@@ -238,6 +252,11 @@ static inline struct d350_desc *to_d350_desc(struct virt_dma_desc *vd)
 	return container_of(vd, struct d350_desc, vd);
 }
 
+static inline struct d350 *to_d350(struct dma_device *dd)
+{
+	return container_of(dd, struct d350, dma);
+}
+
 static void d350_desc_free(struct virt_dma_desc *vd)
 {
 	struct d350_chan *dch = to_d350_chan(vd->tx.chan);
@@ -585,6 +604,337 @@ static int d350_slave_config(struct dma_chan *chan, struct dma_slave_config *con
 	return 0;
 }
 
+static struct dma_async_tx_descriptor *d250_prep_memcpy(struct dma_chan *chan,
+		dma_addr_t dest, dma_addr_t src, size_t len, unsigned long flags)
+{
+	struct d350_chan *dch = to_d350_chan(chan);
+	struct d350_desc *desc;
+	u32 *cmd, *la_cmd, tsz;
+	int sglen, i;
+	struct d350_sg *sg;
+	size_t xfer_len, step_max;
+	dma_addr_t phys;
+
+	tsz = __ffs(len | dest | src | (1 << dch->tsz));
+	step_max = ((1UL << 16) - 1) << tsz;
+	sglen = DIV_ROUND_UP(len, step_max);
+
+	desc = kzalloc(struct_size(desc, sg, sglen), GFP_NOWAIT);
+	if (!desc)
+		return NULL;
+
+	desc->sglen = sglen;
+	sglen = 0;
+	while (len) {
+		sg = &desc->sg[sglen];
+		xfer_len = (len > step_max) ? step_max : len;
+		sg->tsz = __ffs(xfer_len | dest | src | (1 << dch->tsz));
+		sg->xsize = lower_16_bits(xfer_len >> sg->tsz);
+
+		sg->command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
+		if (unlikely(!sg->command))
+			goto err_cmd_alloc;
+		sg->phys = phys;
+
+		cmd = sg->command;
+		if (!sglen) {
+			cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_DESADDR |
+				 LINK_XSIZE | LINK_SRCTRANSCFG |
+				 LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR;
+
+			cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, sg->tsz) |
+				 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE);
+
+			cmd[2] = lower_32_bits(src);
+			cmd[3] = lower_32_bits(dest);
+			cmd[4] = FIELD_PREP(CH_XY_SRC, sg->xsize) |
+				 FIELD_PREP(CH_XY_DES, sg->xsize);
+			cmd[5] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+			cmd[6] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+			cmd[7] = FIELD_PREP(CH_XY_SRC, 1) | FIELD_PREP(CH_XY_DES, 1);
+			la_cmd = &cmd[8];
+		} else {
+			*la_cmd = phys | CH_LINKADDR_EN;
+			if (len <= step_max) {
+				cmd[0] = LINK_CTRL | LINK_XSIZE | LINK_LINKADDR;
+				cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, sg->tsz) |
+					 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE);
+				cmd[2] = FIELD_PREP(CH_XY_SRC, sg->xsize) |
+					 FIELD_PREP(CH_XY_DES, sg->xsize);
+				la_cmd = &cmd[3];
+			} else {
+				cmd[0] = LINK_XSIZE | LINK_LINKADDR;
+				cmd[1] = FIELD_PREP(CH_XY_SRC, sg->xsize) |
+					 FIELD_PREP(CH_XY_DES, sg->xsize);
+				la_cmd = &cmd[2];
+			}
+		}
+
+		len -= xfer_len;
+		src += xfer_len;
+		dest += xfer_len;
+		sglen++;
+	}
+
+	/* the last cmdlink */
+	*la_cmd = 0;
+	desc->sg[sglen - 1].command[1] |= FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD);
+
+	mb();
+
+	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
+
+err_cmd_alloc:
+	for (i = 0; i < sglen; i++)
+		dma_pool_free(dch->cmd_pool, desc->sg[i].command, desc->sg[i].phys);
+	kfree(desc);
+	return NULL;
+}
+
+static struct dma_async_tx_descriptor *
+d250_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
+		   unsigned int sg_len, enum dma_transfer_direction dir,
+		   unsigned long flags, void *context)
+{
+	struct d350_chan *dch = to_d350_chan(chan);
+	dma_addr_t src, dst, phys, mem_addr;
+	size_t xfer_len, step_max;
+	u32 len, trig, *cmd, *la_cmd, tsz;
+	struct d350_desc *desc;
+	struct scatterlist *sg;
+	struct d350_sg *dsg;
+	int i, sglen = 0;
+
+	if (unlikely(!is_slave_direction(dir) || !sg_len))
+		return NULL;
+
+	if (dir == DMA_MEM_TO_DEV)
+		tsz = __ffs(dch->config.dst_addr_width | (1 << dch->tsz));
+	else
+		tsz = __ffs(dch->config.src_addr_width | (1 << dch->tsz));
+	step_max = ((1UL << 16) - 1) << tsz;
+
+	for_each_sg(sgl, sg, sg_len, i)
+		sglen += DIV_ROUND_UP(sg_dma_len(sg), step_max);
+
+	desc = kzalloc(struct_size(desc, sg, sglen), GFP_NOWAIT);
+	if (!desc)
+		return NULL;
+
+	desc->sglen = sglen;
+
+	sglen = 0;
+	for_each_sg(sgl, sg, sg_len, i) {
+		len = sg_dma_len(sg);
+		mem_addr = sg_dma_address(sg);
+
+		do {
+			desc->sg[sglen].command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
+			if (unlikely(!desc->sg[sglen].command))
+				goto err_cmd_alloc;
+
+			xfer_len = (len > step_max) ? step_max : len;
+			desc->sg[sglen].phys = phys;
+			dsg = &desc->sg[sglen];
+
+			if (dir == DMA_MEM_TO_DEV) {
+				src = mem_addr;
+				dst = dch->config.dst_addr;
+				trig = CH_CTRL_USEDESTRIGIN;
+			} else {
+				src = dch->config.src_addr;
+				dst = mem_addr;
+				trig = CH_CTRL_USESRCTRIGIN;
+			}
+			dsg->tsz = tsz;
+			dsg->xsize = lower_16_bits(xfer_len >> dsg->tsz);
+
+			cmd = dsg->command;
+			if (!sglen) {
+				cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_DESADDR |
+					 LINK_XSIZE | LINK_SRCTRANSCFG |
+					 LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR;
+
+				cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, dsg->tsz) | trig |
+					 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE);
+
+				cmd[2] = lower_32_bits(src);
+				cmd[3] = lower_32_bits(dst);
+				cmd[4] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
+					 FIELD_PREP(CH_XY_DES, dsg->xsize);
+				if (dir == DMA_MEM_TO_DEV) {
+					cmd[0] |= LINK_DESTRIGINCFG;
+					cmd[5] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+					cmd[6] = TRANSCFG_DEVICE;
+					cmd[7] = FIELD_PREP(CH_XY_SRC, 1);
+					cmd[8] = FIELD_PREP(CH_DESTRIGINMODE, CH_DESTRIG_DMA_FC) |
+						  FIELD_PREP(CH_DESTRIGINTYPE, CH_DESTRIG_HW_REQ);
+				} else {
+					cmd[0] |= LINK_SRCTRIGINCFG;
+					cmd[5] = TRANSCFG_DEVICE;
+					cmd[6] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+					cmd[7] = FIELD_PREP(CH_XY_DES, 1);
+					cmd[8] = FIELD_PREP(CH_SRCTRIGINMODE, CH_SRCTRIG_DMA_FC) |
+						  FIELD_PREP(CH_SRCTRIGINTYPE, CH_SRCTRIG_HW_REQ);
+				}
+				la_cmd = &cmd[9];
+			} else {
+				*la_cmd = phys | CH_LINKADDR_EN;
+				if (sglen == desc->sglen - 1) {
+					cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_DESADDR |
+						 LINK_XSIZE | LINK_LINKADDR;
+					cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, dsg->tsz) | trig |
+						 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE);
+					cmd[2] = lower_32_bits(src);
+					cmd[3] = lower_32_bits(dst);
+					cmd[4] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
+						 FIELD_PREP(CH_XY_DES, dsg->xsize);
+					la_cmd = &cmd[5];
+				} else {
+					cmd[0] = LINK_SRCADDR | LINK_DESADDR |
+						 LINK_XSIZE | LINK_LINKADDR;
+					cmd[1] = lower_32_bits(src);
+					cmd[2] = lower_32_bits(dst);
+					cmd[3] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
+						 FIELD_PREP(CH_XY_DES, dsg->xsize);
+					la_cmd = &cmd[4];
+				}
+			}
+
+			len -= xfer_len;
+			mem_addr += xfer_len;
+			sglen++;
+		} while (len);
+	}
+
+	/* the last command */
+	*la_cmd = 0;
+	desc->sg[sglen - 1].command[1] |= FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD);
+
+	mb();
+
+	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
+
+err_cmd_alloc:
+	for (i = 0; i < sglen; i++)
+		dma_pool_free(dch->cmd_pool, desc->sg[i].command, desc->sg[i].phys);
+	kfree(desc);
+	return NULL;
+}
+
+static struct dma_async_tx_descriptor *
+d250_prep_cyclic(struct dma_chan *chan, dma_addr_t buf_addr,
+		 size_t buf_len, size_t period_len, enum dma_transfer_direction dir,
+		 unsigned long flags)
+{
+	struct d350_chan *dch = to_d350_chan(chan);
+	u32 len, periods, trig, *cmd, tsz;
+	dma_addr_t src, dst, phys, mem_addr;
+	size_t xfer_len, step_max;
+	struct d350_desc *desc;
+	struct scatterlist *sg;
+	struct d350_sg *dsg;
+	int sglen, i;
+
+	if (unlikely(!is_slave_direction(dir) || !buf_len || !period_len))
+		return NULL;
+
+	if (dir == DMA_MEM_TO_DEV)
+		tsz = __ffs(dch->config.dst_addr_width | (1 << dch->tsz));
+	else
+		tsz = __ffs(dch->config.src_addr_width | (1 << dch->tsz));
+	step_max = ((1UL << 16) - 1) << tsz;
+
+	periods = buf_len / period_len;
+	sglen = DIV_ROUND_UP(sg_dma_len(sg), step_max) * periods;
+
+	desc = kzalloc(struct_size(desc, sg, sglen), GFP_NOWAIT);
+	if (!desc)
+		return NULL;
+
+	dch->cyclic = true;
+	dch->periods = periods;
+	desc->sglen = sglen;
+
+	sglen = 0;
+	for (i = 0; i < periods; i++) {
+		len = period_len;
+		mem_addr = buf_addr + i * period_len;
+		do {
+			desc->sg[sglen].command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
+			if (unlikely(!desc->sg[sglen].command))
+				goto err_cmd_alloc;
+
+			xfer_len = (len > step_max) ? step_max : len;
+			desc->sg[sglen].phys = phys;
+			dsg = &desc->sg[sglen];
+
+			if (dir == DMA_MEM_TO_DEV) {
+				src = mem_addr;
+				dst = dch->config.dst_addr;
+				trig = CH_CTRL_USEDESTRIGIN;
+			} else {
+				src = dch->config.src_addr;
+				dst = mem_addr;
+				trig = CH_CTRL_USESRCTRIGIN;
+			}
+			dsg->tsz = tsz;
+			dsg->xsize = lower_16_bits(xfer_len >> dsg->tsz);
+
+			cmd = dsg->command;
+			cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_DESADDR |
+				 LINK_XSIZE | LINK_SRCTRANSCFG |
+				 LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR;
+
+			cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, dsg->tsz) |
+				 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE) |
+				 FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD) | trig;
+
+			cmd[2] = lower_32_bits(src);
+			cmd[3] = lower_32_bits(dst);
+			cmd[4] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
+				 FIELD_PREP(CH_XY_DES, dsg->xsize);
+			if (dir == DMA_MEM_TO_DEV) {
+				cmd[0] |= LINK_DESTRIGINCFG;
+				cmd[5] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+				cmd[6] = TRANSCFG_DEVICE;
+				cmd[7] = FIELD_PREP(CH_XY_SRC, 1);
+				cmd[8] = FIELD_PREP(CH_DESTRIGINMODE, CH_DESTRIG_DMA_FC) |
+					  FIELD_PREP(CH_DESTRIGINTYPE, CH_DESTRIG_HW_REQ);
+			} else {
+				cmd[0] |= LINK_SRCTRIGINCFG;
+				cmd[5] = TRANSCFG_DEVICE;
+				cmd[6] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+				cmd[7] = FIELD_PREP(CH_XY_DES, 1);
+				cmd[8] = FIELD_PREP(CH_SRCTRIGINMODE, CH_SRCTRIG_DMA_FC) |
+					  FIELD_PREP(CH_SRCTRIGINTYPE, CH_SRCTRIG_HW_REQ);
+			}
+
+			if (sglen)
+				desc->sg[sglen - 1].command[9] = phys | CH_LINKADDR_EN;
+
+			len -= xfer_len;
+			mem_addr += xfer_len;
+			sglen++;
+		} while (len);
+		desc->sg[sglen - 1].command[1] |= FIELD_PREP(CH_CTRL_DONETYPE,
+							     CH_CTRL_DONETYPE_CMD);
+	}
+
+	/* cyclic list */
+	desc->sg[sglen - 1].command[9] = desc->sg[0].phys | CH_LINKADDR_EN;
+
+	mb();
+
+	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
+
+err_cmd_alloc:
+	for (i = 0; i < sglen; i++)
+		dma_pool_free(dch->cmd_pool, desc->sg[i].command, desc->sg[i].phys);
+	kfree(desc);
+	return NULL;
+}
+
 static int d350_pause(struct dma_chan *chan)
 {
 	struct d350_chan *dch = to_d350_chan(chan);
@@ -620,20 +970,31 @@ static u32 d350_get_residue(struct d350_chan *dch)
 	u32 res, xsize, xsizehi, linkaddr, linkaddrhi, hi_new;
 	int i, sgcur, retries = 3; /* 1st time unlucky, 2nd improbable, 3rd just broken */
 	struct d350_desc *desc = dch->desc;
+	struct d350 *dmac = to_d350(dch->vc.chan.device);
 
-	hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
-	do {
-		xsizehi = hi_new;
-		xsize = readl_relaxed(dch->base + CH_XSIZE);
+	if (dch->has_xsizehi) {
 		hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
-	} while (xsizehi != hi_new && --retries);
+		do {
+			xsizehi = hi_new;
+			xsize = readl_relaxed(dch->base + CH_XSIZE);
+			hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
+		} while (xsizehi != hi_new && --retries);
+	} else {
+		xsize = readl_relaxed(dch->base + CH_XSIZE);
+		xsizehi = 0;
+	}
 
-	hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
-	do {
-		linkaddrhi = hi_new;
-		linkaddr = readl_relaxed(dch->base + CH_LINKADDR);
+	if (!dmac->is_d250) {
 		hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
-	} while (linkaddrhi != hi_new && --retries);
+		do {
+			linkaddrhi = hi_new;
+			linkaddr = readl_relaxed(dch->base + CH_LINKADDR);
+			hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
+		} while (linkaddrhi != hi_new && --retries);
+	} else {
+		linkaddr = readl_relaxed(dch->base + CH_LINKADDR);
+		linkaddrhi = 0;
+	}
 
 	for (i = 0; i < desc->sglen; i++) {
 		if (desc->sg[i].phys == (((u64)linkaddrhi << 32) | (linkaddr & ~CH_LINKADDR_EN)))
@@ -876,6 +1237,14 @@ static void d350_free_chan_resources(struct dma_chan *chan)
 	dch->cmd_pool = NULL;
 }
 
+static void d250_cntx_mem_release(void *ptr)
+{
+	struct d350 *dmac = ptr;
+	struct device *dev = dmac->dma.dev;
+
+	dma_free_coherent(dev, dmac->cntx_mem_size, dmac->cntx_mem, dmac->cntx_mem_paddr);
+}
+
 static int d350_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -893,8 +1262,9 @@ static int d350_probe(struct platform_device *pdev)
 	r = FIELD_GET(IIDR_VARIANT, reg);
 	p = FIELD_GET(IIDR_REVISION, reg);
 	if (FIELD_GET(IIDR_IMPLEMENTER, reg) != IMPLEMENTER_ARM ||
-	    FIELD_GET(IIDR_PRODUCTID, reg) != PRODUCTID_DMA350)
-		return dev_err_probe(dev, -ENODEV, "Not a DMA-350!");
+	    ((FIELD_GET(IIDR_PRODUCTID, reg) != PRODUCTID_DMA350) &&
+	    FIELD_GET(IIDR_PRODUCTID, reg) != PRODUCTID_DMA250))
+		return dev_err_probe(dev, -ENODEV, "Not a DMA-350/DMA-250!");
 
 	reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG0);
 	nchan = FIELD_GET(DMA_CFG_NUM_CHANNELS, reg) + 1;
@@ -917,13 +1287,38 @@ static int d350_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	if (device_is_compatible(dev, "arm,dma-250")) {
+		u32 cfg2;
+		int secext_present;
+
+		dmac->is_d250 = true;
+
+		cfg2 = readl_relaxed(base + DMAINFO + DMA_BUILDCFG2);
+		secext_present = (cfg2 & DMA_CFG_HAS_TZ) ? 1 : 0;
+		dmac->cntx_mem_size = nchan * 64 * (1 + secext_present);
+		dmac->cntx_mem = dma_alloc_coherent(dev, dmac->cntx_mem_size,
+						    &dmac->cntx_mem_paddr,
+						    GFP_KERNEL);
+		if (!dmac->cntx_mem)
+			return dev_err_probe(dev, -ENOMEM, "Failed to alloc context memory\n");
+
+		ret = devm_add_action_or_reset(dev, d250_cntx_mem_release, dmac);
+		if (ret) {
+			dma_free_coherent(dev, dmac->cntx_mem_size,
+					  dmac->cntx_mem, dmac->cntx_mem_paddr);
+			return ret;
+		}
+		writel_relaxed(dmac->cntx_mem_paddr, base + DMANSECCTRL + NSEC_CNTXBASE);
+	}
+
 	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(aw));
 	coherent = device_get_dma_attr(dev) == DEV_DMA_COHERENT;
 
 	reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG1);
 	dmac->nreq = FIELD_GET(DMA_CFG_NUM_TRIGGER_IN, reg);
 
-	dev_dbg(dev, "DMA-350 r%dp%d with %d channels, %d requests\n", r, p, dmac->nchan, dmac->nreq);
+	dev_info(dev, "%s r%dp%d with %d channels, %d requests\n",
+		 dmac->is_d250 ? "DMA-250" : "DMA-350", r, p, dmac->nchan, dmac->nreq);
 
 	for (int i = min(dw, 16); i > 0; i /= 2) {
 		dmac->dma.src_addr_widths |= BIT(i);
@@ -935,7 +1330,10 @@ static int d350_probe(struct platform_device *pdev)
 	dmac->dma.device_alloc_chan_resources = d350_alloc_chan_resources;
 	dmac->dma.device_free_chan_resources = d350_free_chan_resources;
 	dma_cap_set(DMA_MEMCPY, dmac->dma.cap_mask);
-	dmac->dma.device_prep_dma_memcpy = d350_prep_memcpy;
+	if (dmac->is_d250)
+		dmac->dma.device_prep_dma_memcpy = d250_prep_memcpy;
+	else
+		dmac->dma.device_prep_dma_memcpy = d350_prep_memcpy;
 	dmac->dma.device_pause = d350_pause;
 	dmac->dma.device_resume = d350_resume;
 	dmac->dma.device_terminate_all = d350_terminate_all;
@@ -971,8 +1369,8 @@ static int d350_probe(struct platform_device *pdev)
 			return dch->irq;
 
 		dch->has_wrap = FIELD_GET(CH_CFG_HAS_WRAP, reg);
-		dch->has_trig = FIELD_GET(CH_CFG_HAS_TRIGIN, reg) &
-				FIELD_GET(CH_CFG_HAS_TRIGSEL, reg);
+		dch->has_xsizehi = FIELD_GET(CH_CFG_HAS_XSIZEHI, reg);
+		dch->has_trig = FIELD_GET(CH_CFG_HAS_TRIGIN, reg);
 
 		/* Fill is a special case of Wrap */
 		memset &= dch->has_wrap;
@@ -994,8 +1392,13 @@ static int d350_probe(struct platform_device *pdev)
 		dma_cap_set(DMA_SLAVE, dmac->dma.cap_mask);
 		dma_cap_set(DMA_CYCLIC, dmac->dma.cap_mask);
 		dmac->dma.device_config = d350_slave_config;
-		dmac->dma.device_prep_slave_sg = d350_prep_slave_sg;
-		dmac->dma.device_prep_dma_cyclic = d350_prep_cyclic;
+		if (dmac->is_d250) {
+			dmac->dma.device_prep_slave_sg = d250_prep_slave_sg;
+			dmac->dma.device_prep_dma_cyclic = d250_prep_cyclic;
+		} else {
+			dmac->dma.device_prep_slave_sg = d350_prep_slave_sg;
+			dmac->dma.device_prep_dma_cyclic = d350_prep_cyclic;
+		}
 	}
 
 	if (memset) {
@@ -1019,6 +1422,7 @@ static void d350_remove(struct platform_device *pdev)
 
 static const struct of_device_id d350_of_match[] __maybe_unused = {
 	{ .compatible = "arm,dma-350" },
+	{ .compatible = "arm,dma-250" },
 	{}
 };
 MODULE_DEVICE_TABLE(of, d350_of_match);
@@ -1035,5 +1439,5 @@ module_platform_driver(d350_driver);
 
 MODULE_AUTHOR("Robin Murphy <robin.murphy@arm.com>");
 MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
-MODULE_DESCRIPTION("Arm DMA-350 driver");
+MODULE_DESCRIPTION("Arm DMA-350/DMA-250 driver");
 MODULE_LICENSE("GPL v2");
-- 
2.50.0



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names
  2025-08-23 15:40 ` [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names Jisheng Zhang
@ 2025-08-23 16:09   ` Krzysztof Kozlowski
  2025-08-24  9:49     ` Jisheng Zhang
  2025-08-29 11:08   ` Robin Murphy
  1 sibling, 1 reply; 35+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-23 16:09 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 23/08/2025 17:40, Jisheng Zhang wrote:
> Currently, the dma350 driver assumes all channels are available to
> linux, this may not be true on some platforms, so it's possible no
> irq(s) for the unavailable channel(s). What's more, the available
> channels may not be continuous. To handle this case, we'd better
> get the irq of each channel by name.

You did not solve the actual problem - binding still lists the
interrupts in specific order.

> 
> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>  Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 5 +++++
>  1 file changed, 5 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> index 429f682f15d8..94752516e51a 100644
> --- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> +++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> @@ -32,6 +32,10 @@ properties:
>        - description: Channel 6 interrupt
>        - description: Channel 7 interrupt
>  
> +  interrupt-names:
> +    minItems: 1
> +    maxItems: 8

You need to list the items.


> +
>    "#dma-cells":
>      const: 1
>      description: The cell is the trigger input number
> @@ -40,5 +44,6 @@ required:
>    - compatible
>    - reg
>    - interrupts
> +  - interrupt-names

That's ABI break, so no.



Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 09/14] dmaengine: dma350: Support dma-channel-mask
  2025-08-23 15:40 ` [PATCH 09/14] dmaengine: dma350: Support dma-channel-mask Jisheng Zhang
@ 2025-08-23 16:10   ` Krzysztof Kozlowski
  2025-08-24  7:01   ` kernel test robot
  1 sibling, 0 replies; 35+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-23 16:10 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 23/08/2025 17:40, Jisheng Zhang wrote:
> Not all channels are available to kernel, we need to support
> dma-channel-mask.
> 
> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>  drivers/dma/arm-dma350.c | 20 ++++++++++++++++++--
>  1 file changed, 18 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index 6a6d1c2a3ee6..72067518799e 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -534,7 +534,7 @@ static int d350_probe(struct platform_device *pdev)
>  	struct device *dev = &pdev->dev;
>  	struct d350 *dmac;
>  	void __iomem *base;
> -	u32 reg;
> +	u32 reg, dma_chan_mask;
>  	int ret, nchan, dw, aw, r, p;
>  	bool coherent, memset;
>  
> @@ -563,6 +563,15 @@ static int d350_probe(struct platform_device *pdev)
>  
>  	dmac->nchan = nchan;
>  
> +	/* Enable all channels by default */
> +	dma_chan_mask = nchan - 1;
> +
> +	ret = of_property_read_u32(dev->of_node, "dma-channel-mask", &dma_chan_mask);
> +	if (ret < 0 && (ret != -EINVAL)) {
> +		dev_err(&pdev->dev, "dma-channel-mask is not complete.\n");
> +		return ret;
> +	}
> +
>  	reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG1);
>  	dmac->nreq = FIELD_GET(DMA_CFG_NUM_TRIGGER_IN, reg);
>  
> @@ -592,6 +601,11 @@ static int d350_probe(struct platform_device *pdev)
>  	memset = true;
>  	for (int i = 0; i < nchan; i++) {
>  		struct d350_chan *dch = &dmac->channels[i];
> +		char ch_irqname[8];
> +
> +		/* skip for reserved channels */
> +		if (!test_bit(i, (unsigned long *)&dma_chan_mask))
> +			continue;
>  
>  		dch->coherent = coherent;
>  		dch->base = base + DMACH(i);
> @@ -602,7 +616,9 @@ static int d350_probe(struct platform_device *pdev)
>  			dev_warn(dev, "No command link support on channel %d\n", i);
>  			continue;
>  		}
> -		dch->irq = platform_get_irq(pdev, i);
> +
> +		snprintf(ch_irqname, sizeof(ch_irqname), "ch%d", i);
> +		dch->irq = platform_get_irq_byname(pdev, ch_irqname);

Actual ABI break.

That's a no-go, sorry. You cannot decide to break all users just because
"Not all channels are available to the kernel". That's really, really
incomplete ABI breakage reasoning.

See also writing bindings doc.

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 13/14] dt-bindings: dma: dma350: Support ARM DMA-250
  2025-08-23 15:40 ` [PATCH 13/14] dt-bindings: dma: dma350: Support ARM DMA-250 Jisheng Zhang
@ 2025-08-23 16:11   ` Krzysztof Kozlowski
  2025-08-29 11:16     ` Robin Murphy
  0 siblings, 1 reply; 35+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-23 16:11 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 23/08/2025 17:40, Jisheng Zhang wrote:
> Compared with ARM DMA-350, DMA-250 is a simplified version, they share
> many common parts, but they do have difference. The difference will be
> handled in next driver patch, while let's add DMA-250 compatible string
> to dt-binding now.
> 
> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>  Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> index 94752516e51a..d49736b7de5e 100644
> --- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> +++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> @@ -15,6 +15,7 @@ allOf:
>  properties:
>    compatible:
>      const: arm,dma-350
> +    const: arm,dma-250

This obviously cannot work and was NEVER tested. Please test your code
before you send it to mailing lists.

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 14/14] dmaengine: dma350: Support ARM DMA-250
  2025-08-23 15:40 ` [PATCH 14/14] dmaengine: " Jisheng Zhang
@ 2025-08-23 16:13   ` Krzysztof Kozlowski
  2025-08-24 19:38   ` kernel test robot
  2025-08-29 22:24   ` Robin Murphy
  2 siblings, 0 replies; 35+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-23 16:13 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robin Murphy
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 23/08/2025 17:40, Jisheng Zhang wrote:
>  	struct device *dev = &pdev->dev;
> @@ -893,8 +1262,9 @@ static int d350_probe(struct platform_device *pdev)
>  	r = FIELD_GET(IIDR_VARIANT, reg);
>  	p = FIELD_GET(IIDR_REVISION, reg);
>  	if (FIELD_GET(IIDR_IMPLEMENTER, reg) != IMPLEMENTER_ARM ||
> -	    FIELD_GET(IIDR_PRODUCTID, reg) != PRODUCTID_DMA350)
> -		return dev_err_probe(dev, -ENODEV, "Not a DMA-350!");
> +	    ((FIELD_GET(IIDR_PRODUCTID, reg) != PRODUCTID_DMA350) &&
> +	    FIELD_GET(IIDR_PRODUCTID, reg) != PRODUCTID_DMA250))
> +		return dev_err_probe(dev, -ENODEV, "Not a DMA-350/DMA-250!");
>  
>  	reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG0);
>  	nchan = FIELD_GET(DMA_CFG_NUM_CHANNELS, reg) + 1;
> @@ -917,13 +1287,38 @@ static int d350_probe(struct platform_device *pdev)
>  		return ret;
>  	}
>  
> +	if (device_is_compatible(dev, "arm,dma-250")) {

No, don't sprinkle compatibles through driver code. driver match data is
for that.

> +		u32 cfg2;
> +		int secext_present;
> +
> +		dmac->is_d250 = true;
> +
> +		cfg2 = readl_relaxed(base + DMAINFO + DMA_BUILDCFG2);
> +		secext_present = (cfg2 & DMA_CFG_HAS_TZ) ? 1 : 0;
> +		dmac->cntx_mem_size = nchan * 64 * (1 + secext_present);
> +		dmac->cntx_mem = dma_alloc_coherent(dev, dmac->cntx_mem_size,
> +						    &dmac->cntx_mem_paddr,
> +						    GFP_KERNEL);
> +		if (!dmac->cntx_mem)
> +			return dev_err_probe(dev, -ENOMEM, "Failed to alloc context memory\n");
> +
> +		ret = devm_add_action_or_reset(dev, d250_cntx_mem_release, dmac);
> +		if (ret) {
> +			dma_free_coherent(dev, dmac->cntx_mem_size,
> +					  dmac->cntx_mem, dmac->cntx_mem_paddr);
> +			return ret;
> +		}
> +		writel_relaxed(dmac->cntx_mem_paddr, base + DMANSECCTRL + NSEC_CNTXBASE);
> +	}
> +
>  	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(aw));
>  	coherent = device_get_dma_attr(dev) == DEV_DMA_COHERENT;
>  
>  	reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG1);
>  	dmac->nreq = FIELD_GET(DMA_CFG_NUM_TRIGGER_IN, reg);
>  
> -	dev_dbg(dev, "DMA-350 r%dp%d with %d channels, %d requests\n", r, p, dmac->nchan, dmac->nreq);
> +	dev_info(dev, "%s r%dp%d with %d channels, %d requests\n",
> +		 dmac->is_d250 ? "DMA-250" : "DMA-350", r, p, dmac->nchan, dmac->nreq);

No, don't makek drivers more verbose. Please follow Linux driver
design/coding style - this should be silent on success.

>  
>  	for (int i = min(dw, 16); i > 0; i /= 2) {
>  		dmac->dma.src_addr_widths |= BIT(i);
> @@ -935,7 +1330,10 @@ static int d350_probe(struct platform_device *pdev)
>  	dmac->dma.device_alloc_chan_resources = d350_alloc_chan_resources;
>  	dmac->dma.device_free_chan_resources = d350_free_chan_resources;
>  	dma_cap_set(DMA_MEMCPY, dmac->dma.cap_mask);
> -	dmac->dma.device_prep_dma_memcpy = d350_prep_memcpy;
> +	if (dmac->is_d250)
> +		dmac->dma.device_prep_dma_memcpy = d250_prep_memcpy;
> +	else
> +		dmac->dma.device_prep_dma_memcpy = d350_prep_memcpy;
>  	dmac->dma.device_pause = d350_pause;
>  	dmac->dma.device_resume = d350_resume;
>  	dmac->dma.device_terminate_all = d350_terminate_all;
> @@ -971,8 +1369,8 @@ static int d350_probe(struct platform_device *pdev)
>  			return dch->irq;
>  
>  		dch->has_wrap = FIELD_GET(CH_CFG_HAS_WRAP, reg);
> -		dch->has_trig = FIELD_GET(CH_CFG_HAS_TRIGIN, reg) &
> -				FIELD_GET(CH_CFG_HAS_TRIGSEL, reg);
> +		dch->has_xsizehi = FIELD_GET(CH_CFG_HAS_XSIZEHI, reg);
> +		dch->has_trig = FIELD_GET(CH_CFG_HAS_TRIGIN, reg);
>  
>  		/* Fill is a special case of Wrap */
>  		memset &= dch->has_wrap;
> @@ -994,8 +1392,13 @@ static int d350_probe(struct platform_device *pdev)
>  		dma_cap_set(DMA_SLAVE, dmac->dma.cap_mask);
>  		dma_cap_set(DMA_CYCLIC, dmac->dma.cap_mask);
>  		dmac->dma.device_config = d350_slave_config;
> -		dmac->dma.device_prep_slave_sg = d350_prep_slave_sg;
> -		dmac->dma.device_prep_dma_cyclic = d350_prep_cyclic;
> +		if (dmac->is_d250) {
> +			dmac->dma.device_prep_slave_sg = d250_prep_slave_sg;
> +			dmac->dma.device_prep_dma_cyclic = d250_prep_cyclic;
> +		} else {
> +			dmac->dma.device_prep_slave_sg = d350_prep_slave_sg;
> +			dmac->dma.device_prep_dma_cyclic = d350_prep_cyclic;
> +		}
>  	}
>  
>  	if (memset) {
> @@ -1019,6 +1422,7 @@ static void d350_remove(struct platform_device *pdev)
>  
>  static const struct of_device_id d350_of_match[] __maybe_unused = {
>  	{ .compatible = "arm,dma-350" },
> +	{ .compatible = "arm,dma-250" },

And based on that devices would be compatible...

BTW, incorrect order - 2 < 3.



Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 09/14] dmaengine: dma350: Support dma-channel-mask
  2025-08-23 15:40 ` [PATCH 09/14] dmaengine: dma350: Support dma-channel-mask Jisheng Zhang
  2025-08-23 16:10   ` Krzysztof Kozlowski
@ 2025-08-24  7:01   ` kernel test robot
  1 sibling, 0 replies; 35+ messages in thread
From: kernel test robot @ 2025-08-24  7:01 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robin Murphy
  Cc: oe-kbuild-all, dmaengine, devicetree, linux-arm-kernel,
	linux-kernel

Hi Jisheng,

kernel test robot noticed the following build warnings:

[auto build test WARNING on vkoul-dmaengine/next]
[also build test WARNING on robh/for-next linus/master v6.17-rc2 next-20250822]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Jisheng-Zhang/dmaengine-dma350-Fix-CH_CTRL_USESRCTRIGIN-definition/20250824-000425
base:   https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git next
patch link:    https://lore.kernel.org/r/20250823154009.25992-10-jszhang%40kernel.org
patch subject: [PATCH 09/14] dmaengine: dma350: Support dma-channel-mask
config: arm64-randconfig-002-20250824 (https://download.01.org/0day-ci/archive/20250824/202508241415.b7kiTLel-lkp@intel.com/config)
compiler: aarch64-linux-gcc (GCC) 11.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250824/202508241415.b7kiTLel-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202508241415.b7kiTLel-lkp@intel.com/

All warnings (new ones prefixed by >>):

   drivers/dma/arm-dma350.c: In function 'd350_probe':
>> drivers/dma/arm-dma350.c:620:61: warning: '%d' directive output may be truncated writing between 1 and 10 bytes into a region of size 6 [-Wformat-truncation=]
     620 |                 snprintf(ch_irqname, sizeof(ch_irqname), "ch%d", i);
         |                                                             ^~
   drivers/dma/arm-dma350.c:620:58: note: directive argument in the range [0, 2147483646]
     620 |                 snprintf(ch_irqname, sizeof(ch_irqname), "ch%d", i);
         |                                                          ^~~~~~
   drivers/dma/arm-dma350.c:620:17: note: 'snprintf' output between 4 and 13 bytes into a destination of size 8
     620 |                 snprintf(ch_irqname, sizeof(ch_irqname), "ch%d", i);
         |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


vim +620 drivers/dma/arm-dma350.c

   531	
   532	static int d350_probe(struct platform_device *pdev)
   533	{
   534		struct device *dev = &pdev->dev;
   535		struct d350 *dmac;
   536		void __iomem *base;
   537		u32 reg, dma_chan_mask;
   538		int ret, nchan, dw, aw, r, p;
   539		bool coherent, memset;
   540	
   541		base = devm_platform_ioremap_resource(pdev, 0);
   542		if (IS_ERR(base))
   543			return PTR_ERR(base);
   544	
   545		reg = readl_relaxed(base + DMAINFO + IIDR);
   546		r = FIELD_GET(IIDR_VARIANT, reg);
   547		p = FIELD_GET(IIDR_REVISION, reg);
   548		if (FIELD_GET(IIDR_IMPLEMENTER, reg) != IMPLEMENTER_ARM ||
   549		    FIELD_GET(IIDR_PRODUCTID, reg) != PRODUCTID_DMA350)
   550			return dev_err_probe(dev, -ENODEV, "Not a DMA-350!");
   551	
   552		reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG0);
   553		nchan = FIELD_GET(DMA_CFG_NUM_CHANNELS, reg) + 1;
   554		dw = 1 << FIELD_GET(DMA_CFG_DATA_WIDTH, reg);
   555		aw = FIELD_GET(DMA_CFG_ADDR_WIDTH, reg) + 1;
   556	
   557		dma_set_mask_and_coherent(dev, DMA_BIT_MASK(aw));
   558		coherent = device_get_dma_attr(dev) == DEV_DMA_COHERENT;
   559	
   560		dmac = devm_kzalloc(dev, struct_size(dmac, channels, nchan), GFP_KERNEL);
   561		if (!dmac)
   562			return -ENOMEM;
   563	
   564		dmac->nchan = nchan;
   565	
   566		/* Enable all channels by default */
   567		dma_chan_mask = nchan - 1;
   568	
   569		ret = of_property_read_u32(dev->of_node, "dma-channel-mask", &dma_chan_mask);
   570		if (ret < 0 && (ret != -EINVAL)) {
   571			dev_err(&pdev->dev, "dma-channel-mask is not complete.\n");
   572			return ret;
   573		}
   574	
   575		reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG1);
   576		dmac->nreq = FIELD_GET(DMA_CFG_NUM_TRIGGER_IN, reg);
   577	
   578		dev_dbg(dev, "DMA-350 r%dp%d with %d channels, %d requests\n", r, p, dmac->nchan, dmac->nreq);
   579	
   580		dmac->dma.dev = dev;
   581		for (int i = min(dw, 16); i > 0; i /= 2) {
   582			dmac->dma.src_addr_widths |= BIT(i);
   583			dmac->dma.dst_addr_widths |= BIT(i);
   584		}
   585		dmac->dma.directions = BIT(DMA_MEM_TO_MEM);
   586		dmac->dma.descriptor_reuse = true;
   587		dmac->dma.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
   588		dmac->dma.device_alloc_chan_resources = d350_alloc_chan_resources;
   589		dmac->dma.device_free_chan_resources = d350_free_chan_resources;
   590		dma_cap_set(DMA_MEMCPY, dmac->dma.cap_mask);
   591		dmac->dma.device_prep_dma_memcpy = d350_prep_memcpy;
   592		dmac->dma.device_pause = d350_pause;
   593		dmac->dma.device_resume = d350_resume;
   594		dmac->dma.device_terminate_all = d350_terminate_all;
   595		dmac->dma.device_synchronize = d350_synchronize;
   596		dmac->dma.device_tx_status = d350_tx_status;
   597		dmac->dma.device_issue_pending = d350_issue_pending;
   598		INIT_LIST_HEAD(&dmac->dma.channels);
   599	
   600		/* Would be nice to have per-channel caps for this... */
   601		memset = true;
   602		for (int i = 0; i < nchan; i++) {
   603			struct d350_chan *dch = &dmac->channels[i];
   604			char ch_irqname[8];
   605	
   606			/* skip for reserved channels */
   607			if (!test_bit(i, (unsigned long *)&dma_chan_mask))
   608				continue;
   609	
   610			dch->coherent = coherent;
   611			dch->base = base + DMACH(i);
   612			writel_relaxed(CH_CMD_CLEAR, dch->base + CH_CMD);
   613	
   614			reg = readl_relaxed(dch->base + CH_BUILDCFG1);
   615			if (!(FIELD_GET(CH_CFG_HAS_CMDLINK, reg))) {
   616				dev_warn(dev, "No command link support on channel %d\n", i);
   617				continue;
   618			}
   619	
 > 620			snprintf(ch_irqname, sizeof(ch_irqname), "ch%d", i);
   621			dch->irq = platform_get_irq_byname(pdev, ch_irqname);
   622			if (dch->irq < 0)
   623				return dch->irq;
   624	
   625			dch->has_wrap = FIELD_GET(CH_CFG_HAS_WRAP, reg);
   626			dch->has_trig = FIELD_GET(CH_CFG_HAS_TRIGIN, reg) &
   627					FIELD_GET(CH_CFG_HAS_TRIGSEL, reg);
   628	
   629			/* Fill is a special case of Wrap */
   630			memset &= dch->has_wrap;
   631	
   632			reg = readl_relaxed(dch->base + CH_BUILDCFG0);
   633			dch->tsz = FIELD_GET(CH_CFG_DATA_WIDTH, reg);
   634	
   635			reg = FIELD_PREP(CH_LINK_SHAREATTR, coherent ? SHAREATTR_ISH : SHAREATTR_OSH);
   636			reg |= FIELD_PREP(CH_LINK_MEMATTR, coherent ? MEMATTR_WB : MEMATTR_NC);
   637			writel_relaxed(reg, dch->base + CH_LINKATTR);
   638	
   639			dch->vc.desc_free = d350_desc_free;
   640			vchan_init(&dch->vc, &dmac->dma);
   641		}
   642	
   643		if (memset) {
   644			dma_cap_set(DMA_MEMSET, dmac->dma.cap_mask);
   645			dmac->dma.device_prep_dma_memset = d350_prep_memset;
   646		}
   647	
   648		platform_set_drvdata(pdev, dmac);
   649	
   650		ret = dmaenginem_async_device_register(&dmac->dma);
   651		if (ret)
   652			return dev_err_probe(dev, ret, "Failed to register DMA device\n");
   653	
   654		return of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id, &dmac->dma);
   655	}
   656	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names
  2025-08-23 16:09   ` Krzysztof Kozlowski
@ 2025-08-24  9:49     ` Jisheng Zhang
  2025-08-24 10:30       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-24  9:49 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy, dmaengine, devicetree, linux-arm-kernel,
	linux-kernel

On Sat, Aug 23, 2025 at 06:09:22PM +0200, Krzysztof Kozlowski wrote:
> On 23/08/2025 17:40, Jisheng Zhang wrote:
> > Currently, the dma350 driver assumes all channels are available to
> > linux, this may not be true on some platforms, so it's possible no
> > irq(s) for the unavailable channel(s). What's more, the available
> > channels may not be continuous. To handle this case, we'd better
> > get the irq of each channel by name.
> 
> You did not solve the actual problem - binding still lists the
> interrupts in specific order.
> 
> > 
> > Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> > ---
> >  Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 5 +++++
> >  1 file changed, 5 insertions(+)
> > 
> > diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> > index 429f682f15d8..94752516e51a 100644
> > --- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> > +++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> > @@ -32,6 +32,10 @@ properties:
> >        - description: Channel 6 interrupt
> >        - description: Channel 7 interrupt
> >  
> > +  interrupt-names:
> > +    minItems: 1
> > +    maxItems: 8
> 
> You need to list the items.

I found in current dt-bindings, not all doc list the items. So is it
changed now?

> 
> 
> > +
> >    "#dma-cells":
> >      const: 1
> >      description: The cell is the trigger input number
> > @@ -40,5 +44,6 @@ required:
> >    - compatible
> >    - reg
> >    - interrupts
> > +  - interrupt-names
> 
> That's ABI break, so no.

If there's no users of arm-dma350 in upstream so far, is ABI break
allowed? The reason is simple: to simplify the driver to parse
the irq.

Thanks


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names
  2025-08-24  9:49     ` Jisheng Zhang
@ 2025-08-24 10:30       ` Krzysztof Kozlowski
  2025-08-24 13:19         ` Jisheng Zhang
  0 siblings, 1 reply; 35+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-24 10:30 UTC (permalink / raw)
  To: Jisheng Zhang
  Cc: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy, dmaengine, devicetree, linux-arm-kernel,
	linux-kernel

On 24/08/2025 11:49, Jisheng Zhang wrote:
> On Sat, Aug 23, 2025 at 06:09:22PM +0200, Krzysztof Kozlowski wrote:
>> On 23/08/2025 17:40, Jisheng Zhang wrote:
>>> Currently, the dma350 driver assumes all channels are available to
>>> linux, this may not be true on some platforms, so it's possible no
>>> irq(s) for the unavailable channel(s). What's more, the available
>>> channels may not be continuous. To handle this case, we'd better
>>> get the irq of each channel by name.
>>
>> You did not solve the actual problem - binding still lists the
>> interrupts in specific order.
>>
>>>
>>> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
>>> ---
>>>  Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 5 +++++
>>>  1 file changed, 5 insertions(+)
>>>
>>> diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
>>> index 429f682f15d8..94752516e51a 100644
>>> --- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
>>> +++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
>>> @@ -32,6 +32,10 @@ properties:
>>>        - description: Channel 6 interrupt
>>>        - description: Channel 7 interrupt
>>>  
>>> +  interrupt-names:
>>> +    minItems: 1
>>> +    maxItems: 8
>>
>> You need to list the items.
> 
> I found in current dt-bindings, not all doc list the items. So is it
> changed now?

Close to impossible... :) But even if you found 1% of bindings with
mistake, please kindly take 99% of bindings as the example. Not 1%.

Which bindings were these with undefined names?

> 
>>
>>
>>> +
>>>    "#dma-cells":
>>>      const: 1
>>>      description: The cell is the trigger input number
>>> @@ -40,5 +44,6 @@ required:
>>>    - compatible
>>>    - reg
>>>    - interrupts
>>> +  - interrupt-names
>>
>> That's ABI break, so no.
> 
> If there's no users of arm-dma350 in upstream so far, is ABI break
> allowed? The reason is simple: to simplify the driver to parse
> the irq.

You can try to make your case - see writing bindings. But what about all
out of tree users? All other open source projects? All other kernels? I
really do not ask about anything new here - that's a policy since long time.


Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names
  2025-08-24 10:30       ` Krzysztof Kozlowski
@ 2025-08-24 13:19         ` Jisheng Zhang
  0 siblings, 0 replies; 35+ messages in thread
From: Jisheng Zhang @ 2025-08-24 13:19 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Robin Murphy, dmaengine, devicetree, linux-arm-kernel,
	linux-kernel

On Sun, Aug 24, 2025 at 12:30:40PM +0200, Krzysztof Kozlowski wrote:
> On 24/08/2025 11:49, Jisheng Zhang wrote:
> > On Sat, Aug 23, 2025 at 06:09:22PM +0200, Krzysztof Kozlowski wrote:
> >> On 23/08/2025 17:40, Jisheng Zhang wrote:
> >>> Currently, the dma350 driver assumes all channels are available to
> >>> linux, this may not be true on some platforms, so it's possible no
> >>> irq(s) for the unavailable channel(s). What's more, the available
> >>> channels may not be continuous. To handle this case, we'd better
> >>> get the irq of each channel by name.
> >>
> >> You did not solve the actual problem - binding still lists the
> >> interrupts in specific order.
> >>
> >>>
> >>> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> >>> ---
> >>>  Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 5 +++++
> >>>  1 file changed, 5 insertions(+)
> >>>
> >>> diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> >>> index 429f682f15d8..94752516e51a 100644
> >>> --- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> >>> +++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> >>> @@ -32,6 +32,10 @@ properties:
> >>>        - description: Channel 6 interrupt
> >>>        - description: Channel 7 interrupt
> >>>  
> >>> +  interrupt-names:
> >>> +    minItems: 1
> >>> +    maxItems: 8
> >>
> >> You need to list the items.
> > 
> > I found in current dt-bindings, not all doc list the items. So is it
> > changed now?
> 
> Close to impossible... :) But even if you found 1% of bindings with
> mistake, please kindly take 99% of bindings as the example. Not 1%.
> 
> Which bindings were these with undefined names?
> 
> > 
> >>
> >>
> >>> +
> >>>    "#dma-cells":
> >>>      const: 1
> >>>      description: The cell is the trigger input number
> >>> @@ -40,5 +44,6 @@ required:
> >>>    - compatible
> >>>    - reg
> >>>    - interrupts
> >>> +  - interrupt-names
> >>
> >> That's ABI break, so no.
> > 
> > If there's no users of arm-dma350 in upstream so far, is ABI break
> > allowed? The reason is simple: to simplify the driver to parse
> > the irq.
> 
> You can try to make your case - see writing bindings. But what about all
> out of tree users? All other open source projects? All other kernels? I

make sense! I will address your comments in v2. Before that, let me collect
some review comments, especially from dmaengine maintainer and Robin.

Thanks

> really do not ask about anything new here - that's a policy since long time.


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 14/14] dmaengine: dma350: Support ARM DMA-250
  2025-08-23 15:40 ` [PATCH 14/14] dmaengine: " Jisheng Zhang
  2025-08-23 16:13   ` Krzysztof Kozlowski
@ 2025-08-24 19:38   ` kernel test robot
  2025-08-29 22:24   ` Robin Murphy
  2 siblings, 0 replies; 35+ messages in thread
From: kernel test robot @ 2025-08-24 19:38 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robin Murphy
  Cc: llvm, oe-kbuild-all, dmaengine, devicetree, linux-arm-kernel,
	linux-kernel

Hi Jisheng,

kernel test robot noticed the following build warnings:

[auto build test WARNING on vkoul-dmaengine/next]
[also build test WARNING on robh/for-next krzk-dt/for-next linus/master v6.17-rc2 next-20250822]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Jisheng-Zhang/dmaengine-dma350-Fix-CH_CTRL_USESRCTRIGIN-definition/20250824-000425
base:   https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git next
patch link:    https://lore.kernel.org/r/20250823154009.25992-15-jszhang%40kernel.org
patch subject: [PATCH 14/14] dmaengine: dma350: Support ARM DMA-250
config: x86_64-buildonly-randconfig-002-20250824 (https://download.01.org/0day-ci/archive/20250825/202508250351.vxyvTsJa-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250825/202508250351.vxyvTsJa-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202508250351.vxyvTsJa-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/dma/arm-dma350.c:849:34: warning: variable 'sg' is uninitialized when used here [-Wuninitialized]
     849 |         sglen = DIV_ROUND_UP(sg_dma_len(sg), step_max) * periods;
         |                                         ^~
   include/linux/scatterlist.h:34:27: note: expanded from macro 'sg_dma_len'
      34 | #define sg_dma_len(sg)          ((sg)->dma_length)
         |                                   ^~
   include/uapi/linux/const.h:51:40: note: expanded from macro '__KERNEL_DIV_ROUND_UP'
      51 | #define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
         |                                        ^
   drivers/dma/arm-dma350.c:835:24: note: initialize the variable 'sg' to silence this warning
     835 |         struct scatterlist *sg;
         |                               ^
         |                                = NULL
   1 warning generated.


vim +/sg +849 drivers/dma/arm-dma350.c

   824	
   825	static struct dma_async_tx_descriptor *
   826	d250_prep_cyclic(struct dma_chan *chan, dma_addr_t buf_addr,
   827			 size_t buf_len, size_t period_len, enum dma_transfer_direction dir,
   828			 unsigned long flags)
   829	{
   830		struct d350_chan *dch = to_d350_chan(chan);
   831		u32 len, periods, trig, *cmd, tsz;
   832		dma_addr_t src, dst, phys, mem_addr;
   833		size_t xfer_len, step_max;
   834		struct d350_desc *desc;
   835		struct scatterlist *sg;
   836		struct d350_sg *dsg;
   837		int sglen, i;
   838	
   839		if (unlikely(!is_slave_direction(dir) || !buf_len || !period_len))
   840			return NULL;
   841	
   842		if (dir == DMA_MEM_TO_DEV)
   843			tsz = __ffs(dch->config.dst_addr_width | (1 << dch->tsz));
   844		else
   845			tsz = __ffs(dch->config.src_addr_width | (1 << dch->tsz));
   846		step_max = ((1UL << 16) - 1) << tsz;
   847	
   848		periods = buf_len / period_len;
 > 849		sglen = DIV_ROUND_UP(sg_dma_len(sg), step_max) * periods;
   850	
   851		desc = kzalloc(struct_size(desc, sg, sglen), GFP_NOWAIT);
   852		if (!desc)
   853			return NULL;
   854	
   855		dch->cyclic = true;
   856		dch->periods = periods;
   857		desc->sglen = sglen;
   858	
   859		sglen = 0;
   860		for (i = 0; i < periods; i++) {
   861			len = period_len;
   862			mem_addr = buf_addr + i * period_len;
   863			do {
   864				desc->sg[sglen].command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
   865				if (unlikely(!desc->sg[sglen].command))
   866					goto err_cmd_alloc;
   867	
   868				xfer_len = (len > step_max) ? step_max : len;
   869				desc->sg[sglen].phys = phys;
   870				dsg = &desc->sg[sglen];
   871	
   872				if (dir == DMA_MEM_TO_DEV) {
   873					src = mem_addr;
   874					dst = dch->config.dst_addr;
   875					trig = CH_CTRL_USEDESTRIGIN;
   876				} else {
   877					src = dch->config.src_addr;
   878					dst = mem_addr;
   879					trig = CH_CTRL_USESRCTRIGIN;
   880				}
   881				dsg->tsz = tsz;
   882				dsg->xsize = lower_16_bits(xfer_len >> dsg->tsz);
   883	
   884				cmd = dsg->command;
   885				cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_DESADDR |
   886					 LINK_XSIZE | LINK_SRCTRANSCFG |
   887					 LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR;
   888	
   889				cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, dsg->tsz) |
   890					 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE) |
   891					 FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD) | trig;
   892	
   893				cmd[2] = lower_32_bits(src);
   894				cmd[3] = lower_32_bits(dst);
   895				cmd[4] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
   896					 FIELD_PREP(CH_XY_DES, dsg->xsize);
   897				if (dir == DMA_MEM_TO_DEV) {
   898					cmd[0] |= LINK_DESTRIGINCFG;
   899					cmd[5] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
   900					cmd[6] = TRANSCFG_DEVICE;
   901					cmd[7] = FIELD_PREP(CH_XY_SRC, 1);
   902					cmd[8] = FIELD_PREP(CH_DESTRIGINMODE, CH_DESTRIG_DMA_FC) |
   903						  FIELD_PREP(CH_DESTRIGINTYPE, CH_DESTRIG_HW_REQ);
   904				} else {
   905					cmd[0] |= LINK_SRCTRIGINCFG;
   906					cmd[5] = TRANSCFG_DEVICE;
   907					cmd[6] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
   908					cmd[7] = FIELD_PREP(CH_XY_DES, 1);
   909					cmd[8] = FIELD_PREP(CH_SRCTRIGINMODE, CH_SRCTRIG_DMA_FC) |
   910						  FIELD_PREP(CH_SRCTRIGINTYPE, CH_SRCTRIG_HW_REQ);
   911				}
   912	
   913				if (sglen)
   914					desc->sg[sglen - 1].command[9] = phys | CH_LINKADDR_EN;
   915	
   916				len -= xfer_len;
   917				mem_addr += xfer_len;
   918				sglen++;
   919			} while (len);
   920			desc->sg[sglen - 1].command[1] |= FIELD_PREP(CH_CTRL_DONETYPE,
   921								     CH_CTRL_DONETYPE_CMD);
   922		}
   923	
   924		/* cyclic list */
   925		desc->sg[sglen - 1].command[9] = desc->sg[0].phys | CH_LINKADDR_EN;
   926	
   927		mb();
   928	
   929		return vchan_tx_prep(&dch->vc, &desc->vd, flags);
   930	
   931	err_cmd_alloc:
   932		for (i = 0; i < sglen; i++)
   933			dma_pool_free(dch->cmd_pool, desc->sg[i].command, desc->sg[i].phys);
   934		kfree(desc);
   935		return NULL;
   936	}
   937	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 03/14] dmaengine: dma350: Check vchan_next_desc() return value
  2025-08-23 15:39 ` [PATCH 03/14] dmaengine: dma350: Check vchan_next_desc() return value Jisheng Zhang
@ 2025-08-29 10:02   ` Robin Murphy
  0 siblings, 0 replies; 35+ messages in thread
From: Robin Murphy @ 2025-08-29 10:02 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 2025-08-23 4:39 pm, Jisheng Zhang wrote:
> vchan_next_desc() may return NULL, check its return value.

IIRC it's important that dch->desc gets set to NULL in that case, 
otherwise things can go wonky after a completion interrupt - i.e. the 
current code *is* using the return value both ways, just the sneaky 
thing is that it does actually depend on "vd" being the first member of 
d350_desc to do it concisely, sorry I didn't document that.

Thanks,
Robin.
> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>   drivers/dma/arm-dma350.c | 7 +++++--
>   1 file changed, 5 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index 24cbadc5f076..96350d15ed85 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -399,11 +399,14 @@ static enum dma_status d350_tx_status(struct dma_chan *chan, dma_cookie_t cookie
>   static void d350_start_next(struct d350_chan *dch)
>   {
>   	u32 hdr, *reg;
> +	struct virt_dma_desc *vd;
>   
> -	dch->desc = to_d350_desc(vchan_next_desc(&dch->vc));
> -	if (!dch->desc)
> +	vd = vchan_next_desc(&dch->vc);
> +	if (!vd)
>   		return;
>   
> +	dch->desc = to_d350_desc(vd);
> +
>   	list_del(&dch->desc->vd.node);
>   	dch->status = DMA_IN_PROGRESS;
>   	dch->cookie = dch->desc->vd.tx.cookie;



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 04/14] dmaengine: dma350: Check dma_cookie_status() ret code and txstate
  2025-08-23 15:39 ` [PATCH 04/14] dmaengine: dma350: Check dma_cookie_status() ret code and txstate Jisheng Zhang
@ 2025-08-29 10:42   ` Robin Murphy
  0 siblings, 0 replies; 35+ messages in thread
From: Robin Murphy @ 2025-08-29 10:42 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 2025-08-23 4:39 pm, Jisheng Zhang wrote:
> If dma_cookie_status() returns DMA_COMPLETE, we can return immediately.
> 
>  From another side, the txstate is an optional parameter used to get a
> struct with auxilary transfer status information. When not provided
> the call to device_tx_status() should return the status of the dma
> cookie. Return the status of dma cookie when the txstate optional
> parameter is not provided.

Again, the current code was definitely intentional - I think this was 
down to the hardware error case, where for reasons I now can't remember 
I still had to nominally complete the aborted descriptor from the IRQ 
handler to avoid causing some worse problem, and hence we don't return 
early without cross-checking dch->status here, because returning 
DMA_COMPLETE when the descriptor hasn't done its job makes dmatest unhappy.

I did spend a *lot* of time exercising all the error cases, and trying 
to get a sensible result across all of the different reporting APIs was 
fiddly to say the least - there's a huge lack of consistency between 
drivers in this regard, and this was just my attempt to be the 
least-worst one :)

Thanks,
Robin.

> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>   drivers/dma/arm-dma350.c | 2 ++
>   1 file changed, 2 insertions(+)
> 
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index 96350d15ed85..17af9bb2a18f 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -377,6 +377,8 @@ static enum dma_status d350_tx_status(struct dma_chan *chan, dma_cookie_t cookie
>   	u32 residue = 0;
>   
>   	status = dma_cookie_status(chan, cookie, state);
> +	if (status == DMA_COMPLETE || !state)
> +		return status;
>   
>   	spin_lock_irqsave(&dch->vc.lock, flags);
>   	if (cookie == dch->cookie) {



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 01/14] dmaengine: dma350: Fix CH_CTRL_USESRCTRIGIN definition
  2025-08-23 15:39 ` [PATCH 01/14] dmaengine: dma350: Fix CH_CTRL_USESRCTRIGIN definition Jisheng Zhang
@ 2025-08-29 10:44   ` Robin Murphy
  0 siblings, 0 replies; 35+ messages in thread
From: Robin Murphy @ 2025-08-29 10:44 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 2025-08-23 4:39 pm, Jisheng Zhang wrote:
> Per the arm-dma350 TRM, The CH_CTRL_USESRCTRIGIN is BIT(25).

Oops!

Reviewed-by: Robin Murphy <robin.murphy@arm.com>

> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>   drivers/dma/arm-dma350.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index 9efe2ca7d5ec..bf3962f00650 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -58,7 +58,7 @@
>   
>   #define CH_CTRL			0x0c
>   #define CH_CTRL_USEDESTRIGIN	BIT(26)
> -#define CH_CTRL_USESRCTRIGIN	BIT(26)
> +#define CH_CTRL_USESRCTRIGIN	BIT(25)
>   #define CH_CTRL_DONETYPE	GENMASK(23, 21)
>   #define CH_CTRL_REGRELOADTYPE	GENMASK(20, 18)
>   #define CH_CTRL_XTYPE		GENMASK(11, 9)



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 02/14] dmaengine: dma350: Add missing dch->coherent setting
  2025-08-23 15:39 ` [PATCH 02/14] dmaengine: dma350: Add missing dch->coherent setting Jisheng Zhang
@ 2025-08-29 10:52   ` Robin Murphy
  0 siblings, 0 replies; 35+ messages in thread
From: Robin Murphy @ 2025-08-29 10:52 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 2025-08-23 4:39 pm, Jisheng Zhang wrote:
> The dch->coherent setting is missing.
> 
> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>   drivers/dma/arm-dma350.c | 1 +
>   1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index bf3962f00650..24cbadc5f076 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -587,6 +587,7 @@ static int d350_probe(struct platform_device *pdev)
>   	for (int i = 0; i < nchan; i++) {
>   		struct d350_chan *dch = &dmac->channels[i];
>   
> +		dch->coherent = coherent;

Nit: I'd put this a bit further down with the CH_LINKATTR setup, but 
otherwise,

Reviewed-by: Robin Murphy <robin.murphy@arm.com>

>   		dch->base = base + DMACH(i);
>   		writel_relaxed(CH_CMD_CLEAR, dch->base + CH_CMD);
>   



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names
  2025-08-23 15:40 ` [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names Jisheng Zhang
  2025-08-23 16:09   ` Krzysztof Kozlowski
@ 2025-08-29 11:08   ` Robin Murphy
  1 sibling, 0 replies; 35+ messages in thread
From: Robin Murphy @ 2025-08-29 11:08 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 2025-08-23 4:40 pm, Jisheng Zhang wrote:
> Currently, the dma350 driver assumes all channels are available to
> linux,

No, it doesn't - the CH_CFG_HAS_CMDLINK check is designed to safely skip 
channels reserved for Secure world (or that don't exist due to an FVP 
bug...), since their CH_BUILDCFG1 will as zero.

> this may not be true on some platforms, so it's possible no
> irq(s) for the unavailable channel(s). What's more, the available
> channels may not be continuous. To handle this case, we'd better
> get the irq of each channel by name.

Unless you're suggesting these channels physically do not have their 
IRQs wired up to anything at all, what's wrong with describing the 
hardware in DT? Linux won't request IRQs for channels it isn't actually 
using, and nor should any other reasonable DT consumer - precisely 
because channels *can* be arbitrarily taken by the Secure world, and 
that can be entirely dynamic based on firmware configuration even for a 
single hardware platform.

And even in the weird case that some channel literally has no IRQ, my 
thought was we'd just have a placeholder entry in the array, such that 
attempting to request it would fail.

Thanks,
Robin.

> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>   Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 5 +++++
>   1 file changed, 5 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> index 429f682f15d8..94752516e51a 100644
> --- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> +++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> @@ -32,6 +32,10 @@ properties:
>         - description: Channel 6 interrupt
>         - description: Channel 7 interrupt
>   
> +  interrupt-names:
> +    minItems: 1
> +    maxItems: 8
> +
>     "#dma-cells":
>       const: 1
>       description: The cell is the trigger input number
> @@ -40,5 +44,6 @@ required:
>     - compatible
>     - reg
>     - interrupts
> +  - interrupt-names
>   
>   unevaluatedProperties: false



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 13/14] dt-bindings: dma: dma350: Support ARM DMA-250
  2025-08-23 16:11   ` Krzysztof Kozlowski
@ 2025-08-29 11:16     ` Robin Murphy
  0 siblings, 0 replies; 35+ messages in thread
From: Robin Murphy @ 2025-08-29 11:16 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Jisheng Zhang, Vinod Koul, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 2025-08-23 5:11 pm, Krzysztof Kozlowski wrote:
> On 23/08/2025 17:40, Jisheng Zhang wrote:
>> Compared with ARM DMA-350, DMA-250 is a simplified version, they share
>> many common parts, but they do have difference. The difference will be
>> handled in next driver patch, while let's add DMA-250 compatible string
>> to dt-binding now.
>>
>> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
>> ---
>>   Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 1 +
>>   1 file changed, 1 insertion(+)
>>
>> diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
>> index 94752516e51a..d49736b7de5e 100644
>> --- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
>> +++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
>> @@ -15,6 +15,7 @@ allOf:
>>   properties:
>>     compatible:
>>       const: arm,dma-350
>> +    const: arm,dma-250
> 
> This obviously cannot work and was NEVER tested. Please test your code
> before you send it to mailing lists.

Also, DMA-250 should be 100% "compatible" with DMA-350 in the DT sense, 
since it shares the same register layout and general functionality, and 
the detailed features and even exact model are discoverable from ID 
registers (hence why the current driver explicitly checks for 
PRODUCTID_DMA350 as that's the only one it knows it definitely understands).

Thanks,
Robin.


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 10/14] dmaengine: dma350: Alloc command[] from dma pool
  2025-08-23 15:40 ` [PATCH 10/14] dmaengine: dma350: Alloc command[] from dma pool Jisheng Zhang
@ 2025-08-29 12:56   ` Robin Murphy
  2025-09-01 10:27   ` Dan Carpenter
  1 sibling, 0 replies; 35+ messages in thread
From: Robin Murphy @ 2025-08-29 12:56 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 2025-08-23 4:40 pm, Jisheng Zhang wrote:
> Currently, the command[] is allocated with kzalloc(), but dma350 may be
> used on dma-non-coherent platforms, to prepare the support of peripheral
> and scatter-gather chaining on both dma-coherent and dma-non-coherent
> platforms, let's alloc them from dma pool.

FWIW my plan was to use dma_map_single() for command linking, since the 
descriptors themselves are short-lived one-way data transfers which 
really don't need any of the (potentially costly) properties of a 
dma-coherent allocation.

> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>   drivers/dma/arm-dma350.c | 143 +++++++++++++++++++++++++++++++--------
>   1 file changed, 113 insertions(+), 30 deletions(-)
> 
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index 72067518799e..3d26a1f020df 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -4,6 +4,7 @@
>   
>   #include <linux/bitfield.h>
>   #include <linux/dmaengine.h>
> +#include <linux/dmapool.h>
>   #include <linux/dma-mapping.h>
>   #include <linux/io.h>
>   #include <linux/of.h>
> @@ -143,6 +144,7 @@
>   #define LINK_LINKADDR		BIT(30)
>   #define LINK_LINKADDRHI		BIT(31)
>   
> +#define D350_MAX_CMDS		16

What's that based on? We should be able to link arbitrarily-long chains 
of commands, no?

>   enum ch_ctrl_donetype {
>   	CH_CTRL_DONETYPE_NONE = 0,
> @@ -169,18 +171,25 @@ enum ch_cfg_memattr {
>   	MEMATTR_WB = 0xff
>   };
>   
> -struct d350_desc {
> -	struct virt_dma_desc vd;
> -	u32 command[16];
> +struct d350_sg {
> +	u32 *command;
> +	dma_addr_t phys;
>   	u16 xsize;
>   	u16 xsizehi;
>   	u8 tsz;
>   };
>   
> +struct d350_desc {
> +	struct virt_dma_desc vd;
> +	u32 sglen;
> +	struct d350_sg sg[] __counted_by(sglen);
> +};

Perhaps it's mostly the naming, but this seems rather hard to make sense 
of. To clarify, the current driver design was in anticipation of a split 
more like so:

struct d350_cmd {
	u32 command[16];
	u16 xsize;
	u16 xsizehi;
	u8 tsz;
	struct d350_cmd *next;
};

struct d350_desc {
	struct virt_dma_desc vd;
	// any totals etc. to help with residue calculation
	struct d350_cmd cmd;
};

Or perhaps what I'd more likely have ended up with (which is maybe sort 
of what you've tried to do?):

struct d350_cmd {
	u32 command[16];
	u16 xsize;
	u16 xsizehi;
	u8 tsz;
};

struct d350_desc {
	struct virt_dma_desc vd;
	// anything else as above
	int num_cmds;
	struct d350_cmd cmd[1]; //extensible
};

Conveniently taking advantage of the fact that either way the DMA 
address will inherently be stored in the LINKADDR fields of the first 
command (which doesn't need DMA mapping itself), so at worst we still 
only need one or two allocations plus a single dma_map per prep 
operation (since we can keep all the linked commands as a single block). 
I don't see a DMA pool approach being beneficial here, since it seems 
like it's always going to be considerably less efficient.

(Not to mention that separate pools per channel is complete overkill 
anyway.)

Thanks,
Robin.

> +
>   struct d350_chan {
>   	struct virt_dma_chan vc;
>   	struct d350_desc *desc;
>   	void __iomem *base;
> +	struct dma_pool *cmd_pool;
>   	int irq;
>   	enum dma_status status;
>   	dma_cookie_t cookie;
> @@ -210,7 +219,14 @@ static inline struct d350_desc *to_d350_desc(struct virt_dma_desc *vd)
>   
>   static void d350_desc_free(struct virt_dma_desc *vd)
>   {
> -	kfree(to_d350_desc(vd));
> +	struct d350_chan *dch = to_d350_chan(vd->tx.chan);
> +	struct d350_desc *desc = to_d350_desc(vd);
> +	int i;
> +
> +	for (i = 0; i < desc->sglen; i++)
> +		dma_pool_free(dch->cmd_pool, desc->sg[i].command, desc->sg[i].phys);
> +
> +	kfree(desc);
>   }
>   
>   static struct dma_async_tx_descriptor *d350_prep_memcpy(struct dma_chan *chan,
> @@ -218,22 +234,32 @@ static struct dma_async_tx_descriptor *d350_prep_memcpy(struct dma_chan *chan,
>   {
>   	struct d350_chan *dch = to_d350_chan(chan);
>   	struct d350_desc *desc;
> +	struct d350_sg *sg;
> +	dma_addr_t phys;
>   	u32 *cmd;
>   
> -	desc = kzalloc(sizeof(*desc), GFP_NOWAIT);
> +	desc = kzalloc(struct_size(desc, sg, 1), GFP_NOWAIT);
>   	if (!desc)
>   		return NULL;
>   
> -	desc->tsz = __ffs(len | dest | src | (1 << dch->tsz));
> -	desc->xsize = lower_16_bits(len >> desc->tsz);
> -	desc->xsizehi = upper_16_bits(len >> desc->tsz);
> +	sg = &desc->sg[0];
> +	sg->command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
> +	if (unlikely(!sg->command)) {
> +		kfree(desc);
> +		return NULL;
> +	}
> +	sg->phys = phys;
> +
> +	sg->tsz = __ffs(len | dest | src | (1 << dch->tsz));
> +	sg->xsize = lower_16_bits(len >> sg->tsz);
> +	sg->xsizehi = upper_16_bits(len >> sg->tsz);
>   
> -	cmd = desc->command;
> +	cmd = sg->command;
>   	cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
>   		 LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_SRCTRANSCFG |
>   		 LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR;
>   
> -	cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, desc->tsz) |
> +	cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, sg->tsz) |
>   		 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE) |
>   		 FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD);
>   
> @@ -241,13 +267,15 @@ static struct dma_async_tx_descriptor *d350_prep_memcpy(struct dma_chan *chan,
>   	cmd[3] = upper_32_bits(src);
>   	cmd[4] = lower_32_bits(dest);
>   	cmd[5] = upper_32_bits(dest);
> -	cmd[6] = FIELD_PREP(CH_XY_SRC, desc->xsize) | FIELD_PREP(CH_XY_DES, desc->xsize);
> -	cmd[7] = FIELD_PREP(CH_XY_SRC, desc->xsizehi) | FIELD_PREP(CH_XY_DES, desc->xsizehi);
> +	cmd[6] = FIELD_PREP(CH_XY_SRC, sg->xsize) | FIELD_PREP(CH_XY_DES, sg->xsize);
> +	cmd[7] = FIELD_PREP(CH_XY_SRC, sg->xsizehi) | FIELD_PREP(CH_XY_DES, sg->xsizehi);
>   	cmd[8] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
>   	cmd[9] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
>   	cmd[10] = FIELD_PREP(CH_XY_SRC, 1) | FIELD_PREP(CH_XY_DES, 1);
>   	cmd[11] = 0;
>   
> +	mb();
> +
>   	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
>   }
>   
> @@ -256,34 +284,46 @@ static struct dma_async_tx_descriptor *d350_prep_memset(struct dma_chan *chan,
>   {
>   	struct d350_chan *dch = to_d350_chan(chan);
>   	struct d350_desc *desc;
> +	struct d350_sg *sg;
> +	dma_addr_t phys;
>   	u32 *cmd;
>   
> -	desc = kzalloc(sizeof(*desc), GFP_NOWAIT);
> +	desc = kzalloc(struct_size(desc, sg, 1), GFP_NOWAIT);
>   	if (!desc)
>   		return NULL;
>   
> -	desc->tsz = __ffs(len | dest | (1 << dch->tsz));
> -	desc->xsize = lower_16_bits(len >> desc->tsz);
> -	desc->xsizehi = upper_16_bits(len >> desc->tsz);
> +	sg = &desc->sg[0];
> +	sg->command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
> +	if (unlikely(!sg->command)) {
> +		kfree(desc);
> +		return NULL;
> +	}
> +	sg->phys = phys;
> +
> +	sg->tsz = __ffs(len | dest | (1 << dch->tsz));
> +	sg->xsize = lower_16_bits(len >> sg->tsz);
> +	sg->xsizehi = upper_16_bits(len >> sg->tsz);
>   
> -	cmd = desc->command;
> +	cmd = sg->command;
>   	cmd[0] = LINK_CTRL | LINK_DESADDR | LINK_DESADDRHI |
>   		 LINK_XSIZE | LINK_XSIZEHI | LINK_DESTRANSCFG |
>   		 LINK_XADDRINC | LINK_FILLVAL | LINK_LINKADDR;
>   
> -	cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, desc->tsz) |
> +	cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, sg->tsz) |
>   		 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_FILL) |
>   		 FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD);
>   
>   	cmd[2] = lower_32_bits(dest);
>   	cmd[3] = upper_32_bits(dest);
> -	cmd[4] = FIELD_PREP(CH_XY_DES, desc->xsize);
> -	cmd[5] = FIELD_PREP(CH_XY_DES, desc->xsizehi);
> +	cmd[4] = FIELD_PREP(CH_XY_DES, sg->xsize);
> +	cmd[5] = FIELD_PREP(CH_XY_DES, sg->xsizehi);
>   	cmd[6] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
>   	cmd[7] = FIELD_PREP(CH_XY_DES, 1);
>   	cmd[8] = (u8)value * 0x01010101;
>   	cmd[9] = 0;
>   
> +	mb();
> +
>   	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
>   }
>   
> @@ -319,8 +359,9 @@ static int d350_resume(struct dma_chan *chan)
>   
>   static u32 d350_get_residue(struct d350_chan *dch)
>   {
> -	u32 res, xsize, xsizehi, hi_new;
> -	int retries = 3; /* 1st time unlucky, 2nd improbable, 3rd just broken */
> +	u32 res, xsize, xsizehi, linkaddr, linkaddrhi, hi_new;
> +	int i, sgcur, retries = 3; /* 1st time unlucky, 2nd improbable, 3rd just broken */
> +	struct d350_desc *desc = dch->desc;
>   
>   	hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
>   	do {
> @@ -329,10 +370,26 @@ static u32 d350_get_residue(struct d350_chan *dch)
>   		hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
>   	} while (xsizehi != hi_new && --retries);
>   
> +	hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
> +	do {
> +		linkaddrhi = hi_new;
> +		linkaddr = readl_relaxed(dch->base + CH_LINKADDR);
> +		hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
> +	} while (linkaddrhi != hi_new && --retries);
> +
> +	for (i = 0; i < desc->sglen; i++) {
> +		if (desc->sg[i].phys == (((u64)linkaddrhi << 32) | (linkaddr & ~CH_LINKADDR_EN)))
> +			sgcur = i;
> +	}
> +
>   	res = FIELD_GET(CH_XY_DES, xsize);
>   	res |= FIELD_GET(CH_XY_DES, xsizehi) << 16;
> +	res <<= desc->sg[sgcur].tsz;
> +
> +	for (i = sgcur + 1; i < desc->sglen; i++)
> +		res += (((u32)desc->sg[i].xsizehi << 16 | desc->sg[i].xsize) << desc->sg[i].tsz);
>   
> -	return res << dch->desc->tsz;
> +	return res;
>   }
>   
>   static int d350_terminate_all(struct dma_chan *chan)
> @@ -365,7 +422,13 @@ static void d350_synchronize(struct dma_chan *chan)
>   
>   static u32 d350_desc_bytes(struct d350_desc *desc)
>   {
> -	return ((u32)desc->xsizehi << 16 | desc->xsize) << desc->tsz;
> +	int i;
> +	u32 bytes = 0;
> +
> +	for (i = 0; i < desc->sglen; i++)
> +		bytes += (((u32)desc->sg[i].xsizehi << 16 | desc->sg[i].xsize) << desc->sg[i].tsz);
> +
> +	return bytes;
>   }
>   
>   static enum dma_status d350_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
> @@ -415,8 +478,8 @@ static void d350_start_next(struct d350_chan *dch)
>   	dch->cookie = dch->desc->vd.tx.cookie;
>   	dch->residue = d350_desc_bytes(dch->desc);
>   
> -	hdr = dch->desc->command[0];
> -	reg = &dch->desc->command[1];
> +	hdr = dch->desc->sg[0].command[0];
> +	reg = &dch->desc->sg[0].command[1];
>   
>   	if (hdr & LINK_INTREN)
>   		writel_relaxed(*reg++, dch->base + CH_INTREN);
> @@ -512,11 +575,29 @@ static irqreturn_t d350_irq(int irq, void *data)
>   static int d350_alloc_chan_resources(struct dma_chan *chan)
>   {
>   	struct d350_chan *dch = to_d350_chan(chan);
> -	int ret = request_irq(dch->irq, d350_irq, IRQF_SHARED,
> -			      dev_name(&dch->vc.chan.dev->device), dch);
> -	if (!ret)
> -		writel_relaxed(CH_INTREN_DONE | CH_INTREN_ERR, dch->base + CH_INTREN);
> +	int ret;
> +
> +	dch->cmd_pool = dma_pool_create(dma_chan_name(chan),
> +					  chan->device->dev,
> +					  D350_MAX_CMDS * sizeof(u32),
> +					  sizeof(u32), 0);
> +	if (!dch->cmd_pool) {
> +		dev_err(chan->device->dev, "No memory for cmd pool\n");
> +		return -ENOMEM;
> +	}
> +
> +	ret = request_irq(dch->irq, d350_irq, 0,
> +			  dev_name(&dch->vc.chan.dev->device), dch);
> +	if (ret < 0)
> +		goto err_irq;
> +
> +	writel_relaxed(CH_INTREN_DONE | CH_INTREN_ERR, dch->base + CH_INTREN);
> +
> +	return 0;
>   
> +err_irq:
> +	dma_pool_destroy(dch->cmd_pool);
> +	dch->cmd_pool = NULL;
>   	return ret;
>   }
>   
> @@ -527,6 +608,8 @@ static void d350_free_chan_resources(struct dma_chan *chan)
>   	writel_relaxed(0, dch->base + CH_INTREN);
>   	free_irq(dch->irq, dch);
>   	vchan_free_chan_resources(&dch->vc);
> +	dma_pool_destroy(dch->cmd_pool);
> +	dch->cmd_pool = NULL;
>   }
>   
>   static int d350_probe(struct platform_device *pdev)


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 05/14] dmaengine: dma350: Register the DMA controller to DT DMA helpers
  2025-08-23 15:40 ` [PATCH 05/14] dmaengine: dma350: Register the DMA controller to DT DMA helpers Jisheng Zhang
@ 2025-08-29 20:37   ` Robin Murphy
  0 siblings, 0 replies; 35+ messages in thread
From: Robin Murphy @ 2025-08-29 20:37 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 2025-08-23 4:40 pm, Jisheng Zhang wrote:
> Register the DMA controller to DT DMA helpers so that we convert a DT
> phandle to a dma_chan structure.
> 
> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>   drivers/dma/arm-dma350.c | 4 +++-
>   1 file changed, 3 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index 17af9bb2a18f..6a9f81f941b0 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -7,6 +7,7 @@
>   #include <linux/dma-mapping.h>
>   #include <linux/io.h>
>   #include <linux/of.h>
> +#include <linux/of_dma.h>
>   #include <linux/module.h>
>   #include <linux/platform_device.h>
>   
> @@ -635,7 +636,7 @@ static int d350_probe(struct platform_device *pdev)
>   	if (ret)
>   		return dev_err_probe(dev, ret, "Failed to register DMA device\n");
>   
> -	return 0;
> +	return of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id, &dmac->dma);

This only works for channels with HAS_TRIGSEL=0 (where I guess HAS_TRIG 
can be assumed from DT describing one) - with selectable triggers the 
trigger number (which the dma-cell specifier actually is) doesn't bear 
any relation to the channel number, so channel selection is both simpler 
and more complicated at the same time, since we could pick any free 
channel with HAS_TRIGSEL, but that's not necessarily just *any* free 
channel...

Given that at this point the driver only considers nominal trigger 
capability for channels with HAS_TRIGSEL=1, this patch seems effectively 
broken.

Thanks,
Robin.

>   }
>   
>   static void d350_remove(struct platform_device *pdev)
> @@ -643,6 +644,7 @@ static void d350_remove(struct platform_device *pdev)
>   	struct d350 *dmac = platform_get_drvdata(pdev);
>   
>   	dma_async_device_unregister(&dmac->dma);
> +	of_dma_controller_free(pdev->dev.of_node);
>   }
>   
>   static const struct of_device_id d350_of_match[] __maybe_unused = {


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 14/14] dmaengine: dma350: Support ARM DMA-250
  2025-08-23 15:40 ` [PATCH 14/14] dmaengine: " Jisheng Zhang
  2025-08-23 16:13   ` Krzysztof Kozlowski
  2025-08-24 19:38   ` kernel test robot
@ 2025-08-29 22:24   ` Robin Murphy
  2 siblings, 0 replies; 35+ messages in thread
From: Robin Murphy @ 2025-08-29 22:24 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 2025-08-23 4:40 pm, Jisheng Zhang wrote:
> Compared with ARM DMA-350, DMA-250 is a simplified version. They share
> many common parts, but they do have difference. Add DMA-250 support
> by handling their difference by using different device_prep_slave_sg,
> device_prep_dma_cyclic and device_prep_dma_memcpy. DMA-250 doesn't
> support device_prep_dma_memset.
> 
> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>   drivers/dma/arm-dma350.c | 444 +++++++++++++++++++++++++++++++++++++--
>   1 file changed, 424 insertions(+), 20 deletions(-)
> 
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index 5abb965c6687..0ee807424b7e 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -1,7 +1,7 @@
>   // SPDX-License-Identifier: GPL-2.0
>   // Copyright (C) 2024-2025 Arm Limited
>   // Copyright (C) 2025 Synaptics Incorporated
> -// Arm DMA-350 driver
> +// Arm DMA-350/DMA-250 driver

Yeah, that's going to get old fast... By all means update the Kconfig 
help text if you think it's helpful to end users, but I don't think 
anyone expects comments like this to be exhaustive, so honestly I'd save 
the churn.
>   #include <linux/bitfield.h>
>   #include <linux/dmaengine.h>
> @@ -16,6 +16,10 @@
>   #include "dmaengine.h"
>   #include "virt-dma.h"
>   
> +#define DMANSECCTRL		0x0200
> +
> +#define NSEC_CNTXBASE		0x10
> +
>   #define DMAINFO			0x0f00
>   
>   #define DMA_BUILDCFG0		0xb0
> @@ -26,12 +30,16 @@
>   #define DMA_BUILDCFG1		0xb4
>   #define DMA_CFG_NUM_TRIGGER_IN	GENMASK(8, 0)
>   
> +#define DMA_BUILDCFG2		0xb8
> +#define DMA_CFG_HAS_TZ		BIT(8)I don't think we need to care about that. Yes, the TRM describes the 
total context memory size required from the PoV of the hardware itself, 
but even if SEC_CNTXBASE does exist, Non-Secure Linux can't set it, so 
clearly Linux can't need to provide memory for it.

> +
>   #define IIDR			0xc8
>   #define IIDR_PRODUCTID		GENMASK(31, 20)
>   #define IIDR_VARIANT		GENMASK(19, 16)
>   #define IIDR_REVISION		GENMASK(15, 12)
>   #define IIDR_IMPLEMENTER	GENMASK(11, 0)
>   
> +#define PRODUCTID_DMA250	0x250
>   #define PRODUCTID_DMA350	0x3a0
>   #define IMPLEMENTER_ARM		0x43b
>   
> @@ -140,6 +148,7 @@
>   #define CH_CFG_HAS_TRIGSEL	BIT(7)
>   #define CH_CFG_HAS_TRIGIN	BIT(5)
>   #define CH_CFG_HAS_WRAP		BIT(1)
> +#define CH_CFG_HAS_XSIZEHI	BIT(0)
>   
>   
>   #define LINK_REGCLEAR		BIT(0)
> @@ -218,6 +227,7 @@ struct d350_chan {
>   	bool cyclic;
>   	bool has_trig;
>   	bool has_wrap;
> +	bool has_xsizehi;
>   	bool coherent;
>   };
>   
> @@ -225,6 +235,10 @@ struct d350 {
>   	struct dma_device dma;
>   	int nchan;
>   	int nreq;
> +	bool is_d250;

That won't scale, but it also shouldn't be needed anyway - other than 
the context memory which is easily handled within the scope of the probe 
routine that already has the IIDR to hand, everything else ought to be 
based on the relevant feature flags.

> +	dma_addr_t cntx_mem_paddr;
> +	void *cntx_mem;
> +	u32 cntx_mem_size;
>   	struct d350_chan channels[] __counted_by(nchan);
>   };
>   
> @@ -238,6 +252,11 @@ static inline struct d350_desc *to_d350_desc(struct virt_dma_desc *vd)
>   	return container_of(vd, struct d350_desc, vd);
>   }
>   
> +static inline struct d350 *to_d350(struct dma_device *dd)
> +{
> +	return container_of(dd, struct d350, dma);
> +}
> +
>   static void d350_desc_free(struct virt_dma_desc *vd)
>   {
>   	struct d350_chan *dch = to_d350_chan(vd->tx.chan);
> @@ -585,6 +604,337 @@ static int d350_slave_config(struct dma_chan *chan, struct dma_slave_config *con
>   	return 0;
>   }
>   
> +static struct dma_async_tx_descriptor *d250_prep_memcpy(struct dma_chan *chan,
> +		dma_addr_t dest, dma_addr_t src, size_t len, unsigned long flags)

Case in point: We don't need a mess of separate copy-pasted functions, 
we just need to evolve the existing ones to split the respective 
operations into either 32-bit or 16-bit chunks depending on has_xsizehi 
- even on DMA-350, >32-bit sizes aren't properly supported since I never 
got as far as command linking, but there's no reason they shouldn't be.

> +{
> +	struct d350_chan *dch = to_d350_chan(chan);
> +	struct d350_desc *desc;
> +	u32 *cmd, *la_cmd, tsz;
> +	int sglen, i;
> +	struct d350_sg *sg;
> +	size_t xfer_len, step_max;
> +	dma_addr_t phys;
> +
> +	tsz = __ffs(len | dest | src | (1 << dch->tsz));
> +	step_max = ((1UL << 16) - 1) << tsz;
> +	sglen = DIV_ROUND_UP(len, step_max);
> +
> +	desc = kzalloc(struct_size(desc, sg, sglen), GFP_NOWAIT);
> +	if (!desc)
> +		return NULL;
> +
> +	desc->sglen = sglen;
> +	sglen = 0;
> +	while (len) {
> +		sg = &desc->sg[sglen];
> +		xfer_len = (len > step_max) ? step_max : len;

If only we had a min() function...

> +		sg->tsz = __ffs(xfer_len | dest | src | (1 << dch->tsz));

Um, what? By this point we've already decided to split based on the 
initial tsz, what purpose does recalculating it serve?

> +		sg->xsize = lower_16_bits(xfer_len >> sg->tsz);
> +
> +		sg->command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
> +		if (unlikely(!sg->command))
> +			goto err_cmd_alloc;
> +		sg->phys = phys;
> +
> +		cmd = sg->command;
> +		if (!sglen) {
> +			cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_DESADDR |
> +				 LINK_XSIZE | LINK_SRCTRANSCFG |
> +				 LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR;
> +
> +			cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, sg->tsz) |
> +				 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE);
> +
> +			cmd[2] = lower_32_bits(src);
> +			cmd[3] = lower_32_bits(dest);
> +			cmd[4] = FIELD_PREP(CH_XY_SRC, sg->xsize) |
> +				 FIELD_PREP(CH_XY_DES, sg->xsize);
> +			cmd[5] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
> +			cmd[6] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
> +			cmd[7] = FIELD_PREP(CH_XY_SRC, 1) | FIELD_PREP(CH_XY_DES, 1);
> +			la_cmd = &cmd[8];
> +		} else {
> +			*la_cmd = phys | CH_LINKADDR_EN;
> +			if (len <= step_max) {
> +				cmd[0] = LINK_CTRL | LINK_XSIZE | LINK_LINKADDR;
> +				cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, sg->tsz) |
> +					 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE);
> +				cmd[2] = FIELD_PREP(CH_XY_SRC, sg->xsize) |
> +					 FIELD_PREP(CH_XY_DES, sg->xsize);
> +				la_cmd = &cmd[3];
> +			} else {
> +				cmd[0] = LINK_XSIZE | LINK_LINKADDR;
> +				cmd[1] = FIELD_PREP(CH_XY_SRC, sg->xsize) |
> +					 FIELD_PREP(CH_XY_DES, sg->xsize);
> +				la_cmd = &cmd[2];
> +			}

Ok, we really need to figure out a better abstraction for command 
construction, the hard-coded array indices were a bad enough idea to 
start with, but this is almost impossible to make sense of.

> +		}
> +
> +		len -= xfer_len;
> +		src += xfer_len;
> +		dest += xfer_len;
> +		sglen++;
> +	}
> +
> +	/* the last cmdlink */
> +	*la_cmd = 0;
> +	desc->sg[sglen - 1].command[1] |= FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD);

As for that, I don't even...

Furthermore, all these loops and conditionals are crazy anyway, and 
thoroughly failing to do justice to the hardware actually being pretty 
cool, namely that *commands can loop themselves*! Any single buffer/sg 
segment should take at most two commands - one dividing as much of the 
length as possible between XSIZE{HI} and CMDRESTARTCOUNT using 
REGRELOADTYPE=1, and/or one to transfer whatever non-multiple tail 
portion remains.

Honestly I'm sad the project for which I originally started this driver 
got canned, as this is the part I was really looking forward to having 
some fun with...

[...]
>   static int d350_pause(struct dma_chan *chan)
>   {
>   	struct d350_chan *dch = to_d350_chan(chan);
> @@ -620,20 +970,31 @@ static u32 d350_get_residue(struct d350_chan *dch)
>   	u32 res, xsize, xsizehi, linkaddr, linkaddrhi, hi_new;
>   	int i, sgcur, retries = 3; /* 1st time unlucky, 2nd improbable, 3rd just broken */
>   	struct d350_desc *desc = dch->desc;
> +	struct d350 *dmac = to_d350(dch->vc.chan.device);
>   
> -	hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
> -	do {
> -		xsizehi = hi_new;
> -		xsize = readl_relaxed(dch->base + CH_XSIZE);
> +	if (dch->has_xsizehi) {
>   		hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
> -	} while (xsizehi != hi_new && --retries);
> +		do {
> +			xsizehi = hi_new;
> +			xsize = readl_relaxed(dch->base + CH_XSIZE);
> +			hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
> +		} while (xsizehi != hi_new && --retries);
> +	} else {
> +		xsize = readl_relaxed(dch->base + CH_XSIZE);
> +		xsizehi = 0;
> +	}
This is unnecessary - if the CH_XSIZEHI location isn't the actual 
register then it's RAZ/WI, which means the existing logic can take full 
advantage of it reading as zero and still work just the same.

> -	hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
> -	do {
> -		linkaddrhi = hi_new;
> -		linkaddr = readl_relaxed(dch->base + CH_LINKADDR);
> +	if (!dmac->is_d250) {

And similarly here. The only thing we should perhaps do specially for 
LINKADDRHI is omit it from command generation when ADDR_WIDTH <= 32 in 
general. I admit I was lazy there, since it's currently harmless for 
d350_start_next() to write the register location unconditionally, but 
I'm not sure how a 32-bit DMA-350 would handle it in an actual command 
link header.

>   		hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
> -	} while (linkaddrhi != hi_new && --retries);
> +		do {
> +			linkaddrhi = hi_new;
> +			linkaddr = readl_relaxed(dch->base + CH_LINKADDR);
> +			hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
> +		} while (linkaddrhi != hi_new && --retries);
> +	} else {
> +		linkaddr = readl_relaxed(dch->base + CH_LINKADDR);
> +		linkaddrhi = 0;
> +	}
>   
>   	for (i = 0; i < desc->sglen; i++) {
>   		if (desc->sg[i].phys == (((u64)linkaddrhi << 32) | (linkaddr & ~CH_LINKADDR_EN)))
> @@ -876,6 +1237,14 @@ static void d350_free_chan_resources(struct dma_chan *chan)
>   	dch->cmd_pool = NULL;
>   }
>   
> +static void d250_cntx_mem_release(void *ptr)
> +{
> +	struct d350 *dmac = ptr;
> +	struct device *dev = dmac->dma.dev;
> +
> +	dma_free_coherent(dev, dmac->cntx_mem_size, dmac->cntx_mem, dmac->cntx_mem_paddr);
> +}
> +
>   static int d350_probe(struct platform_device *pdev)
>   {
>   	struct device *dev = &pdev->dev;
> @@ -893,8 +1262,9 @@ static int d350_probe(struct platform_device *pdev)
>   	r = FIELD_GET(IIDR_VARIANT, reg);
>   	p = FIELD_GET(IIDR_REVISION, reg);
>   	if (FIELD_GET(IIDR_IMPLEMENTER, reg) != IMPLEMENTER_ARM ||
> -	    FIELD_GET(IIDR_PRODUCTID, reg) != PRODUCTID_DMA350)
> -		return dev_err_probe(dev, -ENODEV, "Not a DMA-350!");
> +	    ((FIELD_GET(IIDR_PRODUCTID, reg) != PRODUCTID_DMA350) &&
> +	    FIELD_GET(IIDR_PRODUCTID, reg) != PRODUCTID_DMA250))
> +		return dev_err_probe(dev, -ENODEV, "Not a DMA-350/DMA-250!");
>   
>   	reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG0);
>   	nchan = FIELD_GET(DMA_CFG_NUM_CHANNELS, reg) + 1;
> @@ -917,13 +1287,38 @@ static int d350_probe(struct platform_device *pdev)
>   		return ret;
>   	}
>   
> +	if (device_is_compatible(dev, "arm,dma-250")) {
If only we had a completely reliable product ID from the hardware itself...

> +		u32 cfg2;
> +		int secext_present;
> +
> +		dmac->is_d250 = true;
> +
> +		cfg2 = readl_relaxed(base + DMAINFO + DMA_BUILDCFG2);
> +		secext_present = (cfg2 & DMA_CFG_HAS_TZ) ? 1 : 0;
> +		dmac->cntx_mem_size = nchan * 64 * (1 + secext_present);

As before I think that's wrong.

> +		dmac->cntx_mem = dma_alloc_coherent(dev, dmac->cntx_mem_size,
> +						    &dmac->cntx_mem_paddr,
> +						    GFP_KERNEL);

This is too early, it needs to wait until after we've set the DMA mask. 
Also since this is purely private memory for the device, it may as well 
use DMA_ATTR_NO_KERNEL_MAPPING.

> +		if (!dmac->cntx_mem)
> +			return dev_err_probe(dev, -ENOMEM, "Failed to alloc context memory\n");
Just return -ENOMEM - dev_err_probe() adds nothing.

> +		ret = devm_add_action_or_reset(dev, d250_cntx_mem_release, dmac);
> +		if (ret) {
> +			dma_free_coherent(dev, dmac->cntx_mem_size,
> +					  dmac->cntx_mem, dmac->cntx_mem_paddr);

a) Understand that the mildly non-obvious "or reset" means it already 
calls the cleanup action on error, so this would be a double-free.

b) Don't reinvent dmam_alloc_*() in the first place though.

> +			return ret;
> +		}
> +		writel_relaxed(dmac->cntx_mem_paddr, base + DMANSECCTRL + NSEC_CNTXBASE);

Perhaps we should check that this hasn't already been set up first? I 
mean, we can't necessarily even be sure teh context memory interface can 
access the same address space as the DMA transfer interface at all; the 
design intent is at least partly to allow connecting a dedicated SRAM 
directly, see figure 1 here: 
https://developer.arm.com/documentation/108001/0000/DMAC-interfaces/AHB5-manager-interfaces/Separate-AHB5-ports-for-data-and-virtual-channel-context?lang=en

However I'm not sure how feasible that is to detect from software - the 
base register alone clearly isn't foolproof since 0 could be a valid 
address (especially in a private SRAM). At worst I suppose we might end 
up needing a DMA-250-specific DT property to say whether it does or 
doesn't need context memory from the OS...

> +	}
> +
>   	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(aw));
>   	coherent = device_get_dma_attr(dev) == DEV_DMA_COHERENT;
>   
>   	reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG1);
>   	dmac->nreq = FIELD_GET(DMA_CFG_NUM_TRIGGER_IN, reg);
>   
> -	dev_dbg(dev, "DMA-350 r%dp%d with %d channels, %d requests\n", r, p, dmac->nchan, dmac->nreq);
> +	dev_info(dev, "%s r%dp%d with %d channels, %d requests\n",
> +		 dmac->is_d250 ? "DMA-250" : "DMA-350", r, p, dmac->nchan, dmac->nreq);

As Krzysztof said, this is a debug message and it's staying a debug 
message. And just replace "DMA-350" with "ProductID 0x%x" - it's only 
meant as a sanity-check that we're looking at the hardware we expect to 
be looking at.
>   	for (int i = min(dw, 16); i > 0; i /= 2) {
>   		dmac->dma.src_addr_widths |= BIT(i);
> @@ -935,7 +1330,10 @@ static int d350_probe(struct platform_device *pdev)
>   	dmac->dma.device_alloc_chan_resources = d350_alloc_chan_resources;
>   	dmac->dma.device_free_chan_resources = d350_free_chan_resources;
>   	dma_cap_set(DMA_MEMCPY, dmac->dma.cap_mask);
> -	dmac->dma.device_prep_dma_memcpy = d350_prep_memcpy;
> +	if (dmac->is_d250)
> +		dmac->dma.device_prep_dma_memcpy = d250_prep_memcpy;
> +	else
> +		dmac->dma.device_prep_dma_memcpy = d350_prep_memcpy;
>   	dmac->dma.device_pause = d350_pause;
>   	dmac->dma.device_resume = d350_resume;
>   	dmac->dma.device_terminate_all = d350_terminate_all;
> @@ -971,8 +1369,8 @@ static int d350_probe(struct platform_device *pdev)
>   			return dch->irq;
>   
>   		dch->has_wrap = FIELD_GET(CH_CFG_HAS_WRAP, reg);
> -		dch->has_trig = FIELD_GET(CH_CFG_HAS_TRIGIN, reg) &
> -				FIELD_GET(CH_CFG_HAS_TRIGSEL, reg);

Not only is this in the wrong patch, it's the wrong change to make 
anyway. If you're only adding support for fixed triggers, you need to 
explicitly *exclude* selectable triggers from that, because they work 
differently.

Thanks,
Robin.

> +		dch->has_xsizehi = FIELD_GET(CH_CFG_HAS_XSIZEHI, reg);
> +		dch->has_trig = FIELD_GET(CH_CFG_HAS_TRIGIN, reg);
>   
>   		/* Fill is a special case of Wrap */
>   		memset &= dch->has_wrap;
> @@ -994,8 +1392,13 @@ static int d350_probe(struct platform_device *pdev)
>   		dma_cap_set(DMA_SLAVE, dmac->dma.cap_mask);
>   		dma_cap_set(DMA_CYCLIC, dmac->dma.cap_mask);
>   		dmac->dma.device_config = d350_slave_config;
> -		dmac->dma.device_prep_slave_sg = d350_prep_slave_sg;
> -		dmac->dma.device_prep_dma_cyclic = d350_prep_cyclic;
> +		if (dmac->is_d250) {
> +			dmac->dma.device_prep_slave_sg = d250_prep_slave_sg;
> +			dmac->dma.device_prep_dma_cyclic = d250_prep_cyclic;
> +		} else {
> +			dmac->dma.device_prep_slave_sg = d350_prep_slave_sg;
> +			dmac->dma.device_prep_dma_cyclic = d350_prep_cyclic;
> +		}
>   	}
>   
>   	if (memset) {
> @@ -1019,6 +1422,7 @@ static void d350_remove(struct platform_device *pdev)
>   
>   static const struct of_device_id d350_of_match[] __maybe_unused = {
>   	{ .compatible = "arm,dma-350" },
> +	{ .compatible = "arm,dma-250" },
>   	{}
>   };
>   MODULE_DEVICE_TABLE(of, d350_of_match);
> @@ -1035,5 +1439,5 @@ module_platform_driver(d350_driver);
>   
>   MODULE_AUTHOR("Robin Murphy <robin.murphy@arm.com>");
>   MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
> -MODULE_DESCRIPTION("Arm DMA-350 driver");
> +MODULE_DESCRIPTION("Arm DMA-350/DMA-250 driver");
>   MODULE_LICENSE("GPL v2");


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 11/14] dmaengine: dma350: Support device_prep_slave_sg
  2025-08-23 15:40 ` [PATCH 11/14] dmaengine: dma350: Support device_prep_slave_sg Jisheng Zhang
@ 2025-08-30  0:00   ` Robin Murphy
  0 siblings, 0 replies; 35+ messages in thread
From: Robin Murphy @ 2025-08-30  0:00 UTC (permalink / raw)
  To: Jisheng Zhang, Vinod Koul, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: dmaengine, devicetree, linux-arm-kernel, linux-kernel

On 2025-08-23 4:40 pm, Jisheng Zhang wrote:
> Add device_prep_slave_sg() callback function so that DMA_MEM_TO_DEV
> and DMA_DEV_TO_MEM operations in single mode can be supported.
> 
> Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
> ---
>   drivers/dma/arm-dma350.c | 180 +++++++++++++++++++++++++++++++++++++--
>   1 file changed, 174 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index 3d26a1f020df..a285778264b9 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -1,5 +1,6 @@
>   // SPDX-License-Identifier: GPL-2.0
>   // Copyright (C) 2024-2025 Arm Limited
> +// Copyright (C) 2025 Synaptics Incorporated
>   // Arm DMA-350 driver
>   
>   #include <linux/bitfield.h>
> @@ -98,7 +99,23 @@
>   
>   #define CH_FILLVAL		0x38
>   #define CH_SRCTRIGINCFG		0x4c
> +#define CH_SRCTRIGINMODE	GENMASK(11, 10)
> +#define CH_SRCTRIG_CMD		0
> +#define CH_SRCTRIG_DMA_FC	2
> +#define CH_SRCTRIG_PERIF_FC	3
> +#define CH_SRCTRIGINTYPE	GENMASK(9, 8)
> +#define CH_SRCTRIG_SW_REQ	0
> +#define CH_SRCTRIG_HW_REQ	2
> +#define CH_SRCTRIG_INTERN_REQ	3
>   #define CH_DESTRIGINCFG		0x50
> +#define CH_DESTRIGINMODE	GENMASK(11, 10)
> +#define CH_DESTRIG_CMD		0
> +#define CH_DESTRIG_DMA_FC	2
> +#define CH_DESTRIG_PERIF_FC	3
> +#define CH_DESTRIGINTYPE	GENMASK(9, 8)
> +#define CH_DESTRIG_SW_REQ	0
> +#define CH_DESTRIG_HW_REQ	2
> +#define CH_DESTRIG_INTERN_REQ	3

Like the CH_CFG_* definitions, let's just have common CH_TRIG* fields 
that work for both SRC and DES to simplify matters. FWIW, my attempt to 
balance clarity with conciseness would be:

#define CH_TRIGINCFG_BLKSIZE	GENMASK(23, 16)
#define CH_TRIGINCFG_MODE	GENMASK(11, 10)
#define CH_TRIG_MODE_CMD	0
#define CH_TRIG_MODE_DMA	2
#define CH_TRIG_MODE_PERIPH	3
#define CH_TRIGINCFG_TYPE	GENMASK(9, 8)
#define CH_TRIG_TYPE_SW		0
#define CH_TRIG_TYPE_HW		2
#define CH_TRIG_TYPE_INTERN	3
#define CH_TRIGINCFG_SEL	GENMASK(7, 0)

>   #define CH_LINKATTR		0x70
>   #define CH_LINK_SHAREATTR	GENMASK(9, 8)
>   #define CH_LINK_MEMATTR		GENMASK(7, 0)
> @@ -190,11 +207,13 @@ struct d350_chan {
>   	struct d350_desc *desc;
>   	void __iomem *base;
>   	struct dma_pool *cmd_pool;
> +	struct dma_slave_config config;
>   	int irq;
>   	enum dma_status status;
>   	dma_cookie_t cookie;
>   	u32 residue;
>   	u8 tsz;
> +	u8 ch;

What's this for? It doesn't seem to be anything meaningful.

>   	bool has_trig;
>   	bool has_wrap;
>   	bool coherent;
> @@ -327,6 +346,144 @@ static struct dma_async_tx_descriptor *d350_prep_memset(struct dma_chan *chan,
>   	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
>   }
>   
> +static struct dma_async_tx_descriptor *
> +d350_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
> +		   unsigned int sg_len, enum dma_transfer_direction dir,
> +		   unsigned long flags, void *context)
> +{
> +	struct d350_chan *dch = to_d350_chan(chan);
> +	dma_addr_t src, dst, phys;
> +	struct d350_desc *desc;
> +	struct scatterlist *sg;
> +	u32 len, trig, *cmd, *la_cmd, tsz;
> +	struct d350_sg *dsg;
> +	int i, j;
> +
> +	if (unlikely(!is_slave_direction(dir) || !sg_len))
> +		return NULL;
> +
> +	desc = kzalloc(struct_size(desc, sg, sg_len), GFP_NOWAIT);
> +	if (!desc)
> +		return NULL;
> +
> +	desc->sglen = sg_len;
> +
> +	if (dir == DMA_MEM_TO_DEV)
> +		tsz = __ffs(dch->config.dst_addr_width | (1 << dch->tsz));
> +	else
> +		tsz = __ffs(dch->config.src_addr_width | (1 << dch->tsz));

Surely we should just use the exact addr_width requested?

> +	for_each_sg(sgl, sg, sg_len, i) {
> +		desc->sg[i].command = dma_pool_zalloc(dch->cmd_pool, GFP_NOWAIT, &phys);
> +		if (unlikely(!desc->sg[i].command))
> +			goto err_cmd_alloc;
> +
> +		desc->sg[i].phys = phys;
> +		dsg = &desc->sg[i];
> +		len = sg_dma_len(sg);
> +
> +		if (dir == DMA_MEM_TO_DEV) {
> +			src = sg_dma_address(sg);
> +			dst = dch->config.dst_addr;
> +			trig = CH_CTRL_USEDESTRIGIN;
> +		} else {
> +			src = dch->config.src_addr;
> +			dst = sg_dma_address(sg);
> +			trig = CH_CTRL_USESRCTRIGIN;
> +		}
> +		dsg->tsz = tsz;
> +		dsg->xsize = lower_16_bits(len >> dsg->tsz);
> +		dsg->xsizehi = upper_16_bits(len >> dsg->tsz);
> +
> +		cmd = dsg->command;
> +		if (!i) {
> +			cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
> +				 LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_SRCTRANSCFG |
> +				 LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR;
> +
> +			cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, dsg->tsz) | trig |
> +				 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE);
> +
> +			cmd[2] = lower_32_bits(src);
> +			cmd[3] = upper_32_bits(src);
> +			cmd[4] = lower_32_bits(dst);
> +			cmd[5] = upper_32_bits(dst);
> +			cmd[6] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
> +				 FIELD_PREP(CH_XY_DES, dsg->xsize);
> +			cmd[7] = FIELD_PREP(CH_XY_SRC, dsg->xsizehi) |
> +				 FIELD_PREP(CH_XY_DES, dsg->xsizehi);
> +			if (dir == DMA_MEM_TO_DEV) {
> +				cmd[0] |= LINK_DESTRIGINCFG;
> +				cmd[8] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
> +				cmd[9] = TRANSCFG_DEVICE;
> +				cmd[10] = FIELD_PREP(CH_XY_SRC, 1);
> +				cmd[11] = FIELD_PREP(CH_DESTRIGINMODE, CH_DESTRIG_DMA_FC) |
> +					  FIELD_PREP(CH_DESTRIGINTYPE, CH_DESTRIG_HW_REQ);
> +			} else {
> +				cmd[0] |= LINK_SRCTRIGINCFG;
> +				cmd[8] = TRANSCFG_DEVICE;
> +				cmd[9] = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
> +				cmd[10] = FIELD_PREP(CH_XY_DES, 1);
> +				cmd[11] = FIELD_PREP(CH_SRCTRIGINMODE, CH_SRCTRIG_DMA_FC) |
> +					  FIELD_PREP(CH_SRCTRIGINTYPE, CH_SRCTRIG_HW_REQ);
> +			}
> +			la_cmd = &cmd[12];
> +		} else {
> +			*la_cmd = phys | CH_LINKADDR_EN;
> +			if (i == sg_len - 1) {
> +				cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
> +					 LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_LINKADDR;
> +				cmd[1] = FIELD_PREP(CH_CTRL_TRANSIZE, dsg->tsz) | trig |
> +					 FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE);
> +				cmd[2] = lower_32_bits(src);
> +				cmd[3] = upper_32_bits(src);
> +				cmd[4] = lower_32_bits(dst);
> +				cmd[5] = upper_32_bits(dst);
> +				cmd[6] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
> +					 FIELD_PREP(CH_XY_DES, dsg->xsize);
> +				cmd[7] = FIELD_PREP(CH_XY_SRC, dsg->xsizehi) |
> +					 FIELD_PREP(CH_XY_DES, dsg->xsizehi);
> +				la_cmd = &cmd[8];
> +			} else {
> +				cmd[0] = LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
> +					 LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_LINKADDR;
> +				cmd[1] = lower_32_bits(src);
> +				cmd[2] = upper_32_bits(src);
> +				cmd[3] = lower_32_bits(dst);
> +				cmd[4] = upper_32_bits(dst);
> +				cmd[5] = FIELD_PREP(CH_XY_SRC, dsg->xsize) |
> +					 FIELD_PREP(CH_XY_DES, dsg->xsize);
> +				cmd[6] = FIELD_PREP(CH_XY_SRC, dsg->xsizehi) |
> +					 FIELD_PREP(CH_XY_DES, dsg->xsizehi);
> +				la_cmd = &cmd[7];
> +			}
> +		}
> +	}
Again, the structure and duplication here is unbearable. I fail to 
comprehend why the "if (i == sg_len - 1)" case even exists...

More crucially, it clearly can't even work for DMA-350 since it would 
always be selecting trigger 0. How much of this has been tested?

> +	/* the last command */
> +	*la_cmd = 0;
> +	desc->sg[sg_len - 1].command[1] |= FIELD_PREP(CH_CTRL_DONETYPE, CH_CTRL_DONETYPE_CMD);
> +
> +	mb();
> +
> +	return vchan_tx_prep(&dch->vc, &desc->vd, flags);
> +
> +err_cmd_alloc:
> +	for (j = 0; j < i; j++)
> +		dma_pool_free(dch->cmd_pool, desc->sg[j].command, desc->sg[j].phys);
> +	kfree(desc);
> +	return NULL;
> +}
> +
> +static int d350_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
> +{
> +	struct d350_chan *dch = to_d350_chan(chan);
Shouldn't we validate that the given channel has any chance of 
supporting the given config?

> +	memcpy(&dch->config, config, sizeof(*config));
> +
> +	return 0;
> +}
> +
>   static int d350_pause(struct dma_chan *chan)
>   {
>   	struct d350_chan *dch = to_d350_chan(chan);
> @@ -558,8 +715,9 @@ static irqreturn_t d350_irq(int irq, void *data)
>   	writel_relaxed(ch_status, dch->base + CH_STATUS);
>   
>   	spin_lock(&dch->vc.lock);
> -	vchan_cookie_complete(vd);
>   	if (ch_status & CH_STAT_INTR_DONE) {
> +		vchan_cookie_complete(vd);
> +		dch->desc = NULL;

What's this about? As I mentioned on the earlier patches it was very 
fiddly to get consistently appropriate handling of errors for 
MEM_TO_MEM, so please explain if this change is somehow necessary for 
MEM_TO_DEV/DEV_TO_MEM. Otherwise, if anything it just looks like an 
undocumented bodge around what those previous patches subtly broke.

>   		dch->status = DMA_COMPLETE;
>   		dch->residue = 0;
>   		d350_start_next(dch);
> @@ -617,7 +775,7 @@ static int d350_probe(struct platform_device *pdev)
>   	struct device *dev = &pdev->dev;
>   	struct d350 *dmac;
>   	void __iomem *base;
> -	u32 reg, dma_chan_mask;
> +	u32 reg, dma_chan_mask, trig_bits = 0;
>   	int ret, nchan, dw, aw, r, p;
>   	bool coherent, memset;
>   
> @@ -637,13 +795,11 @@ static int d350_probe(struct platform_device *pdev)
>   	dw = 1 << FIELD_GET(DMA_CFG_DATA_WIDTH, reg);
>   	aw = FIELD_GET(DMA_CFG_ADDR_WIDTH, reg) + 1;
>   
> -	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(aw));
> -	coherent = device_get_dma_attr(dev) == DEV_DMA_COHERENT;
> -
>   	dmac = devm_kzalloc(dev, struct_size(dmac, channels, nchan), GFP_KERNEL);
>   	if (!dmac)
>   		return -ENOMEM;
>   
> +	dmac->dma.dev = dev;

Similarly, why are these two hunks just moving lines around apparently 
at random?

>   	dmac->nchan = nchan;
>   
>   	/* Enable all channels by default */
> @@ -655,12 +811,14 @@ static int d350_probe(struct platform_device *pdev)
>   		return ret;
>   	}
>   
> +	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(aw));
> +	coherent = device_get_dma_attr(dev) == DEV_DMA_COHERENT;
> +
>   	reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG1);
>   	dmac->nreq = FIELD_GET(DMA_CFG_NUM_TRIGGER_IN, reg);
>   
>   	dev_dbg(dev, "DMA-350 r%dp%d with %d channels, %d requests\n", r, p, dmac->nchan, dmac->nreq);
>   
> -	dmac->dma.dev = dev;
>   	for (int i = min(dw, 16); i > 0; i /= 2) {
>   		dmac->dma.src_addr_widths |= BIT(i);
>   		dmac->dma.dst_addr_widths |= BIT(i);
> @@ -692,6 +850,7 @@ static int d350_probe(struct platform_device *pdev)
>   
>   		dch->coherent = coherent;
>   		dch->base = base + DMACH(i);
> +		dch->ch = i;
>   		writel_relaxed(CH_CMD_CLEAR, dch->base + CH_CMD);
>   
>   		reg = readl_relaxed(dch->base + CH_BUILDCFG1);
> @@ -711,6 +870,7 @@ static int d350_probe(struct platform_device *pdev)
>   
>   		/* Fill is a special case of Wrap */
>   		memset &= dch->has_wrap;
> +		trig_bits |= dch->has_trig << dch->ch;

This is a bool merely dressed up as a bitmap, the shift is doing nothing.

Thanks,
Robin.

>   		reg = readl_relaxed(dch->base + CH_BUILDCFG0);
>   		dch->tsz = FIELD_GET(CH_CFG_DATA_WIDTH, reg);
> @@ -723,6 +883,13 @@ static int d350_probe(struct platform_device *pdev)
>   		vchan_init(&dch->vc, &dmac->dma);
>   	}
>   
> +	if (trig_bits) {
> +		dmac->dma.directions |= (BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV));
> +		dma_cap_set(DMA_SLAVE, dmac->dma.cap_mask);
> +		dmac->dma.device_config = d350_slave_config;
> +		dmac->dma.device_prep_slave_sg = d350_prep_slave_sg;
> +	}
> +
>   	if (memset) {
>   		dma_cap_set(DMA_MEMSET, dmac->dma.cap_mask);
>   		dmac->dma.device_prep_dma_memset = d350_prep_memset;
> @@ -759,5 +926,6 @@ static struct platform_driver d350_driver = {
>   module_platform_driver(d350_driver);
>   
>   MODULE_AUTHOR("Robin Murphy <robin.murphy@arm.com>");
> +MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
>   MODULE_DESCRIPTION("Arm DMA-350 driver");
>   MODULE_LICENSE("GPL v2");


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 10/14] dmaengine: dma350: Alloc command[] from dma pool
  2025-08-23 15:40 ` [PATCH 10/14] dmaengine: dma350: Alloc command[] from dma pool Jisheng Zhang
  2025-08-29 12:56   ` Robin Murphy
@ 2025-09-01 10:27   ` Dan Carpenter
  1 sibling, 0 replies; 35+ messages in thread
From: Dan Carpenter @ 2025-09-01 10:27 UTC (permalink / raw)
  To: oe-kbuild, Jisheng Zhang, Vinod Koul, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Robin Murphy
  Cc: lkp, oe-kbuild-all, dmaengine, devicetree, linux-arm-kernel,
	linux-kernel

Hi Jisheng,

kernel test robot noticed the following build warnings:

https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Jisheng-Zhang/dmaengine-dma350-Fix-CH_CTRL_USESRCTRIGIN-definition/20250824-000425
base:   https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git next
patch link:    https://lore.kernel.org/r/20250823154009.25992-11-jszhang%40kernel.org
patch subject: [PATCH 10/14] dmaengine: dma350: Alloc command[] from dma pool
config: arm-randconfig-r073-20250829 (https://download.01.org/0day-ci/archive/20250829/202508291556.kjNumYgR-lkp@intel.com/config)
compiler: arm-linux-gnueabi-gcc (GCC) 13.4.0

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
| Closes: https://lore.kernel.org/r/202508291556.kjNumYgR-lkp@intel.com/

smatch warnings:
drivers/dma/arm-dma350.c:387 d350_get_residue() error: uninitialized symbol 'sgcur'.

vim +/sgcur +387 drivers/dma/arm-dma350.c

5d099706449d54 Robin Murphy  2025-03-12  360  static u32 d350_get_residue(struct d350_chan *dch)
5d099706449d54 Robin Murphy  2025-03-12  361  {
eae79fde2ff50c Jisheng Zhang 2025-08-23  362  	u32 res, xsize, xsizehi, linkaddr, linkaddrhi, hi_new;
eae79fde2ff50c Jisheng Zhang 2025-08-23  363  	int i, sgcur, retries = 3; /* 1st time unlucky, 2nd improbable, 3rd just broken */
eae79fde2ff50c Jisheng Zhang 2025-08-23  364  	struct d350_desc *desc = dch->desc;
5d099706449d54 Robin Murphy  2025-03-12  365  
5d099706449d54 Robin Murphy  2025-03-12  366  	hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
5d099706449d54 Robin Murphy  2025-03-12  367  	do {
5d099706449d54 Robin Murphy  2025-03-12  368  		xsizehi = hi_new;
5d099706449d54 Robin Murphy  2025-03-12  369  		xsize = readl_relaxed(dch->base + CH_XSIZE);
5d099706449d54 Robin Murphy  2025-03-12  370  		hi_new = readl_relaxed(dch->base + CH_XSIZEHI);
5d099706449d54 Robin Murphy  2025-03-12  371  	} while (xsizehi != hi_new && --retries);
5d099706449d54 Robin Murphy  2025-03-12  372  
eae79fde2ff50c Jisheng Zhang 2025-08-23  373  	hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
eae79fde2ff50c Jisheng Zhang 2025-08-23  374  	do {
eae79fde2ff50c Jisheng Zhang 2025-08-23  375  		linkaddrhi = hi_new;
eae79fde2ff50c Jisheng Zhang 2025-08-23  376  		linkaddr = readl_relaxed(dch->base + CH_LINKADDR);
eae79fde2ff50c Jisheng Zhang 2025-08-23  377  		hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
eae79fde2ff50c Jisheng Zhang 2025-08-23  378  	} while (linkaddrhi != hi_new && --retries);
eae79fde2ff50c Jisheng Zhang 2025-08-23  379  
eae79fde2ff50c Jisheng Zhang 2025-08-23  380  	for (i = 0; i < desc->sglen; i++) {
eae79fde2ff50c Jisheng Zhang 2025-08-23  381  		if (desc->sg[i].phys == (((u64)linkaddrhi << 32) | (linkaddr & ~CH_LINKADDR_EN)))
eae79fde2ff50c Jisheng Zhang 2025-08-23  382  			sgcur = i;

I'm suprised there isn't a break statement after this assignment.
What if we exit the loop with i == desc->sglen?

eae79fde2ff50c Jisheng Zhang 2025-08-23  383  	}
eae79fde2ff50c Jisheng Zhang 2025-08-23  384  
5d099706449d54 Robin Murphy  2025-03-12  385  	res = FIELD_GET(CH_XY_DES, xsize);
5d099706449d54 Robin Murphy  2025-03-12  386  	res |= FIELD_GET(CH_XY_DES, xsizehi) << 16;
eae79fde2ff50c Jisheng Zhang 2025-08-23 @387  	res <<= desc->sg[sgcur].tsz;
                                                                 ^^^^^
Uninitialized.

eae79fde2ff50c Jisheng Zhang 2025-08-23  388  
eae79fde2ff50c Jisheng Zhang 2025-08-23  389  	for (i = sgcur + 1; i < desc->sglen; i++)
eae79fde2ff50c Jisheng Zhang 2025-08-23  390  		res += (((u32)desc->sg[i].xsizehi << 16 | desc->sg[i].xsize) << desc->sg[i].tsz);
5d099706449d54 Robin Murphy  2025-03-12  391  
eae79fde2ff50c Jisheng Zhang 2025-08-23  392  	return res;
5d099706449d54 Robin Murphy  2025-03-12  393  }

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki



^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2025-09-01 12:31 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-23 15:39 [PATCH 00/14] dmaengine: dma350: Support slave_sg, cyclic and DMA-250 Jisheng Zhang
2025-08-23 15:39 ` [PATCH 01/14] dmaengine: dma350: Fix CH_CTRL_USESRCTRIGIN definition Jisheng Zhang
2025-08-29 10:44   ` Robin Murphy
2025-08-23 15:39 ` [PATCH 02/14] dmaengine: dma350: Add missing dch->coherent setting Jisheng Zhang
2025-08-29 10:52   ` Robin Murphy
2025-08-23 15:39 ` [PATCH 03/14] dmaengine: dma350: Check vchan_next_desc() return value Jisheng Zhang
2025-08-29 10:02   ` Robin Murphy
2025-08-23 15:39 ` [PATCH 04/14] dmaengine: dma350: Check dma_cookie_status() ret code and txstate Jisheng Zhang
2025-08-29 10:42   ` Robin Murphy
2025-08-23 15:40 ` [PATCH 05/14] dmaengine: dma350: Register the DMA controller to DT DMA helpers Jisheng Zhang
2025-08-29 20:37   ` Robin Murphy
2025-08-23 15:40 ` [PATCH 06/14] dmaengine: dma350: Use dmaenginem_async_device_register Jisheng Zhang
2025-08-23 15:40 ` [PATCH 07/14] dmaengine: dma350: Remove redundant err msg if platform_get_irq() fails Jisheng Zhang
2025-08-23 15:40 ` [PATCH 08/14] dt-bindings: dma: dma350: Document interrupt-names Jisheng Zhang
2025-08-23 16:09   ` Krzysztof Kozlowski
2025-08-24  9:49     ` Jisheng Zhang
2025-08-24 10:30       ` Krzysztof Kozlowski
2025-08-24 13:19         ` Jisheng Zhang
2025-08-29 11:08   ` Robin Murphy
2025-08-23 15:40 ` [PATCH 09/14] dmaengine: dma350: Support dma-channel-mask Jisheng Zhang
2025-08-23 16:10   ` Krzysztof Kozlowski
2025-08-24  7:01   ` kernel test robot
2025-08-23 15:40 ` [PATCH 10/14] dmaengine: dma350: Alloc command[] from dma pool Jisheng Zhang
2025-08-29 12:56   ` Robin Murphy
2025-09-01 10:27   ` Dan Carpenter
2025-08-23 15:40 ` [PATCH 11/14] dmaengine: dma350: Support device_prep_slave_sg Jisheng Zhang
2025-08-30  0:00   ` Robin Murphy
2025-08-23 15:40 ` [PATCH 12/14] dmaengine: dma350: Support device_prep_dma_cyclic Jisheng Zhang
2025-08-23 15:40 ` [PATCH 13/14] dt-bindings: dma: dma350: Support ARM DMA-250 Jisheng Zhang
2025-08-23 16:11   ` Krzysztof Kozlowski
2025-08-29 11:16     ` Robin Murphy
2025-08-23 15:40 ` [PATCH 14/14] dmaengine: " Jisheng Zhang
2025-08-23 16:13   ` Krzysztof Kozlowski
2025-08-24 19:38   ` kernel test robot
2025-08-29 22:24   ` Robin Murphy

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).