LoongArch architecture development
 help / color / mirror / Atom feed
* [PATCH v2 0/3] Add Loongson-2K0300 APB DMA mux support
@ 2026-10-09  9:19 Binbin Zhou
  2026-10-09  9:19 ` [PATCH v2 1/3] dt-bindings: dma: loongson: Add Loongson-2 DMA mux request IDs Binbin Zhou
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Binbin Zhou @ 2026-10-09  9:19 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine
  Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Keguang Zhang,
	Binbin Zhou

Hi all:

Loongson-2K0300 integrates a CMC DMA controller with 8 physical
channels. Peripheral DMA requests are not hard-wired to these channels;
they are routed through the CHIP_CTRL13/14/15 DMA_MAP fields.

This series adds support for that DMA request router.

Patch 1 adds the DT binding constants for the peripheral request IDs.
Patch 2 adds the DT binding documentation for the DMA mux.
Patch 3 adds the driver, Kconfig symbol and Makefile entry.

The driver registers with of_dma_router_register(). In the route
allocation callback it validates the <request> <channel> <flags>
specifier, programs the SoC-specific route register, and forwards
<channel> <flags> to the parent CMC DMA controller.

Thanks.
Binbin.

=========
V2:
Patch(1/3):
 - Drop LS2K0300_DMA_REQ_MAX as it is not part of the ABI;
Patch(2/3):
 - More detailed description of `dma-cells`;
 - Add the `dma_requests` constraint
Patch(3/3):
 - Drop loongson2_dmamux_map->shift;
 - Move of_parse_phandle() into loongson2_dmamux_route_allocate() avoid
   UAF;
 - Drop loongson2_dmamux->dma_np;
 - Check dma_requests;
 - Add devm_add_action_or_reset() instead of .remove();
 - Replace `metux()` with `spinlock()`, which has lower overhead;
 - Add loongson2_dmamux_route_free().

Link to V1:
https://lore.kernel.org/all/cover.1790068409.git.zhoubinbin@loongson.cn/

Binbin Zhou (3):
  dt-bindings: dma: loongson: Add Loongson-2 DMA mux request IDs
  dt-bindings: dma: loongson: Add Loongson-2K0300 DMA mux support
  dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver

 .../dma/loongson,ls2k0300-dmamux.yaml         |  72 ++++
 MAINTAINERS                                   |   3 +
 drivers/dma/loongson/Makefile                 |   1 +
 drivers/dma/loongson/loongson2-apb-dmamux.c   | 398 ++++++++++++++++++
 .../dt-bindings/dma/loongson,ls2k-dmamux.h    |  53 +++
 5 files changed, 527 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml
 create mode 100644 drivers/dma/loongson/loongson2-apb-dmamux.c
 create mode 100644 include/dt-bindings/dma/loongson,ls2k-dmamux.h


base-commit: 1d8ae2003de08f61095e4e928b1b8cce6c23cabd
-- 
2.52.0


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

* [PATCH v2 1/3] dt-bindings: dma: loongson: Add Loongson-2 DMA mux request IDs
  2026-10-09  9:19 [PATCH v2 0/3] Add Loongson-2K0300 APB DMA mux support Binbin Zhou
@ 2026-10-09  9:19 ` Binbin Zhou
  2026-10-09 14:38   ` Conor Dooley
  2026-10-09  9:19 ` [PATCH v2 2/3] dt-bindings: dma: loongson: Add Loongson-2K0300 DMA mux support Binbin Zhou
  2026-10-09  9:19 ` [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver Binbin Zhou
  2 siblings, 1 reply; 11+ messages in thread
From: Binbin Zhou @ 2026-10-09  9:19 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine
  Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Keguang Zhang,
	Binbin Zhou

Add DT binding constants for the Loongson-2 DMA mux.

The header defines the peripheral request IDs used in the DMA specifier
<request channel flags> for Loongson-2K0300, covering UART0-9, I2C0-3,
SPI2/3, I2S, ADC and CAN0-3. It also documents the channel-pair semantics
for peripherals that use paired DMA channels.

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 MAINTAINERS                                   |  1 +
 .../dt-bindings/dma/loongson,ls2k-dmamux.h    | 53 +++++++++++++++++++
 2 files changed, 54 insertions(+)
 create mode 100644 include/dt-bindings/dma/loongson,ls2k-dmamux.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 42f5165019ee..172dde21275f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15354,6 +15354,7 @@ F:	Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
 F:	Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
 F:	drivers/dma/loongson/loongson2-apb-cmc-dma.c
 F:	drivers/dma/loongson/loongson2-apb-dma.c
+F:	include/dt-bindings/dma/loongson,ls2k-dmamux.h
 
 LOONGSON LS2X I2C DRIVER
 M:	Binbin Zhou <zhoubinbin@loongson.cn>
diff --git a/include/dt-bindings/dma/loongson,ls2k-dmamux.h b/include/dt-bindings/dma/loongson,ls2k-dmamux.h
new file mode 100644
index 000000000000..1e2907ab069d
--- /dev/null
+++ b/include/dt-bindings/dma/loongson,ls2k-dmamux.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Loongson-2 SoC DMA Mux dt-bindings
+ *
+ * DMA specifier format:
+ *   dmas = <&dma_mux <request> <channel> <flags>>;
+ *
+ * The <channel> cell selects a physical CMC DMA channel (0-7)
+ * For peripherals using channel pairs (UART, I2C, SPI2/3, I2S),
+ * the channel number selects the pair: 0/1 -> pair 0, 2/3 -> pair 1,
+ * 4/5 -> pair 2, 6/7 -> pair 3.
+ *
+ * Copyright (C) 2026 Loongson Technology Corporation Limited
+ */
+
+#ifndef _DT_BINDINGS_DMA_LOONGSON_LS2K_DMAMUX_H
+#define _DT_BINDINGS_DMA_LOONGSON_LS2K_DMAMUX_H
+
+/* UART controllers */
+#define LS2K0300_DMA_UART0		0
+#define LS2K0300_DMA_UART1		1
+#define LS2K0300_DMA_UART2		2
+#define LS2K0300_DMA_UART3		3
+#define LS2K0300_DMA_UART4		4
+#define LS2K0300_DMA_UART5		5
+#define LS2K0300_DMA_UART6		6
+#define LS2K0300_DMA_UART7		7
+#define LS2K0300_DMA_UART8		8
+#define LS2K0300_DMA_UART9		9
+
+/* I2C controllers */
+#define LS2K0300_DMA_I2C0		10
+#define LS2K0300_DMA_I2C1		11
+#define LS2K0300_DMA_I2C2		12
+#define LS2K0300_DMA_I2C3		13
+
+/* SPI controllers (IO mode) */
+#define LS2K0300_DMA_SPI2		14
+#define LS2K0300_DMA_SPI3		15
+
+/* I2S controller */
+#define LS2K0300_DMA_I2S		16
+
+/* ADC controller */
+#define LS2K0300_DMA_ADC		17
+
+/* CAN-FD controllers */
+#define LS2K0300_DMA_CAN0		18
+#define LS2K0300_DMA_CAN1		19
+#define LS2K0300_DMA_CAN2		20
+#define LS2K0300_DMA_CAN3		21
+
+#endif /* _DT_BINDINGS_DMA_LOONGSON_LS2K_DMAMUX_H */
-- 
2.52.0


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

* [PATCH v2 2/3] dt-bindings: dma: loongson: Add Loongson-2K0300 DMA mux support
  2026-10-09  9:19 [PATCH v2 0/3] Add Loongson-2K0300 APB DMA mux support Binbin Zhou
  2026-10-09  9:19 ` [PATCH v2 1/3] dt-bindings: dma: loongson: Add Loongson-2 DMA mux request IDs Binbin Zhou
@ 2026-10-09  9:19 ` Binbin Zhou
  2026-10-09 14:43   ` Conor Dooley
  2026-10-09  9:19 ` [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver Binbin Zhou
  2 siblings, 1 reply; 11+ messages in thread
From: Binbin Zhou @ 2026-10-09  9:19 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine
  Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Keguang Zhang,
	Binbin Zhou

The DMA mux on Loongson-2K0300 is not a standalone hardware block. It is
implemented as part of the chip configuration registers CHIP_CTRL13/14/15,
which route peripheral DMA requests to the physical channels of the CMC
DMA controller.

Document this as a DMA router. The #dma-cells = <3> specifier contains
the peripheral request ID, the target CMC DMA channel, and channel
configuration flags forwarded to the parent DMA controller.

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 .../dma/loongson,ls2k0300-dmamux.yaml         | 72 +++++++++++++++++++
 MAINTAINERS                                   |  1 +
 2 files changed, 73 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml

diff --git a/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml
new file mode 100644
index 000000000000..0e6c794e1c90
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dma/loongson,ls2k0300-dmamux.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Loongson-2 APB DMA MUX (DMA request router)
+
+maintainers:
+  - Binbin Zhou <zhoubinbin@loongson.cn>
+
+allOf:
+  - $ref: /schemas/dma/dma-router.yaml#
+
+properties:
+  compatible:
+    const: loongson,ls2k0300-dmamux
+
+  reg:
+    maxItems: 1
+
+  '#dma-cells':
+    const: 3
+    description: |
+      Each cell represents the following:
+      1. Peripheral request ID: This describes the corresponding bit field
+         for that peripheral in the configuration register; see (
+         include/dt-bindings/dma/loongson,ls2k-dmamux.h) for details.
+      2. Physical DMA channel (0-7): This not only represents the DMA
+         channel used by the peripheral but is also converted into a value for
+         the corresponding bit field in the configuration register, indicating
+         the legitimacy of that peripheral using that channel.
+         Specifically:
+         - I2S/SPI/UART/I2C: Require adjacent channels in pairs; valid
+            values are 0/1, 2/3, 4/5, 6/7;
+         - SM3/SM4/DES/AES: Single channel; valid values are limited to 0/1;
+         - CAN/ADC: Single channel; valid values range from 0 to 7.
+      3. DMA Channel Configuration: A 32-bit mask used to specify
+         device-specific DMA channel configurations.
+         - Bit 6: Peripheral Address Increment Mode
+            0x0: Disabled
+            0x1: Enabled
+         - Bit 7: Memory Address Increment Mode
+            0x0: Disabled
+            0x1: Enabled
+         - Bits 12-13: Priority
+            0x0: Low
+            0x1: Medium
+            0x2: High
+            0x3: Very High
+
+  dma-requests:
+    const: 22
+
+required:
+  - compatible
+  - reg
+  - dma-requests
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/dma/loongson,ls2k-dmamux.h>
+    dma-router@16000134 {
+        compatible = "loongson,ls2k0300-dmamux";
+        reg = <0x16000134 0xc>;
+        #dma-cells = <3>;
+        dma-masters = <&dma>;
+        dma-requests = <22>;
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 172dde21275f..78622b255f88 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15351,6 +15351,7 @@ M:	Binbin Zhou <zhoubinbin@loongson.cn>
 L:	dmaengine@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
+F:	Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml
 F:	Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
 F:	drivers/dma/loongson/loongson2-apb-cmc-dma.c
 F:	drivers/dma/loongson/loongson2-apb-dma.c
-- 
2.52.0


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

* [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver
  2026-10-09  9:19 [PATCH v2 0/3] Add Loongson-2K0300 APB DMA mux support Binbin Zhou
  2026-10-09  9:19 ` [PATCH v2 1/3] dt-bindings: dma: loongson: Add Loongson-2 DMA mux request IDs Binbin Zhou
  2026-10-09  9:19 ` [PATCH v2 2/3] dt-bindings: dma: loongson: Add Loongson-2K0300 DMA mux support Binbin Zhou
@ 2026-10-09  9:19 ` Binbin Zhou
  2026-10-15 17:03   ` Frank Li
  2026-10-25  8:19   ` Huacai Chen
  2 siblings, 2 replies; 11+ messages in thread
From: Binbin Zhou @ 2026-10-09  9:19 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine
  Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Keguang Zhang,
	Binbin Zhou

The Loongson-2K0300 DMA mux is not a standalone DMA controller. It is
implemented as a set of routing fields in the chip global configuration
registers CHIP_CTRL13/14/15, which select which physical channel of the
on-chip CMC DMA controller handles each peripheral DMA request.

