* [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, ®_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, ®_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, ®_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