Add a driver that exposes these routing fields as an OF DMA router. The
router's allocation callback validates the <request> <channel> <flags>
specifier, programs the corresponding CHIP_CTRL register field, and then
rewrites the specifier to <channel> <flags> so that the parent CMC DMA
controller's xlate is used.

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 MAINTAINERS                                 |   1 +
 drivers/dma/loongson/Makefile               |   1 +
 drivers/dma/loongson/loongson2-apb-dmamux.c | 398 ++++++++++++++++++++
 3 files changed, 400 insertions(+)
 create mode 100644 drivers/dma/loongson/loongson2-apb-dmamux.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 78622b255f88..0b0c6ea41c64 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15355,6 +15355,7 @@ F:	Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml
 F:	Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
 F:	drivers/dma/loongson/loongson2-apb-cmc-dma.c
 F:	drivers/dma/loongson/loongson2-apb-dma.c
+F:	drivers/dma/loongson/loongson2-apb-dmamux.c
 F:	include/dt-bindings/dma/loongson,ls2k-dmamux.h
 
 LOONGSON LS2X I2C DRIVER
diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
index 48c19781e729..271fd38fa8f2 100644
--- a/drivers/dma/loongson/Makefile
+++ b/drivers/dma/loongson/Makefile
@@ -2,3 +2,4 @@
 obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
 obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
 obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
+obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-dmamux.o
diff --git a/drivers/dma/loongson/loongson2-apb-dmamux.c b/drivers/dma/loongson/loongson2-apb-dmamux.c
new file mode 100644
index 000000000000..01bd3ce4ca25
--- /dev/null
+++ b/drivers/dma/loongson/loongson2-apb-dmamux.c
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Loongson2 SoC APB DMA Mux Controller
+ *
+ * Copyright (C) 2026 Loongson Technology Corporation Limited
+ *
+ * Device tree bindings:
+ *
+ *   2K0300:
+ *     dma_mux: dma-mux@16000134 {
+ *             compatible = "loongson,ls2k0300-dmamux";
+ *             reg = <0x0 0x16000134 0x0 0xc>;
+ *             #dma-cells = <3>;
+ *             dma-masters = <&dma>;
+ *             dma-requests = <22>;
+ *     };
+ *
+ * DMA specifier: <request> <channel> <flags>
+ *   - request: peripheral request ID (LS2K0300_DMA_*)
+ *   - channel: target physical DMA channel
+ *   - flags:   channel configuration flags forwarded to the parent DMA
+ *              controller
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/dmaengine.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_dma.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <dt-bindings/dma/loongson,ls2k-dmamux.h>
+
+/* Loongson-2K0300 registers definitions */
+#define LS2K0300_CHIP_CTRL13		0x00
+#define LS2K0300_CHIP_CTRL14		0x04
+#define LS2K0300_CHIP_CTRL15		0x08
+
+/* CHIP_CTRL13 bitfields */
+#define CTRL13_UART0_DMA_MAP		GENMASK(17, 16)
+#define CTRL13_UART1_DMA_MAP		GENMASK(19, 18)
+#define CTRL13_UART2_DMA_MAP		GENMASK(21, 20)
+#define CTRL13_UART3_DMA_MAP		GENMASK(23, 22)
+#define CTRL13_UART4_DMA_MAP		GENMASK(25, 24)
+#define CTRL13_UART5_DMA_MAP		GENMASK(27, 26)
+#define CTRL13_UART6_DMA_MAP		GENMASK(29, 28)
+#define CTRL13_UART7_DMA_MAP		GENMASK(31, 30)
+
+/* CHIP_CTRL14 bitfields */
+#define CTRL14_UART8_DMA_MAP		GENMASK(1, 0)
+#define CTRL14_UART9_DMA_MAP		GENMASK(3, 2)
+#define CTRL14_I2C0_DMA_MAP		GENMASK(5, 4)
+#define CTRL14_I2C1_DMA_MAP		GENMASK(7, 6)
+#define CTRL14_I2C2_DMA_MAP		GENMASK(9, 8)
+#define CTRL14_I2C3_DMA_MAP		GENMASK(11, 10)
+#define CTRL14_SPI2_DMA_MAP		GENMASK(13, 12)
+#define CTRL14_SPI3_DMA_MAP		GENMASK(15, 14)
+#define CTRL14_I2S_DMA_MAP		GENMASK(17, 16)
+#define CTRL14_ADC_DMA_MAP		GENMASK(20, 18)
+#define CTRL14_CAN0_DMA_MAP		GENMASK(23, 21)
+#define CTRL14_CAN1_DMA_MAP		GENMASK(26, 24)
+#define CTRL14_CAN2_DMA_MAP		GENMASK(29, 27)
+#define CTRL14_CAN3_DMA_MAP		GENMASK(31, 30)
+
+/* CHIP_CTRL15 bitfields */
+#define CTRL15_CAN3_DMA_MAP_HI		BIT(0)
+
+#define LS2K0300_DMA_MAX_CHANNEL	7
+
+/**
+ * enum loongson2_dmamux_type - Type of the mapping field
+ * @LOONGSON_DMAMUX_PAIR: 2-bit field, value selects a channel pair
+ *                        (0/1, 2/3, 4/5, 6/7).
+ * @LOONGSON_DMAMUX_SINGLE: 3-bit field, value selects a single channel
+ *                          or controller index directly.
+ */
+enum loongson2_dmamux_type {
+	LOONGSON_DMAMUX_PAIR,
+	LOONGSON_DMAMUX_SINGLE,
+};
+
+/**
+ * struct loongson2_dmamux_map - DMA request to register bitfield mapping
+ * @reg: Register offset from the controller base
+ * @mask: Bitfield mask within the register
+ * @type: Mapping type (pair or single)
+ * @high_reg: Optional extension register offset (0 if unused)
+ * @high_shift: Bit position in the extension register
+ */
+struct loongson2_dmamux_map {
+	u32 reg;
+	u32 mask;
+	enum loongson2_dmamux_type type;
+	u32 high_reg;
+	u8 high_shift;
+};
+
+/**
+ * struct loongson2_dmamux_config - SoC-specific DMA mux configuration
+ * @maps: Pointer to the request mapping table
+ * @num_maps: Number of entries in @maps
+ * @max_channel: Highest valid channel/controller index
+ * @parent_args_count: Number of cells to write to dma_spec->args[]
+ *                     when forwarding to the parent xlate.
+ */
+struct loongson2_dmamux_config {
+	const struct loongson2_dmamux_map *maps;
+	u32 num_maps;
+	u32 max_channel;
+	u32 parent_args_count;
+};
+
+/**
+ * struct loongson2_dmamux - DMA mux controller state
+ * @base: I/O-mapped register base
+ * @lock: Protects register read-modify-write sequences
+ * @dma_router: Router structure passed to of_dma_router_register()
+ * @config: SoC-specific configuration
+ */
+struct loongson2_dmamux {
+	void __iomem *base;
+	spinlock_t lock;	/* Protects register read-modify-write sequences */
+	struct dma_router dma_router;
+	const struct loongson2_dmamux_config *config;
+};
+
+/* Loongson-2K0300 mapping table */
+static const struct loongson2_dmamux_map ls2k0300_dmamux_maps[] = {
+	[LS2K0300_DMA_UART0] = { LS2K0300_CHIP_CTRL13, CTRL13_UART0_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_UART1] = { LS2K0300_CHIP_CTRL13, CTRL13_UART1_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_UART2] = { LS2K0300_CHIP_CTRL13, CTRL13_UART2_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_UART3] = { LS2K0300_CHIP_CTRL13, CTRL13_UART3_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_UART4] = { LS2K0300_CHIP_CTRL13, CTRL13_UART4_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_UART5] = { LS2K0300_CHIP_CTRL13, CTRL13_UART5_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_UART6] = { LS2K0300_CHIP_CTRL13, CTRL13_UART6_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_UART7] = { LS2K0300_CHIP_CTRL13, CTRL13_UART7_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_UART8] = { LS2K0300_CHIP_CTRL14, CTRL14_UART8_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_UART9] = { LS2K0300_CHIP_CTRL14, CTRL14_UART9_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_I2C0]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C0_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_I2C1]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C1_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_I2C2]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C2_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_I2C3]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C3_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_SPI2]  = { LS2K0300_CHIP_CTRL14, CTRL14_SPI2_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_SPI3]  = { LS2K0300_CHIP_CTRL14, CTRL14_SPI3_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_I2S]   = { LS2K0300_CHIP_CTRL14, CTRL14_I2S_DMA_MAP, LOONGSON_DMAMUX_PAIR,
+				 0, 0 },
+	[LS2K0300_DMA_ADC]   = { LS2K0300_CHIP_CTRL14, CTRL14_ADC_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
+				 0, 0 },
+	[LS2K0300_DMA_CAN0]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN0_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
+				 0, 0 },
+	[LS2K0300_DMA_CAN1]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN1_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
+				 0, 0 },
+	[LS2K0300_DMA_CAN2]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN2_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
+				 0, 0 },
+	[LS2K0300_DMA_CAN3]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN3_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
+				 LS2K0300_CHIP_CTRL15, 0 },
+};
+
+static const struct loongson2_dmamux_config ls2k0300_dmamux_config = {
+	.maps			= ls2k0300_dmamux_maps,
+	.num_maps		= ARRAY_SIZE(ls2k0300_dmamux_maps),
+	.max_channel		= LS2K0300_DMA_MAX_CHANNEL,
+	.parent_args_count	= 2,
+};
+
+static inline u32 loongson2_dmamux_read(struct loongson2_dmamux *mux, u32 reg)
+{
+	return readl(mux->base + reg);
+}
+
+static inline void loongson2_dmamux_write(struct loongson2_dmamux *mux, u32 reg, u32 val)
+{
+	writel(val, mux->base + reg);
+}
+
+/**
+ * loongson2_dmamux_set_route() - Program the DMA request route
+ * @mux:     DMA mux controller
+ * @request: Peripheral DMA request ID
+ * @channel: Target physical DMA channel or controller index
+ *
+ * Computes the register value according to the mapping type:
+ *   - PAIR   : channel / 2 selects the pair index
+ *   - SINGLE : channel is written directly
+ *
+ * Return: 0 on success, negative error code otherwise.
+ */
+static int loongson2_dmamux_set_route(struct loongson2_dmamux *mux, u32 request, u32 channel)
+{
+	const struct loongson2_dmamux_map *map;
+	u32 reg_val, route_val, high_val = 0;
+
+	if (request >= mux->config->num_maps)
+		return -EINVAL;
+
+	if (channel > mux->config->max_channel)
+		return -EINVAL;
+
+	map = &mux->config->maps[request];
+
+	switch (map->type) {
+	case LOONGSON_DMAMUX_PAIR:
+		route_val = channel / 2;
+		break;
+	case LOONGSON_DMAMUX_SINGLE:
+		route_val = channel;
+		if (map->high_reg)
+			high_val = (channel >> 2) & 0x1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	guard(spinlock_irqsave)(&mux->lock);
+
+	/* Write the primary register field */
+	reg_val = loongson2_dmamux_read(mux, map->reg);
+	reg_val &= ~map->mask;
+	reg_val |= field_prep(map->mask, route_val);
+	loongson2_dmamux_write(mux, map->reg, reg_val);
+
+	/* Write the extension register field if present */
+	if (map->high_reg) {
+		reg_val = loongson2_dmamux_read(mux, map->high_reg);
+		reg_val &= ~BIT(map->high_shift);
+		reg_val |= high_val << map->high_shift;
+		loongson2_dmamux_write(mux, map->high_reg, reg_val);
+	}
+
+	return 0;
+}
+
+/**
+ * loongson2_dmamux_route_allocate() - DMA router allocation callback
+ * @dma_spec: OF DMA specifier <request> <channel> <flags>
+ * @ofdma:    Router's of_dma structure; ofdma->dma_router points to our
+ *            struct dma_router embedded in struct loongson2_dmamux.
+ *
+ * Return: NULL as route_data (nothing to free), or an ERR_PTR on failure.
+ */
+static void *loongson2_dmamux_route_allocate(struct of_phandle_args *dma_spec,
+					     struct of_dma *ofdma)
+{
+	struct loongson2_dmamux *mux;
+	u32 request, channel, flags;
+	int ret;
+
+	if (!ofdma->dma_router)
+		return ERR_PTR(-EINVAL);
+
+	mux = container_of(ofdma->dma_router, struct loongson2_dmamux, dma_router);
+
+	if (dma_spec->args_count != 3)
+		return ERR_PTR(-EINVAL);
+
+	request = dma_spec->args[0];
+	channel = dma_spec->args[1];
+	flags = dma_spec->args[2];
+
+	if (request >= mux->config->num_maps ||
+	    channel > mux->config->max_channel)
+		return ERR_PTR(-EINVAL);
+
+	/* Program the SoC-specific route register */
+	ret = loongson2_dmamux_set_route(mux, request, channel);
+	if (ret)
+		return ERR_PTR(ret);
+
+	/* The of_node_put() will be done in of_dma_router_xlate function */
+	dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0);
+	if (!dma_spec->np)
+		return ERR_PTR(-ENODEV);
+
+	dma_spec->args[0] = channel;
+	dma_spec->args[1] = flags;
+	dma_spec->args_count = mux->config->parent_args_count;
+
+	return NULL;
+}
+
+/**
+ * loongson2_dmamux_route_free() - DMA router free callback
+ * @dev: Back pointer to the router's device
+ * @route_data: Router-specific data returned by route_allocate
+ *
+ * The driver does not allocate any per-route resources, so this callback
+ * is intentionally empty. It must still be provided because the OF DMA
+ * core calls it unconditionally on some error paths (e.g. -EPROBE_DEFER)
+ * without checking for a NULL pointer.
+ */
+static void loongson2_dmamux_route_free(struct device *dev, void *route_data)
+{
+}
+
+/**
+ * loongson2_dmamux_unregister_of_dma() - devm action to unregister the router
+ * @data: Pointer to struct loongson2_dmamux
+ *
+ * Called automatically by the device-managed resource framework both on
+ * probe failure and on driver removal, ensuring the OF DMA router is
+ * always unregistered.
+ */
+static void loongson2_dmamux_unregister_of_dma(void *data)
+{
+	struct loongson2_dmamux *mux = data;
+
+	of_dma_router_free(mux->dma_router.dev->of_node);
+}
+
+static int loongson2_dmamux_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct loongson2_dmamux *mux;
+	u32 dma_requests;
+	int ret;
+
+	mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+	if (!mux)
+		return -ENOMEM;
+
+	mux->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(mux->base))
+		return PTR_ERR(mux->base);
+
+	spin_lock_init(&mux->lock);
+	mux->config = &ls2k0300_dmamux_config;
+
+	ret = of_property_read_u32(dev->of_node, "dma-requests", &dma_requests);
+	if (ret)
+		return dev_err_probe(dev, ret, "missing dma-requests property\n");
+
+	if (dma_requests != mux->config->num_maps)
+		return dev_err_probe(dev, -EINVAL,
+				     "dma-requests=%u mismatches driver num_maps=%u",
+				     dma_requests, mux->config->num_maps);
+
+	mux->dma_router.dev = dev;
+	mux->dma_router.route_free = loongson2_dmamux_route_free;
+
+	platform_set_drvdata(pdev, mux);
+
+	ret = of_dma_router_register(dev->of_node, loongson2_dmamux_route_allocate,
+				     &mux->dma_router);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to register DMA router");
+
+	ret = devm_add_action_or_reset(dev, loongson2_dmamux_unregister_of_dma, mux);
+	if (ret)
+		return ret;
+
+	dev_info(dev,
+		 "Loongson2 DMA Mux Controller registered (max_channel=%u, requests=%u)",
+		 mux->config->max_channel, mux->config->num_maps);
+
+	return 0;
+}
+
+static const struct of_device_id loongson2_dmamux_of_match[] = {
+	{ .compatible = "loongson,ls2k0300-dmamux" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, loongson2_dmamux_of_match);
+
+static struct platform_driver loongson2_dmamux_driver = {
+	.driver = {
+		.name = "loongson2-dmamux",
+		.of_match_table = loongson2_dmamux_of_match,
+	},
+	.probe = loongson2_dmamux_probe,
+};
+module_platform_driver(loongson2_dmamux_driver);
+
+MODULE_DESCRIPTION("Loongson2 SoC DMA Mux Controller driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_LICENSE("GPL");
-- 
2.52.0


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

* Re: [PATCH v2 1/3] dt-bindings: dma: loongson: Add Loongson-2 DMA mux request IDs
  2026-10-09  9:19 ` [PATCH v2 1/3] dt-bindings: dma: loongson: Add Loongson-2 DMA mux request IDs Binbin Zhou
@ 2026-10-09 14:38   ` Conor Dooley
  0 siblings, 0 replies; 11+ messages in thread
From: Conor Dooley @ 2026-10-09 14:38 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine, Huacai Chen,
	Xuerui Wang, loongarch, devicetree, Keguang Zhang

[-- Attachment #1: Type: text/plain, Size: 3381 bytes --]

On Fri, Oct 09, 2026 at 05:19:32PM +0800, Binbin Zhou wrote:
> Add DT binding constants for the Loongson-2 DMA mux.
> 
> The header defines the peripheral request IDs used in the DMA specifier
> <request channel flags> for Loongson-2K0300, covering UART0-9, I2C0-3,
> SPI2/3, I2S, ADC and CAN0-3. It also documents the channel-pair semantics
> for peripherals that use paired DMA channels.
> 
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>

Squash this with the binding patch please.
If checkpatch told you to split this, binding headers are bindings and
such that warning is a false positive.

pw-bot: changes-requested

Thanks,
Conor.
> ---
>  MAINTAINERS                                   |  1 +
>  .../dt-bindings/dma/loongson,ls2k-dmamux.h    | 53 +++++++++++++++++++
>  2 files changed, 54 insertions(+)
>  create mode 100644 include/dt-bindings/dma/loongson,ls2k-dmamux.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 42f5165019ee..172dde21275f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15354,6 +15354,7 @@ F:	Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
>  F:	Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
>  F:	drivers/dma/loongson/loongson2-apb-cmc-dma.c
>  F:	drivers/dma/loongson/loongson2-apb-dma.c
> +F:	include/dt-bindings/dma/loongson,ls2k-dmamux.h
>  
>  LOONGSON LS2X I2C DRIVER
>  M:	Binbin Zhou <zhoubinbin@loongson.cn>
> diff --git a/include/dt-bindings/dma/loongson,ls2k-dmamux.h b/include/dt-bindings/dma/loongson,ls2k-dmamux.h
> new file mode 100644
> index 000000000000..1e2907ab069d
> --- /dev/null
> +++ b/include/dt-bindings/dma/loongson,ls2k-dmamux.h
> @@ -0,0 +1,53 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +/*
> + * Loongson-2 SoC DMA Mux dt-bindings
> + *
> + * DMA specifier format:
> + *   dmas = <&dma_mux <request> <channel> <flags>>;
> + *
> + * The <channel> cell selects a physical CMC DMA channel (0-7)
> + * For peripherals using channel pairs (UART, I2C, SPI2/3, I2S),
> + * the channel number selects the pair: 0/1 -> pair 0, 2/3 -> pair 1,
> + * 4/5 -> pair 2, 6/7 -> pair 3.
> + *
> + * Copyright (C) 2026 Loongson Technology Corporation Limited
> + */
> +
> +#ifndef _DT_BINDINGS_DMA_LOONGSON_LS2K_DMAMUX_H
> +#define _DT_BINDINGS_DMA_LOONGSON_LS2K_DMAMUX_H
> +
> +/* UART controllers */
> +#define LS2K0300_DMA_UART0		0
> +#define LS2K0300_DMA_UART1		1
> +#define LS2K0300_DMA_UART2		2
> +#define LS2K0300_DMA_UART3		3
> +#define LS2K0300_DMA_UART4		4
> +#define LS2K0300_DMA_UART5		5
> +#define LS2K0300_DMA_UART6		6
> +#define LS2K0300_DMA_UART7		7
> +#define LS2K0300_DMA_UART8		8
> +#define LS2K0300_DMA_UART9		9
> +
> +/* I2C controllers */
> +#define LS2K0300_DMA_I2C0		10
> +#define LS2K0300_DMA_I2C1		11
> +#define LS2K0300_DMA_I2C2		12
> +#define LS2K0300_DMA_I2C3		13
> +
> +/* SPI controllers (IO mode) */
> +#define LS2K0300_DMA_SPI2		14
> +#define LS2K0300_DMA_SPI3		15
> +
> +/* I2S controller */
> +#define LS2K0300_DMA_I2S		16
> +
> +/* ADC controller */
> +#define LS2K0300_DMA_ADC		17
> +
> +/* CAN-FD controllers */
> +#define LS2K0300_DMA_CAN0		18
> +#define LS2K0300_DMA_CAN1		19
> +#define LS2K0300_DMA_CAN2		20
> +#define LS2K0300_DMA_CAN3		21
> +
> +#endif /* _DT_BINDINGS_DMA_LOONGSON_LS2K_DMAMUX_H */
> -- 
> 2.52.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

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

* Re: [PATCH v2 2/3] dt-bindings: dma: loongson: Add Loongson-2K0300 DMA mux support
  2026-10-09  9:19 ` [PATCH v2 2/3] dt-bindings: dma: loongson: Add Loongson-2K0300 DMA mux support Binbin Zhou
@ 2026-10-09 14:43   ` Conor Dooley
  0 siblings, 0 replies; 11+ messages in thread
From: Conor Dooley @ 2026-10-09 14:43 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine, Huacai Chen,
	Xuerui Wang, loongarch, devicetree, Keguang Zhang

[-- Attachment #1: Type: text/plain, Size: 75 bytes --]

Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

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

* Re: [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver
  2026-10-09  9:19 ` [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver Binbin Zhou
@ 2026-10-15 17:03   ` Frank Li
  2026-10-21  9:34     ` Binbin Zhou
  2026-10-25  8:19   ` Huacai Chen
  1 sibling, 1 reply; 11+ messages in thread
From: Frank Li @ 2026-10-15 17:03 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine, Huacai Chen,
	Xuerui Wang, loongarch, devicetree, Keguang Zhang

On Fri, Oct 09, 2026 at 05:19:34PM +0800, Binbin Zhou wrote:
> The Loongson-2K0300 DMA mux is not a standalone DMA controller. It is
> implemented as a set of routing fields in the chip global configuration
> registers CHIP_CTRL13/14/15, which select which physical channel of the
> on-chip CMC DMA controller handles each peripheral DMA request.
>
> Add a driver that exposes these routing fields as an OF DMA router. The
> router's allocation callback validates the <request> <channel> <flags>
> specifier, programs the corresponding CHIP_CTRL register field, and then
> rewrites the specifier to <channel> <flags> so that the parent CMC DMA
> controller's xlate is used.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
>  MAINTAINERS                                 |   1 +
>  drivers/dma/loongson/Makefile               |   1 +
>  drivers/dma/loongson/loongson2-apb-dmamux.c | 398 ++++++++++++++++++++
>  3 files changed, 400 insertions(+)
>  create mode 100644 drivers/dma/loongson/loongson2-apb-dmamux.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 78622b255f88..0b0c6ea41c64 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15355,6 +15355,7 @@ F:	Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml
>  F:	Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
>  F:	drivers/dma/loongson/loongson2-apb-cmc-dma.c
>  F:	drivers/dma/loongson/loongson2-apb-dma.c
> +F:	drivers/dma/loongson/loongson2-apb-dmamux.c
>  F:	include/dt-bindings/dma/loongson,ls2k-dmamux.h
>
>  LOONGSON LS2X I2C DRIVER
> diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> index 48c19781e729..271fd38fa8f2 100644
> --- a/drivers/dma/loongson/Makefile
> +++ b/drivers/dma/loongson/Makefile
> @@ -2,3 +2,4 @@
>  obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
>  obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
>  obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-dmamux.o
> diff --git a/drivers/dma/loongson/loongson2-apb-dmamux.c b/drivers/dma/loongson/loongson2-apb-dmamux.c
> new file mode 100644
> index 000000000000..01bd3ce4ca25
> --- /dev/null
> +++ b/drivers/dma/loongson/loongson2-apb-dmamux.c
> @@ -0,0 +1,398 @@
...
> +static int loongson2_dmamux_set_route(struct loongson2_dmamux *mux, u32 request, u32 channel)
> +{
> +	const struct loongson2_dmamux_map *map;
> +	u32 reg_val, route_val, high_val = 0;
> +
> +	if (request >= mux->config->num_maps)
> +		return -EINVAL;
> +
> +	if (channel > mux->config->max_channel)
> +		return -EINVAL;
> +
> +	map = &mux->config->maps[request];
> +
> +	switch (map->type) {
> +	case LOONGSON_DMAMUX_PAIR:
> +		route_val = channel / 2;
> +		break;
> +	case LOONGSON_DMAMUX_SINGLE:
> +		route_val = channel;
> +		if (map->high_reg)
> +			high_val = (channel >> 2) & 0x1;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	guard(spinlock_irqsave)(&mux->lock);
> +
> +	/* Write the primary register field */
> +	reg_val = loongson2_dmamux_read(mux, map->reg);
> +	reg_val &= ~map->mask;
> +	reg_val |= field_prep(map->mask, route_val);
> +	loongson2_dmamux_write(mux, map->reg, reg_val);
> +
> +	/* Write the extension register field if present */
> +	if (map->high_reg) {
> +		reg_val = loongson2_dmamux_read(mux, map->high_reg);
> +		reg_val &= ~BIT(map->high_shift);
> +		reg_val |= high_val << map->high_shift;

assign_bit(map->high_shift, &reg_val, high_val);

other looks good

Frank
> +		loongson2_dmamux_write(mux, map->high_reg, reg_val);
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * loongson2_dmamux_route_allocate() - DMA router allocation callback
> + * @dma_spec: OF DMA specifier <request> <channel> <flags>
> + * @ofdma:    Router's of_dma structure; ofdma->dma_router points to our
> + *            struct dma_router embedded in struct loongson2_dmamux.
> + *
> + * Return: NULL as route_data (nothing to free), or an ERR_PTR on failure.
> + */
> +static void *loongson2_dmamux_route_allocate(struct of_phandle_args *dma_spec,
> +					     struct of_dma *ofdma)
> +{
> +	struct loongson2_dmamux *mux;
> +	u32 request, channel, flags;
> +	int ret;
> +
> +	if (!ofdma->dma_router)
> +		return ERR_PTR(-EINVAL);
> +
> +	mux = container_of(ofdma->dma_router, struct loongson2_dmamux, dma_router);
> +
> +	if (dma_spec->args_count != 3)
> +		return ERR_PTR(-EINVAL);
> +
> +	request = dma_spec->args[0];
> +	channel = dma_spec->args[1];
> +	flags = dma_spec->args[2];
> +
> +	if (request >= mux->config->num_maps ||
> +	    channel > mux->config->max_channel)
> +		return ERR_PTR(-EINVAL);
> +
> +	/* Program the SoC-specific route register */
> +	ret = loongson2_dmamux_set_route(mux, request, channel);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	/* The of_node_put() will be done in of_dma_router_xlate function */
> +	dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0);
> +	if (!dma_spec->np)
> +		return ERR_PTR(-ENODEV);
> +
> +	dma_spec->args[0] = channel;
> +	dma_spec->args[1] = flags;
> +	dma_spec->args_count = mux->config->parent_args_count;
> +
> +	return NULL;
> +}
> +
> +/**
> + * loongson2_dmamux_route_free() - DMA router free callback
> + * @dev: Back pointer to the router's device
> + * @route_data: Router-specific data returned by route_allocate
> + *
> + * The driver does not allocate any per-route resources, so this callback
> + * is intentionally empty. It must still be provided because the OF DMA
> + * core calls it unconditionally on some error paths (e.g. -EPROBE_DEFER)
> + * without checking for a NULL pointer.
> + */
> +static void loongson2_dmamux_route_free(struct device *dev, void *route_data)
> +{
> +}
> +
> +/**
> + * loongson2_dmamux_unregister_of_dma() - devm action to unregister the router
> + * @data: Pointer to struct loongson2_dmamux
> + *
> + * Called automatically by the device-managed resource framework both on
> + * probe failure and on driver removal, ensuring the OF DMA router is
> + * always unregistered.
> + */
> +static void loongson2_dmamux_unregister_of_dma(void *data)
> +{
> +	struct loongson2_dmamux *mux = data;
> +
> +	of_dma_router_free(mux->dma_router.dev->of_node);
> +}
> +
> +static int loongson2_dmamux_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct loongson2_dmamux *mux;
> +	u32 dma_requests;
> +	int ret;
> +
> +	mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
> +	if (!mux)
> +		return -ENOMEM;
> +
> +	mux->base = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(mux->base))
> +		return PTR_ERR(mux->base);
> +
> +	spin_lock_init(&mux->lock);
> +	mux->config = &ls2k0300_dmamux_config;
> +
> +	ret = of_property_read_u32(dev->of_node, "dma-requests", &dma_requests);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "missing dma-requests property\n");
> +
> +	if (dma_requests != mux->config->num_maps)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "dma-requests=%u mismatches driver num_maps=%u",
> +				     dma_requests, mux->config->num_maps);
> +
> +	mux->dma_router.dev = dev;
> +	mux->dma_router.route_free = loongson2_dmamux_route_free;
> +
> +	platform_set_drvdata(pdev, mux);
> +
> +	ret = of_dma_router_register(dev->of_node, loongson2_dmamux_route_allocate,
> +				     &mux->dma_router);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Failed to register DMA router");
> +
> +	ret = devm_add_action_or_reset(dev, loongson2_dmamux_unregister_of_dma, mux);
> +	if (ret)
> +		return ret;
> +
> +	dev_info(dev,
> +		 "Loongson2 DMA Mux Controller registered (max_channel=%u, requests=%u)",
> +		 mux->config->max_channel, mux->config->num_maps);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id loongson2_dmamux_of_match[] = {
> +	{ .compatible = "loongson,ls2k0300-dmamux" },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, loongson2_dmamux_of_match);
> +
> +static struct platform_driver loongson2_dmamux_driver = {
> +	.driver = {
> +		.name = "loongson2-dmamux",
> +		.of_match_table = loongson2_dmamux_of_match,
> +	},
> +	.probe = loongson2_dmamux_probe,
> +};
> +module_platform_driver(loongson2_dmamux_driver);
> +
> +MODULE_DESCRIPTION("Loongson2 SoC DMA Mux Controller driver");
> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> +MODULE_LICENSE("GPL");
> --
> 2.52.0
>

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

* Re: [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver
  2026-10-15 17:03   ` Frank Li
@ 2026-10-21  9:34     ` Binbin Zhou
  2026-10-21 15:05       ` Frank Li
  0 siblings, 1 reply; 11+ messages in thread
From: Binbin Zhou @ 2026-10-21  9:34 UTC (permalink / raw)
  To: Frank Li
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine, Huacai Chen,
	Xuerui Wang, loongarch, devicetree, Keguang Zhang

Hi Frank:

On Fri, Oct 16, 2026 at 1:03 AM Frank Li <Frank.li@oss.nxp.com> wrote:
>
> On Fri, Oct 09, 2026 at 05:19:34PM +0800, Binbin Zhou wrote:
> > The Loongson-2K0300 DMA mux is not a standalone DMA controller. It is
> > implemented as a set of routing fields in the chip global configuration
> > registers CHIP_CTRL13/14/15, which select which physical channel of the
> > on-chip CMC DMA controller handles each peripheral DMA request.
> >
> > Add a driver that exposes these routing fields as an OF DMA router. The
> > router's allocation callback validates the <request> <channel> <flags>
> > specifier, programs the corresponding CHIP_CTRL register field, and then
> > rewrites the specifier to <channel> <flags> so that the parent CMC DMA
> > controller's xlate is used.
> >
> > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > ---
> >  MAINTAINERS                                 |   1 +
> >  drivers/dma/loongson/Makefile               |   1 +
> >  drivers/dma/loongson/loongson2-apb-dmamux.c | 398 ++++++++++++++++++++
> >  3 files changed, 400 insertions(+)
> >  create mode 100644 drivers/dma/loongson/loongson2-apb-dmamux.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 78622b255f88..0b0c6ea41c64 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -15355,6 +15355,7 @@ F:    Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml
> >  F:   Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> >  F:   drivers/dma/loongson/loongson2-apb-cmc-dma.c
> >  F:   drivers/dma/loongson/loongson2-apb-dma.c
> > +F:   drivers/dma/loongson/loongson2-apb-dmamux.c
> >  F:   include/dt-bindings/dma/loongson,ls2k-dmamux.h
> >
> >  LOONGSON LS2X I2C DRIVER
> > diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> > index 48c19781e729..271fd38fa8f2 100644
> > --- a/drivers/dma/loongson/Makefile
> > +++ b/drivers/dma/loongson/Makefile
> > @@ -2,3 +2,4 @@
> >  obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> >  obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> >  obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> > +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-dmamux.o
> > diff --git a/drivers/dma/loongson/loongson2-apb-dmamux.c b/drivers/dma/loongson/loongson2-apb-dmamux.c
> > new file mode 100644
> > index 000000000000..01bd3ce4ca25
> > --- /dev/null
> > +++ b/drivers/dma/loongson/loongson2-apb-dmamux.c
> > @@ -0,0 +1,398 @@
> ...
> > +static int loongson2_dmamux_set_route(struct loongson2_dmamux *mux, u32 request, u32 channel)
> > +{
> > +     const struct loongson2_dmamux_map *map;
> > +     u32 reg_val, route_val, high_val = 0;
> > +
> > +     if (request >= mux->config->num_maps)
> > +             return -EINVAL;
> > +
> > +     if (channel > mux->config->max_channel)
> > +             return -EINVAL;
> > +
> > +     map = &mux->config->maps[request];
> > +
> > +     switch (map->type) {
> > +     case LOONGSON_DMAMUX_PAIR:
> > +             route_val = channel / 2;
> > +             break;
> > +     case LOONGSON_DMAMUX_SINGLE:
> > +             route_val = channel;
> > +             if (map->high_reg)
> > +                     high_val = (channel >> 2) & 0x1;
> > +             break;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +
> > +     guard(spinlock_irqsave)(&mux->lock);
> > +
> > +     /* Write the primary register field */
> > +     reg_val = loongson2_dmamux_read(mux, map->reg);
> > +     reg_val &= ~map->mask;
> > +     reg_val |= field_prep(map->mask, route_val);
> > +     loongson2_dmamux_write(mux, map->reg, reg_val);
> > +
> > +     /* Write the extension register field if present */
> > +     if (map->high_reg) {
> > +             reg_val = loongson2_dmamux_read(mux, map->high_reg);
> > +             reg_val &= ~BIT(map->high_shift);
> > +             reg_val |= high_val << map->high_shift;
>
> assign_bit(map->high_shift, &reg_val, high_val);

I think `assign_bit()` here is not quite suitable, reg_val is a u32
(returned by loongson2_dmamux_read()), but assign_bit() expects a
`volatile unsigned long *`.

How about adding a helper function:

static void loongson2_dmamux_assign_bits(struct loongson2_dmamux *priv,
                                         unsigned int reg, u32 mask, u32 val)
{
        u32 reg_val;

        reg_val = loongson2_dmamux_read(priv, reg);
        reg_val &= ~mask;
        reg_val |= val & mask;
        loongson2_dmamux_write(priv, reg, reg_val);
}

Then we can call this function:

/* Primary register field */
loongson2_dmamux_assign_bits(priv, map->reg, map->mask,
                             field_prep(map->mask, route_val));

/* Extension register bit if present */
if (map->high_reg)
        loongson2_dmamux_assign_bits(priv, map->high_reg,
                                     BIT(map->high_shift),
                                     high_val ? BIT(map->high_shift) : 0);

>
> other looks good
>
> Frank
> > +             loongson2_dmamux_write(mux, map->high_reg, reg_val);
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +/**
> > + * loongson2_dmamux_route_allocate() - DMA router allocation callback
> > + * @dma_spec: OF DMA specifier <request> <channel> <flags>
> > + * @ofdma:    Router's of_dma structure; ofdma->dma_router points to our
> > + *            struct dma_router embedded in struct loongson2_dmamux.
> > + *
> > + * Return: NULL as route_data (nothing to free), or an ERR_PTR on failure.
> > + */
> > +static void *loongson2_dmamux_route_allocate(struct of_phandle_args *dma_spec,
> > +                                          struct of_dma *ofdma)
> > +{
> > +     struct loongson2_dmamux *mux;
> > +     u32 request, channel, flags;
> > +     int ret;
> > +
> > +     if (!ofdma->dma_router)
> > +             return ERR_PTR(-EINVAL);
> > +
> > +     mux = container_of(ofdma->dma_router, struct loongson2_dmamux, dma_router);
> > +
> > +     if (dma_spec->args_count != 3)
> > +             return ERR_PTR(-EINVAL);
> > +
> > +     request = dma_spec->args[0];
> > +     channel = dma_spec->args[1];
> > +     flags = dma_spec->args[2];
> > +
> > +     if (request >= mux->config->num_maps ||
> > +         channel > mux->config->max_channel)
> > +             return ERR_PTR(-EINVAL);
> > +
> > +     /* Program the SoC-specific route register */
> > +     ret = loongson2_dmamux_set_route(mux, request, channel);
> > +     if (ret)
> > +             return ERR_PTR(ret);
> > +
> > +     /* The of_node_put() will be done in of_dma_router_xlate function */
> > +     dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0);
> > +     if (!dma_spec->np)
> > +             return ERR_PTR(-ENODEV);
> > +
> > +     dma_spec->args[0] = channel;
> > +     dma_spec->args[1] = flags;
> > +     dma_spec->args_count = mux->config->parent_args_count;
> > +
> > +     return NULL;
> > +}
> > +
> > +/**
> > + * loongson2_dmamux_route_free() - DMA router free callback
> > + * @dev: Back pointer to the router's device
> > + * @route_data: Router-specific data returned by route_allocate
> > + *
> > + * The driver does not allocate any per-route resources, so this callback
> > + * is intentionally empty. It must still be provided because the OF DMA
> > + * core calls it unconditionally on some error paths (e.g. -EPROBE_DEFER)
> > + * without checking for a NULL pointer.
> > + */
> > +static void loongson2_dmamux_route_free(struct device *dev, void *route_data)
> > +{
> > +}
> > +
> > +/**
> > + * loongson2_dmamux_unregister_of_dma() - devm action to unregister the router
> > + * @data: Pointer to struct loongson2_dmamux
> > + *
> > + * Called automatically by the device-managed resource framework both on
> > + * probe failure and on driver removal, ensuring the OF DMA router is
> > + * always unregistered.
> > + */
> > +static void loongson2_dmamux_unregister_of_dma(void *data)
> > +{
> > +     struct loongson2_dmamux *mux = data;
> > +
> > +     of_dma_router_free(mux->dma_router.dev->of_node);
> > +}
> > +
> > +static int loongson2_dmamux_probe(struct platform_device *pdev)
> > +{
> > +     struct device *dev = &pdev->dev;
> > +     struct loongson2_dmamux *mux;
> > +     u32 dma_requests;
> > +     int ret;
> > +
> > +     mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
> > +     if (!mux)
> > +             return -ENOMEM;
> > +
> > +     mux->base = devm_platform_ioremap_resource(pdev, 0);
> > +     if (IS_ERR(mux->base))
> > +             return PTR_ERR(mux->base);
> > +
> > +     spin_lock_init(&mux->lock);
> > +     mux->config = &ls2k0300_dmamux_config;
> > +
> > +     ret = of_property_read_u32(dev->of_node, "dma-requests", &dma_requests);
> > +     if (ret)
> > +             return dev_err_probe(dev, ret, "missing dma-requests property\n");
> > +
> > +     if (dma_requests != mux->config->num_maps)
> > +             return dev_err_probe(dev, -EINVAL,
> > +                                  "dma-requests=%u mismatches driver num_maps=%u",
> > +                                  dma_requests, mux->config->num_maps);
> > +
> > +     mux->dma_router.dev = dev;
> > +     mux->dma_router.route_free = loongson2_dmamux_route_free;
> > +
> > +     platform_set_drvdata(pdev, mux);
> > +
> > +     ret = of_dma_router_register(dev->of_node, loongson2_dmamux_route_allocate,
> > +                                  &mux->dma_router);
> > +     if (ret)
> > +             return dev_err_probe(dev, ret,
> > +                                  "Failed to register DMA router");
> > +
> > +     ret = devm_add_action_or_reset(dev, loongson2_dmamux_unregister_of_dma, mux);
> > +     if (ret)
> > +             return ret;
> > +
> > +     dev_info(dev,
> > +              "Loongson2 DMA Mux Controller registered (max_channel=%u, requests=%u)",
> > +              mux->config->max_channel, mux->config->num_maps);
> > +
> > +     return 0;
> > +}
> > +
> > +static const struct of_device_id loongson2_dmamux_of_match[] = {
> > +     { .compatible = "loongson,ls2k0300-dmamux" },
> > +     { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, loongson2_dmamux_of_match);
> > +
> > +static struct platform_driver loongson2_dmamux_driver = {
> > +     .driver = {
> > +             .name = "loongson2-dmamux",
> > +             .of_match_table = loongson2_dmamux_of_match,
> > +     },
> > +     .probe = loongson2_dmamux_probe,
> > +};
> > +module_platform_driver(loongson2_dmamux_driver);
> > +
> > +MODULE_DESCRIPTION("Loongson2 SoC DMA Mux Controller driver");
> > +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> > +MODULE_LICENSE("GPL");
> > --
> > 2.52.0
> >

--
Thanks.
Binbin

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

* Re: [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver
  2026-10-21  9:34     ` Binbin Zhou
@ 2026-10-21 15:05       ` Frank Li
  0 siblings, 0 replies; 11+ messages in thread
From: Frank Li @ 2026-10-21 15:05 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine, Huacai Chen,
	Xuerui Wang, loongarch, devicetree, Keguang Zhang

On Wed, Oct 21, 2026 at 05:34:29PM +0800, Binbin Zhou wrote:
> Hi Frank:
>
> On Fri, Oct 16, 2026 at 1:03 AM Frank Li <Frank.li@oss.nxp.com> wrote:
> >
> > On Fri, Oct 09, 2026 at 05:19:34PM +0800, Binbin Zhou wrote:
> > > The Loongson-2K0300 DMA mux is not a standalone DMA controller. It is
> > > implemented as a set of routing fields in the chip global configuration
> > > registers CHIP_CTRL13/14/15, which select which physical channel of the
> > > on-chip CMC DMA controller handles each peripheral DMA request.
> > >
> > > Add a driver that exposes these routing fields as an OF DMA router. The
> > > router's allocation callback validates the <request> <channel> <flags>
> > > specifier, programs the corresponding CHIP_CTRL register field, and then
> > > rewrites the specifier to <channel> <flags> so that the parent CMC DMA
> > > controller's xlate is used.
> > >
> > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > > ---
> > >  MAINTAINERS                                 |   1 +
> > >  drivers/dma/loongson/Makefile               |   1 +
> > >  drivers/dma/loongson/loongson2-apb-dmamux.c | 398 ++++++++++++++++++++
> > >  3 files changed, 400 insertions(+)
> > >  create mode 100644 drivers/dma/loongson/loongson2-apb-dmamux.c
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 78622b255f88..0b0c6ea41c64 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -15355,6 +15355,7 @@ F:    Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml
> > >  F:   Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> > >  F:   drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > >  F:   drivers/dma/loongson/loongson2-apb-dma.c
> > > +F:   drivers/dma/loongson/loongson2-apb-dmamux.c
> > >  F:   include/dt-bindings/dma/loongson,ls2k-dmamux.h
> > >
> > >  LOONGSON LS2X I2C DRIVER
> > > diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> > > index 48c19781e729..271fd38fa8f2 100644
> > > --- a/drivers/dma/loongson/Makefile
> > > +++ b/drivers/dma/loongson/Makefile
> > > @@ -2,3 +2,4 @@
> > >  obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > >  obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> > >  obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> > > +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-dmamux.o
> > > diff --git a/drivers/dma/loongson/loongson2-apb-dmamux.c b/drivers/dma/loongson/loongson2-apb-dmamux.c
> > > new file mode 100644
> > > index 000000000000..01bd3ce4ca25
> > > --- /dev/null
> > > +++ b/drivers/dma/loongson/loongson2-apb-dmamux.c
> > > @@ -0,0 +1,398 @@
> > ...
> > > +static int loongson2_dmamux_set_route(struct loongson2_dmamux *mux, u32 request, u32 channel)
> > > +{
> > > +     const struct loongson2_dmamux_map *map;
> > > +     u32 reg_val, route_val, high_val = 0;
> > > +
> > > +     if (request >= mux->config->num_maps)
> > > +             return -EINVAL;
> > > +
> > > +     if (channel > mux->config->max_channel)
> > > +             return -EINVAL;
> > > +
> > > +     map = &mux->config->maps[request];
> > > +
> > > +     switch (map->type) {
> > > +     case LOONGSON_DMAMUX_PAIR:
> > > +             route_val = channel / 2;
> > > +             break;
> > > +     case LOONGSON_DMAMUX_SINGLE:
> > > +             route_val = channel;
> > > +             if (map->high_reg)
> > > +                     high_val = (channel >> 2) & 0x1;
> > > +             break;
> > > +     default:
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     guard(spinlock_irqsave)(&mux->lock);
> > > +
> > > +     /* Write the primary register field */
> > > +     reg_val = loongson2_dmamux_read(mux, map->reg);
> > > +     reg_val &= ~map->mask;
> > > +     reg_val |= field_prep(map->mask, route_val);
> > > +     loongson2_dmamux_write(mux, map->reg, reg_val);
> > > +
> > > +     /* Write the extension register field if present */
> > > +     if (map->high_reg) {
> > > +             reg_val = loongson2_dmamux_read(mux, map->high_reg);
> > > +             reg_val &= ~BIT(map->high_shift);
> > > +             reg_val |= high_val << map->high_shift;
> >
> > assign_bit(map->high_shift, &reg_val, high_val);
>
> I think `assign_bit()` here is not quite suitable, reg_val is a u32
> (returned by loongson2_dmamux_read()), but assign_bit() expects a
> `volatile unsigned long *`.
>
> How about adding a helper function:
>
> static void loongson2_dmamux_assign_bits(struct loongson2_dmamux *priv,
>                                          unsigned int reg, u32 mask, u32 val)
> {
>         u32 reg_val;
>
>         reg_val = loongson2_dmamux_read(priv, reg);
>         reg_val &= ~mask;
>         reg_val |= val & mask;
>         loongson2_dmamux_write(priv, reg, reg_val);
> }
>

Okay, if only use once, old code should be fine.

Reviewed-by: Frank Li <Frank.Li@nxp.com>


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

* Re: [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver
  2026-10-09  9:19 ` [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver Binbin Zhou
  2026-10-15 17:03   ` Frank Li
@ 2026-10-25  8:19   ` Huacai Chen
  2026-10-25 13:49     ` Binbin Zhou
  1 sibling, 1 reply; 11+ messages in thread
From: Huacai Chen @ 2026-10-25  8:19 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine, Xuerui Wang,
	loongarch, devicetree, Keguang Zhang

Hi, Binbin,

On Fri, Oct 9, 2026 at 5:20 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
>
> The Loongson-2K0300 DMA mux is not a standalone DMA controller. It is
> implemented as a set of routing fields in the chip global configuration
> registers CHIP_CTRL13/14/15, which select which physical channel of the
> on-chip CMC DMA controller handles each peripheral DMA request.
>
> Add a driver that exposes these routing fields as an OF DMA router. The
> router's allocation callback validates the <request> <channel> <flags>
> specifier, programs the corresponding CHIP_CTRL register field, and then
> rewrites the specifier to <channel> <flags> so that the parent CMC DMA
> controller's xlate is used.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
>  MAINTAINERS                                 |   1 +
>  drivers/dma/loongson/Makefile               |   1 +
>  drivers/dma/loongson/loongson2-apb-dmamux.c | 398 ++++++++++++++++++++
>  3 files changed, 400 insertions(+)
>  create mode 100644 drivers/dma/loongson/loongson2-apb-dmamux.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 78622b255f88..0b0c6ea41c64 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15355,6 +15355,7 @@ F:      Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml
>  F:     Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
>  F:     drivers/dma/loongson/loongson2-apb-cmc-dma.c
>  F:     drivers/dma/loongson/loongson2-apb-dma.c
> +F:     drivers/dma/loongson/loongson2-apb-dmamux.c
>  F:     include/dt-bindings/dma/loongson,ls2k-dmamux.h
>
>  LOONGSON LS2X I2C DRIVER
> diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> index 48c19781e729..271fd38fa8f2 100644
> --- a/drivers/dma/loongson/Makefile
> +++ b/drivers/dma/loongson/Makefile
> @@ -2,3 +2,4 @@
>  obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
>  obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
>  obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-dmamux.o
Here we can use
obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
loongson2-apb-dmamux.o

Huacai

> diff --git a/drivers/dma/loongson/loongson2-apb-dmamux.c b/drivers/dma/loongson/loongson2-apb-dmamux.c
> new file mode 100644
> index 000000000000..01bd3ce4ca25
> --- /dev/null
> +++ b/drivers/dma/loongson/loongson2-apb-dmamux.c
> @@ -0,0 +1,398 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Loongson2 SoC APB DMA Mux Controller
> + *
> + * Copyright (C) 2026 Loongson Technology Corporation Limited
> + *
> + * Device tree bindings:
> + *
> + *   2K0300:
> + *     dma_mux: dma-mux@16000134 {
> + *             compatible = "loongson,ls2k0300-dmamux";
> + *             reg = <0x0 0x16000134 0x0 0xc>;
> + *             #dma-cells = <3>;
> + *             dma-masters = <&dma>;
> + *             dma-requests = <22>;
> + *     };
> + *
> + * DMA specifier: <request> <channel> <flags>
> + *   - request: peripheral request ID (LS2K0300_DMA_*)
> + *   - channel: target physical DMA channel
> + *   - flags:   channel configuration flags forwarded to the parent DMA
> + *              controller
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/cleanup.h>
> +#include <linux/device.h>
> +#include <linux/dmaengine.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_dma.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#include <dt-bindings/dma/loongson,ls2k-dmamux.h>
> +
> +/* Loongson-2K0300 registers definitions */
> +#define LS2K0300_CHIP_CTRL13           0x00
> +#define LS2K0300_CHIP_CTRL14           0x04
> +#define LS2K0300_CHIP_CTRL15           0x08
> +
> +/* CHIP_CTRL13 bitfields */
> +#define CTRL13_UART0_DMA_MAP           GENMASK(17, 16)
> +#define CTRL13_UART1_DMA_MAP           GENMASK(19, 18)
> +#define CTRL13_UART2_DMA_MAP           GENMASK(21, 20)
> +#define CTRL13_UART3_DMA_MAP           GENMASK(23, 22)
> +#define CTRL13_UART4_DMA_MAP           GENMASK(25, 24)
> +#define CTRL13_UART5_DMA_MAP           GENMASK(27, 26)
> +#define CTRL13_UART6_DMA_MAP           GENMASK(29, 28)
> +#define CTRL13_UART7_DMA_MAP           GENMASK(31, 30)
> +
> +/* CHIP_CTRL14 bitfields */
> +#define CTRL14_UART8_DMA_MAP           GENMASK(1, 0)
> +#define CTRL14_UART9_DMA_MAP           GENMASK(3, 2)
> +#define CTRL14_I2C0_DMA_MAP            GENMASK(5, 4)
> +#define CTRL14_I2C1_DMA_MAP            GENMASK(7, 6)
> +#define CTRL14_I2C2_DMA_MAP            GENMASK(9, 8)
> +#define CTRL14_I2C3_DMA_MAP            GENMASK(11, 10)
> +#define CTRL14_SPI2_DMA_MAP            GENMASK(13, 12)
> +#define CTRL14_SPI3_DMA_MAP            GENMASK(15, 14)
> +#define CTRL14_I2S_DMA_MAP             GENMASK(17, 16)
> +#define CTRL14_ADC_DMA_MAP             GENMASK(20, 18)
> +#define CTRL14_CAN0_DMA_MAP            GENMASK(23, 21)
> +#define CTRL14_CAN1_DMA_MAP            GENMASK(26, 24)
> +#define CTRL14_CAN2_DMA_MAP            GENMASK(29, 27)
> +#define CTRL14_CAN3_DMA_MAP            GENMASK(31, 30)
> +
> +/* CHIP_CTRL15 bitfields */
> +#define CTRL15_CAN3_DMA_MAP_HI         BIT(0)
> +
> +#define LS2K0300_DMA_MAX_CHANNEL       7
> +
> +/**
> + * enum loongson2_dmamux_type - Type of the mapping field
> + * @LOONGSON_DMAMUX_PAIR: 2-bit field, value selects a channel pair
> + *                        (0/1, 2/3, 4/5, 6/7).
> + * @LOONGSON_DMAMUX_SINGLE: 3-bit field, value selects a single channel
> + *                          or controller index directly.
> + */
> +enum loongson2_dmamux_type {
> +       LOONGSON_DMAMUX_PAIR,
> +       LOONGSON_DMAMUX_SINGLE,
> +};
> +
> +/**
> + * struct loongson2_dmamux_map - DMA request to register bitfield mapping
> + * @reg: Register offset from the controller base
> + * @mask: Bitfield mask within the register
> + * @type: Mapping type (pair or single)
> + * @high_reg: Optional extension register offset (0 if unused)
> + * @high_shift: Bit position in the extension register
> + */
> +struct loongson2_dmamux_map {
> +       u32 reg;
> +       u32 mask;
> +       enum loongson2_dmamux_type type;
> +       u32 high_reg;
> +       u8 high_shift;
> +};
> +
> +/**
> + * struct loongson2_dmamux_config - SoC-specific DMA mux configuration
> + * @maps: Pointer to the request mapping table
> + * @num_maps: Number of entries in @maps
> + * @max_channel: Highest valid channel/controller index
> + * @parent_args_count: Number of cells to write to dma_spec->args[]
> + *                     when forwarding to the parent xlate.
> + */
> +struct loongson2_dmamux_config {
> +       const struct loongson2_dmamux_map *maps;
> +       u32 num_maps;
> +       u32 max_channel;
> +       u32 parent_args_count;
> +};
> +
> +/**
> + * struct loongson2_dmamux - DMA mux controller state
> + * @base: I/O-mapped register base
> + * @lock: Protects register read-modify-write sequences
> + * @dma_router: Router structure passed to of_dma_router_register()
> + * @config: SoC-specific configuration
> + */
> +struct loongson2_dmamux {
> +       void __iomem *base;
> +       spinlock_t lock;        /* Protects register read-modify-write sequences */
> +       struct dma_router dma_router;
> +       const struct loongson2_dmamux_config *config;
> +};
> +
> +/* Loongson-2K0300 mapping table */
> +static const struct loongson2_dmamux_map ls2k0300_dmamux_maps[] = {
> +       [LS2K0300_DMA_UART0] = { LS2K0300_CHIP_CTRL13, CTRL13_UART0_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_UART1] = { LS2K0300_CHIP_CTRL13, CTRL13_UART1_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_UART2] = { LS2K0300_CHIP_CTRL13, CTRL13_UART2_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_UART3] = { LS2K0300_CHIP_CTRL13, CTRL13_UART3_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_UART4] = { LS2K0300_CHIP_CTRL13, CTRL13_UART4_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_UART5] = { LS2K0300_CHIP_CTRL13, CTRL13_UART5_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_UART6] = { LS2K0300_CHIP_CTRL13, CTRL13_UART6_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_UART7] = { LS2K0300_CHIP_CTRL13, CTRL13_UART7_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_UART8] = { LS2K0300_CHIP_CTRL14, CTRL14_UART8_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_UART9] = { LS2K0300_CHIP_CTRL14, CTRL14_UART9_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_I2C0]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C0_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_I2C1]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C1_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_I2C2]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C2_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_I2C3]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C3_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_SPI2]  = { LS2K0300_CHIP_CTRL14, CTRL14_SPI2_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_SPI3]  = { LS2K0300_CHIP_CTRL14, CTRL14_SPI3_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_I2S]   = { LS2K0300_CHIP_CTRL14, CTRL14_I2S_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> +                                0, 0 },
> +       [LS2K0300_DMA_ADC]   = { LS2K0300_CHIP_CTRL14, CTRL14_ADC_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
> +                                0, 0 },
> +       [LS2K0300_DMA_CAN0]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN0_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
> +                                0, 0 },
> +       [LS2K0300_DMA_CAN1]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN1_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
> +                                0, 0 },
> +       [LS2K0300_DMA_CAN2]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN2_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
> +                                0, 0 },
> +       [LS2K0300_DMA_CAN3]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN3_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
> +                                LS2K0300_CHIP_CTRL15, 0 },
> +};
> +
> +static const struct loongson2_dmamux_config ls2k0300_dmamux_config = {
> +       .maps                   = ls2k0300_dmamux_maps,
> +       .num_maps               = ARRAY_SIZE(ls2k0300_dmamux_maps),
> +       .max_channel            = LS2K0300_DMA_MAX_CHANNEL,
> +       .parent_args_count      = 2,
> +};
> +
> +static inline u32 loongson2_dmamux_read(struct loongson2_dmamux *mux, u32 reg)
> +{
> +       return readl(mux->base + reg);
> +}
> +
> +static inline void loongson2_dmamux_write(struct loongson2_dmamux *mux, u32 reg, u32 val)
> +{
> +       writel(val, mux->base + reg);
> +}
> +
> +/**
> + * loongson2_dmamux_set_route() - Program the DMA request route
> + * @mux:     DMA mux controller
> + * @request: Peripheral DMA request ID
> + * @channel: Target physical DMA channel or controller index
> + *
> + * Computes the register value according to the mapping type:
> + *   - PAIR   : channel / 2 selects the pair index
> + *   - SINGLE : channel is written directly
> + *
> + * Return: 0 on success, negative error code otherwise.
> + */
> +static int loongson2_dmamux_set_route(struct loongson2_dmamux *mux, u32 request, u32 channel)
> +{
> +       const struct loongson2_dmamux_map *map;
> +       u32 reg_val, route_val, high_val = 0;
> +
> +       if (request >= mux->config->num_maps)
> +               return -EINVAL;
> +
> +       if (channel > mux->config->max_channel)
> +               return -EINVAL;
> +
> +       map = &mux->config->maps[request];
> +
> +       switch (map->type) {
> +       case LOONGSON_DMAMUX_PAIR:
> +               route_val = channel / 2;
> +               break;
> +       case LOONGSON_DMAMUX_SINGLE:
> +               route_val = channel;
> +               if (map->high_reg)
> +                       high_val = (channel >> 2) & 0x1;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       guard(spinlock_irqsave)(&mux->lock);
> +
> +       /* Write the primary register field */
> +       reg_val = loongson2_dmamux_read(mux, map->reg);
> +       reg_val &= ~map->mask;
> +       reg_val |= field_prep(map->mask, route_val);
> +       loongson2_dmamux_write(mux, map->reg, reg_val);
> +
> +       /* Write the extension register field if present */
> +       if (map->high_reg) {
> +               reg_val = loongson2_dmamux_read(mux, map->high_reg);
> +               reg_val &= ~BIT(map->high_shift);
> +               reg_val |= high_val << map->high_shift;
> +               loongson2_dmamux_write(mux, map->high_reg, reg_val);
> +       }
> +
> +       return 0;
> +}
> +
> +/**
> + * loongson2_dmamux_route_allocate() - DMA router allocation callback
> + * @dma_spec: OF DMA specifier <request> <channel> <flags>
> + * @ofdma:    Router's of_dma structure; ofdma->dma_router points to our
> + *            struct dma_router embedded in struct loongson2_dmamux.
> + *
> + * Return: NULL as route_data (nothing to free), or an ERR_PTR on failure.
> + */
> +static void *loongson2_dmamux_route_allocate(struct of_phandle_args *dma_spec,
> +                                            struct of_dma *ofdma)
> +{
> +       struct loongson2_dmamux *mux;
> +       u32 request, channel, flags;
> +       int ret;
> +
> +       if (!ofdma->dma_router)
> +               return ERR_PTR(-EINVAL);
> +
> +       mux = container_of(ofdma->dma_router, struct loongson2_dmamux, dma_router);
> +
> +       if (dma_spec->args_count != 3)
> +               return ERR_PTR(-EINVAL);
> +
> +       request = dma_spec->args[0];
> +       channel = dma_spec->args[1];
> +       flags = dma_spec->args[2];
> +
> +       if (request >= mux->config->num_maps ||
> +           channel > mux->config->max_channel)
> +               return ERR_PTR(-EINVAL);
> +
> +       /* Program the SoC-specific route register */
> +       ret = loongson2_dmamux_set_route(mux, request, channel);
> +       if (ret)
> +               return ERR_PTR(ret);
> +
> +       /* The of_node_put() will be done in of_dma_router_xlate function */
> +       dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0);
> +       if (!dma_spec->np)
> +               return ERR_PTR(-ENODEV);
> +
> +       dma_spec->args[0] = channel;
> +       dma_spec->args[1] = flags;
> +       dma_spec->args_count = mux->config->parent_args_count;
> +
> +       return NULL;
> +}
> +
> +/**
> + * loongson2_dmamux_route_free() - DMA router free callback
> + * @dev: Back pointer to the router's device
> + * @route_data: Router-specific data returned by route_allocate
> + *
> + * The driver does not allocate any per-route resources, so this callback
> + * is intentionally empty. It must still be provided because the OF DMA
> + * core calls it unconditionally on some error paths (e.g. -EPROBE_DEFER)
> + * without checking for a NULL pointer.
> + */
> +static void loongson2_dmamux_route_free(struct device *dev, void *route_data)
> +{
> +}
> +
> +/**
> + * loongson2_dmamux_unregister_of_dma() - devm action to unregister the router
> + * @data: Pointer to struct loongson2_dmamux
> + *
> + * Called automatically by the device-managed resource framework both on
> + * probe failure and on driver removal, ensuring the OF DMA router is
> + * always unregistered.
> + */
> +static void loongson2_dmamux_unregister_of_dma(void *data)
> +{
> +       struct loongson2_dmamux *mux = data;
> +
> +       of_dma_router_free(mux->dma_router.dev->of_node);
> +}
> +
> +static int loongson2_dmamux_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct loongson2_dmamux *mux;
> +       u32 dma_requests;
> +       int ret;
> +
> +       mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
> +       if (!mux)
> +               return -ENOMEM;
> +
> +       mux->base = devm_platform_ioremap_resource(pdev, 0);
> +       if (IS_ERR(mux->base))
> +               return PTR_ERR(mux->base);
> +
> +       spin_lock_init(&mux->lock);
> +       mux->config = &ls2k0300_dmamux_config;
> +
> +       ret = of_property_read_u32(dev->of_node, "dma-requests", &dma_requests);
> +       if (ret)
> +               return dev_err_probe(dev, ret, "missing dma-requests property\n");
> +
> +       if (dma_requests != mux->config->num_maps)
> +               return dev_err_probe(dev, -EINVAL,
> +                                    "dma-requests=%u mismatches driver num_maps=%u",
> +                                    dma_requests, mux->config->num_maps);
> +
> +       mux->dma_router.dev = dev;
> +       mux->dma_router.route_free = loongson2_dmamux_route_free;
> +
> +       platform_set_drvdata(pdev, mux);
> +
> +       ret = of_dma_router_register(dev->of_node, loongson2_dmamux_route_allocate,
> +                                    &mux->dma_router);
> +       if (ret)
> +               return dev_err_probe(dev, ret,
> +                                    "Failed to register DMA router");
> +
> +       ret = devm_add_action_or_reset(dev, loongson2_dmamux_unregister_of_dma, mux);
> +       if (ret)
> +               return ret;
> +
> +       dev_info(dev,
> +                "Loongson2 DMA Mux Controller registered (max_channel=%u, requests=%u)",
> +                mux->config->max_channel, mux->config->num_maps);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id loongson2_dmamux_of_match[] = {
> +       { .compatible = "loongson,ls2k0300-dmamux" },
> +       { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, loongson2_dmamux_of_match);
> +
> +static struct platform_driver loongson2_dmamux_driver = {
> +       .driver = {
> +               .name = "loongson2-dmamux",
> +               .of_match_table = loongson2_dmamux_of_match,
> +       },
> +       .probe = loongson2_dmamux_probe,
> +};
> +module_platform_driver(loongson2_dmamux_driver);
> +
> +MODULE_DESCRIPTION("Loongson2 SoC DMA Mux Controller driver");
> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> +MODULE_LICENSE("GPL");
> --
> 2.52.0
>
>

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

* Re: [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver
  2026-10-25  8:19   ` Huacai Chen
@ 2026-10-25 13:49     ` Binbin Zhou
  0 siblings, 0 replies; 11+ messages in thread
From: Binbin Zhou @ 2026-10-25 13:49 UTC (permalink / raw)
  To: Huacai Chen
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Vinod Koul, Frank Li, dmaengine, Xuerui Wang,
	loongarch, devicetree, Keguang Zhang

On Sun, Oct 25, 2026 at 4:19 PM Huacai Chen <chenhuacai@kernel.org> wrote:
>
> Hi, Binbin,
>
> On Fri, Oct 9, 2026 at 5:20 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
> >
> > The Loongson-2K0300 DMA mux is not a standalone DMA controller. It is
> > implemented as a set of routing fields in the chip global configuration
> > registers CHIP_CTRL13/14/15, which select which physical channel of the
> > on-chip CMC DMA controller handles each peripheral DMA request.
> >
> > Add a driver that exposes these routing fields as an OF DMA router. The
> > router's allocation callback validates the <request> <channel> <flags>
> > specifier, programs the corresponding CHIP_CTRL register field, and then
> > rewrites the specifier to <channel> <flags> so that the parent CMC DMA
> > controller's xlate is used.
> >
> > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > ---
> >  MAINTAINERS                                 |   1 +
> >  drivers/dma/loongson/Makefile               |   1 +
> >  drivers/dma/loongson/loongson2-apb-dmamux.c | 398 ++++++++++++++++++++
> >  3 files changed, 400 insertions(+)
> >  create mode 100644 drivers/dma/loongson/loongson2-apb-dmamux.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 78622b255f88..0b0c6ea41c64 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -15355,6 +15355,7 @@ F:      Documentation/devicetree/bindings/dma/loongson,ls2k0300-dmamux.yaml
> >  F:     Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> >  F:     drivers/dma/loongson/loongson2-apb-cmc-dma.c
> >  F:     drivers/dma/loongson/loongson2-apb-dma.c
> > +F:     drivers/dma/loongson/loongson2-apb-dmamux.c
> >  F:     include/dt-bindings/dma/loongson,ls2k-dmamux.h
> >
> >  LOONGSON LS2X I2C DRIVER
> > diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> > index 48c19781e729..271fd38fa8f2 100644
> > --- a/drivers/dma/loongson/Makefile
> > +++ b/drivers/dma/loongson/Makefile
> > @@ -2,3 +2,4 @@
> >  obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> >  obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> >  obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> > +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-dmamux.o
> Here we can use
> obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> loongson2-apb-dmamux.o

OK. I will do it in the next version.
>
> Huacai
>
> > diff --git a/drivers/dma/loongson/loongson2-apb-dmamux.c b/drivers/dma/loongson/loongson2-apb-dmamux.c
> > new file mode 100644
> > index 000000000000..01bd3ce4ca25
> > --- /dev/null
> > +++ b/drivers/dma/loongson/loongson2-apb-dmamux.c
> > @@ -0,0 +1,398 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Loongson2 SoC APB DMA Mux Controller
> > + *
> > + * Copyright (C) 2026 Loongson Technology Corporation Limited
> > + *
> > + * Device tree bindings:
> > + *
> > + *   2K0300:
> > + *     dma_mux: dma-mux@16000134 {
> > + *             compatible = "loongson,ls2k0300-dmamux";
> > + *             reg = <0x0 0x16000134 0x0 0xc>;
> > + *             #dma-cells = <3>;
> > + *             dma-masters = <&dma>;
> > + *             dma-requests = <22>;
> > + *     };
> > + *
> > + * DMA specifier: <request> <channel> <flags>
> > + *   - request: peripheral request ID (LS2K0300_DMA_*)
> > + *   - channel: target physical DMA channel
> > + *   - flags:   channel configuration flags forwarded to the parent DMA
> > + *              controller
> > + */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/cleanup.h>
> > +#include <linux/device.h>
> > +#include <linux/dmaengine.h>
> > +#include <linux/err.h>
> > +#include <linux/io.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/of_dma.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/slab.h>
> > +#include <linux/spinlock.h>
> > +
> > +#include <dt-bindings/dma/loongson,ls2k-dmamux.h>
> > +
> > +/* Loongson-2K0300 registers definitions */
> > +#define LS2K0300_CHIP_CTRL13           0x00
> > +#define LS2K0300_CHIP_CTRL14           0x04
> > +#define LS2K0300_CHIP_CTRL15           0x08
> > +
> > +/* CHIP_CTRL13 bitfields */
> > +#define CTRL13_UART0_DMA_MAP           GENMASK(17, 16)
> > +#define CTRL13_UART1_DMA_MAP           GENMASK(19, 18)
> > +#define CTRL13_UART2_DMA_MAP           GENMASK(21, 20)
> > +#define CTRL13_UART3_DMA_MAP           GENMASK(23, 22)
> > +#define CTRL13_UART4_DMA_MAP           GENMASK(25, 24)
> > +#define CTRL13_UART5_DMA_MAP           GENMASK(27, 26)
> > +#define CTRL13_UART6_DMA_MAP           GENMASK(29, 28)
> > +#define CTRL13_UART7_DMA_MAP           GENMASK(31, 30)
> > +
> > +/* CHIP_CTRL14 bitfields */
> > +#define CTRL14_UART8_DMA_MAP           GENMASK(1, 0)
> > +#define CTRL14_UART9_DMA_MAP           GENMASK(3, 2)
> > +#define CTRL14_I2C0_DMA_MAP            GENMASK(5, 4)
> > +#define CTRL14_I2C1_DMA_MAP            GENMASK(7, 6)
> > +#define CTRL14_I2C2_DMA_MAP            GENMASK(9, 8)
> > +#define CTRL14_I2C3_DMA_MAP            GENMASK(11, 10)
> > +#define CTRL14_SPI2_DMA_MAP            GENMASK(13, 12)
> > +#define CTRL14_SPI3_DMA_MAP            GENMASK(15, 14)
> > +#define CTRL14_I2S_DMA_MAP             GENMASK(17, 16)
> > +#define CTRL14_ADC_DMA_MAP             GENMASK(20, 18)
> > +#define CTRL14_CAN0_DMA_MAP            GENMASK(23, 21)
> > +#define CTRL14_CAN1_DMA_MAP            GENMASK(26, 24)
> > +#define CTRL14_CAN2_DMA_MAP            GENMASK(29, 27)
> > +#define CTRL14_CAN3_DMA_MAP            GENMASK(31, 30)
> > +
> > +/* CHIP_CTRL15 bitfields */
> > +#define CTRL15_CAN3_DMA_MAP_HI         BIT(0)
> > +
> > +#define LS2K0300_DMA_MAX_CHANNEL       7
> > +
> > +/**
> > + * enum loongson2_dmamux_type - Type of the mapping field
> > + * @LOONGSON_DMAMUX_PAIR: 2-bit field, value selects a channel pair
> > + *                        (0/1, 2/3, 4/5, 6/7).
> > + * @LOONGSON_DMAMUX_SINGLE: 3-bit field, value selects a single channel
> > + *                          or controller index directly.
> > + */
> > +enum loongson2_dmamux_type {
> > +       LOONGSON_DMAMUX_PAIR,
> > +       LOONGSON_DMAMUX_SINGLE,
> > +};
> > +
> > +/**
> > + * struct loongson2_dmamux_map - DMA request to register bitfield mapping
> > + * @reg: Register offset from the controller base
> > + * @mask: Bitfield mask within the register
> > + * @type: Mapping type (pair or single)
> > + * @high_reg: Optional extension register offset (0 if unused)
> > + * @high_shift: Bit position in the extension register
> > + */
> > +struct loongson2_dmamux_map {
> > +       u32 reg;
> > +       u32 mask;
> > +       enum loongson2_dmamux_type type;
> > +       u32 high_reg;
> > +       u8 high_shift;
> > +};
> > +
> > +/**
> > + * struct loongson2_dmamux_config - SoC-specific DMA mux configuration
> > + * @maps: Pointer to the request mapping table
> > + * @num_maps: Number of entries in @maps
> > + * @max_channel: Highest valid channel/controller index
> > + * @parent_args_count: Number of cells to write to dma_spec->args[]
> > + *                     when forwarding to the parent xlate.
> > + */
> > +struct loongson2_dmamux_config {
> > +       const struct loongson2_dmamux_map *maps;
> > +       u32 num_maps;
> > +       u32 max_channel;
> > +       u32 parent_args_count;
> > +};
> > +
> > +/**
> > + * struct loongson2_dmamux - DMA mux controller state
> > + * @base: I/O-mapped register base
> > + * @lock: Protects register read-modify-write sequences
> > + * @dma_router: Router structure passed to of_dma_router_register()
> > + * @config: SoC-specific configuration
> > + */
> > +struct loongson2_dmamux {
> > +       void __iomem *base;
> > +       spinlock_t lock;        /* Protects register read-modify-write sequences */
> > +       struct dma_router dma_router;
> > +       const struct loongson2_dmamux_config *config;
> > +};
> > +
> > +/* Loongson-2K0300 mapping table */
> > +static const struct loongson2_dmamux_map ls2k0300_dmamux_maps[] = {
> > +       [LS2K0300_DMA_UART0] = { LS2K0300_CHIP_CTRL13, CTRL13_UART0_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_UART1] = { LS2K0300_CHIP_CTRL13, CTRL13_UART1_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_UART2] = { LS2K0300_CHIP_CTRL13, CTRL13_UART2_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_UART3] = { LS2K0300_CHIP_CTRL13, CTRL13_UART3_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_UART4] = { LS2K0300_CHIP_CTRL13, CTRL13_UART4_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_UART5] = { LS2K0300_CHIP_CTRL13, CTRL13_UART5_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_UART6] = { LS2K0300_CHIP_CTRL13, CTRL13_UART6_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_UART7] = { LS2K0300_CHIP_CTRL13, CTRL13_UART7_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_UART8] = { LS2K0300_CHIP_CTRL14, CTRL14_UART8_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_UART9] = { LS2K0300_CHIP_CTRL14, CTRL14_UART9_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_I2C0]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C0_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_I2C1]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C1_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_I2C2]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C2_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_I2C3]  = { LS2K0300_CHIP_CTRL14, CTRL14_I2C3_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_SPI2]  = { LS2K0300_CHIP_CTRL14, CTRL14_SPI2_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_SPI3]  = { LS2K0300_CHIP_CTRL14, CTRL14_SPI3_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_I2S]   = { LS2K0300_CHIP_CTRL14, CTRL14_I2S_DMA_MAP, LOONGSON_DMAMUX_PAIR,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_ADC]   = { LS2K0300_CHIP_CTRL14, CTRL14_ADC_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_CAN0]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN0_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_CAN1]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN1_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_CAN2]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN2_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
> > +                                0, 0 },
> > +       [LS2K0300_DMA_CAN3]  = { LS2K0300_CHIP_CTRL14, CTRL14_CAN3_DMA_MAP, LOONGSON_DMAMUX_SINGLE,
> > +                                LS2K0300_CHIP_CTRL15, 0 },
> > +};
> > +
> > +static const struct loongson2_dmamux_config ls2k0300_dmamux_config = {
> > +       .maps                   = ls2k0300_dmamux_maps,
> > +       .num_maps               = ARRAY_SIZE(ls2k0300_dmamux_maps),
> > +       .max_channel            = LS2K0300_DMA_MAX_CHANNEL,
> > +       .parent_args_count      = 2,
> > +};
> > +
> > +static inline u32 loongson2_dmamux_read(struct loongson2_dmamux *mux, u32 reg)
> > +{
> > +       return readl(mux->base + reg);
> > +}
> > +
> > +static inline void loongson2_dmamux_write(struct loongson2_dmamux *mux, u32 reg, u32 val)
> > +{
> > +       writel(val, mux->base + reg);
> > +}
> > +
> > +/**
> > + * loongson2_dmamux_set_route() - Program the DMA request route
> > + * @mux:     DMA mux controller
> > + * @request: Peripheral DMA request ID
> > + * @channel: Target physical DMA channel or controller index
> > + *
> > + * Computes the register value according to the mapping type:
> > + *   - PAIR   : channel / 2 selects the pair index
> > + *   - SINGLE : channel is written directly
> > + *
> > + * Return: 0 on success, negative error code otherwise.
> > + */
> > +static int loongson2_dmamux_set_route(struct loongson2_dmamux *mux, u32 request, u32 channel)
> > +{
> > +       const struct loongson2_dmamux_map *map;
> > +       u32 reg_val, route_val, high_val = 0;
> > +
> > +       if (request >= mux->config->num_maps)
> > +               return -EINVAL;
> > +
> > +       if (channel > mux->config->max_channel)
> > +               return -EINVAL;
> > +
> > +       map = &mux->config->maps[request];
> > +
> > +       switch (map->type) {
> > +       case LOONGSON_DMAMUX_PAIR:
> > +               route_val = channel / 2;
> > +               break;
> > +       case LOONGSON_DMAMUX_SINGLE:
> > +               route_val = channel;
> > +               if (map->high_reg)
> > +                       high_val = (channel >> 2) & 0x1;
> > +               break;
> > +       default:
> > +               return -EINVAL;
> > +       }
> > +
> > +       guard(spinlock_irqsave)(&mux->lock);
> > +
> > +       /* Write the primary register field */
> > +       reg_val = loongson2_dmamux_read(mux, map->reg);
> > +       reg_val &= ~map->mask;
> > +       reg_val |= field_prep(map->mask, route_val);
> > +       loongson2_dmamux_write(mux, map->reg, reg_val);
> > +
> > +       /* Write the extension register field if present */
> > +       if (map->high_reg) {
> > +               reg_val = loongson2_dmamux_read(mux, map->high_reg);
> > +               reg_val &= ~BIT(map->high_shift);
> > +               reg_val |= high_val << map->high_shift;
> > +               loongson2_dmamux_write(mux, map->high_reg, reg_val);
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +/**
> > + * loongson2_dmamux_route_allocate() - DMA router allocation callback
> > + * @dma_spec: OF DMA specifier <request> <channel> <flags>
> > + * @ofdma:    Router's of_dma structure; ofdma->dma_router points to our
> > + *            struct dma_router embedded in struct loongson2_dmamux.
> > + *
> > + * Return: NULL as route_data (nothing to free), or an ERR_PTR on failure.
> > + */
> > +static void *loongson2_dmamux_route_allocate(struct of_phandle_args *dma_spec,
> > +                                            struct of_dma *ofdma)
> > +{
> > +       struct loongson2_dmamux *mux;
> > +       u32 request, channel, flags;
> > +       int ret;
> > +
> > +       if (!ofdma->dma_router)
> > +               return ERR_PTR(-EINVAL);
> > +
> > +       mux = container_of(ofdma->dma_router, struct loongson2_dmamux, dma_router);
> > +
> > +       if (dma_spec->args_count != 3)
> > +               return ERR_PTR(-EINVAL);
> > +
> > +       request = dma_spec->args[0];
> > +       channel = dma_spec->args[1];
> > +       flags = dma_spec->args[2];
> > +
> > +       if (request >= mux->config->num_maps ||
> > +           channel > mux->config->max_channel)
> > +               return ERR_PTR(-EINVAL);
> > +
> > +       /* Program the SoC-specific route register */
> > +       ret = loongson2_dmamux_set_route(mux, request, channel);
> > +       if (ret)
> > +               return ERR_PTR(ret);
> > +
> > +       /* The of_node_put() will be done in of_dma_router_xlate function */
> > +       dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0);
> > +       if (!dma_spec->np)
> > +               return ERR_PTR(-ENODEV);
> > +
> > +       dma_spec->args[0] = channel;
> > +       dma_spec->args[1] = flags;
> > +       dma_spec->args_count = mux->config->parent_args_count;
> > +
> > +       return NULL;
> > +}
> > +
> > +/**
> > + * loongson2_dmamux_route_free() - DMA router free callback
> > + * @dev: Back pointer to the router's device
> > + * @route_data: Router-specific data returned by route_allocate
> > + *
> > + * The driver does not allocate any per-route resources, so this callback
> > + * is intentionally empty. It must still be provided because the OF DMA
> > + * core calls it unconditionally on some error paths (e.g. -EPROBE_DEFER)
> > + * without checking for a NULL pointer.
> > + */
> > +static void loongson2_dmamux_route_free(struct device *dev, void *route_data)
> > +{
> > +}
> > +
> > +/**
> > + * loongson2_dmamux_unregister_of_dma() - devm action to unregister the router
> > + * @data: Pointer to struct loongson2_dmamux
> > + *
> > + * Called automatically by the device-managed resource framework both on
> > + * probe failure and on driver removal, ensuring the OF DMA router is
> > + * always unregistered.
> > + */
> > +static void loongson2_dmamux_unregister_of_dma(void *data)
> > +{
> > +       struct loongson2_dmamux *mux = data;
> > +
> > +       of_dma_router_free(mux->dma_router.dev->of_node);
> > +}
> > +
> > +static int loongson2_dmamux_probe(struct platform_device *pdev)
> > +{
> > +       struct device *dev = &pdev->dev;
> > +       struct loongson2_dmamux *mux;
> > +       u32 dma_requests;
> > +       int ret;
> > +
> > +       mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
> > +       if (!mux)
> > +               return -ENOMEM;
> > +
> > +       mux->base = devm_platform_ioremap_resource(pdev, 0);
> > +       if (IS_ERR(mux->base))
> > +               return PTR_ERR(mux->base);
> > +
> > +       spin_lock_init(&mux->lock);
> > +       mux->config = &ls2k0300_dmamux_config;
> > +
> > +       ret = of_property_read_u32(dev->of_node, "dma-requests", &dma_requests);
> > +       if (ret)
> > +               return dev_err_probe(dev, ret, "missing dma-requests property\n");
> > +
> > +       if (dma_requests != mux->config->num_maps)
> > +               return dev_err_probe(dev, -EINVAL,
> > +                                    "dma-requests=%u mismatches driver num_maps=%u",
> > +                                    dma_requests, mux->config->num_maps);
> > +
> > +       mux->dma_router.dev = dev;
> > +       mux->dma_router.route_free = loongson2_dmamux_route_free;
> > +
> > +       platform_set_drvdata(pdev, mux);
> > +
> > +       ret = of_dma_router_register(dev->of_node, loongson2_dmamux_route_allocate,
> > +                                    &mux->dma_router);
> > +       if (ret)
> > +               return dev_err_probe(dev, ret,
> > +                                    "Failed to register DMA router");
> > +
> > +       ret = devm_add_action_or_reset(dev, loongson2_dmamux_unregister_of_dma, mux);
> > +       if (ret)
> > +               return ret;
> > +
> > +       dev_info(dev,
> > +                "Loongson2 DMA Mux Controller registered (max_channel=%u, requests=%u)",
> > +                mux->config->max_channel, mux->config->num_maps);
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct of_device_id loongson2_dmamux_of_match[] = {
> > +       { .compatible = "loongson,ls2k0300-dmamux" },
> > +       { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, loongson2_dmamux_of_match);
> > +
> > +static struct platform_driver loongson2_dmamux_driver = {
> > +       .driver = {
> > +               .name = "loongson2-dmamux",
> > +               .of_match_table = loongson2_dmamux_of_match,
> > +       },
> > +       .probe = loongson2_dmamux_probe,
> > +};
> > +module_platform_driver(loongson2_dmamux_driver);
> > +
> > +MODULE_DESCRIPTION("Loongson2 SoC DMA Mux Controller driver");
> > +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> > +MODULE_LICENSE("GPL");
> > --
> > 2.52.0
> >
> >

-- 
Thanks.
Binbin

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

end of thread, other threads:[~2026-10-25 13:50 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-09  9:19 [PATCH v2 0/3] Add Loongson-2K0300 APB DMA mux support Binbin Zhou
2026-10-09  9:19 ` [PATCH v2 1/3] dt-bindings: dma: loongson: Add Loongson-2 DMA mux request IDs Binbin Zhou
2026-10-09 14:38   ` Conor Dooley
2026-10-09  9:19 ` [PATCH v2 2/3] dt-bindings: dma: loongson: Add Loongson-2K0300 DMA mux support Binbin Zhou
2026-10-09 14:43   ` Conor Dooley
2026-10-09  9:19 ` [PATCH v2 3/3] dmaengine: loongson: Add Loongson-2K0300 APB DMA mux driver Binbin Zhou
2026-10-15 17:03   ` Frank Li
2026-10-21  9:34     ` Binbin Zhou
2026-10-21 15:05       ` Frank Li
2026-10-25  8:19   ` Huacai Chen
2026-10-25 13:49     ` Binbin Zhou

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox