linux-gpio.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/10] RDA8810PL SD/MMC support
@ 2025-09-18 18:48 Dang Huynh via B4 Relay
  2025-09-18 18:48 ` [PATCH 01/10] dt-bindings: gpio: rda: Make interrupts optional Dang Huynh via B4 Relay
                   ` (10 more replies)
  0 siblings, 11 replies; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh, Conor Dooley

This patch series aims to add SDMMC driver and various drivers required
for SDMMC controller to function.

This also fixed a bug where all the GPIO switched from INPUT to OUTPUT
after the GPIO driver probed or by reading the GPIO debugfs.

This patch series is a split from [1] to ease the maintainers.

Tested on Orange Pi 2G-IOT using a Buildroot environment.

[1]: https://lore.kernel.org/all/20250917-rda8810pl-drivers-v1-0-9ca9184ca977@mainlining.org/

Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
Dang Huynh (10):
      dt-bindings: gpio: rda: Make interrupts optional
      dt-bindings: clock: Add RDA Micro RDA8810PL clock/reset controller
      dt-bindings: dma: Add RDA IFC DMA
      dt-bindings: mmc: Add RDA SDMMC controller
      gpio: rda: Make IRQ optional
      gpio: rda: Make direction register unreadable
      clk: Add Clock and Reset Driver for RDA Micro RDA8810PL SoC
      dmaengine: Add RDA IFC driver
      mmc: host: Add RDA Micro SD/MMC driver
      ARM: dts: unisoc: rda8810pl: Add SDMMC controllers

 .../bindings/clock/rda,8810pl-apsyscon.yaml        |  43 ++
 Documentation/devicetree/bindings/dma/rda,ifc.yaml |  45 ++
 .../devicetree/bindings/gpio/gpio-rda.yaml         |   3 -
 Documentation/devicetree/bindings/mmc/rda,mmc.yaml |  92 +++
 MAINTAINERS                                        |  18 +
 .../boot/dts/unisoc/rda8810pl-orangepi-2g-iot.dts  |  20 +
 .../arm/boot/dts/unisoc/rda8810pl-orangepi-i96.dts |  20 +
 arch/arm/boot/dts/unisoc/rda8810pl.dtsi            |  47 +-
 drivers/clk/Kconfig                                |   1 +
 drivers/clk/Makefile                               |   1 +
 drivers/clk/rda/Kconfig                            |  14 +
 drivers/clk/rda/Makefile                           |   2 +
 drivers/clk/rda/clk-rda8810.c                      | 769 +++++++++++++++++++
 drivers/dma/Kconfig                                |  10 +
 drivers/dma/Makefile                               |   1 +
 drivers/dma/rda-ifc.c                              | 450 +++++++++++
 drivers/gpio/gpio-rda.c                            |   4 +-
 drivers/mmc/host/Kconfig                           |  12 +
 drivers/mmc/host/Makefile                          |   1 +
 drivers/mmc/host/rda-mmc.c                         | 853 +++++++++++++++++++++
 include/dt-bindings/clock/rda,8810pl-apclk.h       |  70 ++
 include/dt-bindings/dma/rda-ifc.h                  |  28 +
 22 files changed, 2495 insertions(+), 9 deletions(-)
---
base-commit: ae2d20002576d2893ecaff25db3d7ef9190ac0b6
change-id: 20250918-rda8810pl-mmc-3f33b83c313d

Best regards,
-- 
Dang Huynh <dang.huynh@mainlining.org>



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

* [PATCH 01/10] dt-bindings: gpio: rda: Make interrupts optional
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
@ 2025-09-18 18:48 ` Dang Huynh via B4 Relay
  2025-09-18 18:48 ` [PATCH 02/10] dt-bindings: clock: Add RDA Micro RDA8810PL clock/reset controller Dang Huynh via B4 Relay
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh, Conor Dooley

From: Dang Huynh <dang.huynh@mainlining.org>

The GPIO controller from the modem does not have an interrupt.

Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
 Documentation/devicetree/bindings/gpio/gpio-rda.yaml | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/gpio/gpio-rda.yaml b/Documentation/devicetree/bindings/gpio/gpio-rda.yaml
index 6ece555f074f84b396537917d7149d4061724dcc..dbb73b4d33ed39aa65024376b1af0c4e2fb896db 100644
--- a/Documentation/devicetree/bindings/gpio/gpio-rda.yaml
+++ b/Documentation/devicetree/bindings/gpio/gpio-rda.yaml
@@ -41,9 +41,6 @@ required:
   - gpio-controller
   - "#gpio-cells"
   - ngpios
-  - interrupt-controller
-  - "#interrupt-cells"
-  - interrupts
 
 additionalProperties: false
 

-- 
2.51.0



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

* [PATCH 02/10] dt-bindings: clock: Add RDA Micro RDA8810PL clock/reset controller
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
  2025-09-18 18:48 ` [PATCH 01/10] dt-bindings: gpio: rda: Make interrupts optional Dang Huynh via B4 Relay
@ 2025-09-18 18:48 ` Dang Huynh via B4 Relay
  2025-09-22 18:04   ` Rob Herring
  2025-09-18 18:48 ` [PATCH 03/10] dt-bindings: dma: Add RDA IFC DMA Dang Huynh via B4 Relay
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh

From: Dang Huynh <dang.huynh@mainlining.org>

Add documentation describing the RDA8810PL Clock and Reset
controller.

Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
 .../bindings/clock/rda,8810pl-apsyscon.yaml        | 43 +++++++++++++
 include/dt-bindings/clock/rda,8810pl-apclk.h       | 70 ++++++++++++++++++++++
 2 files changed, 113 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/rda,8810pl-apsyscon.yaml b/Documentation/devicetree/bindings/clock/rda,8810pl-apsyscon.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d90dae8ebaa270aa822e4855d2a4a892168c5eea
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/rda,8810pl-apsyscon.yaml
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/rda,8810pl-apsyscon.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: RDA Micro RDA8810PL AP Clock Controller
+
+maintainers:
+  - Dang Huynh <dang.huynh@mainlining.org>
+
+properties:
+  compatible:
+    items:
+      - const: rda,8810pl-apsyscon
+      - const: syscon
+
+  reg:
+    maxItems: 1
+
+  '#clock-cells':
+    const: 1
+
+  '#reset-cells':
+    const: 1
+
+required:
+  - compatible
+  - reg
+  - '#clock-cells'
+  - '#reset-cells'
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/rda,8810pl-apclk.h>
+    syscon@0 {
+      compatible = "rda,8810pl-apsyscon", "syscon";
+      reg = <0x0 0x1000>;
+      #clock-cells = <1>;
+      #reset-cells = <1>;
+    };
diff --git a/include/dt-bindings/clock/rda,8810pl-apclk.h b/include/dt-bindings/clock/rda,8810pl-apclk.h
new file mode 100644
index 0000000000000000000000000000000000000000..b6d68af9596a7ef827f6d18f1f19ed359e147629
--- /dev/null
+++ b/include/dt-bindings/clock/rda,8810pl-apclk.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+
+#ifndef _DT_BINDINGS_CLK_RDA8810_H_
+#define _DT_BINDINGS_CLK_RDA8810_H_
+
+/* soc clocks */
+#define CLK_CPU			0
+#define CLK_BUS			1
+#define CLK_MEM			2
+#define CLK_USB			3
+#define CLK_AXI			4
+#define CLK_GCG			5
+#define CLK_AHB1			6
+#define CLK_APB1			7
+#define CLK_APB2			8
+#define CLK_GPU			9
+#define CLK_VPU			10
+#define CLK_VOC			11
+#define CLK_SFLSH			12
+#define CLK_UART1			13
+#define CLK_UART2			14
+#define CLK_UART3			15
+#define CLK_VOC2			16
+#define CLK_EMMC			17
+
+/* resets */
+#define RST_CPU			0
+
+#define RST_AXI_VOC			1
+#define RST_AXI_DMA			2
+#define RST_AXI_CONNECT		3
+#define RST_AXI_VPU			4
+
+#define RST_GCG_GOUDA			5
+#define RST_GCG_CAMERA			6
+#define RST_GCG_LCDC			7
+
+#define RST_AHB1_USBC			8
+#define RST_AHB1_SPIFLASH		9
+#define RST_APB1_TIMER			10
+#define RST_APB1_KEYPAD		11
+#define RST_APB1_GPIO			12
+#define RST_APB1_PWM			13
+#define RST_APB1_AIF			14
+#define RST_APB1_AUIFC			15
+#define RST_APB1_I2C1			16
+#define RST_APB1_I2C2			17
+#define RST_APB1_I2C3			18
+#define RST_APB1_COMREGS		19
+#define RST_APB1_DMC			20
+#define RST_APB1_DDRPHY_P		21
+
+#define RST_APB2_IFC			22
+#define RST_APB2_UART1			23
+#define RST_APB2_UART2			24
+#define RST_APB2_UART3			25
+#define RST_APB2_SPI1			26
+#define RST_APB2_SPI2			27
+#define RST_APB2_SPI3			28
+#define RST_APB2_SDMMC1		29
+#define RST_APB2_SDMMC2		30
+#define RST_APB2_SDMMC3		31
+#define RST_APB2_NAND			32
+
+#define RST_MEM_GPU			33
+#define RST_MEM_VPU			34
+#define RST_MEM_DMC			35
+#define RST_MEM_DDRPHY_P		36
+
+#endif /* _DT_BINDINGS_CLK_RDA8810_H_ */

-- 
2.51.0



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

* [PATCH 03/10] dt-bindings: dma: Add RDA IFC DMA
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
  2025-09-18 18:48 ` [PATCH 01/10] dt-bindings: gpio: rda: Make interrupts optional Dang Huynh via B4 Relay
  2025-09-18 18:48 ` [PATCH 02/10] dt-bindings: clock: Add RDA Micro RDA8810PL clock/reset controller Dang Huynh via B4 Relay
@ 2025-09-18 18:48 ` Dang Huynh via B4 Relay
  2025-09-22 18:07   ` Rob Herring
  2025-09-22 18:08   ` Rob Herring
  2025-09-18 18:48 ` [PATCH 04/10] dt-bindings: mmc: Add RDA SDMMC controller Dang Huynh via B4 Relay
                   ` (7 subsequent siblings)
  10 siblings, 2 replies; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh

From: Dang Huynh <dang.huynh@mainlining.org>

The Intelligent Flow Controller (IFC) is a scatter/gather DMA
controller.

Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
 Documentation/devicetree/bindings/dma/rda,ifc.yaml | 45 ++++++++++++++++++++++
 include/dt-bindings/dma/rda-ifc.h                  | 28 ++++++++++++++
 2 files changed, 73 insertions(+)

diff --git a/Documentation/devicetree/bindings/dma/rda,ifc.yaml b/Documentation/devicetree/bindings/dma/rda,ifc.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..323e1e87cee09cfc7b64bf44bef61e4d1e91afa5
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/rda,ifc.yaml
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dma/rda,ifc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: RDA Intelligent Flow Controller (IFC)
+
+maintainers:
+  - Dang Huynh <dang.huynh@mainlining.org>
+
+description: |
+  RDA IFC is a DMA controller, it only supports scatter/gather lists.
+
+allOf:
+  - $ref: dma-controller.yaml#
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - rda,8810pl-ifc
+      - const: rda,ifc
+
+  reg:
+    maxItems: 1
+
+  "#dma-cells":
+    const: 1
+    description:
+      The cell corresponding to DMA request ID
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    dma-controller@f0000 {
+      compatible = "rda,8810pl-ifc", "rda,ifc";
+      reg = <0xf0000 0x1000>;
+      #dma-cells = <1>;
+    };
diff --git a/include/dt-bindings/dma/rda-ifc.h b/include/dt-bindings/dma/rda-ifc.h
new file mode 100644
index 0000000000000000000000000000000000000000..af4bae0542aa71690351e2406d0945a61eff72c1
--- /dev/null
+++ b/include/dt-bindings/dma/rda-ifc.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
+
+#ifndef __DT_BINDINGS_DMA_RDA_IFC_H__
+#define __DT_BINDINGS_DMA_RDA_IFC_H__
+
+#define IFC_UART1_TX		0
+#define IFC_UART1_RX		1
+#define IFC_UART2_TX		2
+#define IFC_UART2_RX		3
+#define IFC_SPI1_TX		4
+#define IFC_SPI1_RX		5
+#define IFC_SPI2_TX		6
+#define IFC_SPI2_RX		7
+#define IFC_SPI3_TX		8
+#define IFC_SPI3_RX		9
+#define IFC_SDMMC1_TX		10
+#define IFC_SDMMC1_RX		11
+#define IFC_SDMMC2_TX		12
+#define IFC_SDMMC2_RX		13
+#define IFC_SDMMC3_TX		14
+#define IFC_SDMMC3_RX		15
+#define IFC_NFSC_TX		16
+#define IFC_NFSC_RX		17
+#define IFC_UART3_TX		18
+#define IFC_UART3_RX		19
+#define IFC_NO_REQUEST		20
+
+#endif /* __DT_BINDINGS_DMA_RDA_IFC_H__ */

-- 
2.51.0



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

* [PATCH 04/10] dt-bindings: mmc: Add RDA SDMMC controller
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
                   ` (2 preceding siblings ...)
  2025-09-18 18:48 ` [PATCH 03/10] dt-bindings: dma: Add RDA IFC DMA Dang Huynh via B4 Relay
@ 2025-09-18 18:48 ` Dang Huynh via B4 Relay
  2025-09-22 18:13   ` Rob Herring
  2025-09-18 18:48 ` [PATCH 05/10] gpio: rda: Make IRQ optional Dang Huynh via B4 Relay
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh

From: Dang Huynh <dang.huynh@mainlining.org>

Add documentation describing the SD/MMC controller in RDA Micro
RDA8810PL SoC.

Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
 Documentation/devicetree/bindings/mmc/rda,mmc.yaml | 92 ++++++++++++++++++++++
 1 file changed, 92 insertions(+)

diff --git a/Documentation/devicetree/bindings/mmc/rda,mmc.yaml b/Documentation/devicetree/bindings/mmc/rda,mmc.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f3cda38abd66023ca9bc94d1c29df5ae9e211e7c
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/rda,mmc.yaml
@@ -0,0 +1,92 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mmc/rda,mmc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: RDA Micro SD/MMC Controller
+
+allOf:
+  - $ref: mmc-controller.yaml
+
+maintainers:
+  - Dang Huynh <dang.huynh@mainlining.org>
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - rda,8810pl-mmc
+      - const: rda,mmc
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  clock-names:
+    const: mclk
+
+  resets:
+    maxItems: 1
+
+  dmas:
+    maxItems: 2
+
+  dma-names:
+    items:
+      - const: tx
+      - const: rx
+
+  rda,mclk-adj:
+    $ref: /schemas/types.yaml#/definitions/uint8
+    description:
+      Some board need MCLK output to be delayed for the card to work.
+      If not present, MCLK output is not delayed.
+    minimum: 0
+    maximum: 255
+
+  rda,mclk-inv:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Some board need MCLK to be inverted for the card to work.
+      If not present, MCLK is not inverted.
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - clock-names
+  - resets
+  - dmas
+  - dma-names
+  - vmmc-supply
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/clock/rda,8810pl-apclk.h>
+    #include <dt-bindings/dma/rda-ifc.h>
+    mmc@20950000 {
+      compatible = "rda,8810pl-mmc", "rda,mmc";
+      reg = <0x20950000 0x1000>;
+      interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
+      clocks = <&ap_syscon CLK_APB2>;
+      clock-names = "mclk";
+      resets = <&ap_syscon RST_APB2_SDMMC1>;
+      dmas = <&ifc IFC_SDMMC1_TX>, <&ifc IFC_SDMMC1_RX>;
+      dma-names = "tx", "rx";
+      vmmc-supply = <&vdd_sdmmc>;
+      rda,mclk-adj = /bits/ 8 <1>;
+      rda,mclk-inv;
+    };
+
+...

-- 
2.51.0



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

* [PATCH 05/10] gpio: rda: Make IRQ optional
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
                   ` (3 preceding siblings ...)
  2025-09-18 18:48 ` [PATCH 04/10] dt-bindings: mmc: Add RDA SDMMC controller Dang Huynh via B4 Relay
@ 2025-09-18 18:48 ` Dang Huynh via B4 Relay
  2025-09-18 18:48 ` [PATCH 06/10] gpio: rda: Make direction register unreadable Dang Huynh via B4 Relay
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh

From: Dang Huynh <dang.huynh@mainlining.org>

Not all GPIO have IRQ. When we use this driver with
GPIOC (handled by modem) we get:

rda-gpio 11a08000.gpioc: error -ENXIO: IRQ index 0 not found

Let's mark IRQ as optional so this error doesn't show up.

Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
 drivers/gpio/gpio-rda.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpio/gpio-rda.c b/drivers/gpio/gpio-rda.c
index fb479d13eb01a49218ad4229e7d4f70f096f5a2e..7d20dbbb6ec41149a1dbf4d9ef9ac86449773c35 100644
--- a/drivers/gpio/gpio-rda.c
+++ b/drivers/gpio/gpio-rda.c
@@ -229,7 +229,7 @@ static int rda_gpio_probe(struct platform_device *pdev)
 	 * RDA8810PL, GPIOC doesn't support interrupt. So we must handle
 	 * those also.
 	 */
-	rda_gpio->irq = platform_get_irq(pdev, 0);
+	rda_gpio->irq = platform_get_irq_optional(pdev, 0);
 
 	rda_gpio->base = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(rda_gpio->base))

-- 
2.51.0



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

* [PATCH 06/10] gpio: rda: Make direction register unreadable
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
                   ` (4 preceding siblings ...)
  2025-09-18 18:48 ` [PATCH 05/10] gpio: rda: Make IRQ optional Dang Huynh via B4 Relay
@ 2025-09-18 18:48 ` Dang Huynh via B4 Relay
  2025-09-22 14:20   ` Bartosz Golaszewski
  2025-09-18 18:48 ` [PATCH 07/10] clk: Add Clock and Reset Driver for RDA Micro RDA8810PL SoC Dang Huynh via B4 Relay
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh

From: Dang Huynh <dang.huynh@mainlining.org>

The register doesn't like to be read and would cause all the input
to be switched to output.

This causes the SD Card Detect GPIO to misbehaves in the OS and/or
may cause hardware to malfunction.

Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
 drivers/gpio/gpio-rda.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpio/gpio-rda.c b/drivers/gpio/gpio-rda.c
index 7d20dbbb6ec41149a1dbf4d9ef9ac86449773c35..ae2efa22755986b5445fdb8d4f004d595c189590 100644
--- a/drivers/gpio/gpio-rda.c
+++ b/drivers/gpio/gpio-rda.c
@@ -245,7 +245,7 @@ static int rda_gpio_probe(struct platform_device *pdev)
 		.clr = rda_gpio->base + RDA_GPIO_CLR,
 		.dirout = rda_gpio->base + RDA_GPIO_OEN_SET_OUT,
 		.dirin = rda_gpio->base + RDA_GPIO_OEN_SET_IN,
-		.flags = BGPIOF_READ_OUTPUT_REG_SET,
+		.flags = BGPIOF_READ_OUTPUT_REG_SET | BGPIOF_UNREADABLE_REG_DIR,
 	};
 
 	ret = gpio_generic_chip_init(&rda_gpio->chip, &config);

-- 
2.51.0



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

* [PATCH 07/10] clk: Add Clock and Reset Driver for RDA Micro RDA8810PL SoC
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
                   ` (5 preceding siblings ...)
  2025-09-18 18:48 ` [PATCH 06/10] gpio: rda: Make direction register unreadable Dang Huynh via B4 Relay
@ 2025-09-18 18:48 ` Dang Huynh via B4 Relay
  2025-09-18 18:48 ` [PATCH 08/10] dmaengine: Add RDA IFC driver Dang Huynh via B4 Relay
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh

From: Dang Huynh <dang.huynh@mainlining.org>

Add a clock/reset driver for RDA8810PL SoC, which provides clocks for
various subsystems.

Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
 MAINTAINERS                   |   6 +
 drivers/clk/Kconfig           |   1 +
 drivers/clk/Makefile          |   1 +
 drivers/clk/rda/Kconfig       |  14 +
 drivers/clk/rda/Makefile      |   2 +
 drivers/clk/rda/clk-rda8810.c | 769 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 793 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 8886d66bd8242ae76ca52393af3958435bf6b9a5..56f9d19fbf421eefffe554987e14604c764daab2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21418,6 +21418,12 @@ S:	Supported
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/rcu/linux.git rcu/dev
 F:	tools/testing/selftests/rcutorture
 
+RDA MICRO CLOCK AND RESET DRIVER
+M:	Dang Huynh <dang.huynh@mainlining.org>
+S:	Maintained
+F:	Documentation/devicetree/bindings/clock/rda,8810pl-apsyscon.yaml
+F:	drivers/clk/rda/clk-rda8810.c
+
 RDACM20 Camera Sensor
 M:	Jacopo Mondi <jacopo+renesas@jmondi.org>
 M:	Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index b1425aed659387a676bd933fe50ac4894c7156fe..15f5bc9108b565acb1c3c6e978ad0e5a71f5550d 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -520,6 +520,7 @@ source "drivers/clk/nuvoton/Kconfig"
 source "drivers/clk/pistachio/Kconfig"
 source "drivers/clk/qcom/Kconfig"
 source "drivers/clk/ralink/Kconfig"
+source "drivers/clk/rda/Kconfig"
 source "drivers/clk/renesas/Kconfig"
 source "drivers/clk/rockchip/Kconfig"
 source "drivers/clk/samsung/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 18ed29cfdc1133b6c254190c6092eb263366d5ac..8241bb7f88daaebde766ba92d718b2ca710d6b5f 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -141,6 +141,7 @@ obj-$(CONFIG_COMMON_CLK_PISTACHIO)	+= pistachio/
 obj-$(CONFIG_COMMON_CLK_PXA)		+= pxa/
 obj-$(CONFIG_COMMON_CLK_QCOM)		+= qcom/
 obj-y					+= ralink/
+obj-y					+= rda/
 obj-y					+= renesas/
 obj-$(CONFIG_ARCH_ROCKCHIP)		+= rockchip/
 obj-$(CONFIG_COMMON_CLK_SAMSUNG)	+= samsung/
diff --git a/drivers/clk/rda/Kconfig b/drivers/clk/rda/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..b505e3e552cef1e7ea3da4aa46d61d0d0a3d5db0
--- /dev/null
+++ b/drivers/clk/rda/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config CLK_RDA8810
+	bool "RDA Micro RDA8810PL Clock and Reset Controller"
+	depends on ARCH_RDA || COMPILE_TEST
+	select MFD_SYSCON
+	select REGMAP_MMIO
+	select RESET_CONTROLLER
+	help
+	  This driver supports clock and reset for RDA Micro RDA8810 platform.
+	  If you have a board with the RDA8810PL SoC, say Y to use most of the
+	  board peripherals.
+
+	  If unsure, say N.
+
diff --git a/drivers/clk/rda/Makefile b/drivers/clk/rda/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..98848dccabe5d2199d5e9469d6bde154b2b3d86a
--- /dev/null
+++ b/drivers/clk/rda/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_CLK_RDA8810) += clk-rda8810.o
diff --git a/drivers/clk/rda/clk-rda8810.c b/drivers/clk/rda/clk-rda8810.c
new file mode 100644
index 0000000000000000000000000000000000000000..8d844f30a0deede267fe531dbf7b370a8328cff0
--- /dev/null
+++ b/drivers/clk/rda/clk-rda8810.c
@@ -0,0 +1,769 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * RDA8810PL Clock and Reset driver
+ *
+ * Copyright (C) 2013 RDA Microelectronics Inc.
+ * Copyright (c) 2025 Dang Huynh <dang.huynh@mainlining.org>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/reset-controller.h>
+#include <dt-bindings/clock/rda,8810pl-apclk.h>
+
+#define MHZ 1000000U
+
+/*
+ * Some registers are protected, we need to write AP_CTRL_PROTECT_UNLOCK to
+ * AP_REG_DBG then we can make changes to them.
+ */
+#define AP_CTRL_PROTECT_LOCK	0xA50000
+#define AP_CTRL_PROTECT_UNLOCK	0xA50001
+
+/* Register Base */
+#define AP_REG_DBG 0x0
+#define AP_REG_CPU_ENABLE 0x60
+#define AP_REG_AXI_ENABLE 0x6C
+#define AP_REG_AXIDIV2_ENABLE 0x78
+#define AP_REG_GCG_ENABLE 0x84
+#define AP_REG_AHB1_ENABLE 0x90
+#define AP_REG_APB1_ENABLE 0x9C
+#define AP_REG_APB2_ENABLE 0xA8
+#define AP_REG_MEM_ENABLE 0xB4
+#define AP_REG_APO_ENABLE 0xC0
+
+/* AP Clk Config Bits */
+#define AP_PERI_SRC_DIV BIT(12)
+
+/* UART Clock Bits */
+#define AP_UART_DIVIDER GENMASK(9, 0)
+#define AP_UART_SET_PLL BIT(12)
+
+/* AP Clk Enable */
+#define AP_ENABLE_CPU_CORE BIT(0)
+#define AP_ENABLE_CPU_DUMMY BIT(1)
+#define AP_ENABLE_AHB0_CONF BIT(0)
+#define AP_ENABLE_APB0_CONF BIT(1)
+#define AP_ENABLE_AXI_VOC  BIT(2)
+#define AP_ENABLE_AXI_DMA  BIT(3)
+#define AP_ENABLE_AXI_ALWAYS BIT(4)
+#define AP_ENABLE_AXI_CONNECT BIT(5)
+#define AP_ENABLE_APB0_IRQ BIT(6)
+#define AP_ENABLE_AXIDIV2_IMEM BIT(0)
+#define AP_ENABLE_AXIDIV2_ALWAYS BIT(1)
+#define AP_ENABLE_AXIDIV2_CONNECT BIT(2)
+#define AP_ENABLE_AXIDIV2_VPU BIT(3)
+#define AP_ENABLE_GCG_APB_CONF BIT(0)
+#define AP_ENABLE_GCG_GOUDA BIT(1)
+#define AP_ENABLE_GCG_CAMERA BIT(2)
+#define AP_ENABLE_GCG_ALWAYS BIT(3)
+#define AP_ENABLE_GCG_CONNECT BIT(4)
+#define AP_ENABLE_GCG_DPI BIT(7)
+#define AP_ENABLE_AHB1_USBC BIT(0)
+#define AP_ENABLE_AHB1_ALWAYS BIT(1)
+#define AP_ENABLE_AHB1_SPIFLASH BIT(2)
+#define AP_ENABLE_APB1_CONF BIT(0)
+#define AP_ENABLE_APB1_AIF BIT(1)
+#define AP_ENABLE_APB1_AUIFC BIT(2)
+#define AP_ENABLE_APB1_AUIFC_CH0 BIT(3)
+#define AP_ENABLE_APB1_AUIFC_CH1 BIT(4)
+#define AP_ENABLE_APB1_I2C1 BIT(5)
+#define AP_ENABLE_APB1_I2C2 BIT(6)
+#define AP_ENABLE_APB1_I2C3 BIT(7)
+#define AP_ENABLE_APB1D_OSC BIT(8)
+#define AP_ENABLE_APB1D_PWM BIT(9)
+#define AP_ENABLE_APB1_ALWAYS BIT(10)
+#define AP_ENABLE_APB1_DAPLITE BIT(11)
+#define AP_ENABLE_APB1_TIMER BIT(12)
+#define AP_ENABLE_APB1_GPIO BIT(13)
+#define AP_ENABLE_APB2_CONF BIT(0)
+#define AP_ENABLE_APB2_IFC BIT(1)
+#define AP_ENABLE_APB2_IFC_CH0 BIT(2)
+#define AP_ENABLE_APB2_IFC_CH1 BIT(3)
+#define AP_ENABLE_APB2_IFC_CH2 BIT(4)
+#define AP_ENABLE_APB2_IFC_CH3 BIT(5)
+#define AP_ENABLE_APB2_IFC_CH4 BIT(6)
+#define AP_ENABLE_APB2_IFC_CH5 BIT(7)
+#define AP_ENABLE_APB2_IFC_CH6 BIT(8)
+#define AP_ENABLE_APB2_IFC_CH7 BIT(9)
+#define AP_ENABLE_APB2_UART1 BIT(10)
+#define AP_ENABLE_APB2_UART2 BIT(11)
+#define AP_ENABLE_APB2_UART3 BIT(12)
+#define AP_ENABLE_APB2_SPI1 BIT(13)
+#define AP_ENABLE_APB2_SPI2 BIT(14)
+#define AP_ENABLE_APB2_SPI3 BIT(15)
+#define AP_ENABLE_APB2_SDMMC1 BIT(16)
+#define AP_ENABLE_APB2_SDMMC2 BIT(17)
+#define AP_ENABLE_APB2_SDMMC3 BIT(18)
+#define AP_ENABLE_APB2_ALWAYS BIT(19)
+#define AP_ENABLE_APB2_NANDFLASH BIT(20)
+#define AP_ENABLE_MEM_CONF BIT(0)
+#define AP_ENABLE_MEM_DMC  BIT(1)
+#define AP_ENABLE_MEM_GPU  BIT(2)
+#define AP_ENABLE_MEM_VPU  BIT(3)
+#define AP_ENABLE_MEM_DDRPHY_P BIT(4)
+#define AP_ENABLE_MEM_CONNECT BIT(5)
+#define AP_ENABLE_APOC_VPU BIT(0)
+#define AP_ENABLE_APOC_BCK BIT(1)
+#define AP_ENABLE_APOC_UART1 BIT(2)
+#define AP_ENABLE_APOC_UART2 BIT(3)
+#define AP_ENABLE_APOC_UART3 BIT(4)
+#define AP_ENABLE_APOC_VOC_CORE BIT(5)
+#define AP_ENABLE_APOC_VOC BIT(6)
+#define AP_ENABLE_APOC_VOC_ALWAYS BIT(7)
+#define AP_ENABLE_APOC_DDRPHY_N BIT(8)
+#define AP_ENABLE_APOC_DDRPHY2XP BIT(9)
+#define AP_ENABLE_APOC_DDRPHY2XN BIT(10)
+#define AP_ENABLE_APOC_GPU BIT(11)
+#define AP_ENABLE_APOC_USBPHY BIT(12)
+#define AP_ENABLE_APOC_CSI BIT(13)
+#define AP_ENABLE_APOC_DSI BIT(14)
+#define AP_ENABLE_APOC_GPIO BIT(15)
+#define AP_ENABLE_APOC_SPIFLASH BIT(16)
+#define AP_ENABLE_APOC_PIX BIT(17)
+#define AP_ENABLE_APOC_PDGB BIT(18)
+
+/* AP Reset */
+#define AP_RST_CPU_REG 0x1C
+#define AP_RST_AXI_REG 0x24
+#define AP_RST_AXIDIV2_REG 0x2C
+#define AP_RST_GCG_REG 0x34
+#define AP_RST_AHB1_REG 0x3C
+#define AP_RST_APB1_REG 0x44
+#define AP_RST_APB2_REG 0x4C
+#define AP_RST_MEM_REG 0x54
+
+/* Bits */
+#define AP_RST_CPU_CORE BIT(0)
+#define AP_RST_CPU_SYS BIT(1)
+#define AP_RST_AXI_VOC BIT(0)
+#define AP_RST_AXI_DMA BIT(1)
+#define AP_RST_AXI_SYS BIT(2)
+#define AP_RST_AXI_CONNECT BIT(3)
+#define AP_RST_AXI_VPU BIT(5)
+#define AP_RST_AXIDIV2_IMEM BIT(0)
+#define AP_RST_AXIDIV2_SYS BIT(1)
+#define AP_RST_AXIDIV2_VPU BIT(2)
+#define AP_RST_GCG_SYS BIT(0)
+#define AP_RST_GCG_GOUDA BIT(1)
+#define AP_RST_GCG_CAMERA BIT(2)
+#define AP_RST_GCG_LCDC BIT(4)
+#define AP_RST_AHB1_SYS BIT(0)
+#define AP_RST_AHB1_USBC BIT(1)
+#define AP_RST_AHB1_SPIFLASH BIT(2)
+#define AP_RST_APB1_SYS BIT(0)
+#define AP_RST_APB1_TIMER BIT(1)
+#define AP_RST_APB1_KEYPAD BIT(2)
+#define AP_RST_APB1_GPIO BIT(3)
+#define AP_RST_APB1_PWM BIT(4)
+#define AP_RST_APB1_AIF BIT(5)
+#define AP_RST_APB1_AUIFC BIT(6)
+#define AP_RST_APB1_I2C1 BIT(7)
+#define AP_RST_APB1_I2C2 BIT(8)
+#define AP_RST_APB1_I2C3 BIT(9)
+#define AP_RST_APB1_COMREGS BIT(10)
+#define AP_RST_APB1_DMC BIT(11)
+#define AP_RST_APB1_DDRPHY_P BIT(12)
+#define AP_RST_APB2_SYS BIT(0)
+#define AP_RST_APB2_IFC BIT(1)
+#define AP_RST_APB2_UART1 BIT(2)
+#define AP_RST_APB2_UART2 BIT(3)
+#define AP_RST_APB2_UART3 BIT(4)
+#define AP_RST_APB2_SPI1 BIT(5)
+#define AP_RST_APB2_SPI2 BIT(6)
+#define AP_RST_APB2_SPI3 BIT(7)
+#define AP_RST_APB2_SDMMC1 BIT(8)
+#define AP_RST_APB2_SDMMC2 BIT(9)
+#define AP_RST_APB2_SDMMC3 BIT(10)
+#define AP_RST_APB2_NANDFLASH BIT(11)
+#define AP_RST_MEM_SYS BIT(0)
+#define AP_RST_MEM_GPU BIT(1)
+#define AP_RST_MEM_VPU BIT(2)
+#define AP_RST_MEM_DMC BIT(3)
+#define AP_RST_MEM_DDRPHY_P BIT(4)
+
+/* Default PLL frequency */
+#define AP_PLL_CPU_FREQ (988 * MHZ)
+#define AP_PLL_BUS_FREQ (800 * MHZ)
+#define AP_PLL_MEM_FREQ (260 * MHZ)
+#define AP_PLL_USB_FREQ (480 * MHZ)
+
+struct rda8810_reset_list {
+	int reg;
+	int bit;
+};
+
+struct rda_clk_priv {
+	struct device *dev;
+	struct regmap *regmap;
+	struct clk_hw_onecell_data *onecell;
+	struct reset_controller_dev rstctl;
+	const struct rda8810_reset_list *rstlist;
+};
+
+struct rda_clk_hw {
+	int id;
+	int reg;
+	struct clk_hw hw;
+
+	int ena_reg;
+	int ena_bit;
+
+	struct rda_clk_priv *priv;
+};
+
+struct rda_clk_matchdata {
+	const struct rda_clk_hw *clk_list;
+	int max_clocks;
+};
+
+static const struct clk_ops rda8810_clk_ops;
+
+#define RDA_CLK_INIT(_id, _name, _parent, _flags, _reg, _ena_reg, _ena_bit) { \
+	.id = _id, \
+	.reg = _reg, \
+	.ena_reg = _ena_reg, \
+	.ena_bit = _ena_bit, \
+	.hw.init = CLK_HW_INIT_PARENTS_DATA(_name, \
+			_parent, \
+			&rda8810_clk_ops, \
+			_flags) \
+}
+
+#define RDA_CLK_INIT_NO_PARENT(_id, _name, _flags, _reg, _ena_reg, _ena_bit) { \
+	.id = _id, \
+	.reg = _reg, \
+	.ena_reg = _ena_reg, \
+	.ena_bit = _ena_bit, \
+	.hw.init = CLK_HW_INIT_NO_PARENT(_name, &rda8810_clk_ops, _flags) \
+}
+
+#define to_rda_rst(p) container_of(p, struct rda_clk_priv, rstctl)
+
+static inline struct rda_clk_hw *to_rda_hw(struct clk_hw *hw)
+{
+	return container_of(hw, struct rda_clk_hw, hw);
+}
+
+/* clock division value map */
+static const u8 clk_div_map[] = {
+	4*60,	/* 0 */
+	4*60,	/* 1 */
+	4*60,	/* 2 */
+	4*60,	/* 3 */
+	4*60,	/* 4 */
+	4*60,	/* 5 */
+	4*60,	/* 6 */
+	4*60,	/* 7 */
+	4*40,	/* 8 */
+	4*30,	/* 9 */
+	4*24,	/* 10 */
+	4*20,	/* 11 */
+	4*17,	/* 12 */
+	4*15,	/* 13 */
+	4*13,	/* 14 */
+	4*12,	/* 15 */
+	4*11,	/* 16 */
+	4*10,	/* 17 */
+	4*9,	/* 18 */
+	4*8,	/* 19 */
+	4*7,	/* 20 */
+	4*13/2,	/* 21 */
+	4*6,	/* 22 */
+	4*11/2,	/* 23 */
+	4*5,	/* 24 */
+	4*9/2,	/* 25 */
+	4*4,	/* 26 */
+	4*7/2,	/* 27 */
+	4*3,	/* 28 */
+	4*5/2,	/* 29 */
+	4*2,	/* 30 */
+	4*1,	/* 31 */
+};
+
+static u32 apsys_get_divreg(u32 basefreq, u32 reqfreq, u32 *pdiv2)
+{
+	int i;
+	int index;
+	u32 adiv;
+	u32 ndiv;
+
+	adiv = basefreq / (reqfreq >> 2);
+	if (pdiv2) {
+		/* try div2 mode first */
+		ndiv = adiv >> 1;
+	} else {
+		ndiv = adiv;
+	}
+
+	for (i = ARRAY_SIZE(clk_div_map) - 1; i >= 1; i--)
+		if (ndiv < ((clk_div_map[i] + clk_div_map[i-1]) >> 1))
+			break;
+	index = i;
+
+	if (pdiv2) {
+		if (adiv == (clk_div_map[index] << 1)) {
+			/* div2 mode is OK */
+			*pdiv2 = 1;
+		} else {
+			/* try div1 mode */
+			for (i = ARRAY_SIZE(clk_div_map) - 1; i >= 1; i--)
+				if (adiv < ((clk_div_map[i] + clk_div_map[i-1]) >> 1))
+					break;
+			/* compare the results between div1 and div2 */
+			if (abs(adiv - (clk_div_map[index] << 1)) <=
+					abs(adiv - clk_div_map[i])) {
+				*pdiv2 = 1;
+			} else {
+				*pdiv2 = 0;
+				index = i;
+			}
+		}
+	}
+
+	return index;
+}
+
+static u32 apsys_cal_freq_by_divreg(u32 basefreq, u32 reg, u32 div2)
+{
+	u32 newfreq;
+
+	if (reg >= ARRAY_SIZE(clk_div_map))
+		reg = ARRAY_SIZE(clk_div_map) - 1;
+
+	/* Assuming basefreq is smaller than 2^31 (2.147G Hz) */
+	newfreq = (basefreq << (div2 ? 0 : 1)) / (clk_div_map[reg] >> 1);
+	return newfreq;
+}
+
+static void apsys_get_reg_div(struct rda_clk_hw *rda_hw, u32 *reg, u32 *div2)
+{
+	struct rda_clk_priv *priv = rda_hw->priv;
+	int tmp_reg, tmp_div2;
+	int ret;
+
+	ret = regmap_read(priv->regmap, rda_hw->reg, &tmp_reg);
+	if (ret)
+		return;
+
+	tmp_div2 = tmp_reg & AP_PERI_SRC_DIV;
+
+	*reg = tmp_reg;
+	*div2 = tmp_div2;
+}
+
+static int apsys_get_uart_clock(unsigned long parent_rate, u32 *reg)
+{
+	int clksrc = 26000000;
+	u32 div;
+	int rate = 0;
+
+	if (*reg & AP_UART_SET_PLL)
+		clksrc = parent_rate / 8;
+
+	div = FIELD_GET(AP_UART_DIVIDER, *reg);
+
+	/* rate = clksrc / divmode / (div+2) */
+	rate =  clksrc / 4 / (div + 2);
+
+	return rate;
+}
+
+static int apsys_cal_uart_clock(int freq)
+{
+	int new_freq = freq;
+
+	/*
+	 * To calculate maximum clock:
+	 *     freq = 26 MHz / div / (0 + 2)
+	 *
+	 * For lowest clock:
+	 *     freq = 26 MHz / div / (0x3FF + 2)
+	 */
+	if (freq > 3250000)
+		new_freq = 3250000;
+	else if (freq < 6342)
+		new_freq = 6342;
+
+	new_freq = (26000000 + 4 / 2 * new_freq) / (4 * new_freq) - 2;
+
+	return new_freq;
+}
+
+static int rda8810_clk_set_rate(struct clk_hw *clk, unsigned long rate,
+		unsigned long parent_rate)
+{
+	struct rda_clk_hw *rda_hw = to_rda_hw(clk);
+	struct rda_clk_priv *priv = rda_hw->priv;
+	struct device *dev = priv->dev;
+	int val, div2 = 0;
+	int ret;
+
+	switch (rda_hw->id) {
+	case CLK_CPU:
+		val = apsys_get_divreg(AP_PLL_CPU_FREQ, rate, NULL);
+		break;
+	case CLK_AXI:
+	case CLK_AHB1:
+	case CLK_APB1:
+	case CLK_APB2:
+	case CLK_GCG:
+	case CLK_GPU:
+	case CLK_SFLSH:
+	case CLK_VOC:
+	case CLK_VPU:
+		val = apsys_get_divreg(parent_rate, rate, &div2);
+		break;
+	case CLK_UART1:
+	case CLK_UART2:
+	case CLK_UART3:
+		val = apsys_cal_uart_clock(rate);
+		if (val == 0)
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (div2)
+		val |= AP_PERI_SRC_DIV;
+
+	dev_dbg(dev, "clk_id: %d - rate: %ld - parent rate: %ld - val: %d - div: %d\n",
+			rda_hw->id, rate, parent_rate, val, div2);
+
+	ret = regmap_write(priv->regmap, rda_hw->reg, val);
+	if (ret < 0)
+		return ret;
+
+	return rate;
+}
+
+static unsigned long rda8810_clk_recalc_rate(struct clk_hw *clk,
+		unsigned long parent_rate)
+{
+	struct rda_clk_hw *rda_hw = to_rda_hw(clk);
+	u32 reg, div2;
+
+	apsys_get_reg_div(rda_hw, &reg, &div2);
+
+	switch (rda_hw->id) {
+	case CLK_CPU:
+		return apsys_cal_freq_by_divreg(AP_PLL_CPU_FREQ, reg, 0);
+	case CLK_BUS:
+		return AP_PLL_BUS_FREQ;
+	case CLK_MEM:
+		return AP_PLL_MEM_FREQ >> (2 + div2);
+	/* Bus peripherals */
+	case CLK_USB:
+		return AP_PLL_USB_FREQ;
+	case CLK_AXI:
+	case CLK_AHB1:
+	case CLK_APB1:
+	case CLK_APB2:
+	case CLK_GCG:
+	case CLK_GPU:
+	case CLK_SFLSH:
+	case CLK_VOC:
+	case CLK_VPU:
+		return apsys_cal_freq_by_divreg(parent_rate, reg, div2);
+	/* For UART clocks, we'll have to do more calculation */
+	case CLK_UART1:
+	case CLK_UART2:
+	case CLK_UART3:
+		return apsys_get_uart_clock(parent_rate, &reg);
+	default:
+		return 0;
+	}
+}
+
+static long rda8810_clk_round_rate(struct clk_hw *clk, unsigned long rate,
+		unsigned long *parent_rate)
+{
+	return rate;
+}
+
+static int rda8810_clk_enable(struct clk_hw *clk)
+{
+	struct rda_clk_hw *rda_hw = to_rda_hw(clk);
+	struct rda_clk_priv *priv = rda_hw->priv;
+
+	if (rda_hw->ena_reg < 0 || rda_hw->ena_bit < 0)
+		return 0;
+
+	return regmap_write(priv->regmap, rda_hw->ena_reg, rda_hw->ena_bit);
+}
+
+static void rda8810_clk_disable(struct clk_hw *clk)
+{
+	struct rda_clk_hw *rda_hw = to_rda_hw(clk);
+	struct rda_clk_priv *priv = rda_hw->priv;
+
+	if (rda_hw->ena_reg < 0 || rda_hw->ena_bit < 0)
+		return;
+
+	regmap_write(priv->regmap, rda_hw->ena_reg + 4, rda_hw->ena_bit);
+}
+
+static const struct clk_ops rda8810_clk_ops = {
+	.enable = rda8810_clk_enable,
+	.disable = rda8810_clk_disable,
+
+	.recalc_rate = rda8810_clk_recalc_rate,
+	.round_rate = rda8810_clk_round_rate,
+	.set_rate = rda8810_clk_set_rate,
+};
+
+/* Root clocks */
+static struct rda_clk_hw rda8810_clk_cpu_desc =
+RDA_CLK_INIT_NO_PARENT(CLK_CPU, "cpu", CLK_IS_CRITICAL, 0xC8,
+		AP_REG_CPU_ENABLE, AP_ENABLE_CPU_CORE);
+static struct rda_clk_hw rda8810_clk_mem_desc =
+RDA_CLK_INIT_NO_PARENT(CLK_MEM, "mem", CLK_IS_CRITICAL, 0xE0, -1, -1);
+
+static struct rda_clk_hw rda8810_clk_bus_desc =
+RDA_CLK_INIT_NO_PARENT(CLK_BUS, "bus", CLK_IS_CRITICAL, -1, -1, -1);
+
+/* Bus clocks */
+static struct rda_clk_hw rda8810_clk_usb_desc = RDA_CLK_INIT(CLK_USB, "usb",
+		(const struct clk_parent_data[]) { { .hw = &rda8810_clk_bus_desc.hw } },
+		0, -1, -1, -1);
+static struct rda_clk_hw rda8810_clk_axi_desc = RDA_CLK_INIT(CLK_AXI, "axi",
+		(const struct clk_parent_data[]) { { .hw = &rda8810_clk_bus_desc.hw } },
+		0, 0xCC, -1, -1);
+static struct rda_clk_hw rda8810_clk_gcg_desc = RDA_CLK_INIT(CLK_GCG, "gcg",
+		(const struct clk_parent_data[]) { { .hw = &rda8810_clk_bus_desc.hw } },
+		0, 0xD0, -1, -1);
+static struct rda_clk_hw rda8810_clk_ahb1_desc = RDA_CLK_INIT(CLK_AHB1, "ahb1",
+		(const struct clk_parent_data[]) { { .hw = &rda8810_clk_bus_desc.hw } },
+		0, 0xD4, -1, -1);
+static struct rda_clk_hw rda8810_clk_apb1_desc = RDA_CLK_INIT(CLK_APB1, "apb1",
+		(const struct clk_parent_data[]) { { .hw = &rda8810_clk_bus_desc.hw } },
+		0, 0xD8, -1, -1);
+static struct rda_clk_hw rda8810_clk_apb2_desc = RDA_CLK_INIT(CLK_APB2, "apb2",
+		(const struct clk_parent_data[]) { { .hw = &rda8810_clk_bus_desc.hw } },
+		0, 0xDC, -1, -1);
+static struct rda_clk_hw rda8810_clk_gpu_desc = RDA_CLK_INIT(CLK_GPU, "gpu",
+		(const struct clk_parent_data[]) { { .hw = &rda8810_clk_bus_desc.hw } },
+		0, 0xE4, AP_REG_APO_ENABLE, AP_ENABLE_APOC_GPU);
+static struct rda_clk_hw rda8810_clk_vpu_desc = RDA_CLK_INIT(CLK_VPU, "vpu",
+		(const struct clk_parent_data[]) { { .hw = &rda8810_clk_bus_desc.hw } },
+		0, 0xE8, AP_REG_APO_ENABLE, AP_ENABLE_APOC_VPU);
+static struct rda_clk_hw rda8810_clk_voc_desc = RDA_CLK_INIT(CLK_VOC, "voc",
+		(const struct clk_parent_data[]) { { .hw = &rda8810_clk_bus_desc.hw } },
+		0, 0xEC, AP_REG_APO_ENABLE,
+		AP_ENABLE_APOC_VOC | AP_ENABLE_APOC_VOC_CORE | AP_ENABLE_APOC_VOC_ALWAYS);
+
+/* APB1 peripherals */
+static struct rda_clk_hw rda8810_clk_spiflash_desc = RDA_CLK_INIT(CLK_SFLSH, "spiflash",
+		(const struct clk_parent_data[]) { { .hw = &rda8810_clk_apb1_desc.hw } },
+		0, 0xF0, AP_REG_APO_ENABLE, AP_ENABLE_APOC_SPIFLASH);
+
+/* APB2 peripherals */
+static struct rda_clk_hw rda8810_clk_uart_desc[] = {
+	RDA_CLK_INIT(CLK_UART1, "uart1",
+			(const struct clk_parent_data[]) { { .hw = &rda8810_clk_apb2_desc.hw } },
+			0, 0xF4,
+			AP_REG_APO_ENABLE, AP_ENABLE_APOC_UART1),
+	RDA_CLK_INIT(CLK_UART2, "uart2",
+			(const struct clk_parent_data[]) { { .hw = &rda8810_clk_apb2_desc.hw } },
+			0, 0xF8,
+			AP_REG_APO_ENABLE, AP_ENABLE_APOC_UART2),
+	RDA_CLK_INIT(CLK_UART3, "uart3",
+			(const struct clk_parent_data[]) { { .hw = &rda8810_clk_apb2_desc.hw } },
+			0, 0xFC,
+			AP_REG_APO_ENABLE, AP_ENABLE_APOC_UART3),
+};
+
+static struct rda_clk_hw *const rda8810_clk_list[] = {
+	&rda8810_clk_cpu_desc,
+	&rda8810_clk_bus_desc,
+	&rda8810_clk_mem_desc,
+
+	&rda8810_clk_usb_desc,
+	&rda8810_clk_axi_desc,
+	&rda8810_clk_gcg_desc,
+	&rda8810_clk_ahb1_desc,
+	&rda8810_clk_apb1_desc,
+	&rda8810_clk_apb2_desc,
+
+	&rda8810_clk_gpu_desc,
+	&rda8810_clk_vpu_desc,
+	&rda8810_clk_voc_desc,
+
+	&rda8810_clk_spiflash_desc,
+
+	&rda8810_clk_uart_desc[0],
+	&rda8810_clk_uart_desc[1],
+	&rda8810_clk_uart_desc[2],
+};
+
+static const struct rda8810_reset_list rda8810_rst_data[] = {
+	/* ID, REG */
+
+	/* CPU */
+	[RST_CPU] = { AP_RST_CPU_REG, AP_RST_CPU_CORE },
+
+	/* AXI */
+	[RST_AXI_VOC] = { AP_RST_AXI_REG, AP_RST_AXI_VOC },
+	[RST_AXI_DMA] = { AP_RST_AXI_REG, AP_RST_AXI_DMA },
+	[RST_AXI_CONNECT] = { AP_RST_AXI_REG, AP_RST_AXI_CONNECT },
+	[RST_AXI_VPU] = { AP_RST_AXI_REG, AP_RST_AXI_VPU },
+
+	/* GCG */
+	[RST_GCG_GOUDA] = { AP_RST_GCG_REG, AP_RST_GCG_GOUDA },
+	[RST_GCG_CAMERA] = { AP_RST_GCG_REG, AP_RST_GCG_CAMERA },
+	[RST_GCG_LCDC] = { AP_RST_GCG_REG, AP_RST_GCG_LCDC },
+
+	/* AHB1 */
+	[RST_AHB1_USBC] = { AP_RST_AHB1_REG, AP_RST_AHB1_USBC },
+	[RST_AHB1_SPIFLASH] = { AP_RST_AHB1_REG, AP_RST_AHB1_SPIFLASH },
+
+	/* APB1 */
+	[RST_APB1_TIMER] = { AP_RST_APB1_REG, AP_RST_APB1_TIMER },
+	[RST_APB1_KEYPAD] = { AP_RST_APB1_REG, AP_RST_APB1_KEYPAD },
+	[RST_APB1_GPIO] = { AP_RST_APB1_REG, AP_RST_APB1_GPIO },
+	[RST_APB1_PWM] = { AP_RST_APB1_REG, AP_RST_APB1_PWM },
+	[RST_APB1_AIF] = { AP_RST_APB1_REG, AP_RST_APB1_AIF },
+	[RST_APB1_AUIFC] = { AP_RST_APB1_REG, AP_RST_APB1_AUIFC },
+	[RST_APB1_I2C1] = { AP_RST_APB1_REG, AP_RST_APB1_I2C1 },
+	[RST_APB1_I2C2] = { AP_RST_APB1_REG, AP_RST_APB1_I2C2 },
+	[RST_APB1_I2C3] = { AP_RST_APB1_REG, AP_RST_APB1_I2C3 },
+	[RST_APB1_COMREGS] = { AP_RST_APB1_REG, AP_RST_APB1_COMREGS },
+	[RST_APB1_DMC] = { AP_RST_APB1_REG, AP_RST_APB1_DMC },
+	[RST_APB1_DDRPHY_P] = { AP_RST_APB1_REG, AP_RST_APB1_DDRPHY_P },
+
+	/* APB2 */
+	[RST_APB2_IFC] = { AP_RST_APB2_REG, AP_RST_APB2_IFC },
+	[RST_APB2_UART1] = { AP_RST_APB2_REG, AP_RST_APB2_UART1 },
+	[RST_APB2_UART2] = { AP_RST_APB2_REG, AP_RST_APB2_UART2 },
+	[RST_APB2_UART3] = { AP_RST_APB2_REG, AP_RST_APB2_UART3 },
+	[RST_APB2_SPI1] = { AP_RST_APB2_REG, AP_RST_APB2_SPI1 },
+	[RST_APB2_SPI2] = { AP_RST_APB2_REG, AP_RST_APB2_SPI2 },
+	[RST_APB2_SPI3] = { AP_RST_APB2_REG, AP_RST_APB2_SPI3 },
+	[RST_APB2_SDMMC1] = { AP_RST_APB2_REG, AP_RST_APB2_SDMMC1 },
+	[RST_APB2_SDMMC2] = { AP_RST_APB2_REG, AP_RST_APB2_SDMMC2 },
+	[RST_APB2_SDMMC3] = { AP_RST_APB2_REG, AP_RST_APB2_SDMMC3 },
+	[RST_APB2_NAND] = { AP_RST_APB2_REG, AP_RST_APB2_NANDFLASH },
+
+	/* MEM */
+	[RST_MEM_GPU] = { AP_RST_MEM_REG, AP_RST_MEM_GPU },
+	[RST_MEM_VPU] = { AP_RST_MEM_REG, AP_RST_MEM_VPU },
+	[RST_MEM_DMC] = { AP_RST_MEM_REG, AP_RST_MEM_DMC },
+	[RST_MEM_DDRPHY_P] = { AP_RST_MEM_REG, AP_RST_MEM_DDRPHY_P },
+};
+
+static int rda8810_reset_assert(struct reset_controller_dev *rstctl, unsigned long id)
+{
+	struct rda_clk_priv *priv = to_rda_rst(rstctl);
+
+	return regmap_write(priv->regmap, rda8810_rst_data[id].reg, rda8810_rst_data[id].bit);
+}
+
+static int rda8810_reset_deassert(struct reset_controller_dev *rstctl, unsigned long id)
+{
+	struct rda_clk_priv *priv = to_rda_rst(rstctl);
+
+	return regmap_write(priv->regmap, rda8810_rst_data[id].reg + 4, rda8810_rst_data[id].bit);
+}
+
+static const struct reset_control_ops rda8810_rst_ops = {
+	.assert = &rda8810_reset_assert,
+	.deassert = &rda8810_reset_deassert,
+};
+
+static int rda8810_clk_register(struct rda_clk_priv *priv)
+{
+	struct device *dev = priv->dev;
+	struct clk_hw_onecell_data *onecell_data;
+	int ret;
+	int i;
+
+	onecell_data = devm_kzalloc(dev,
+			struct_size(onecell_data, hws, ARRAY_SIZE(rda8810_clk_list)),
+			GFP_KERNEL);
+	if (!onecell_data)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(rda8810_clk_list); i++) {
+		rda8810_clk_list[i]->priv = priv;
+
+		ret = devm_clk_hw_register(dev, &rda8810_clk_list[i]->hw);
+		if (ret) {
+			dev_err(dev, "Failed to register clock: %d\n", ret);
+			return ret;
+		}
+		onecell_data->hws[i] = &rda8810_clk_list[i]->hw;
+	}
+	onecell_data->num = i;
+	priv->onecell = onecell_data;
+
+	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, onecell_data);
+}
+
+static int rda8810_rst_register(struct rda_clk_priv *priv)
+{
+	struct device *dev = priv->dev;
+
+	priv->rstctl.dev = priv->dev;
+	priv->rstctl.nr_resets = 37;
+	priv->rstctl.of_node = priv->dev->of_node;
+	priv->rstctl.ops = &rda8810_rst_ops;
+	priv->rstctl.owner = THIS_MODULE;
+
+	return devm_reset_controller_register(dev, &priv->rstctl);
+}
+
+static int rda8810_clk_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rda_clk_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return dev_err_probe(dev, -ENOMEM, "Cannot allocate memory\n");
+
+	priv->dev = dev;
+
+	priv->regmap = syscon_node_to_regmap(dev->of_node);
+	if (IS_ERR(priv->regmap))
+		return dev_err_probe(dev, -ENOMEM, "Cannot initialize regmap\n");
+
+	ret = rda8810_clk_register(priv);
+	if (ret)
+		return dev_err_probe(dev, -EINVAL, "Failed to setup clock: %d\n", ret);
+
+	ret = rda8810_rst_register(priv);
+	if (ret)
+		return dev_err_probe(dev, -EINVAL, "Failed to setup reset: %d\n", ret);
+
+	platform_set_drvdata(pdev, priv);
+
+	return 0;
+}
+
+static const struct of_device_id rda8810_clk_of_match_table[] = {
+	{ .compatible = "rda,8810pl-apsyscon", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rda8810_clk_of_match_table);
+
+static struct platform_driver rda8810_clk_driver = {
+	.probe = rda8810_clk_probe,
+	.driver = {
+		.name = "rda8810-clk",
+		.of_match_table = rda8810_clk_of_match_table,
+	},
+};
+module_platform_driver(rda8810_clk_driver);
+
+MODULE_AUTHOR("Dang Huynh <dang.huynh@mainlining.org>");
+MODULE_DESCRIPTION("RDA8810PL clock and reset driver");
+MODULE_LICENSE("GPL");

-- 
2.51.0



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

* [PATCH 08/10] dmaengine: Add RDA IFC driver
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
                   ` (6 preceding siblings ...)
  2025-09-18 18:48 ` [PATCH 07/10] clk: Add Clock and Reset Driver for RDA Micro RDA8810PL SoC Dang Huynh via B4 Relay
@ 2025-09-18 18:48 ` Dang Huynh via B4 Relay
  2025-09-21  4:42   ` kernel test robot
  2025-09-18 18:48 ` [PATCH 09/10] mmc: host: Add RDA Micro SD/MMC driver Dang Huynh via B4 Relay
                   ` (2 subsequent siblings)
  10 siblings, 1 reply; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh

From: Dang Huynh <dang.huynh@mainlining.org>

IFC stands for Intelligent Flow Control, a scatter/gather DMA
controller.

Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
 MAINTAINERS           |   6 +
 drivers/dma/Kconfig   |  10 ++
 drivers/dma/Makefile  |   1 +
 drivers/dma/rda-ifc.c | 450 ++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 467 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 56f9d19fbf421eefffe554987e14604c764daab2..923101a9d6c2edea339d1211b1cfdf4b917d1208 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21424,6 +21424,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/clock/rda,8810pl-apsyscon.yaml
 F:	drivers/clk/rda/clk-rda8810.c
 
+RDA MICRO INTELLIGENT FLOW CONTROLLER DRIVER
+M:	Dang Huynh <dang.huynh@mainlining.org>
+S:	Maintained
+F:	Documentation/devicetree/bindings/dma/rda,ifc.yaml
+F:	drivers/dma/rda-ifc.c
+
 RDACM20 Camera Sensor
 M:	Jacopo Mondi <jacopo+renesas@jmondi.org>
 M:	Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index b8a74b1798ba1d44b26553990428c065de6fc535..b6b94cb053bd0d6a15d3c603bfab9b515020fad8 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -571,6 +571,16 @@ config PLX_DMA
 	  These are exposed via extra functions on the switch's
 	  upstream port. Each function exposes one DMA channel.
 
+config RDA_IFC
+	bool "RDA IFC support"
+	depends on ARCH_RDA || COMPILE_TEST
+	select DMA_ENGINE
+	help
+	  Support RDA Intelligent Flow Controller for RDA Micro SoC.
+	  The Intelligent Flow Controller is a scatter/gather DMA controller.
+
+	  If unsure, say N.
+
 config SOPHGO_CV1800B_DMAMUX
 	tristate "Sophgo CV1800/SG2000 series SoC DMA multiplexer support"
 	depends on MFD_SYSCON
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index a54d7688392b1a0e956fa5d23633507f52f017d9..40f6c61dcce739f3ffd064fbdc23388cfca83184 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_PL330_DMA) += pl330.o
 obj-$(CONFIG_PLX_DMA) += plx_dma.o
 obj-$(CONFIG_PPC_BESTCOMM) += bestcomm/
 obj-$(CONFIG_PXA_DMA) += pxa_dma.o
+obj-$(CONFIG_RDA_IFC) += rda-ifc.o
 obj-$(CONFIG_RENESAS_DMA) += sh/
 obj-$(CONFIG_SF_PDMA) += sf-pdma/
 obj-$(CONFIG_SOPHGO_CV1800B_DMAMUX) += cv1800b-dmamux.o
diff --git a/drivers/dma/rda-ifc.c b/drivers/dma/rda-ifc.c
new file mode 100644
index 0000000000000000000000000000000000000000..ff7f59876a5895fbdc1adf584e11519bcfcfdb11
--- /dev/null
+++ b/drivers/dma/rda-ifc.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RDA Micro Intelligent Flow Controller
+ *
+ * Copyright (C) 2013 RDA Microelectronics Inc.
+ * Copyright (C) 2025 Dang Huynh <dang.huynh@mainlining.org>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_dma.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/scatterlist.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include "dmaengine.h"
+
+#include <dt-bindings/dma/rda-ifc.h>
+
+/* Registers */
+#define IFC_REG_GET_CH 0x0
+#define IFC_REG_DMA_STATUS 0x4
+#define IFC_REG_DEBUG_STATUS 0x8
+
+/* Channel registers */
+#define IFC_REG_SG_CONTROL 0x0
+#define IFC_REG_SG_STATUS 0x4
+#define IFC_REG_SG_START_ADDR 0x8
+#define IFC_REG_SG_TC 0xC
+
+#define IFC_REG_CH_RFSPI_CONTROL 0x80
+#define IFC_REG_CH_RFSPI_STATUS 0x84
+#define IFC_REG_CH_RFSPI_START_ADDR 0x88
+#define IFC_REG_CH_RFSPI_END_ADDR 0x8C
+#define IFC_REG_CH_RFSPI_TC 0x90
+
+/* Bits */
+/* DMA_STATUS */
+#define IFC_DMA_CH_ENABLE GENMASK(8, 0)
+#define IFC_DMA_CH_BUSY GENMASK(23, 16)
+/* DEBUG_STATUS */
+#define IFC_DBG_STATUS BIT(0)
+/* CONTROL */
+#define IFC_CTL_ENABLE BIT(0)
+#define IFC_CTL_DISABLE BIT(1)
+#define IFC_CTL_CH_RD_HW_EXCH BIT(2)
+#define IFC_CTL_CH_WR_HW_EXCH BIT(3)
+#define IFC_CTL_AUTODISABLE BIT(4)
+#define IFC_CTL_SIZE GENMASK(7, 5) /* byte: 0 - halfword: 1 - word: 2 */
+#define IFC_CTL_REQ_SRC GENMASK(15, 8)
+#define IFC_CTL_FLUSH BIT(16)
+#define IFC_CTL_SG_NUM GENMASK(24, 17)
+/* STATUS */
+#define IFC_STATUS_ENABLE BIT(0)
+#define IFC_STATUS_FIFO_EMPTY BIT(4)
+
+/*
+ * An available IFC channel can be obtained by reading IFC_REG_GET_CH register,
+ * if no channel are available the register reads 0xF.
+ *
+ * Memory map for each channel (starts at 0x10):
+ *
+ * [IFC_REG_SG_CONTROL]
+ * [IFC_REG_SG_STATUS]
+ * [IFC_REG_SG_START_ADDR] (1st sg_table)
+ * [IFC_REG_SG_TC]
+ * ...
+ *
+ * Depends on the hardware, it might support more than one sg table. If it
+ * does, the next sg table is right next to previous table.
+ *
+ * The next channel is right after the memory map above.
+ *
+ * The DMA channel MUST be disabled after the transaction is done or some IP
+ * might misbehaves.
+ */
+
+struct rda_ifc_chan {
+	struct rda_ifc *rda_ifc;
+	void __iomem *chan_base;
+	spinlock_t lock;
+
+	struct dma_chan chan;
+	unsigned int request_id;
+
+	enum dma_transfer_direction direction;
+	struct dma_slave_config sconfig;
+	struct dma_async_tx_descriptor tx;
+};
+
+struct rda_ifc {
+	struct device *dev;
+	void __iomem *base;
+
+	struct dma_device ddev;
+
+	int sg_max;
+	int max_chan;
+	struct rda_ifc_chan channels[] __counted_by(max_chan);
+};
+
+struct rda_ifc_platinfo {
+	int sg_max;
+	int std_channb;
+};
+
+static struct rda_ifc_chan *to_ifc_chan(struct dma_chan *chan)
+{
+	return container_of(chan, struct rda_ifc_chan, chan);
+}
+
+static int rda_ifc_device_config(struct dma_chan *chan, struct dma_slave_config *config)
+{
+	struct rda_ifc_chan *ifc_chan = to_ifc_chan(chan);
+
+	ifc_chan->direction = (ifc_chan->request_id & 1) ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV;
+	memcpy(&ifc_chan->sconfig, config, sizeof(*config));
+
+	return 0;
+}
+
+static void rda_ifc_issue_pending(struct dma_chan *chan)
+{
+	struct rda_ifc_chan *ifc_chan = to_ifc_chan(chan);
+	unsigned long flags;
+	u32 control;
+
+	spin_lock_irqsave(&ifc_chan->lock, flags);
+
+	control = readl(ifc_chan->chan_base);
+	control |= IFC_CTL_ENABLE;
+	writel(control, ifc_chan->chan_base);
+
+	spin_unlock_irqrestore(&ifc_chan->lock, flags);
+}
+
+static dma_cookie_t rda_ifc_tx_submit(struct dma_async_tx_descriptor *tx)
+{
+	return dma_cookie_assign(tx);
+}
+
+static struct dma_async_tx_descriptor *rda_ifc_prep_slave_sg(struct dma_chan *chan,
+		struct scatterlist *sgl, unsigned int sg_len,
+		enum dma_transfer_direction direction, unsigned long dma_flags,
+		void *context)
+{
+	struct rda_ifc_chan *ifc_chan = to_ifc_chan(chan);
+	struct rda_ifc *ifc = ifc_chan->rda_ifc;
+	struct device *dev = dmaengine_get_dma_device(chan);
+	struct scatterlist *sg;
+	unsigned long flags;
+	u32 control = 0;
+	int width;
+	int i;
+
+	if (sg_len > ifc->sg_max) {
+		dev_err(dev, "sg_len %d overflowed (max sg %d)\n",
+				sg_len, ifc->sg_max);
+		return NULL;
+	}
+
+	if (direction != ifc_chan->direction) {
+		dev_err(dev, "Inconsistent transfer direction\n");
+		return NULL;
+	}
+
+	spin_lock_irqsave(&ifc_chan->lock, flags);
+
+	if (ifc_chan->direction == DMA_DEV_TO_MEM)
+		width = ifc_chan->sconfig.src_addr_width;
+	else
+		width = ifc_chan->sconfig.dst_addr_width;
+
+	switch (width) {
+	case DMA_SLAVE_BUSWIDTH_1_BYTE:
+		control |= FIELD_PREP(IFC_CTL_SIZE, 0);
+		break;
+	case DMA_SLAVE_BUSWIDTH_2_BYTES:
+		control |= FIELD_PREP(IFC_CTL_SIZE, 1);
+		break;
+	case DMA_SLAVE_BUSWIDTH_4_BYTES:
+		control |= FIELD_PREP(IFC_CTL_SIZE, 2);
+		break;
+	default:
+		return NULL;
+	}
+
+	for_each_sg(sgl, sg, sg_len, i) {
+		if (!IS_ALIGNED(sg_dma_address(sg), width)) {
+			dev_err(dev, "Unaligned DMA address\n");
+			spin_unlock_irqrestore(&ifc_chan->lock, flags);
+			return NULL;
+		}
+
+		writel(sg_dma_address(sg), ifc_chan->chan_base + IFC_REG_SG_START_ADDR + (8 * i));
+		writel(sg_dma_len(sg), ifc_chan->chan_base + IFC_REG_SG_TC + (8 * i));
+	}
+
+	control |= FIELD_PREP(IFC_CTL_REQ_SRC, ifc_chan->request_id) |
+		IFC_CTL_CH_RD_HW_EXCH |
+		FIELD_PREP(IFC_CTL_SG_NUM, sg_len-1);
+	writel(control, ifc_chan->chan_base);
+
+	spin_unlock_irqrestore(&ifc_chan->lock, flags);
+
+	dma_async_tx_descriptor_init(&ifc_chan->tx, chan);
+	ifc_chan->tx.tx_submit = rda_ifc_tx_submit;
+
+	return &ifc_chan->tx;
+}
+
+static enum dma_status rda_ifc_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
+		struct dma_tx_state *tx_state)
+{
+	struct rda_ifc_chan *ifc_chan = to_ifc_chan(chan);
+	enum dma_status dmaret;
+	unsigned long flags;
+	u32 status;
+	int residue = 0;
+	int tmp_residue = 0;
+	int i;
+
+	dmaret = dma_cookie_status(chan, cookie, tx_state);
+	if (!tx_state || (dmaret == DMA_COMPLETE))
+		return dmaret;
+
+	spin_lock_irqsave(&ifc_chan->lock, flags);
+
+	status = readl(ifc_chan->chan_base + 4);
+
+	if (status & IFC_STATUS_FIFO_EMPTY)
+		dmaret = DMA_COMPLETE;
+	else
+		dmaret = DMA_IN_PROGRESS;
+
+	if (dmaret == DMA_IN_PROGRESS) {
+		/* gather residue from all sg */
+		for (i = 0; i < ifc_chan->rda_ifc->sg_max; i++) {
+			tmp_residue = readl(ifc_chan->chan_base + 12 + (8 * i));
+			residue += tmp_residue;
+		}
+
+		dma_set_residue(tx_state, residue);
+	}
+
+	spin_unlock_irqrestore(&ifc_chan->lock, flags);
+
+	return dmaret;
+}
+
+static int rda_ifc_terminate_all(struct dma_chan *chan)
+{
+	struct rda_ifc_chan *ifc_chan = to_ifc_chan(chan);
+	struct device *dev = dmaengine_get_dma_device(chan);
+	unsigned long flags;
+	u32 status, control;
+	int ret;
+	int i;
+
+	spin_lock_irqsave(&ifc_chan->lock, flags);
+
+	status = readl(ifc_chan->chan_base + 4);
+
+	/* Flush operation only supports read requests */
+	if (ifc_chan->direction == DMA_DEV_TO_MEM) {
+		if (status & IFC_STATUS_FIFO_EMPTY)
+			goto clear_chan;
+
+		control = readl(ifc_chan->chan_base);
+		control |= IFC_CTL_FLUSH;
+		writel(control, ifc_chan->chan_base);
+
+		ret = readl_poll_timeout(ifc_chan->chan_base + 4, status,
+				(status & IFC_STATUS_FIFO_EMPTY), 100, 1000*1000);
+		if (ret < 0)
+			dev_err(dev, "Timed out flushing FIFO\n");
+	}
+
+clear_chan:
+	control = readl(ifc_chan->chan_base);
+	control |= IFC_CTL_DISABLE;
+	writel(control, ifc_chan->chan_base);
+
+	for (i = 0; i < ifc_chan->rda_ifc->sg_max; i++)
+		writel(0, ifc_chan->chan_base + 12 + (8 * i));
+
+	spin_unlock_irqrestore(&ifc_chan->lock, flags);
+	return 0;
+}
+
+static int rda_ifc_chan_init(struct rda_ifc *ifc, struct dma_device *ddev,
+		int id)
+{
+	struct rda_ifc_chan *chan = &ifc->channels[id];
+
+	spin_lock_init(&chan->lock);
+	chan->rda_ifc = ifc;
+	chan->chan.chan_id = id;
+	chan->chan.device = &ifc->ddev;
+	chan->chan_base = ifc->base + 0x10 + (id * (8 + (8 * ifc->sg_max)));
+
+	list_add_tail(&chan->chan.device_node, &ddev->channels);
+	return 0;
+}
+
+static int rda_ifc_ddev_init(struct rda_ifc *ifc, struct dma_device *ddev)
+{
+	int ret;
+	int i;
+
+	dma_cap_zero(ddev->cap_mask);
+	dma_cap_set(DMA_SLAVE, ddev->cap_mask);
+	dma_cap_set(DMA_PRIVATE, ddev->cap_mask);
+
+	/* IFC maximum segment size is 32 MB */
+	dma_set_max_seg_size(ddev->dev, 0x1FFFFFF);
+	dma_set_mask_and_coherent(ddev->dev, DMA_BIT_MASK(32));
+
+	/* IFC supports 8-bit and 32-bit transfers */
+	ddev->copy_align = DMAENGINE_ALIGN_4_BYTES;
+	ddev->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
+		BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
+		BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+	ddev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
+		BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
+		BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+	ddev->directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM);
+
+	ddev->device_config = rda_ifc_device_config;
+	ddev->device_issue_pending = rda_ifc_issue_pending;
+	ddev->device_prep_slave_sg = rda_ifc_prep_slave_sg;
+	ddev->device_terminate_all = rda_ifc_terminate_all;
+	ddev->device_tx_status = rda_ifc_tx_status;
+	ddev->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
+
+	INIT_LIST_HEAD(&ddev->channels);
+	for (i = 0; i < ifc->max_chan; i++) {
+		ret = rda_ifc_chan_init(ifc, ddev, i);
+		if (ret)
+			return ret;
+	}
+	ddev->chancnt = i;
+
+	return 0;
+}
+
+static struct dma_chan *rda_ifc_xlate(struct of_phandle_args *dma_spec,
+		struct of_dma *of_dma)
+{
+	struct rda_ifc *ifc = of_dma->of_dma_data;
+	struct rda_ifc_chan *ifc_chan;
+	struct dma_chan *chan;
+	unsigned int request;
+
+	if (dma_spec->args_count != 1)
+		return NULL;
+
+	request = dma_spec->args[0];
+	if (request >= IFC_NO_REQUEST)
+		return NULL;
+
+	chan = dma_get_any_slave_channel(&ifc->ddev);
+	if (!chan)
+		return NULL;
+
+	ifc_chan = to_ifc_chan(chan);
+	ifc_chan->request_id = request;
+
+	return chan;
+}
+
+static int rda_ifc_probe(struct platform_device *pdev)
+{
+	const struct rda_ifc_platinfo *platinfo;
+	struct rda_ifc *ifc;
+	struct dma_device *ddev;
+	int ret;
+
+	platinfo = of_device_get_match_data(&pdev->dev);
+	if (!platinfo)
+		return -EINVAL;
+
+	ifc = devm_kzalloc(&pdev->dev,
+			struct_size(ifc, channels, platinfo->std_channb),
+			GFP_KERNEL);
+	if (!ifc)
+		return dev_err_probe(&pdev->dev, -ENOMEM, "Failed to allocate memory\n");
+
+	ifc->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(ifc->base))
+		return dev_err_probe(&pdev->dev, PTR_ERR(ifc->base), "Cannot get base address\n");
+
+	ifc->max_chan = platinfo->std_channb;
+	ifc->sg_max = platinfo->sg_max;
+
+	ddev = &ifc->ddev;
+	ddev->dev = &pdev->dev;
+	ret = rda_ifc_ddev_init(ifc, ddev);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, ifc);
+
+	ret = dma_async_device_register(ddev);
+	if (ret)
+		return ret;
+
+	ret = of_dma_controller_register(pdev->dev.of_node, rda_ifc_xlate, ifc);
+	if (ret < 0)
+		return dev_err_probe(&pdev->dev, ret, "Cannot register DMA controller\n");
+
+	return 0;
+}
+
+static void rda_ifc_remove(struct platform_device *pdev)
+{
+	struct rda_ifc *ifc = platform_get_drvdata(pdev);
+
+	dma_async_device_unregister(&ifc->ddev);
+	of_dma_controller_free(pdev->dev.of_node);
+}
+
+static const struct rda_ifc_platinfo rda8810pl_data = {
+	.sg_max = 1,
+	.std_channb = 7
+};
+
+static const struct of_device_id rda_ifc_of_match[] = {
+	{ .compatible = "rda,8810pl-ifc", .data = &rda8810pl_data },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rda_ifc_of_match);
+
+static struct platform_driver rda_ifc_driver = {
+	.probe = rda_ifc_probe,
+	.remove = rda_ifc_remove,
+	.driver = {
+		.name = "rda-ifc",
+		.of_match_table = rda_ifc_of_match,
+	},
+};
+module_platform_driver(rda_ifc_driver);
+
+MODULE_AUTHOR("Dang Huynh <dang.huynh@mainlining.org>");
+MODULE_DESCRIPTION("RDA IFC driver");
+MODULE_LICENSE("GPL");

-- 
2.51.0



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

* [PATCH 09/10] mmc: host: Add RDA Micro SD/MMC driver
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
                   ` (7 preceding siblings ...)
  2025-09-18 18:48 ` [PATCH 08/10] dmaengine: Add RDA IFC driver Dang Huynh via B4 Relay
@ 2025-09-18 18:48 ` Dang Huynh via B4 Relay
  2025-10-17 10:25   ` Ulf Hansson
  2025-09-18 18:48 ` [PATCH 10/10] ARM: dts: unisoc: rda8810pl: Add SDMMC controllers Dang Huynh via B4 Relay
  2025-09-22 14:17 ` [PATCH 00/10] RDA8810PL SD/MMC support Bartosz Golaszewski
  10 siblings, 1 reply; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh

From: Dang Huynh <dang.huynh@mainlining.org>

RDA Micro RDA8810PL includes an SD/MMC controller. This controller
supports SD/SDIO/MMC interface.

Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
 MAINTAINERS                |   6 +
 drivers/mmc/host/Kconfig   |  12 +
 drivers/mmc/host/Makefile  |   1 +
 drivers/mmc/host/rda-mmc.c | 853 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 872 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 923101a9d6c2edea339d1211b1cfdf4b917d1208..442dc8218541c1c05c03383f13b3f062f06cdae9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21430,6 +21430,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/dma/rda,ifc.yaml
 F:	drivers/dma/rda-ifc.c
 
+RDA MICRO SECURE DIGITAL AND MULTIMEDIA CARD DRIVER
+M:	Dang Huynh <dang.huynh@mainlining.org>
+S:	Maintained
+F:	Documentation/devicetree/bindings/mmc/rda,mmc.yaml
+F:	drivers/mmc/host/rda-mmc.c
+
 RDACM20 Camera Sensor
 M:	Jacopo Mondi <jacopo+renesas@jmondi.org>
 M:	Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 4afa0130779d97ca9d1c0ed2102b0babdedcaeeb..e7e747ef9a860cbe88dc8fac1015a915a62f10d3 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -1040,6 +1040,18 @@ config MMC_MTK
 	  This is needed if support for any SD/SDIO/MMC devices is required.
 	  If unsure, say N.
 
+config MMC_RDA
+	tristate "RDA Micro SD/MMC Card Interface support"
+	depends on ARCH_RDA || COMPILE_TEST
+	depends on COMMON_CLK
+	depends on HAS_DMA
+	help
+	  This selects the RDA Micro Secure digital and Multimedia card interface. The
+	  controller supports SD/SDIO/MMC interface.
+	  If you have a board with RDA SoC and it uses this interface, say Y or M here.
+
+	  If unsure, say N.
+
 config MMC_SDHCI_MICROCHIP_PIC32
 	tristate "Microchip PIC32MZDA SDHCI support"
 	depends on MMC_SDHCI && PIC32MZDA && MMC_SDHCI_PLTFM
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 5057fea8afb696e210e465a6a2aafc68adad7854..d819e18a478e35cb7de6d67b1cf827e1b3d09815 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_MMC_ALCOR)	+= alcor.o
 obj-$(CONFIG_MMC_MTK)		+= mtk-sd.o
 obj-$(CONFIG_MMC_OMAP)		+= omap.o
 obj-$(CONFIG_MMC_OMAP_HS)	+= omap_hsmmc.o
+obj-$(CONFIG_MMC_RDA)		+= rda-mmc.o
 obj-$(CONFIG_MMC_ATMELMCI)	+= atmel-mci.o
 obj-$(CONFIG_MMC_TIFM_SD)	+= tifm_sd.o
 obj-$(CONFIG_MMC_MVSDIO)	+= mvsdio.o
diff --git a/drivers/mmc/host/rda-mmc.c b/drivers/mmc/host/rda-mmc.c
new file mode 100644
index 0000000000000000000000000000000000000000..c358d170a930cbb7bf93a9066044c3b7ac957229
--- /dev/null
+++ b/drivers/mmc/host/rda-mmc.c
@@ -0,0 +1,853 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SD/MMC driver for RDA Micro platform
+ *
+ * Copyright (C) 2013 RDA Microelectronics Inc.
+ * Copyright (c) 2025 Dang Huynh <dang.huynh@mainlining.org>
+ */
+
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/iopoll.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/slot-gpio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/debugfs.h>
+#include <linux/scatterlist.h>
+#include <linux/spinlock.h>
+
+/* Registers Definitions */
+#define SDMMC_REG_CTRL 0x0
+#define SDMMC_REG_FIFO_TXRX 0x8
+#define SDMMC_REG_CONFIG 0x800
+#define SDMMC_REG_STATUS 0x804
+#define SDMMC_REG_CMD_INDEX 0x808
+#define SDMMC_REG_CMD_ARG 0x80C
+#define SDMMC_REG_RESP_INDEX 0x810
+#define SDMMC_REG_RESP_ARG3 0x814
+#define SDMMC_REG_RESP_ARG2 0x818
+#define SDMMC_REG_RESP_ARG1 0x81C
+#define SDMMC_REG_RESP_ARG0 0x820
+#define SDMMC_REG_DATA_WIDTH 0x824
+#define SDMMC_REG_BLOCK_SIZE 0x828
+#define SDMMC_REG_BLOCK_COUNT 0x82C
+#define SDMMC_REG_INT_STATUS 0x830
+#define SDMMC_REG_INT_MASK 0x834
+#define SDMMC_REG_INT_CLEAR 0x838
+#define SDMMC_REG_TRANS_SPEED 0x83C
+#define SDMMC_REG_MCLK_ADJUST 0x840
+
+/* Bits def */
+/* CTRL */
+#define SDMMC_CTRL_ENDIAN GENMASK(2, 0)
+#define SDMMC_CTRL_SOFTRST_L BIT(3)
+
+/* CONFIG */
+#define SDMMC_CFG_SENDCMD BIT(0)
+#define SDMMC_CFG_SUSPEND BIT(1)
+#define SDMMC_CFG_RSP_EN BIT(4)
+#define SDMMC_CFG_RSP_SEL GENMASK(6, 5)
+#define SDMMC_CFG_RD_WT_EN BIT(8)
+#define SDMMC_CFG_RD_WT_SEL BIT(9)
+#define SDMMC_CFG_S_M_SEL BIT(10)
+#define SDMMC_CFG_AUTO_FLAG_EN BIT(16)
+#define SDMMC_CFG_SAMPLE_EDGE_SEL_FALL_EN BIT(17)
+
+/* STATUS */
+#define SDMMC_STATUS_NOTOVER BIT(0)
+#define SDMMC_STATUS_BUSY BIT(1)
+#define SDMMC_STATUS_DLBUSY BIT(2)
+#define SDMMC_STATUS_SUSPEND BIT(3)
+#define SDMMC_STATUS_RSP_ERR BIT(8)
+#define SDMMC_STATUS_NO_RSP_ERR BIT(9)
+#define SDMMC_STATUS_CRC_STATUS GENMASK(14, 12)
+#define SDMMC_STATUS_DATA_ERROR GENMASK(23, 16)
+#define SDMMC_STATUS_DAT3_VAL BIT(24)
+
+/* INTERRUPTS */
+/* Mask and Clear */
+#define SDMMC_INT_NO_RSP BIT(0)
+#define SDMMC_INT_RSP_ERR BIT(1)
+#define SDMMC_INT_RD_ERR BIT(2)
+#define SDMMC_INT_WR_ERR BIT(3)
+#define SDMMC_INT_DAT_OVER BIT(4)
+#define SDMMC_INT_TXDMA_DONE BIT(5)
+#define SDMMC_INT_RXDMA_DONE BIT(6)
+#define SDMMC_INT_SDIO BIT(7)
+
+#define SDMMC_MCLK_INVERT BIT(4)
+#define SDMMC_MCLK_DISABLE BIT(5)
+
+struct rda_mmc_host {
+	struct device *dev;
+
+	struct mmc_host *mmc;
+	struct mmc_request *mrq;
+
+	unsigned int clock;
+	unsigned int bus_width;
+	unsigned int power_mode;
+	struct regulator *vmmc;
+
+	void __iomem *base;
+	int irq;
+
+	struct clk *clk;
+	struct reset_control *reset;
+
+	dma_cookie_t dma_cookie;
+	struct dma_chan *dma_tx;
+	struct dma_chan *dma_rx;
+
+	bool sdio_irq;
+	bool sdio_irq_trigger;
+
+	spinlock_t lock;
+	struct completion c;
+
+	/* device tree properties */
+	bool mclk_inv;
+	u8 mclk_adj;
+};
+
+static int rda_mmc_hw_init(struct rda_mmc_host *priv)
+{
+	void __iomem *base = priv->base;
+
+	disable_irq(priv->irq);
+
+	writel(FIELD_PREP(SDMMC_CTRL_ENDIAN, 1) | SDMMC_CTRL_SOFTRST_L,
+			base + SDMMC_REG_CTRL);
+	writel(SDMMC_INT_RD_ERR | SDMMC_INT_WR_ERR | SDMMC_INT_DAT_OVER,
+			base + SDMMC_REG_INT_MASK);
+	writel(0xFFFFFFFF, base + SDMMC_REG_INT_CLEAR);
+
+	enable_irq(priv->irq);
+
+	return 0;
+}
+
+static void rda_mmc_reset(struct rda_mmc_host *priv)
+{
+	reset_control_assert(priv->reset);
+	mdelay(1);
+	reset_control_deassert(priv->reset);
+	mdelay(1);
+}
+
+static void rda_mmc_recv_resp(struct mmc_host *host, struct mmc_command *cmd)
+{
+	struct rda_mmc_host *priv = mmc_priv(host);
+	struct device *dev = mmc_dev(host);
+	void __iomem *base = priv->base;
+	u32 status;
+	int ret;
+
+	/* If the controller is busy, wait until it finishes */
+	ret = readl_poll_timeout(base + SDMMC_REG_STATUS, status,
+			!(status & SDMMC_STATUS_NOTOVER), 50, 1000 * 1000);
+	if (ret) {
+		dev_err(dev, "Timed out waiting for the controller\n");
+		cmd->error = ret;
+		return;
+	}
+
+	if (status & SDMMC_STATUS_NO_RSP_ERR)
+		return;
+
+	if (status & SDMMC_STATUS_RSP_ERR) {
+		cmd->error = -EILSEQ;
+		return;
+	}
+
+	if (mmc_resp_type(cmd) & MMC_RSP_R2) {
+		cmd->resp[0] = readl_relaxed(base + SDMMC_REG_RESP_ARG3);
+		cmd->resp[1] = readl_relaxed(base + SDMMC_REG_RESP_ARG2);
+		cmd->resp[2] = readl_relaxed(base + SDMMC_REG_RESP_ARG1);
+		cmd->resp[3] = readl_relaxed(base + SDMMC_REG_RESP_ARG0) << 1;
+	} else {
+		cmd->resp[0] = readl_relaxed(base + SDMMC_REG_RESP_ARG3);
+	}
+
+	dev_dbg(dev, "response: resp[0] = 0x%x, resp[1] = 0x%x, resp[2] = 0x%x, resp[3] = 0x%x\n",
+			cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
+}
+
+static inline struct dma_chan *rda_mmc_get_dma_chan(struct rda_mmc_host *priv,
+		struct mmc_data *data)
+{
+	if (data->flags & MMC_DATA_WRITE)
+		return priv->dma_tx;
+	else
+		return priv->dma_rx;
+}
+
+static int rda_mmc_send_data(struct mmc_host *host, struct mmc_data *data)
+{
+	struct rda_mmc_host *priv = mmc_priv(host);
+	struct device *dev = mmc_dev(host);
+	struct dma_slave_config slave_config;
+	struct dma_async_tx_descriptor *desc;
+	struct dma_chan *chan;
+	int ret;
+
+	if (!data) {
+		dev_err(dev, "No MMC request or data\n");
+		goto fail;
+	}
+
+	if (data->flags & MMC_DATA_WRITE) {
+		slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		slave_config.direction = DMA_MEM_TO_DEV;
+	} else {
+		slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		slave_config.direction = DMA_DEV_TO_MEM;
+	}
+
+	data->sg_count = dma_map_sg(dev, data->sg, data->sg_len, mmc_get_dma_dir(data));
+	if (data->sg_count == 0) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	chan = rda_mmc_get_dma_chan(priv, data);
+
+	ret = dmaengine_slave_config(chan, &slave_config);
+	if (ret) {
+		dev_err(dev, "Failed to configure DMAC\n");
+		goto fail_dma;
+	}
+
+	desc = dmaengine_prep_slave_sg(chan, data->sg, data->sg_count,
+			slave_config.direction, DMA_CTRL_ACK);
+	if (!desc) {
+		dev_err(dev, "Failed to allocate DMA descriptor\n");
+		goto fail_dma;
+	}
+
+	priv->dma_cookie = dmaengine_submit(desc);
+	if (!priv->dma_cookie) {
+		dev_err(dev, "Failed to submit DMA request\n");
+		goto fail_dma;
+	}
+
+	dma_async_issue_pending(chan);
+
+	return 0;
+
+fail_dma:
+	dma_unmap_sg(dev, data->sg, data->sg_len, mmc_get_dma_dir(data));
+fail:
+	return -EINVAL;
+}
+
+static int rda_mmc_prepare_data(struct mmc_host *host, struct mmc_command *cmd,
+		struct mmc_data *data, u32 *cfg)
+{
+	struct rda_mmc_host *priv = mmc_priv(host);
+	struct device *dev = mmc_dev(host);
+	void __iomem *base = priv->base;
+	int hw_blksz = 2; /* 1 word */
+	int i = 0;
+
+	/* If we're still here, we'll assume there's data ops */
+	*cfg |= SDMMC_CFG_RD_WT_EN;
+
+	/* Tell the controller we have a write operation */
+	if (data->flags & MMC_DATA_WRITE)
+		*cfg |= SDMMC_CFG_RD_WT_SEL;
+
+	/* Multiple data read/write */
+	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
+		*cfg |= SDMMC_CFG_S_M_SEL;
+
+		/*
+		 * Tell the controller to automatically issue CMD12 when the last block
+		 * transfer is completed on non-SDIO cards.
+		 */
+		if (!mmc_card_sdio(host->card))
+			*cfg |= SDMMC_CFG_AUTO_FLAG_EN;
+	}
+
+	/* Blocksize on this IP is calculated by how many words are requested */
+	if (data->blksz > 4) {
+		for (i = 4; i < data->blksz; i <<= 1)
+			hw_blksz++;
+	}
+
+	if (unlikely(hw_blksz > 11)) {
+		dev_err(dev, "Requested %d but hardware can only support 11!\n", hw_blksz);
+		return -EINVAL;
+	}
+
+	writel_relaxed(data->blocks, base + SDMMC_REG_BLOCK_COUNT);
+	writel_relaxed(hw_blksz, base + SDMMC_REG_BLOCK_SIZE);
+
+	return 0;
+}
+
+static int rda_mmc_send_cmd(struct mmc_host *host, struct mmc_command *cmd,
+		struct mmc_data *data)
+{
+	struct rda_mmc_host *priv = mmc_priv(host);
+	void __iomem *base = priv->base;
+	u32 cfg = SDMMC_CFG_SENDCMD;
+	int ret;
+
+	switch (mmc_resp_type(cmd)) {
+	case MMC_RSP_R2:
+		cfg |= SDMMC_CFG_RSP_EN | FIELD_PREP(SDMMC_CFG_RSP_SEL, 2);
+		break;
+	case MMC_RSP_R3:
+		cfg |= SDMMC_CFG_RSP_EN | FIELD_PREP(SDMMC_CFG_RSP_SEL, 1);
+		break;
+	default:
+		cfg |= SDMMC_CFG_RSP_EN;
+		break;
+	}
+
+	/* No data */
+	if (!data)
+		goto send_to_soc;
+
+	/* Data operations */
+	ret = rda_mmc_prepare_data(host, cmd, data, &cfg);
+	if (ret < 0)
+		return -EINVAL;
+
+	ret = rda_mmc_send_data(host, data);
+	if (ret < 0)
+		return -EINVAL;
+
+send_to_soc:
+	writel(cmd->opcode, base + SDMMC_REG_CMD_INDEX);
+	writel(cmd->arg, base + SDMMC_REG_CMD_ARG);
+	writel(cfg, base + SDMMC_REG_CONFIG);
+
+	dev_dbg(priv->dev, "mmc_resp_type = %d, cmd->opcode = 0x%x, cmd->arg = 0x%x - cfg: 0x%x\n",
+			mmc_resp_type(cmd), cmd->opcode, cmd->arg, cfg);
+
+	rda_mmc_recv_resp(host, cmd);
+
+	return 0;
+}
+
+/*
+ * Once data transfer failed (or aborted), the controller needs to be
+ * cleaned up.
+ */
+static void rda_mmc_data_abort(struct mmc_host *host, struct mmc_request *mrq)
+{
+	struct rda_mmc_host *priv = mmc_priv(host);
+	struct device *dev = mmc_dev(host);
+	void __iomem *base = priv->base;
+	struct mmc_command stop;
+	int ret;
+
+	writel_relaxed(0, base + SDMMC_REG_BLOCK_COUNT);
+	writel_relaxed(0, base + SDMMC_REG_BLOCK_SIZE);
+
+	if (!host->card)
+		return;
+
+	/*
+	 * Issue a stop command first, because if the controller timed out,
+	 * it'll not return an IRQ or any indicator.
+	 */
+	if (!mmc_card_sdio(host->card)) {
+		if (!mrq->stop) {
+			stop.opcode = MMC_STOP_TRANSMISSION;
+			stop.arg = 0;
+			stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
+			ret = rda_mmc_send_cmd(host, &stop, NULL);
+		} else {
+			ret = rda_mmc_send_cmd(host, mrq->stop, NULL);
+		}
+
+		if (ret < 0)
+			dev_err(dev, "Failed to send stop command\n");
+	}
+}
+
+static void rda_mmc_request(struct mmc_host *host, struct mmc_request *req)
+{
+	struct rda_mmc_host *priv = mmc_priv(host);
+	struct device *dev = mmc_dev(host);
+	struct mmc_data *data = NULL;
+	struct dma_chan *chan;
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	WARN_ON(priv->mrq);
+	priv->mrq = req;
+
+	if (req->data) {
+		dev_dbg(dev, "Block size = %d - Blocks = %d - Offset: %d - Length: %d\n",
+				req->data->blksz, req->data->blocks,
+				req->data->sg->offset, req->data->sg->length);
+		data = req->data;
+	}
+
+	if (rda_mmc_send_cmd(host, req->cmd, data) < 0) {
+		req->cmd->error = -EINVAL;
+		if (data)
+			req->data->error = -EINVAL;
+
+		goto done_irqunlock;
+	}
+
+	/* Interrupt will pick up on this */
+	if (!data)
+		goto done_irqunlock;
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	/*
+	 * On a data operation, we rely on our interrupt to tell us
+	 * when the transmission is finished (or failed).
+	 *
+	 * However with this IP, if the operation timed out, it will
+	 * not trigger an IRQ and we'll not return.
+	 */
+	if (data) {
+		if (wait_for_completion_timeout(&priv->c,
+					msecs_to_jiffies(5000)) == 0) {
+			spin_lock_irqsave(&priv->lock, flags);
+			priv->mrq = NULL;
+
+			dma_unmap_sg(dev, data->sg, data->sg_len,
+					mmc_get_dma_dir(data));
+
+			chan = rda_mmc_get_dma_chan(priv, data);
+
+			dmaengine_terminate_sync(chan);
+			rda_mmc_data_abort(host, req);
+
+			req->cmd->error = -ETIMEDOUT;
+			req->data->error = -ETIMEDOUT;
+			goto done_irqunlock;
+		}
+	}
+
+	return;
+
+done_irqunlock:
+	priv->mrq = NULL;
+	spin_unlock_irqrestore(&priv->lock, flags);
+	mmc_request_done(host, req);
+}
+
+static void rda_mmc_set_ios(struct mmc_host *host, struct mmc_ios *ios)
+{
+	struct rda_mmc_host *priv = mmc_priv(host);
+	struct device *dev = mmc_dev(host);
+	void __iomem *base = priv->base;
+	unsigned long mclk_rate;
+	unsigned int clk_div;
+	unsigned long flags;
+	u32 reg_mclk = 0;
+	int ret;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	if (priv->bus_width != ios->bus_width) {
+		priv->bus_width = ios->bus_width;
+		writel(BIT(priv->bus_width), base + SDMMC_REG_DATA_WIDTH);
+	}
+
+	if (priv->power_mode != ios->power_mode) {
+		priv->power_mode = ios->power_mode;
+		if (priv->power_mode == MMC_POWER_UP) {
+			ret = regulator_enable(priv->vmmc);
+			if (ret)
+				dev_err(dev, "Failed to turn on vmmc\n");
+		} else if (priv->power_mode == MMC_POWER_OFF) {
+			ret = regulator_disable(priv->vmmc);
+			if (ret)
+				dev_err(dev, "Failed to turn off vmmc\n");
+		}
+	}
+
+	if (priv->clock != ios->clock) {
+		priv->clock = ios->clock;
+		if (ios->clock) {
+			/* trans speed  */
+			mclk_rate = clk_get_rate(priv->clk);
+			if (mclk_rate == 0) {
+				dev_err(dev, "Invalid MCLK rate\n");
+				goto bailout;
+			}
+
+			clk_div = mclk_rate / (2 * ios->clock);
+			if (mclk_rate % (2 * ios->clock))
+				clk_div++;
+
+			if (clk_div >= 1)
+				clk_div -= 1;
+
+			if (clk_div > 255)
+				clk_div = 255;
+
+			/* mclk adjust */
+			if (priv->mclk_inv)
+				reg_mclk = SDMMC_MCLK_INVERT;
+
+			reg_mclk |= priv->mclk_adj;
+
+			writel_relaxed(clk_div, base + SDMMC_REG_TRANS_SPEED);
+			writel_relaxed(reg_mclk, base + SDMMC_REG_MCLK_ADJUST);
+
+			dev_dbg(dev, "set clk = %d - mclk = %ld - divider = %d\n",
+					ios->clock, mclk_rate, clk_div);
+		} else {
+			writel_relaxed(SDMMC_MCLK_DISABLE, base + SDMMC_REG_MCLK_ADJUST);
+		}
+	}
+
+bailout:
+	dev_dbg(dev, "buswidth=%d, clock=%d, power=%d\n",
+			ios->bus_width, ios->clock, ios->power_mode);
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static void rda_mmc_crc_status(struct mmc_host *host)
+{
+	struct rda_mmc_host *priv = mmc_priv(host);
+	struct device *dev = mmc_dev(host);
+	void __iomem *base = priv->base;
+	const char *crc_error;
+	u32 status;
+
+	status = readl_relaxed(base + SDMMC_REG_STATUS);
+
+	switch (FIELD_GET(SDMMC_STATUS_CRC_STATUS, status)) {
+	case 0b101:
+		crc_error = "Transmission Error";
+		break;
+	case 0b010:
+		crc_error = "Transmission Right";
+		break;
+	case 0b111:
+		crc_error = "Flash Programming Error";
+		break;
+	default:
+		crc_error = "Unknown";
+		break;
+	}
+
+	dev_err(dev, "CRC Error: %s - DATA_ERROR: 0x%lx\n", crc_error,
+			FIELD_GET(SDMMC_STATUS_DATA_ERROR, status));
+}
+
+static irqreturn_t rda_mmc_irq(int irq, void *dev_id)
+{
+	struct rda_mmc_host *priv = dev_id;
+	struct mmc_host *host = mmc_from_priv(priv);
+	struct device *dev = mmc_dev(host);
+	void __iomem *base = priv->base;
+	struct mmc_request *mrq;
+	u32 status;
+	irqreturn_t irqret = IRQ_NONE;
+
+	status = readl(base + SDMMC_REG_INT_STATUS);
+	writel((status & 0xFF), base + SDMMC_REG_INT_CLEAR);
+
+	dev_dbg(dev, "IRQ requested - status: 0x%x\n", status);
+
+	if (!priv->mrq || !priv->mrq->data)
+		goto irq_done;
+
+	mrq = priv->mrq;
+
+	if (mrq->data && ((status & SDMMC_INT_RD_ERR) || (status & SDMMC_INT_WR_ERR)))
+		mrq->data->error = -EILSEQ;
+
+	if (priv->sdio_irq && (status & SDMMC_INT_SDIO))
+		priv->sdio_irq_trigger = true;
+
+	irqret = IRQ_WAKE_THREAD;
+
+	/* We got an error, no need to do the additional checks */
+	if (mrq->data->error)
+		goto irq_done;
+
+	/*
+	 * If we don't have any error but DAT_OVER isn't triggered, then we'll assume
+	 * that we got an unexpected IRQ (during a data transfer)
+	 */
+	if (!mrq->data->error && !(status & SDMMC_INT_DAT_OVER))
+		irqret = IRQ_HANDLED;
+
+irq_done:
+	if (irqret == IRQ_NONE)
+		dev_info(dev,
+			"Unexpected IRQ - was a data transfer requested? IRQ: 0x%x\n", status);
+
+	return irqret;
+}
+
+static irqreturn_t rda_mmc_irq_fn(int irq, void *dev_id)
+{
+	struct rda_mmc_host *priv = dev_id;
+	struct mmc_host *host = mmc_from_priv(priv);
+	struct device *dev = mmc_dev(host);
+	struct mmc_request *mrq;
+	struct dma_chan *chan;
+	struct dma_tx_state state;
+	enum dma_status dma_status;
+	unsigned long flags;
+
+	if (WARN_ON(!priv->mrq))
+		return IRQ_NONE;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	mrq = priv->mrq;
+
+	if (mrq->data) {
+		chan = rda_mmc_get_dma_chan(priv, mrq->data);
+
+		if (mrq->data->error) {
+			mrq->data->bytes_xfered = 0;
+			rda_mmc_crc_status(host);
+			dmaengine_terminate_sync(chan);
+			rda_mmc_data_abort(host, mrq);
+		} else {
+			mrq->data->bytes_xfered =
+				mrq->data->blocks * mrq->data->blksz;
+
+			/*
+			 * With this IP, just because a TXDMA/RXDMA interrupt is triggered,
+			 * doesn't mean the MMC is fully processed.
+			 */
+			dma_status = dmaengine_tx_status(chan, priv->dma_cookie, &state);
+			dev_dbg(mmc_dev(host), "DMA Status: %d\n", dma_status);
+			if (dma_status != DMA_COMPLETE) {
+				dev_err(dev, "Transmit IRQ triggered but DMA is not finished\n");
+				mrq->data->error = -ETIMEDOUT;
+				mrq->data->bytes_xfered = 0;
+				dmaengine_terminate_sync(chan);
+				rda_mmc_data_abort(host, mrq);
+			}
+		}
+
+		/*
+		 * Since we told the controller to automatically send a stop command,
+		 * we don't have to send a stop command here.
+		 */
+		dma_unmap_sg(dev, mrq->data->sg, mrq->data->sg_len,
+				mmc_get_dma_dir(mrq->data));
+		dmaengine_terminate_sync(chan);
+	}
+
+	priv->mrq = NULL;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	complete(&priv->c);
+	mmc_request_done(host, mrq);
+
+	if (priv->sdio_irq && priv->sdio_irq_trigger)
+		mmc_signal_sdio_irq(host);
+
+	return IRQ_HANDLED;
+}
+
+static int rda_mmc_card_busy(struct mmc_host *host)
+{
+	struct rda_mmc_host *priv = mmc_priv(host);
+	void __iomem *base = priv->base;
+	u32 status = readl(base + SDMMC_REG_STATUS);
+
+	return (status & SDMMC_STATUS_DLBUSY);
+}
+
+static void rda_mmc_sdio_enable_irq(struct mmc_host *host, int enable)
+{
+	struct rda_mmc_host *priv = mmc_priv(host);
+	void __iomem *base = priv->base;
+	u32 intmask = readl(base + SDMMC_REG_INT_MASK);
+
+	if (enable) {
+		intmask |= SDMMC_INT_SDIO;
+		priv->sdio_irq = true;
+	} else {
+		intmask &= ~SDMMC_INT_SDIO;
+		priv->sdio_irq = false;
+	}
+
+	priv->sdio_irq_trigger = false;
+
+	writel(intmask, base + SDMMC_REG_INT_MASK);
+}
+
+static const struct mmc_host_ops rda_mmc_ops = {
+	.request = rda_mmc_request,
+	.set_ios = rda_mmc_set_ios,
+	.get_cd = mmc_gpio_get_cd,
+	.get_ro = mmc_gpio_get_ro,
+	.card_busy = rda_mmc_card_busy,
+	.enable_sdio_irq = rda_mmc_sdio_enable_irq,
+};
+
+static void rda_mmc_of_parse(struct device_node *np, struct rda_mmc_host *priv)
+{
+	bool mclk_inv = false;
+	u8 mclk_adj = 1;
+
+	if (of_property_present(np, "rda,mclk-inv"))
+		mclk_inv = true;
+
+	of_property_read_u8(np, "rda,mclk-adj", &mclk_adj);
+
+	priv->mclk_inv = mclk_inv;
+	priv->mclk_adj = mclk_adj;
+}
+
+static int rda_mmc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rda_mmc_host *priv;
+	struct mmc_host *mmc;
+	struct clk *clk;
+	struct reset_control *reset;
+	struct dma_chan *tx, *rx;
+	struct regulator *vmmc;
+	void __iomem *base;
+	int irq;
+	int ret;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return dev_err_probe(dev, PTR_ERR(base), "Cannot get iomap\n");
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return dev_err_probe(dev, irq, "Cannot get IRQ: %d\n", irq);
+
+	clk = devm_clk_get_enabled(dev, "mclk");
+	if (IS_ERR(clk))
+		return dev_err_probe(dev, PTR_ERR(clk), "Cannot get clock device\n");
+
+	vmmc = devm_regulator_get(dev, "vmmc");
+	if (IS_ERR(vmmc))
+		return dev_err_probe(dev, PTR_ERR(vmmc), "Failed to obtain regulator\n");
+
+	reset = devm_reset_control_get_by_index(dev, 0);
+	if (IS_ERR(reset))
+		return dev_err_probe(dev, PTR_ERR(reset), "Failed to obtain reset\n");
+
+	tx = dma_request_chan(dev, "tx");
+	if (IS_ERR(tx))
+		return dev_err_probe(dev, PTR_ERR(tx), "Failed to request tx channel\n");
+
+	rx = dma_request_chan(dev, "rx");
+	if (IS_ERR(rx))
+		return dev_err_probe(dev, PTR_ERR(rx), "Failed to request rx channel\n");
+
+	mmc = devm_mmc_alloc_host(dev, sizeof(*priv));
+	if (IS_ERR(mmc)) {
+		dev_err(dev, "Cannot allocate memory for MMC\n");
+		ret = PTR_ERR(mmc);
+		goto fail_release_dma;
+	}
+
+	priv = mmc_priv(mmc);
+	priv->dev = dev;
+	priv->base = base;
+	priv->irq = irq;
+	priv->clk = clk;
+	priv->reset = reset;
+	priv->dma_tx = tx;
+	priv->dma_rx = rx;
+	priv->vmmc = vmmc;
+	spin_lock_init(&priv->lock);
+	init_completion(&priv->c);
+
+	mmc->ops = &rda_mmc_ops;
+
+	mmc->max_segs = 1;
+	mmc->max_blk_size = 4096;
+	mmc->max_blk_count = 0xFFFF;
+	mmc->max_req_size = 0xFFFF;
+	mmc->max_seg_size = 0xFFFF;
+
+	mmc->f_min = 1000000;
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+
+	rda_mmc_of_parse(dev->of_node, priv);
+
+	ret = mmc_of_parse(mmc);
+	if (ret) {
+		dev_err(dev, "Failed to parse device tree: %d\n", ret);
+		goto fail_release_dma;
+	}
+
+	rda_mmc_reset(priv);
+	rda_mmc_hw_init(priv);
+
+	priv->bus_width = -1;
+
+	ret = devm_request_threaded_irq(dev, irq, rda_mmc_irq, rda_mmc_irq_fn,
+			IRQF_ONESHOT, mmc_hostname(mmc), priv);
+	if (ret) {
+		dev_err(dev, "Failed to request IRQ: %d\n", ret);
+		goto fail_release_dma;
+	}
+
+	ret = mmc_add_host(mmc);
+	if (ret) {
+		dev_err(dev, "Failed to add MMC host: %d\n", ret);
+		goto fail_release_dma;
+	}
+
+	platform_set_drvdata(pdev, mmc);
+	return 0;
+
+fail_release_dma:
+	dma_release_channel(rx);
+	dma_release_channel(tx);
+	return ret;
+}
+
+static void rda_mmc_remove(struct platform_device *pdev)
+{
+	struct rda_mmc_host *host = platform_get_drvdata(pdev);
+
+	mmc_remove_host(host->mmc);
+	dma_release_channel(host->dma_rx);
+	dma_release_channel(host->dma_tx);
+}
+
+static const struct of_device_id rda_mmc_dt_ids[] = {
+	{ .compatible = "rda,8810pl-mmc", },
+	{ .compatible = "rda,mmc", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rda_mmc_dt_ids);
+
+static struct platform_driver rda_mmc_driver = {
+	.probe		= rda_mmc_probe,
+	.remove		= rda_mmc_remove,
+	.driver		= {
+		.name	= "rda-mmc",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+		.of_match_table = rda_mmc_dt_ids,
+	},
+};
+module_platform_driver(rda_mmc_driver);
+
+MODULE_AUTHOR("Dang Huynh <dang.huynh@mainlining.org>");
+MODULE_DESCRIPTION("RDA Micro SD/MMC driver");
+MODULE_LICENSE("GPL");

-- 
2.51.0



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

* [PATCH 10/10] ARM: dts: unisoc: rda8810pl: Add SDMMC controllers
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
                   ` (8 preceding siblings ...)
  2025-09-18 18:48 ` [PATCH 09/10] mmc: host: Add RDA Micro SD/MMC driver Dang Huynh via B4 Relay
@ 2025-09-18 18:48 ` Dang Huynh via B4 Relay
  2025-09-22 14:17 ` [PATCH 00/10] RDA8810PL SD/MMC support Bartosz Golaszewski
  10 siblings, 0 replies; 22+ messages in thread
From: Dang Huynh via B4 Relay @ 2025-09-18 18:48 UTC (permalink / raw)
  To: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva
  Cc: linux-arm-kernel, linux-unisoc, linux-gpio, devicetree,
	linux-kernel, linux-clk, dmaengine, linux-mmc, linux-hardening,
	Dang Huynh

From: Dang Huynh <dang.huynh@mainlining.org>

Add SDMMC1 and 2 controllers for the RDA8810PL platform and enable it
on the Orange Pi i96 and 2G-IOT.

Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
 .../boot/dts/unisoc/rda8810pl-orangepi-2g-iot.dts  | 20 +++++++++
 .../arm/boot/dts/unisoc/rda8810pl-orangepi-i96.dts | 20 +++++++++
 arch/arm/boot/dts/unisoc/rda8810pl.dtsi            | 47 ++++++++++++++++++++--
 3 files changed, 83 insertions(+), 4 deletions(-)

diff --git a/arch/arm/boot/dts/unisoc/rda8810pl-orangepi-2g-iot.dts b/arch/arm/boot/dts/unisoc/rda8810pl-orangepi-2g-iot.dts
index 98e34248ae80b1fcd673ff01fe045db412d5bcc9..b462057ec0d1eb1877eb770afa6aced99efd84b7 100644
--- a/arch/arm/boot/dts/unisoc/rda8810pl-orangepi-2g-iot.dts
+++ b/arch/arm/boot/dts/unisoc/rda8810pl-orangepi-2g-iot.dts
@@ -6,6 +6,7 @@
 
 /dts-v1/;
 
+#include <dt-bindings/gpio/gpio.h>
 #include "rda8810pl.dtsi"
 
 / {
@@ -27,6 +28,13 @@ memory@80000000 {
 		reg = <0x80000000 0x10000000>;
 	};
 
+	vdd_sdmmc: regulator-fixed {
+		compatible = "regulator-fixed";
+		regulator-name = "vdd_sdmmc";
+		regulator-min-microvolt = <2800000>;
+		regulator-max-microvolt = <2800000>;
+	};
+
 	uart_clk: uart-clk {
 		compatible = "fixed-clock";
 		clock-frequency = <921600>;
@@ -34,6 +42,18 @@ uart_clk: uart-clk {
 	};
 };
 
+&mmc1 {
+	status = "okay";
+	no-sdio;
+	no-mmc;
+	bus-width = <4>;
+	max-frequency = <30000000>;
+	cd-gpios = <&gpiob 4 GPIO_ACTIVE_LOW>;
+	vmmc-supply = <&vdd_sdmmc>;
+	rda,mclk-adj = /bits/ 8 <1>;
+	rda,mclk-inv;
+};
+
 &uart1 {
 	status = "okay";
 	clocks = <&uart_clk>;
diff --git a/arch/arm/boot/dts/unisoc/rda8810pl-orangepi-i96.dts b/arch/arm/boot/dts/unisoc/rda8810pl-orangepi-i96.dts
index 728f76931b995fdfc036b586f899b15a7f07528b..cf4b75f114b33b27fad0ead4951b15abb1f5cabd 100644
--- a/arch/arm/boot/dts/unisoc/rda8810pl-orangepi-i96.dts
+++ b/arch/arm/boot/dts/unisoc/rda8810pl-orangepi-i96.dts
@@ -6,6 +6,7 @@
 
 /dts-v1/;
 
+#include <dt-bindings/gpio/gpio.h>
 #include "rda8810pl.dtsi"
 
 / {
@@ -27,6 +28,13 @@ memory@80000000 {
 		reg = <0x80000000 0x10000000>;
 	};
 
+	vdd_sdmmc: regulator-fixed {
+		compatible = "regulator-fixed";
+		regulator-name = "vdd_sdmmc";
+		regulator-min-microvolt = <2800000>;
+		regulator-max-microvolt = <2800000>;
+	};
+
 	uart_clk: uart-clk {
 		compatible = "fixed-clock";
 		clock-frequency = <921600>;
@@ -34,6 +42,18 @@ uart_clk: uart-clk {
 	};
 };
 
+&mmc1 {
+	status = "okay";
+	no-sdio;
+	no-mmc;
+	bus-width = <4>;
+	max-frequency = <30000000>;
+	cd-gpios = <&gpiob 4 GPIO_ACTIVE_LOW>;
+	vmmc-supply = <&vdd_sdmmc>;
+	rda,mclk-adj = /bits/ 8 <1>;
+	rda,mclk-inv;
+};
+
 &uart1 {
 	status = "okay";
 	clocks = <&uart_clk>;
diff --git a/arch/arm/boot/dts/unisoc/rda8810pl.dtsi b/arch/arm/boot/dts/unisoc/rda8810pl.dtsi
index f30d6ece49fb33d9c5c3ad9522c83bb8e4f8b488..438be79a89060655e4a12edc6d3c42574748108b 100644
--- a/arch/arm/boot/dts/unisoc/rda8810pl.dtsi
+++ b/arch/arm/boot/dts/unisoc/rda8810pl.dtsi
@@ -7,6 +7,8 @@
  */
 
 #include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/clock/rda,8810pl-apclk.h>
+#include <dt-bindings/dma/rda-ifc.h>
 
 / {
 	compatible = "rda,8810pl";
@@ -39,7 +41,7 @@ modem@10000000 {
 		#size-cells = <1>;
 		ranges = <0x0 0x10000000 0xfffffff>;
 
-		gpioc@1a08000 {
+		gpioc: gpioc@1a08000 {
 			compatible = "rda,8810pl-gpio";
 			reg = <0x1a08000 0x1000>;
 			gpio-controller;
@@ -68,6 +70,13 @@ apb@20900000 {
 		#size-cells = <1>;
 		ranges = <0x0 0x20900000 0x100000>;
 
+		ap_syscon: syscon@0 {
+			compatible = "rda,8810pl-apsyscon", "syscon";
+			reg = <0x0 0x1000>;
+			#clock-cells = <1>;
+			#reset-cells = <1>;
+		};
+
 		timer@10000 {
 			compatible = "rda,8810pl-timer";
 			reg = <0x10000 0x1000>;
@@ -76,7 +85,7 @@ timer@10000 {
 			interrupt-names = "hwtimer", "ostimer";
 		};
 
-		gpioa@30000 {
+		gpioa: gpioa@30000 {
 			compatible = "rda,8810pl-gpio";
 			reg = <0x30000 0x1000>;
 			gpio-controller;
@@ -87,7 +96,7 @@ gpioa@30000 {
 			interrupts = <12 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
-		gpiob@31000 {
+		gpiob: gpiob@31000 {
 			compatible = "rda,8810pl-gpio";
 			reg = <0x31000 0x1000>;
 			gpio-controller;
@@ -98,7 +107,7 @@ gpiob@31000 {
 			interrupts = <13 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
-		gpiod@32000 {
+		gpiod: gpiod@32000 {
 			compatible = "rda,8810pl-gpio";
 			reg = <0x32000 0x1000>;
 			gpio-controller;
@@ -123,6 +132,30 @@ uart1: serial@0 {
 			status = "disabled";
 		};
 
+		mmc1: mmc@50000 {
+			compatible = "rda,8810pl-mmc", "rda,mmc";
+			reg = <0x50000 0x1000>;
+			interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&ap_syscon CLK_APB2>;
+			clock-names = "mclk";
+			resets = <&ap_syscon RST_APB2_SDMMC1>;
+			dmas = <&ifc IFC_SDMMC1_TX>, <&ifc IFC_SDMMC1_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+		};
+
+		mmc2: mmc@60000 {
+			compatible = "rda,8810pl-mmc", "rda,mmc";
+			reg = <0x60000 0x1000>;
+			interrupts = <4 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&ap_syscon CLK_APB2>;
+			clock-names = "mclk";
+			resets = <&ap_syscon RST_APB2_SDMMC2>;
+			dmas = <&ifc IFC_SDMMC2_TX>, <&ifc IFC_SDMMC2_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+		};
+
 		uart2: serial@10000 {
 			compatible = "rda,8810pl-uart";
 			reg = <0x10000 0x1000>;
@@ -136,6 +169,12 @@ uart3: serial@90000 {
 			interrupts = <11 IRQ_TYPE_LEVEL_HIGH>;
 			status = "disabled";
 		};
+
+		ifc: dma-controller@f0000 {
+			compatible = "rda,8810pl-ifc", "rda,ifc";
+			reg = <0xf0000 0x1000>;
+			#dma-cells = <1>;
+		};
 	};
 
 	l2: cache-controller@21100000 {

-- 
2.51.0



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

* Re: [PATCH 08/10] dmaengine: Add RDA IFC driver
  2025-09-18 18:48 ` [PATCH 08/10] dmaengine: Add RDA IFC driver Dang Huynh via B4 Relay
@ 2025-09-21  4:42   ` kernel test robot
  0 siblings, 0 replies; 22+ messages in thread
From: kernel test robot @ 2025-09-21  4:42 UTC (permalink / raw)
  To: Dang Huynh via B4 Relay, Manivannan Sadhasivam, Linus Walleij,
	Bartosz Golaszewski, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Michael Turquette, Stephen Boyd, Vinod Koul,
	Ulf Hansson, Philipp Zabel, Kees Cook, Gustavo A. R. Silva
  Cc: oe-kbuild-all, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening, Dang Huynh

Hi Dang,

kernel test robot noticed the following build errors:

[auto build test ERROR on ae2d20002576d2893ecaff25db3d7ef9190ac0b6]

url:    https://github.com/intel-lab-lkp/linux/commits/Dang-Huynh-via-B4-Relay/dt-bindings-gpio-rda-Make-interrupts-optional/20250919-025331
base:   ae2d20002576d2893ecaff25db3d7ef9190ac0b6
patch link:    https://lore.kernel.org/r/20250919-rda8810pl-mmc-v1-8-d4f08a05ba4d%40mainlining.org
patch subject: [PATCH 08/10] dmaengine: Add RDA IFC driver
config: m68k-allmodconfig (https://download.01.org/0day-ci/archive/20250921/202509211252.z0s0XcXk-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 15.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250921/202509211252.z0s0XcXk-lkp@intel.com/reproduce)

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

All errors (new ones prefixed by >>):

   drivers/dma/rda-ifc.c: In function 'rda_ifc_prep_slave_sg':
>> drivers/dma/rda-ifc.c:180:28: error: implicit declaration of function 'FIELD_PREP' [-Wimplicit-function-declaration]
     180 |                 control |= FIELD_PREP(IFC_CTL_SIZE, 0);
         |                            ^~~~~~~~~~


vim +/FIELD_PREP +180 drivers/dma/rda-ifc.c

   145	
   146	static struct dma_async_tx_descriptor *rda_ifc_prep_slave_sg(struct dma_chan *chan,
   147			struct scatterlist *sgl, unsigned int sg_len,
   148			enum dma_transfer_direction direction, unsigned long dma_flags,
   149			void *context)
   150	{
   151		struct rda_ifc_chan *ifc_chan = to_ifc_chan(chan);
   152		struct rda_ifc *ifc = ifc_chan->rda_ifc;
   153		struct device *dev = dmaengine_get_dma_device(chan);
   154		struct scatterlist *sg;
   155		unsigned long flags;
   156		u32 control = 0;
   157		int width;
   158		int i;
   159	
   160		if (sg_len > ifc->sg_max) {
   161			dev_err(dev, "sg_len %d overflowed (max sg %d)\n",
   162					sg_len, ifc->sg_max);
   163			return NULL;
   164		}
   165	
   166		if (direction != ifc_chan->direction) {
   167			dev_err(dev, "Inconsistent transfer direction\n");
   168			return NULL;
   169		}
   170	
   171		spin_lock_irqsave(&ifc_chan->lock, flags);
   172	
   173		if (ifc_chan->direction == DMA_DEV_TO_MEM)
   174			width = ifc_chan->sconfig.src_addr_width;
   175		else
   176			width = ifc_chan->sconfig.dst_addr_width;
   177	
   178		switch (width) {
   179		case DMA_SLAVE_BUSWIDTH_1_BYTE:
 > 180			control |= FIELD_PREP(IFC_CTL_SIZE, 0);
   181			break;
   182		case DMA_SLAVE_BUSWIDTH_2_BYTES:
   183			control |= FIELD_PREP(IFC_CTL_SIZE, 1);
   184			break;
   185		case DMA_SLAVE_BUSWIDTH_4_BYTES:
   186			control |= FIELD_PREP(IFC_CTL_SIZE, 2);
   187			break;
   188		default:
   189			return NULL;
   190		}
   191	
   192		for_each_sg(sgl, sg, sg_len, i) {
   193			if (!IS_ALIGNED(sg_dma_address(sg), width)) {
   194				dev_err(dev, "Unaligned DMA address\n");
   195				spin_unlock_irqrestore(&ifc_chan->lock, flags);
   196				return NULL;
   197			}
   198	
   199			writel(sg_dma_address(sg), ifc_chan->chan_base + IFC_REG_SG_START_ADDR + (8 * i));
   200			writel(sg_dma_len(sg), ifc_chan->chan_base + IFC_REG_SG_TC + (8 * i));
   201		}
   202	
   203		control |= FIELD_PREP(IFC_CTL_REQ_SRC, ifc_chan->request_id) |
   204			IFC_CTL_CH_RD_HW_EXCH |
   205			FIELD_PREP(IFC_CTL_SG_NUM, sg_len-1);
   206		writel(control, ifc_chan->chan_base);
   207	
   208		spin_unlock_irqrestore(&ifc_chan->lock, flags);
   209	
   210		dma_async_tx_descriptor_init(&ifc_chan->tx, chan);
   211		ifc_chan->tx.tx_submit = rda_ifc_tx_submit;
   212	
   213		return &ifc_chan->tx;
   214	}
   215	

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

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

* Re: [PATCH 00/10] RDA8810PL SD/MMC support
  2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
                   ` (9 preceding siblings ...)
  2025-09-18 18:48 ` [PATCH 10/10] ARM: dts: unisoc: rda8810pl: Add SDMMC controllers Dang Huynh via B4 Relay
@ 2025-09-22 14:17 ` Bartosz Golaszewski
  2025-09-23  3:40   ` Dang Huynh
  10 siblings, 1 reply; 22+ messages in thread
From: Bartosz Golaszewski @ 2025-09-22 14:17 UTC (permalink / raw)
  To: dang.huynh
  Cc: Manivannan Sadhasivam, Linus Walleij, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening, Conor Dooley

On Thu, Sep 18, 2025 at 8:49 PM Dang Huynh via B4 Relay
<devnull+dang.huynh.mainlining.org@kernel.org> wrote:
>
> This patch series aims to add SDMMC driver and various drivers required
> for SDMMC controller to function.
>
> This also fixed a bug where all the GPIO switched from INPUT to OUTPUT
> after the GPIO driver probed or by reading the GPIO debugfs.
>
> This patch series is a split from [1] to ease the maintainers.
>

This is still targeting at least 4 subsystems and isn't making the
merging any easier. Are there any build-time dependencies here? If
not, then split it further into small chunks targeting individual
subsystems and the relevant ARM SoC tree.

Bartosz

> Tested on Orange Pi 2G-IOT using a Buildroot environment.
>
> [1]: https://lore.kernel.org/all/20250917-rda8810pl-drivers-v1-0-9ca9184ca977@mainlining.org/
>
> Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
> ---
> Dang Huynh (10):
>       dt-bindings: gpio: rda: Make interrupts optional
>       dt-bindings: clock: Add RDA Micro RDA8810PL clock/reset controller
>       dt-bindings: dma: Add RDA IFC DMA
>       dt-bindings: mmc: Add RDA SDMMC controller
>       gpio: rda: Make IRQ optional
>       gpio: rda: Make direction register unreadable
>       clk: Add Clock and Reset Driver for RDA Micro RDA8810PL SoC
>       dmaengine: Add RDA IFC driver
>       mmc: host: Add RDA Micro SD/MMC driver
>       ARM: dts: unisoc: rda8810pl: Add SDMMC controllers
>
>  .../bindings/clock/rda,8810pl-apsyscon.yaml        |  43 ++
>  Documentation/devicetree/bindings/dma/rda,ifc.yaml |  45 ++
>  .../devicetree/bindings/gpio/gpio-rda.yaml         |   3 -
>  Documentation/devicetree/bindings/mmc/rda,mmc.yaml |  92 +++
>  MAINTAINERS                                        |  18 +
>  .../boot/dts/unisoc/rda8810pl-orangepi-2g-iot.dts  |  20 +
>  .../arm/boot/dts/unisoc/rda8810pl-orangepi-i96.dts |  20 +
>  arch/arm/boot/dts/unisoc/rda8810pl.dtsi            |  47 +-
>  drivers/clk/Kconfig                                |   1 +
>  drivers/clk/Makefile                               |   1 +
>  drivers/clk/rda/Kconfig                            |  14 +
>  drivers/clk/rda/Makefile                           |   2 +
>  drivers/clk/rda/clk-rda8810.c                      | 769 +++++++++++++++++++
>  drivers/dma/Kconfig                                |  10 +
>  drivers/dma/Makefile                               |   1 +
>  drivers/dma/rda-ifc.c                              | 450 +++++++++++
>  drivers/gpio/gpio-rda.c                            |   4 +-
>  drivers/mmc/host/Kconfig                           |  12 +
>  drivers/mmc/host/Makefile                          |   1 +
>  drivers/mmc/host/rda-mmc.c                         | 853 +++++++++++++++++++++
>  include/dt-bindings/clock/rda,8810pl-apclk.h       |  70 ++
>  include/dt-bindings/dma/rda-ifc.h                  |  28 +
>  22 files changed, 2495 insertions(+), 9 deletions(-)
> ---
> base-commit: ae2d20002576d2893ecaff25db3d7ef9190ac0b6
> change-id: 20250918-rda8810pl-mmc-3f33b83c313d
>
> Best regards,
> --
> Dang Huynh <dang.huynh@mainlining.org>
>
>

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

* Re: [PATCH 06/10] gpio: rda: Make direction register unreadable
  2025-09-18 18:48 ` [PATCH 06/10] gpio: rda: Make direction register unreadable Dang Huynh via B4 Relay
@ 2025-09-22 14:20   ` Bartosz Golaszewski
  0 siblings, 0 replies; 22+ messages in thread
From: Bartosz Golaszewski @ 2025-09-22 14:20 UTC (permalink / raw)
  To: dang.huynh
  Cc: Manivannan Sadhasivam, Linus Walleij, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening

On Thu, Sep 18, 2025 at 8:49 PM Dang Huynh via B4 Relay
<devnull+dang.huynh.mainlining.org@kernel.org> wrote:
>
> From: Dang Huynh <dang.huynh@mainlining.org>
>
> The register doesn't like to be read and would cause all the input
> to be switched to output.
>
> This causes the SD Card Detect GPIO to misbehaves in the OS and/or
> may cause hardware to malfunction.
>
> Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
> ---

I have responded to you already[1]. The commit message is still
sub-par. Don't say "the register doesn't like", say: "Reading the X
register causes Y to happen. In order to remedy it, do Z." You haven't
answered my question: what will happen to existing users of this
driver?

Also: you have not bumped the series version to v2, that makes
tracking the changes hard.

Bartosz

[1] https://lore.kernel.org/all/CAMRc=MeHQf_Oa2DRR0T7tum-Tuk3qPh5r5gimxGY3EXTyvoKZQ@mail.gmail.com/

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

* Re: [PATCH 02/10] dt-bindings: clock: Add RDA Micro RDA8810PL clock/reset controller
  2025-09-18 18:48 ` [PATCH 02/10] dt-bindings: clock: Add RDA Micro RDA8810PL clock/reset controller Dang Huynh via B4 Relay
@ 2025-09-22 18:04   ` Rob Herring
  0 siblings, 0 replies; 22+ messages in thread
From: Rob Herring @ 2025-09-22 18:04 UTC (permalink / raw)
  To: Dang Huynh
  Cc: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening

On Fri, Sep 19, 2025 at 01:48:42AM +0700, Dang Huynh wrote:
> Add documentation describing the RDA8810PL Clock and Reset
> controller.
> 
> Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
> ---
>  .../bindings/clock/rda,8810pl-apsyscon.yaml        | 43 +++++++++++++
>  include/dt-bindings/clock/rda,8810pl-apclk.h       | 70 ++++++++++++++++++++++
>  2 files changed, 113 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/clock/rda,8810pl-apsyscon.yaml b/Documentation/devicetree/bindings/clock/rda,8810pl-apsyscon.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..d90dae8ebaa270aa822e4855d2a4a892168c5eea
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/rda,8810pl-apsyscon.yaml
> @@ -0,0 +1,43 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/clock/rda,8810pl-apsyscon.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: RDA Micro RDA8810PL AP Clock Controller
> +
> +maintainers:
> +  - Dang Huynh <dang.huynh@mainlining.org>
> +
> +properties:
> +  compatible:
> +    items:
> +      - const: rda,8810pl-apsyscon
> +      - const: syscon

What makes this a syscon? (Answer in the commit msg). If your only use 
is so the regmap is automagically created for you, don't. Create a 
regmap yourself in your driver.

Rob

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

* Re: [PATCH 03/10] dt-bindings: dma: Add RDA IFC DMA
  2025-09-18 18:48 ` [PATCH 03/10] dt-bindings: dma: Add RDA IFC DMA Dang Huynh via B4 Relay
@ 2025-09-22 18:07   ` Rob Herring
  2025-09-22 18:08   ` Rob Herring
  1 sibling, 0 replies; 22+ messages in thread
From: Rob Herring @ 2025-09-22 18:07 UTC (permalink / raw)
  To: Dang Huynh
  Cc: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening

On Fri, Sep 19, 2025 at 01:48:43AM +0700, Dang Huynh wrote:
> The Intelligent Flow Controller (IFC) is a scatter/gather DMA
> controller.
> 
> Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
> ---
>  Documentation/devicetree/bindings/dma/rda,ifc.yaml | 45 ++++++++++++++++++++++
>  include/dt-bindings/dma/rda-ifc.h                  | 28 ++++++++++++++
>  2 files changed, 73 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/dma/rda,ifc.yaml b/Documentation/devicetree/bindings/dma/rda,ifc.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..323e1e87cee09cfc7b64bf44bef61e4d1e91afa5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dma/rda,ifc.yaml
> @@ -0,0 +1,45 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/dma/rda,ifc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: RDA Intelligent Flow Controller (IFC)
> +
> +maintainers:
> +  - Dang Huynh <dang.huynh@mainlining.org>
> +
> +description: |

Don't need '|'.

> +  RDA IFC is a DMA controller, it only supports scatter/gather lists.
> +
> +allOf:
> +  - $ref: dma-controller.yaml#
> +
> +properties:
> +  compatible:
> +    items:
> +      - enum:
> +          - rda,8810pl-ifc
> +      - const: rda,ifc
> +
> +  reg:
> +    maxItems: 1
> +
> +  "#dma-cells":
> +    const: 1
> +    description:
> +      The cell corresponding to DMA request ID
> +
> +required:
> +  - compatible
> +  - reg
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    dma-controller@f0000 {
> +      compatible = "rda,8810pl-ifc", "rda,ifc";
> +      reg = <0xf0000 0x1000>;
> +      #dma-cells = <1>;
> +    };
> diff --git a/include/dt-bindings/dma/rda-ifc.h b/include/dt-bindings/dma/rda-ifc.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..af4bae0542aa71690351e2406d0945a61eff72c1
> --- /dev/null
> +++ b/include/dt-bindings/dma/rda-ifc.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
> +
> +#ifndef __DT_BINDINGS_DMA_RDA_IFC_H__
> +#define __DT_BINDINGS_DMA_RDA_IFC_H__
> +
> +#define IFC_UART1_TX		0
> +#define IFC_UART1_RX		1
> +#define IFC_UART2_TX		2
> +#define IFC_UART2_RX		3
> +#define IFC_SPI1_TX		4
> +#define IFC_SPI1_RX		5
> +#define IFC_SPI2_TX		6
> +#define IFC_SPI2_RX		7
> +#define IFC_SPI3_TX		8
> +#define IFC_SPI3_RX		9
> +#define IFC_SDMMC1_TX		10
> +#define IFC_SDMMC1_RX		11
> +#define IFC_SDMMC2_TX		12
> +#define IFC_SDMMC2_RX		13
> +#define IFC_SDMMC3_TX		14
> +#define IFC_SDMMC3_RX		15
> +#define IFC_NFSC_TX		16
> +#define IFC_NFSC_RX		17
> +#define IFC_UART3_TX		18
> +#define IFC_UART3_RX		19
> +#define IFC_NO_REQUEST		20

These numbers are defined by the h/w (like IRQ numbers)? If so, drop the 
header. We don't do headers for h/w numbers.

Rob

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

* Re: [PATCH 03/10] dt-bindings: dma: Add RDA IFC DMA
  2025-09-18 18:48 ` [PATCH 03/10] dt-bindings: dma: Add RDA IFC DMA Dang Huynh via B4 Relay
  2025-09-22 18:07   ` Rob Herring
@ 2025-09-22 18:08   ` Rob Herring
  1 sibling, 0 replies; 22+ messages in thread
From: Rob Herring @ 2025-09-22 18:08 UTC (permalink / raw)
  To: Dang Huynh
  Cc: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening

On Fri, Sep 19, 2025 at 01:48:43AM +0700, Dang Huynh wrote:
> The Intelligent Flow Controller (IFC) is a scatter/gather DMA
> controller.
> 
> Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
> ---
>  Documentation/devicetree/bindings/dma/rda,ifc.yaml | 45 ++++++++++++++++++++++
>  include/dt-bindings/dma/rda-ifc.h                  | 28 ++++++++++++++
>  2 files changed, 73 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/dma/rda,ifc.yaml b/Documentation/devicetree/bindings/dma/rda,ifc.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..323e1e87cee09cfc7b64bf44bef61e4d1e91afa5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dma/rda,ifc.yaml
> @@ -0,0 +1,45 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/dma/rda,ifc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: RDA Intelligent Flow Controller (IFC)
> +
> +maintainers:
> +  - Dang Huynh <dang.huynh@mainlining.org>
> +
> +description: |
> +  RDA IFC is a DMA controller, it only supports scatter/gather lists.
> +
> +allOf:
> +  - $ref: dma-controller.yaml#
> +
> +properties:
> +  compatible:
> +    items:
> +      - enum:
> +          - rda,8810pl-ifc
> +      - const: rda,ifc

Also, drop. Generic compatibles are rarely useful. If the next chip has 
the same block, then 'rda,8810pl-ifc' can be the fallback compatible.

Rob

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

* Re: [PATCH 04/10] dt-bindings: mmc: Add RDA SDMMC controller
  2025-09-18 18:48 ` [PATCH 04/10] dt-bindings: mmc: Add RDA SDMMC controller Dang Huynh via B4 Relay
@ 2025-09-22 18:13   ` Rob Herring
  0 siblings, 0 replies; 22+ messages in thread
From: Rob Herring @ 2025-09-22 18:13 UTC (permalink / raw)
  To: Dang Huynh
  Cc: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening

On Fri, Sep 19, 2025 at 01:48:44AM +0700, Dang Huynh wrote:
> Add documentation describing the SD/MMC controller in RDA Micro
> RDA8810PL SoC.
> 
> Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
> ---
>  Documentation/devicetree/bindings/mmc/rda,mmc.yaml | 92 ++++++++++++++++++++++
>  1 file changed, 92 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/mmc/rda,mmc.yaml b/Documentation/devicetree/bindings/mmc/rda,mmc.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..f3cda38abd66023ca9bc94d1c29df5ae9e211e7c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/rda,mmc.yaml
> @@ -0,0 +1,92 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/mmc/rda,mmc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: RDA Micro SD/MMC Controller
> +
> +allOf:
> +  - $ref: mmc-controller.yaml
> +
> +maintainers:
> +  - Dang Huynh <dang.huynh@mainlining.org>
> +
> +properties:
> +  compatible:
> +    items:
> +      - enum:
> +          - rda,8810pl-mmc
> +      - const: rda,mmc

Drop the generic compatible.

> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +
> +  clock-names:
> +    const: mclk
> +
> +  resets:
> +    maxItems: 1
> +
> +  dmas:
> +    maxItems: 2
> +
> +  dma-names:
> +    items:
> +      - const: tx
> +      - const: rx
> +
> +  rda,mclk-adj:
> +    $ref: /schemas/types.yaml#/definitions/uint8
> +    description:
> +      Some board need MCLK output to be delayed for the card to work.
> +      If not present, MCLK output is not delayed.
> +    minimum: 0
> +    maximum: 255

You can drop the constraints if they are not less than uint8 range.

What are the units? 

I guess 'adj' means adjust. Just use 'rda,mclk-delay' to be more 
precise.

> +
> +  rda,mclk-inv:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Some board need MCLK to be inverted for the card to work.
> +      If not present, MCLK is not inverted.
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - clocks
> +  - clock-names
> +  - resets
> +  - dmas
> +  - dma-names
> +  - vmmc-supply
> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    #include <dt-bindings/clock/rda,8810pl-apclk.h>
> +    #include <dt-bindings/dma/rda-ifc.h>
> +    mmc@20950000 {
> +      compatible = "rda,8810pl-mmc", "rda,mmc";
> +      reg = <0x20950000 0x1000>;
> +      interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
> +      clocks = <&ap_syscon CLK_APB2>;
> +      clock-names = "mclk";
> +      resets = <&ap_syscon RST_APB2_SDMMC1>;
> +      dmas = <&ifc IFC_SDMMC1_TX>, <&ifc IFC_SDMMC1_RX>;
> +      dma-names = "tx", "rx";
> +      vmmc-supply = <&vdd_sdmmc>;
> +      rda,mclk-adj = /bits/ 8 <1>;
> +      rda,mclk-inv;
> +    };
> +
> +...
> 
> -- 
> 2.51.0
> 

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

* Re: [PATCH 00/10] RDA8810PL SD/MMC support
  2025-09-22 14:17 ` [PATCH 00/10] RDA8810PL SD/MMC support Bartosz Golaszewski
@ 2025-09-23  3:40   ` Dang Huynh
  2025-09-23  7:48     ` Bartosz Golaszewski
  0 siblings, 1 reply; 22+ messages in thread
From: Dang Huynh @ 2025-09-23  3:40 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Manivannan Sadhasivam, Linus Walleij, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening, Conor Dooley

On Mon, Sep 22, 2025 at 04:17:05PM +0200, Bartosz Golaszewski wrote:
> On Thu, Sep 18, 2025 at 8:49 PM Dang Huynh via B4 Relay
> <devnull+dang.huynh.mainlining.org@kernel.org> wrote:
> >
> > This patch series aims to add SDMMC driver and various drivers required
> > for SDMMC controller to function.
> >
> > This also fixed a bug where all the GPIO switched from INPUT to OUTPUT
> > after the GPIO driver probed or by reading the GPIO debugfs.
> >
> > This patch series is a split from [1] to ease the maintainers.
> >
> 
> This is still targeting at least 4 subsystems and isn't making the
> merging any easier. Are there any build-time dependencies here? If
> not, then split it further into small chunks targeting individual
> subsystems and the relevant ARM SoC tree.
The MMC driver depends on both the clock and the DMA driver.

> 
> Bartosz
> 
> > Tested on Orange Pi 2G-IOT using a Buildroot environment.
> >
> > [1]: https://lore.kernel.org/all/20250917-rda8810pl-drivers-v1-0-9ca9184ca977@mainlining.org/
> >
> > Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
> > ---
> > Dang Huynh (10):
> >       dt-bindings: gpio: rda: Make interrupts optional
> >       dt-bindings: clock: Add RDA Micro RDA8810PL clock/reset controller
> >       dt-bindings: dma: Add RDA IFC DMA
> >       dt-bindings: mmc: Add RDA SDMMC controller
> >       gpio: rda: Make IRQ optional
> >       gpio: rda: Make direction register unreadable
> >       clk: Add Clock and Reset Driver for RDA Micro RDA8810PL SoC
> >       dmaengine: Add RDA IFC driver
> >       mmc: host: Add RDA Micro SD/MMC driver
> >       ARM: dts: unisoc: rda8810pl: Add SDMMC controllers
> >
> >  .../bindings/clock/rda,8810pl-apsyscon.yaml        |  43 ++
> >  Documentation/devicetree/bindings/dma/rda,ifc.yaml |  45 ++
> >  .../devicetree/bindings/gpio/gpio-rda.yaml         |   3 -
> >  Documentation/devicetree/bindings/mmc/rda,mmc.yaml |  92 +++
> >  MAINTAINERS                                        |  18 +
> >  .../boot/dts/unisoc/rda8810pl-orangepi-2g-iot.dts  |  20 +
> >  .../arm/boot/dts/unisoc/rda8810pl-orangepi-i96.dts |  20 +
> >  arch/arm/boot/dts/unisoc/rda8810pl.dtsi            |  47 +-
> >  drivers/clk/Kconfig                                |   1 +
> >  drivers/clk/Makefile                               |   1 +
> >  drivers/clk/rda/Kconfig                            |  14 +
> >  drivers/clk/rda/Makefile                           |   2 +
> >  drivers/clk/rda/clk-rda8810.c                      | 769 +++++++++++++++++++
> >  drivers/dma/Kconfig                                |  10 +
> >  drivers/dma/Makefile                               |   1 +
> >  drivers/dma/rda-ifc.c                              | 450 +++++++++++
> >  drivers/gpio/gpio-rda.c                            |   4 +-
> >  drivers/mmc/host/Kconfig                           |  12 +
> >  drivers/mmc/host/Makefile                          |   1 +
> >  drivers/mmc/host/rda-mmc.c                         | 853 +++++++++++++++++++++
> >  include/dt-bindings/clock/rda,8810pl-apclk.h       |  70 ++
> >  include/dt-bindings/dma/rda-ifc.h                  |  28 +
> >  22 files changed, 2495 insertions(+), 9 deletions(-)
> > ---
> > base-commit: ae2d20002576d2893ecaff25db3d7ef9190ac0b6
> > change-id: 20250918-rda8810pl-mmc-3f33b83c313d
> >
> > Best regards,
> > --
> > Dang Huynh <dang.huynh@mainlining.org>
> >
> >


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

* Re: [PATCH 00/10] RDA8810PL SD/MMC support
  2025-09-23  3:40   ` Dang Huynh
@ 2025-09-23  7:48     ` Bartosz Golaszewski
  2025-09-25  5:30       ` Dang Huynh
  0 siblings, 1 reply; 22+ messages in thread
From: Bartosz Golaszewski @ 2025-09-23  7:48 UTC (permalink / raw)
  To: Dang Huynh
  Cc: Manivannan Sadhasivam, Linus Walleij, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening, Conor Dooley

On Tue, Sep 23, 2025 at 5:45 AM Dang Huynh <dang.huynh@mainlining.org> wrote:
>
> On Mon, Sep 22, 2025 at 04:17:05PM +0200, Bartosz Golaszewski wrote:
> > On Thu, Sep 18, 2025 at 8:49 PM Dang Huynh via B4 Relay
> > <devnull+dang.huynh.mainlining.org@kernel.org> wrote:
> > >
> > > This patch series aims to add SDMMC driver and various drivers required
> > > for SDMMC controller to function.
> > >
> > > This also fixed a bug where all the GPIO switched from INPUT to OUTPUT
> > > after the GPIO driver probed or by reading the GPIO debugfs.
> > >
> > > This patch series is a split from [1] to ease the maintainers.
> > >
> >
> > This is still targeting at least 4 subsystems and isn't making the
> > merging any easier. Are there any build-time dependencies here? If
> > not, then split it further into small chunks targeting individual
> > subsystems and the relevant ARM SoC tree.
> The MMC driver depends on both the clock and the DMA driver.
>

But is the dependency a build-time one or does the MMC DT node
reference clocks and the DMA engine by phandle? I assume it's the
latter in which case it's fine for them to go into next separately.

Bart

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

* Re: [PATCH 00/10] RDA8810PL SD/MMC support
  2025-09-23  7:48     ` Bartosz Golaszewski
@ 2025-09-25  5:30       ` Dang Huynh
  0 siblings, 0 replies; 22+ messages in thread
From: Dang Huynh @ 2025-09-25  5:30 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Manivannan Sadhasivam, Linus Walleij, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Ulf Hansson, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening, Conor Dooley

On Tue, Sep 23, 2025 at 09:48:41AM +0200, Bartosz Golaszewski wrote:
> On Tue, Sep 23, 2025 at 5:45 AM Dang Huynh <dang.huynh@mainlining.org> wrote:
> >
> > On Mon, Sep 22, 2025 at 04:17:05PM +0200, Bartosz Golaszewski wrote:
> > > On Thu, Sep 18, 2025 at 8:49 PM Dang Huynh via B4 Relay
> > > <devnull+dang.huynh.mainlining.org@kernel.org> wrote:
> > > >
> > > > This patch series aims to add SDMMC driver and various drivers required
> > > > for SDMMC controller to function.
> > > >
> > > > This also fixed a bug where all the GPIO switched from INPUT to OUTPUT
> > > > after the GPIO driver probed or by reading the GPIO debugfs.
> > > >
> > > > This patch series is a split from [1] to ease the maintainers.
> > > >
> > >
> > > This is still targeting at least 4 subsystems and isn't making the
> > > merging any easier. Are there any build-time dependencies here? If
> > > not, then split it further into small chunks targeting individual
> > > subsystems and the relevant ARM SoC tree.
> > The MMC driver depends on both the clock and the DMA driver.
> >
> 
> But is the dependency a build-time one or does the MMC DT node
> reference clocks and the DMA engine by phandle? I assume it's the
> latter in which case it's fine for them to go into next separately.
Yeah, it's the latter. I'll submit a v3 in the future.
> 
> Bart

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

* Re: [PATCH 09/10] mmc: host: Add RDA Micro SD/MMC driver
  2025-09-18 18:48 ` [PATCH 09/10] mmc: host: Add RDA Micro SD/MMC driver Dang Huynh via B4 Relay
@ 2025-10-17 10:25   ` Ulf Hansson
  0 siblings, 0 replies; 22+ messages in thread
From: Ulf Hansson @ 2025-10-17 10:25 UTC (permalink / raw)
  To: dang.huynh
  Cc: Manivannan Sadhasivam, Linus Walleij, Bartosz Golaszewski,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Vinod Koul, Philipp Zabel, Kees Cook,
	Gustavo A. R. Silva, linux-arm-kernel, linux-unisoc, linux-gpio,
	devicetree, linux-kernel, linux-clk, dmaengine, linux-mmc,
	linux-hardening

On Thu, 18 Sept 2025 at 20:50, Dang Huynh via B4 Relay
<devnull+dang.huynh.mainlining.org@kernel.org> wrote:
>
> From: Dang Huynh <dang.huynh@mainlining.org>
>
> RDA Micro RDA8810PL includes an SD/MMC controller. This controller
> supports SD/SDIO/MMC interface.
>
> Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>

Hi Dang,

I just wanted to let you know that I have looked through this patch -
and while I have a few minor comments, I thought it was better for me
to await your new version of the series.

Kind regards
Uffe

> ---
>  MAINTAINERS                |   6 +
>  drivers/mmc/host/Kconfig   |  12 +
>  drivers/mmc/host/Makefile  |   1 +
>  drivers/mmc/host/rda-mmc.c | 853 +++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 872 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 923101a9d6c2edea339d1211b1cfdf4b917d1208..442dc8218541c1c05c03383f13b3f062f06cdae9 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -21430,6 +21430,12 @@ S:     Maintained
>  F:     Documentation/devicetree/bindings/dma/rda,ifc.yaml
>  F:     drivers/dma/rda-ifc.c
>
> +RDA MICRO SECURE DIGITAL AND MULTIMEDIA CARD DRIVER
> +M:     Dang Huynh <dang.huynh@mainlining.org>
> +S:     Maintained
> +F:     Documentation/devicetree/bindings/mmc/rda,mmc.yaml
> +F:     drivers/mmc/host/rda-mmc.c
> +
>  RDACM20 Camera Sensor
>  M:     Jacopo Mondi <jacopo+renesas@jmondi.org>
>  M:     Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 4afa0130779d97ca9d1c0ed2102b0babdedcaeeb..e7e747ef9a860cbe88dc8fac1015a915a62f10d3 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -1040,6 +1040,18 @@ config MMC_MTK
>           This is needed if support for any SD/SDIO/MMC devices is required.
>           If unsure, say N.
>
> +config MMC_RDA
> +       tristate "RDA Micro SD/MMC Card Interface support"
> +       depends on ARCH_RDA || COMPILE_TEST
> +       depends on COMMON_CLK
> +       depends on HAS_DMA
> +       help
> +         This selects the RDA Micro Secure digital and Multimedia card interface. The
> +         controller supports SD/SDIO/MMC interface.
> +         If you have a board with RDA SoC and it uses this interface, say Y or M here.
> +
> +         If unsure, say N.
> +
>  config MMC_SDHCI_MICROCHIP_PIC32
>         tristate "Microchip PIC32MZDA SDHCI support"
>         depends on MMC_SDHCI && PIC32MZDA && MMC_SDHCI_PLTFM
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index 5057fea8afb696e210e465a6a2aafc68adad7854..d819e18a478e35cb7de6d67b1cf827e1b3d09815 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -29,6 +29,7 @@ obj-$(CONFIG_MMC_ALCOR)       += alcor.o
>  obj-$(CONFIG_MMC_MTK)          += mtk-sd.o
>  obj-$(CONFIG_MMC_OMAP)         += omap.o
>  obj-$(CONFIG_MMC_OMAP_HS)      += omap_hsmmc.o
> +obj-$(CONFIG_MMC_RDA)          += rda-mmc.o
>  obj-$(CONFIG_MMC_ATMELMCI)     += atmel-mci.o
>  obj-$(CONFIG_MMC_TIFM_SD)      += tifm_sd.o
>  obj-$(CONFIG_MMC_MVSDIO)       += mvsdio.o
> diff --git a/drivers/mmc/host/rda-mmc.c b/drivers/mmc/host/rda-mmc.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..c358d170a930cbb7bf93a9066044c3b7ac957229
> --- /dev/null
> +++ b/drivers/mmc/host/rda-mmc.c
> @@ -0,0 +1,853 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * SD/MMC driver for RDA Micro platform
> + *
> + * Copyright (C) 2013 RDA Microelectronics Inc.
> + * Copyright (c) 2025 Dang Huynh <dang.huynh@mainlining.org>
> + */
> +
> +#include <linux/of.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/dmaengine.h>
> +#include <linux/iopoll.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/slot-gpio.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/reset.h>
> +#include <linux/debugfs.h>
> +#include <linux/scatterlist.h>
> +#include <linux/spinlock.h>
> +
> +/* Registers Definitions */
> +#define SDMMC_REG_CTRL 0x0
> +#define SDMMC_REG_FIFO_TXRX 0x8
> +#define SDMMC_REG_CONFIG 0x800
> +#define SDMMC_REG_STATUS 0x804
> +#define SDMMC_REG_CMD_INDEX 0x808
> +#define SDMMC_REG_CMD_ARG 0x80C
> +#define SDMMC_REG_RESP_INDEX 0x810
> +#define SDMMC_REG_RESP_ARG3 0x814
> +#define SDMMC_REG_RESP_ARG2 0x818
> +#define SDMMC_REG_RESP_ARG1 0x81C
> +#define SDMMC_REG_RESP_ARG0 0x820
> +#define SDMMC_REG_DATA_WIDTH 0x824
> +#define SDMMC_REG_BLOCK_SIZE 0x828
> +#define SDMMC_REG_BLOCK_COUNT 0x82C
> +#define SDMMC_REG_INT_STATUS 0x830
> +#define SDMMC_REG_INT_MASK 0x834
> +#define SDMMC_REG_INT_CLEAR 0x838
> +#define SDMMC_REG_TRANS_SPEED 0x83C
> +#define SDMMC_REG_MCLK_ADJUST 0x840
> +
> +/* Bits def */
> +/* CTRL */
> +#define SDMMC_CTRL_ENDIAN GENMASK(2, 0)
> +#define SDMMC_CTRL_SOFTRST_L BIT(3)
> +
> +/* CONFIG */
> +#define SDMMC_CFG_SENDCMD BIT(0)
> +#define SDMMC_CFG_SUSPEND BIT(1)
> +#define SDMMC_CFG_RSP_EN BIT(4)
> +#define SDMMC_CFG_RSP_SEL GENMASK(6, 5)
> +#define SDMMC_CFG_RD_WT_EN BIT(8)
> +#define SDMMC_CFG_RD_WT_SEL BIT(9)
> +#define SDMMC_CFG_S_M_SEL BIT(10)
> +#define SDMMC_CFG_AUTO_FLAG_EN BIT(16)
> +#define SDMMC_CFG_SAMPLE_EDGE_SEL_FALL_EN BIT(17)
> +
> +/* STATUS */
> +#define SDMMC_STATUS_NOTOVER BIT(0)
> +#define SDMMC_STATUS_BUSY BIT(1)
> +#define SDMMC_STATUS_DLBUSY BIT(2)
> +#define SDMMC_STATUS_SUSPEND BIT(3)
> +#define SDMMC_STATUS_RSP_ERR BIT(8)
> +#define SDMMC_STATUS_NO_RSP_ERR BIT(9)
> +#define SDMMC_STATUS_CRC_STATUS GENMASK(14, 12)
> +#define SDMMC_STATUS_DATA_ERROR GENMASK(23, 16)
> +#define SDMMC_STATUS_DAT3_VAL BIT(24)
> +
> +/* INTERRUPTS */
> +/* Mask and Clear */
> +#define SDMMC_INT_NO_RSP BIT(0)
> +#define SDMMC_INT_RSP_ERR BIT(1)
> +#define SDMMC_INT_RD_ERR BIT(2)
> +#define SDMMC_INT_WR_ERR BIT(3)
> +#define SDMMC_INT_DAT_OVER BIT(4)
> +#define SDMMC_INT_TXDMA_DONE BIT(5)
> +#define SDMMC_INT_RXDMA_DONE BIT(6)
> +#define SDMMC_INT_SDIO BIT(7)
> +
> +#define SDMMC_MCLK_INVERT BIT(4)
> +#define SDMMC_MCLK_DISABLE BIT(5)
> +
> +struct rda_mmc_host {
> +       struct device *dev;
> +
> +       struct mmc_host *mmc;
> +       struct mmc_request *mrq;
> +
> +       unsigned int clock;
> +       unsigned int bus_width;
> +       unsigned int power_mode;
> +       struct regulator *vmmc;
> +
> +       void __iomem *base;
> +       int irq;
> +
> +       struct clk *clk;
> +       struct reset_control *reset;
> +
> +       dma_cookie_t dma_cookie;
> +       struct dma_chan *dma_tx;
> +       struct dma_chan *dma_rx;
> +
> +       bool sdio_irq;
> +       bool sdio_irq_trigger;
> +
> +       spinlock_t lock;
> +       struct completion c;
> +
> +       /* device tree properties */
> +       bool mclk_inv;
> +       u8 mclk_adj;
> +};
> +
> +static int rda_mmc_hw_init(struct rda_mmc_host *priv)
> +{
> +       void __iomem *base = priv->base;
> +
> +       disable_irq(priv->irq);
> +
> +       writel(FIELD_PREP(SDMMC_CTRL_ENDIAN, 1) | SDMMC_CTRL_SOFTRST_L,
> +                       base + SDMMC_REG_CTRL);
> +       writel(SDMMC_INT_RD_ERR | SDMMC_INT_WR_ERR | SDMMC_INT_DAT_OVER,
> +                       base + SDMMC_REG_INT_MASK);
> +       writel(0xFFFFFFFF, base + SDMMC_REG_INT_CLEAR);
> +
> +       enable_irq(priv->irq);
> +
> +       return 0;
> +}
> +
> +static void rda_mmc_reset(struct rda_mmc_host *priv)
> +{
> +       reset_control_assert(priv->reset);
> +       mdelay(1);
> +       reset_control_deassert(priv->reset);
> +       mdelay(1);
> +}
> +
> +static void rda_mmc_recv_resp(struct mmc_host *host, struct mmc_command *cmd)
> +{
> +       struct rda_mmc_host *priv = mmc_priv(host);
> +       struct device *dev = mmc_dev(host);
> +       void __iomem *base = priv->base;
> +       u32 status;
> +       int ret;
> +
> +       /* If the controller is busy, wait until it finishes */
> +       ret = readl_poll_timeout(base + SDMMC_REG_STATUS, status,
> +                       !(status & SDMMC_STATUS_NOTOVER), 50, 1000 * 1000);
> +       if (ret) {
> +               dev_err(dev, "Timed out waiting for the controller\n");
> +               cmd->error = ret;
> +               return;
> +       }
> +
> +       if (status & SDMMC_STATUS_NO_RSP_ERR)
> +               return;
> +
> +       if (status & SDMMC_STATUS_RSP_ERR) {
> +               cmd->error = -EILSEQ;
> +               return;
> +       }
> +
> +       if (mmc_resp_type(cmd) & MMC_RSP_R2) {
> +               cmd->resp[0] = readl_relaxed(base + SDMMC_REG_RESP_ARG3);
> +               cmd->resp[1] = readl_relaxed(base + SDMMC_REG_RESP_ARG2);
> +               cmd->resp[2] = readl_relaxed(base + SDMMC_REG_RESP_ARG1);
> +               cmd->resp[3] = readl_relaxed(base + SDMMC_REG_RESP_ARG0) << 1;
> +       } else {
> +               cmd->resp[0] = readl_relaxed(base + SDMMC_REG_RESP_ARG3);
> +       }
> +
> +       dev_dbg(dev, "response: resp[0] = 0x%x, resp[1] = 0x%x, resp[2] = 0x%x, resp[3] = 0x%x\n",
> +                       cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
> +}
> +
> +static inline struct dma_chan *rda_mmc_get_dma_chan(struct rda_mmc_host *priv,
> +               struct mmc_data *data)
> +{
> +       if (data->flags & MMC_DATA_WRITE)
> +               return priv->dma_tx;
> +       else
> +               return priv->dma_rx;
> +}
> +
> +static int rda_mmc_send_data(struct mmc_host *host, struct mmc_data *data)
> +{
> +       struct rda_mmc_host *priv = mmc_priv(host);
> +       struct device *dev = mmc_dev(host);
> +       struct dma_slave_config slave_config;
> +       struct dma_async_tx_descriptor *desc;
> +       struct dma_chan *chan;
> +       int ret;
> +
> +       if (!data) {
> +               dev_err(dev, "No MMC request or data\n");
> +               goto fail;
> +       }
> +
> +       if (data->flags & MMC_DATA_WRITE) {
> +               slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +               slave_config.direction = DMA_MEM_TO_DEV;
> +       } else {
> +               slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +               slave_config.direction = DMA_DEV_TO_MEM;
> +       }
> +
> +       data->sg_count = dma_map_sg(dev, data->sg, data->sg_len, mmc_get_dma_dir(data));
> +       if (data->sg_count == 0) {
> +               ret = -ENOMEM;
> +               goto fail;
> +       }
> +
> +       chan = rda_mmc_get_dma_chan(priv, data);
> +
> +       ret = dmaengine_slave_config(chan, &slave_config);
> +       if (ret) {
> +               dev_err(dev, "Failed to configure DMAC\n");
> +               goto fail_dma;
> +       }
> +
> +       desc = dmaengine_prep_slave_sg(chan, data->sg, data->sg_count,
> +                       slave_config.direction, DMA_CTRL_ACK);
> +       if (!desc) {
> +               dev_err(dev, "Failed to allocate DMA descriptor\n");
> +               goto fail_dma;
> +       }
> +
> +       priv->dma_cookie = dmaengine_submit(desc);
> +       if (!priv->dma_cookie) {
> +               dev_err(dev, "Failed to submit DMA request\n");
> +               goto fail_dma;
> +       }
> +
> +       dma_async_issue_pending(chan);
> +
> +       return 0;
> +
> +fail_dma:
> +       dma_unmap_sg(dev, data->sg, data->sg_len, mmc_get_dma_dir(data));
> +fail:
> +       return -EINVAL;
> +}
> +
> +static int rda_mmc_prepare_data(struct mmc_host *host, struct mmc_command *cmd,
> +               struct mmc_data *data, u32 *cfg)
> +{
> +       struct rda_mmc_host *priv = mmc_priv(host);
> +       struct device *dev = mmc_dev(host);
> +       void __iomem *base = priv->base;
> +       int hw_blksz = 2; /* 1 word */
> +       int i = 0;
> +
> +       /* If we're still here, we'll assume there's data ops */
> +       *cfg |= SDMMC_CFG_RD_WT_EN;
> +
> +       /* Tell the controller we have a write operation */
> +       if (data->flags & MMC_DATA_WRITE)
> +               *cfg |= SDMMC_CFG_RD_WT_SEL;
> +
> +       /* Multiple data read/write */
> +       if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
> +               *cfg |= SDMMC_CFG_S_M_SEL;
> +
> +               /*
> +                * Tell the controller to automatically issue CMD12 when the last block
> +                * transfer is completed on non-SDIO cards.
> +                */
> +               if (!mmc_card_sdio(host->card))
> +                       *cfg |= SDMMC_CFG_AUTO_FLAG_EN;
> +       }
> +
> +       /* Blocksize on this IP is calculated by how many words are requested */
> +       if (data->blksz > 4) {
> +               for (i = 4; i < data->blksz; i <<= 1)
> +                       hw_blksz++;
> +       }
> +
> +       if (unlikely(hw_blksz > 11)) {
> +               dev_err(dev, "Requested %d but hardware can only support 11!\n", hw_blksz);
> +               return -EINVAL;
> +       }
> +
> +       writel_relaxed(data->blocks, base + SDMMC_REG_BLOCK_COUNT);
> +       writel_relaxed(hw_blksz, base + SDMMC_REG_BLOCK_SIZE);
> +
> +       return 0;
> +}
> +
> +static int rda_mmc_send_cmd(struct mmc_host *host, struct mmc_command *cmd,
> +               struct mmc_data *data)
> +{
> +       struct rda_mmc_host *priv = mmc_priv(host);
> +       void __iomem *base = priv->base;
> +       u32 cfg = SDMMC_CFG_SENDCMD;
> +       int ret;
> +
> +       switch (mmc_resp_type(cmd)) {
> +       case MMC_RSP_R2:
> +               cfg |= SDMMC_CFG_RSP_EN | FIELD_PREP(SDMMC_CFG_RSP_SEL, 2);
> +               break;
> +       case MMC_RSP_R3:
> +               cfg |= SDMMC_CFG_RSP_EN | FIELD_PREP(SDMMC_CFG_RSP_SEL, 1);
> +               break;
> +       default:
> +               cfg |= SDMMC_CFG_RSP_EN;
> +               break;
> +       }
> +
> +       /* No data */
> +       if (!data)
> +               goto send_to_soc;
> +
> +       /* Data operations */
> +       ret = rda_mmc_prepare_data(host, cmd, data, &cfg);
> +       if (ret < 0)
> +               return -EINVAL;
> +
> +       ret = rda_mmc_send_data(host, data);
> +       if (ret < 0)
> +               return -EINVAL;
> +
> +send_to_soc:
> +       writel(cmd->opcode, base + SDMMC_REG_CMD_INDEX);
> +       writel(cmd->arg, base + SDMMC_REG_CMD_ARG);
> +       writel(cfg, base + SDMMC_REG_CONFIG);
> +
> +       dev_dbg(priv->dev, "mmc_resp_type = %d, cmd->opcode = 0x%x, cmd->arg = 0x%x - cfg: 0x%x\n",
> +                       mmc_resp_type(cmd), cmd->opcode, cmd->arg, cfg);
> +
> +       rda_mmc_recv_resp(host, cmd);
> +
> +       return 0;
> +}
> +
> +/*
> + * Once data transfer failed (or aborted), the controller needs to be
> + * cleaned up.
> + */
> +static void rda_mmc_data_abort(struct mmc_host *host, struct mmc_request *mrq)
> +{
> +       struct rda_mmc_host *priv = mmc_priv(host);
> +       struct device *dev = mmc_dev(host);
> +       void __iomem *base = priv->base;
> +       struct mmc_command stop;
> +       int ret;
> +
> +       writel_relaxed(0, base + SDMMC_REG_BLOCK_COUNT);
> +       writel_relaxed(0, base + SDMMC_REG_BLOCK_SIZE);
> +
> +       if (!host->card)
> +               return;
> +
> +       /*
> +        * Issue a stop command first, because if the controller timed out,
> +        * it'll not return an IRQ or any indicator.
> +        */
> +       if (!mmc_card_sdio(host->card)) {
> +               if (!mrq->stop) {
> +                       stop.opcode = MMC_STOP_TRANSMISSION;
> +                       stop.arg = 0;
> +                       stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
> +                       ret = rda_mmc_send_cmd(host, &stop, NULL);
> +               } else {
> +                       ret = rda_mmc_send_cmd(host, mrq->stop, NULL);
> +               }
> +
> +               if (ret < 0)
> +                       dev_err(dev, "Failed to send stop command\n");
> +       }
> +}
> +
> +static void rda_mmc_request(struct mmc_host *host, struct mmc_request *req)
> +{
> +       struct rda_mmc_host *priv = mmc_priv(host);
> +       struct device *dev = mmc_dev(host);
> +       struct mmc_data *data = NULL;
> +       struct dma_chan *chan;
> +       unsigned long flags;
> +
> +       spin_lock_irqsave(&priv->lock, flags);
> +
> +       WARN_ON(priv->mrq);
> +       priv->mrq = req;
> +
> +       if (req->data) {
> +               dev_dbg(dev, "Block size = %d - Blocks = %d - Offset: %d - Length: %d\n",
> +                               req->data->blksz, req->data->blocks,
> +                               req->data->sg->offset, req->data->sg->length);
> +               data = req->data;
> +       }
> +
> +       if (rda_mmc_send_cmd(host, req->cmd, data) < 0) {
> +               req->cmd->error = -EINVAL;
> +               if (data)
> +                       req->data->error = -EINVAL;
> +
> +               goto done_irqunlock;
> +       }
> +
> +       /* Interrupt will pick up on this */
> +       if (!data)
> +               goto done_irqunlock;
> +
> +       spin_unlock_irqrestore(&priv->lock, flags);
> +
> +       /*
> +        * On a data operation, we rely on our interrupt to tell us
> +        * when the transmission is finished (or failed).
> +        *
> +        * However with this IP, if the operation timed out, it will
> +        * not trigger an IRQ and we'll not return.
> +        */
> +       if (data) {
> +               if (wait_for_completion_timeout(&priv->c,
> +                                       msecs_to_jiffies(5000)) == 0) {
> +                       spin_lock_irqsave(&priv->lock, flags);
> +                       priv->mrq = NULL;
> +
> +                       dma_unmap_sg(dev, data->sg, data->sg_len,
> +                                       mmc_get_dma_dir(data));
> +
> +                       chan = rda_mmc_get_dma_chan(priv, data);
> +
> +                       dmaengine_terminate_sync(chan);
> +                       rda_mmc_data_abort(host, req);
> +
> +                       req->cmd->error = -ETIMEDOUT;
> +                       req->data->error = -ETIMEDOUT;
> +                       goto done_irqunlock;
> +               }
> +       }
> +
> +       return;
> +
> +done_irqunlock:
> +       priv->mrq = NULL;
> +       spin_unlock_irqrestore(&priv->lock, flags);
> +       mmc_request_done(host, req);
> +}
> +
> +static void rda_mmc_set_ios(struct mmc_host *host, struct mmc_ios *ios)
> +{
> +       struct rda_mmc_host *priv = mmc_priv(host);
> +       struct device *dev = mmc_dev(host);
> +       void __iomem *base = priv->base;
> +       unsigned long mclk_rate;
> +       unsigned int clk_div;
> +       unsigned long flags;
> +       u32 reg_mclk = 0;
> +       int ret;
> +
> +       spin_lock_irqsave(&priv->lock, flags);
> +
> +       if (priv->bus_width != ios->bus_width) {
> +               priv->bus_width = ios->bus_width;
> +               writel(BIT(priv->bus_width), base + SDMMC_REG_DATA_WIDTH);
> +       }
> +
> +       if (priv->power_mode != ios->power_mode) {
> +               priv->power_mode = ios->power_mode;
> +               if (priv->power_mode == MMC_POWER_UP) {
> +                       ret = regulator_enable(priv->vmmc);
> +                       if (ret)
> +                               dev_err(dev, "Failed to turn on vmmc\n");
> +               } else if (priv->power_mode == MMC_POWER_OFF) {
> +                       ret = regulator_disable(priv->vmmc);
> +                       if (ret)
> +                               dev_err(dev, "Failed to turn off vmmc\n");
> +               }
> +       }
> +
> +       if (priv->clock != ios->clock) {
> +               priv->clock = ios->clock;
> +               if (ios->clock) {
> +                       /* trans speed  */
> +                       mclk_rate = clk_get_rate(priv->clk);
> +                       if (mclk_rate == 0) {
> +                               dev_err(dev, "Invalid MCLK rate\n");
> +                               goto bailout;
> +                       }
> +
> +                       clk_div = mclk_rate / (2 * ios->clock);
> +                       if (mclk_rate % (2 * ios->clock))
> +                               clk_div++;
> +
> +                       if (clk_div >= 1)
> +                               clk_div -= 1;
> +
> +                       if (clk_div > 255)
> +                               clk_div = 255;
> +
> +                       /* mclk adjust */
> +                       if (priv->mclk_inv)
> +                               reg_mclk = SDMMC_MCLK_INVERT;
> +
> +                       reg_mclk |= priv->mclk_adj;
> +
> +                       writel_relaxed(clk_div, base + SDMMC_REG_TRANS_SPEED);
> +                       writel_relaxed(reg_mclk, base + SDMMC_REG_MCLK_ADJUST);
> +
> +                       dev_dbg(dev, "set clk = %d - mclk = %ld - divider = %d\n",
> +                                       ios->clock, mclk_rate, clk_div);
> +               } else {
> +                       writel_relaxed(SDMMC_MCLK_DISABLE, base + SDMMC_REG_MCLK_ADJUST);
> +               }
> +       }
> +
> +bailout:
> +       dev_dbg(dev, "buswidth=%d, clock=%d, power=%d\n",
> +                       ios->bus_width, ios->clock, ios->power_mode);
> +
> +       spin_unlock_irqrestore(&priv->lock, flags);
> +}
> +
> +static void rda_mmc_crc_status(struct mmc_host *host)
> +{
> +       struct rda_mmc_host *priv = mmc_priv(host);
> +       struct device *dev = mmc_dev(host);
> +       void __iomem *base = priv->base;
> +       const char *crc_error;
> +       u32 status;
> +
> +       status = readl_relaxed(base + SDMMC_REG_STATUS);
> +
> +       switch (FIELD_GET(SDMMC_STATUS_CRC_STATUS, status)) {
> +       case 0b101:
> +               crc_error = "Transmission Error";
> +               break;
> +       case 0b010:
> +               crc_error = "Transmission Right";
> +               break;
> +       case 0b111:
> +               crc_error = "Flash Programming Error";
> +               break;
> +       default:
> +               crc_error = "Unknown";
> +               break;
> +       }
> +
> +       dev_err(dev, "CRC Error: %s - DATA_ERROR: 0x%lx\n", crc_error,
> +                       FIELD_GET(SDMMC_STATUS_DATA_ERROR, status));
> +}
> +
> +static irqreturn_t rda_mmc_irq(int irq, void *dev_id)
> +{
> +       struct rda_mmc_host *priv = dev_id;
> +       struct mmc_host *host = mmc_from_priv(priv);
> +       struct device *dev = mmc_dev(host);
> +       void __iomem *base = priv->base;
> +       struct mmc_request *mrq;
> +       u32 status;
> +       irqreturn_t irqret = IRQ_NONE;
> +
> +       status = readl(base + SDMMC_REG_INT_STATUS);
> +       writel((status & 0xFF), base + SDMMC_REG_INT_CLEAR);
> +
> +       dev_dbg(dev, "IRQ requested - status: 0x%x\n", status);
> +
> +       if (!priv->mrq || !priv->mrq->data)
> +               goto irq_done;
> +
> +       mrq = priv->mrq;
> +
> +       if (mrq->data && ((status & SDMMC_INT_RD_ERR) || (status & SDMMC_INT_WR_ERR)))
> +               mrq->data->error = -EILSEQ;
> +
> +       if (priv->sdio_irq && (status & SDMMC_INT_SDIO))
> +               priv->sdio_irq_trigger = true;
> +
> +       irqret = IRQ_WAKE_THREAD;
> +
> +       /* We got an error, no need to do the additional checks */
> +       if (mrq->data->error)
> +               goto irq_done;
> +
> +       /*
> +        * If we don't have any error but DAT_OVER isn't triggered, then we'll assume
> +        * that we got an unexpected IRQ (during a data transfer)
> +        */
> +       if (!mrq->data->error && !(status & SDMMC_INT_DAT_OVER))
> +               irqret = IRQ_HANDLED;
> +
> +irq_done:
> +       if (irqret == IRQ_NONE)
> +               dev_info(dev,
> +                       "Unexpected IRQ - was a data transfer requested? IRQ: 0x%x\n", status);
> +
> +       return irqret;
> +}
> +
> +static irqreturn_t rda_mmc_irq_fn(int irq, void *dev_id)
> +{
> +       struct rda_mmc_host *priv = dev_id;
> +       struct mmc_host *host = mmc_from_priv(priv);
> +       struct device *dev = mmc_dev(host);
> +       struct mmc_request *mrq;
> +       struct dma_chan *chan;
> +       struct dma_tx_state state;
> +       enum dma_status dma_status;
> +       unsigned long flags;
> +
> +       if (WARN_ON(!priv->mrq))
> +               return IRQ_NONE;
> +
> +       spin_lock_irqsave(&priv->lock, flags);
> +
> +       mrq = priv->mrq;
> +
> +       if (mrq->data) {
> +               chan = rda_mmc_get_dma_chan(priv, mrq->data);
> +
> +               if (mrq->data->error) {
> +                       mrq->data->bytes_xfered = 0;
> +                       rda_mmc_crc_status(host);
> +                       dmaengine_terminate_sync(chan);
> +                       rda_mmc_data_abort(host, mrq);
> +               } else {
> +                       mrq->data->bytes_xfered =
> +                               mrq->data->blocks * mrq->data->blksz;
> +
> +                       /*
> +                        * With this IP, just because a TXDMA/RXDMA interrupt is triggered,
> +                        * doesn't mean the MMC is fully processed.
> +                        */
> +                       dma_status = dmaengine_tx_status(chan, priv->dma_cookie, &state);
> +                       dev_dbg(mmc_dev(host), "DMA Status: %d\n", dma_status);
> +                       if (dma_status != DMA_COMPLETE) {
> +                               dev_err(dev, "Transmit IRQ triggered but DMA is not finished\n");
> +                               mrq->data->error = -ETIMEDOUT;
> +                               mrq->data->bytes_xfered = 0;
> +                               dmaengine_terminate_sync(chan);
> +                               rda_mmc_data_abort(host, mrq);
> +                       }
> +               }
> +
> +               /*
> +                * Since we told the controller to automatically send a stop command,
> +                * we don't have to send a stop command here.
> +                */
> +               dma_unmap_sg(dev, mrq->data->sg, mrq->data->sg_len,
> +                               mmc_get_dma_dir(mrq->data));
> +               dmaengine_terminate_sync(chan);
> +       }
> +
> +       priv->mrq = NULL;
> +       spin_unlock_irqrestore(&priv->lock, flags);
> +
> +       complete(&priv->c);
> +       mmc_request_done(host, mrq);
> +
> +       if (priv->sdio_irq && priv->sdio_irq_trigger)
> +               mmc_signal_sdio_irq(host);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int rda_mmc_card_busy(struct mmc_host *host)
> +{
> +       struct rda_mmc_host *priv = mmc_priv(host);
> +       void __iomem *base = priv->base;
> +       u32 status = readl(base + SDMMC_REG_STATUS);
> +
> +       return (status & SDMMC_STATUS_DLBUSY);
> +}
> +
> +static void rda_mmc_sdio_enable_irq(struct mmc_host *host, int enable)
> +{
> +       struct rda_mmc_host *priv = mmc_priv(host);
> +       void __iomem *base = priv->base;
> +       u32 intmask = readl(base + SDMMC_REG_INT_MASK);
> +
> +       if (enable) {
> +               intmask |= SDMMC_INT_SDIO;
> +               priv->sdio_irq = true;
> +       } else {
> +               intmask &= ~SDMMC_INT_SDIO;
> +               priv->sdio_irq = false;
> +       }
> +
> +       priv->sdio_irq_trigger = false;
> +
> +       writel(intmask, base + SDMMC_REG_INT_MASK);
> +}
> +
> +static const struct mmc_host_ops rda_mmc_ops = {
> +       .request = rda_mmc_request,
> +       .set_ios = rda_mmc_set_ios,
> +       .get_cd = mmc_gpio_get_cd,
> +       .get_ro = mmc_gpio_get_ro,
> +       .card_busy = rda_mmc_card_busy,
> +       .enable_sdio_irq = rda_mmc_sdio_enable_irq,
> +};
> +
> +static void rda_mmc_of_parse(struct device_node *np, struct rda_mmc_host *priv)
> +{
> +       bool mclk_inv = false;
> +       u8 mclk_adj = 1;
> +
> +       if (of_property_present(np, "rda,mclk-inv"))
> +               mclk_inv = true;
> +
> +       of_property_read_u8(np, "rda,mclk-adj", &mclk_adj);
> +
> +       priv->mclk_inv = mclk_inv;
> +       priv->mclk_adj = mclk_adj;
> +}
> +
> +static int rda_mmc_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct rda_mmc_host *priv;
> +       struct mmc_host *mmc;
> +       struct clk *clk;
> +       struct reset_control *reset;
> +       struct dma_chan *tx, *rx;
> +       struct regulator *vmmc;
> +       void __iomem *base;
> +       int irq;
> +       int ret;
> +
> +       base = devm_platform_ioremap_resource(pdev, 0);
> +       if (IS_ERR(base))
> +               return dev_err_probe(dev, PTR_ERR(base), "Cannot get iomap\n");
> +
> +       irq = platform_get_irq(pdev, 0);
> +       if (irq < 0)
> +               return dev_err_probe(dev, irq, "Cannot get IRQ: %d\n", irq);
> +
> +       clk = devm_clk_get_enabled(dev, "mclk");
> +       if (IS_ERR(clk))
> +               return dev_err_probe(dev, PTR_ERR(clk), "Cannot get clock device\n");
> +
> +       vmmc = devm_regulator_get(dev, "vmmc");
> +       if (IS_ERR(vmmc))
> +               return dev_err_probe(dev, PTR_ERR(vmmc), "Failed to obtain regulator\n");
> +
> +       reset = devm_reset_control_get_by_index(dev, 0);
> +       if (IS_ERR(reset))
> +               return dev_err_probe(dev, PTR_ERR(reset), "Failed to obtain reset\n");
> +
> +       tx = dma_request_chan(dev, "tx");
> +       if (IS_ERR(tx))
> +               return dev_err_probe(dev, PTR_ERR(tx), "Failed to request tx channel\n");
> +
> +       rx = dma_request_chan(dev, "rx");
> +       if (IS_ERR(rx))
> +               return dev_err_probe(dev, PTR_ERR(rx), "Failed to request rx channel\n");
> +
> +       mmc = devm_mmc_alloc_host(dev, sizeof(*priv));
> +       if (IS_ERR(mmc)) {
> +               dev_err(dev, "Cannot allocate memory for MMC\n");
> +               ret = PTR_ERR(mmc);
> +               goto fail_release_dma;
> +       }
> +
> +       priv = mmc_priv(mmc);
> +       priv->dev = dev;
> +       priv->base = base;
> +       priv->irq = irq;
> +       priv->clk = clk;
> +       priv->reset = reset;
> +       priv->dma_tx = tx;
> +       priv->dma_rx = rx;
> +       priv->vmmc = vmmc;
> +       spin_lock_init(&priv->lock);
> +       init_completion(&priv->c);
> +
> +       mmc->ops = &rda_mmc_ops;
> +
> +       mmc->max_segs = 1;
> +       mmc->max_blk_size = 4096;
> +       mmc->max_blk_count = 0xFFFF;
> +       mmc->max_req_size = 0xFFFF;
> +       mmc->max_seg_size = 0xFFFF;
> +
> +       mmc->f_min = 1000000;
> +       mmc->caps = MMC_CAP_4_BIT_DATA;
> +       mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
> +
> +       rda_mmc_of_parse(dev->of_node, priv);
> +
> +       ret = mmc_of_parse(mmc);
> +       if (ret) {
> +               dev_err(dev, "Failed to parse device tree: %d\n", ret);
> +               goto fail_release_dma;
> +       }
> +
> +       rda_mmc_reset(priv);
> +       rda_mmc_hw_init(priv);
> +
> +       priv->bus_width = -1;
> +
> +       ret = devm_request_threaded_irq(dev, irq, rda_mmc_irq, rda_mmc_irq_fn,
> +                       IRQF_ONESHOT, mmc_hostname(mmc), priv);
> +       if (ret) {
> +               dev_err(dev, "Failed to request IRQ: %d\n", ret);
> +               goto fail_release_dma;
> +       }
> +
> +       ret = mmc_add_host(mmc);
> +       if (ret) {
> +               dev_err(dev, "Failed to add MMC host: %d\n", ret);
> +               goto fail_release_dma;
> +       }
> +
> +       platform_set_drvdata(pdev, mmc);
> +       return 0;
> +
> +fail_release_dma:
> +       dma_release_channel(rx);
> +       dma_release_channel(tx);
> +       return ret;
> +}
> +
> +static void rda_mmc_remove(struct platform_device *pdev)
> +{
> +       struct rda_mmc_host *host = platform_get_drvdata(pdev);
> +
> +       mmc_remove_host(host->mmc);
> +       dma_release_channel(host->dma_rx);
> +       dma_release_channel(host->dma_tx);
> +}
> +
> +static const struct of_device_id rda_mmc_dt_ids[] = {
> +       { .compatible = "rda,8810pl-mmc", },
> +       { .compatible = "rda,mmc", },
> +       {},
> +};
> +MODULE_DEVICE_TABLE(of, rda_mmc_dt_ids);
> +
> +static struct platform_driver rda_mmc_driver = {
> +       .probe          = rda_mmc_probe,
> +       .remove         = rda_mmc_remove,
> +       .driver         = {
> +               .name   = "rda-mmc",
> +               .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +               .of_match_table = rda_mmc_dt_ids,
> +       },
> +};
> +module_platform_driver(rda_mmc_driver);
> +
> +MODULE_AUTHOR("Dang Huynh <dang.huynh@mainlining.org>");
> +MODULE_DESCRIPTION("RDA Micro SD/MMC driver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.51.0
>
>
>

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

end of thread, other threads:[~2025-10-17 10:26 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-18 18:48 [PATCH 00/10] RDA8810PL SD/MMC support Dang Huynh via B4 Relay
2025-09-18 18:48 ` [PATCH 01/10] dt-bindings: gpio: rda: Make interrupts optional Dang Huynh via B4 Relay
2025-09-18 18:48 ` [PATCH 02/10] dt-bindings: clock: Add RDA Micro RDA8810PL clock/reset controller Dang Huynh via B4 Relay
2025-09-22 18:04   ` Rob Herring
2025-09-18 18:48 ` [PATCH 03/10] dt-bindings: dma: Add RDA IFC DMA Dang Huynh via B4 Relay
2025-09-22 18:07   ` Rob Herring
2025-09-22 18:08   ` Rob Herring
2025-09-18 18:48 ` [PATCH 04/10] dt-bindings: mmc: Add RDA SDMMC controller Dang Huynh via B4 Relay
2025-09-22 18:13   ` Rob Herring
2025-09-18 18:48 ` [PATCH 05/10] gpio: rda: Make IRQ optional Dang Huynh via B4 Relay
2025-09-18 18:48 ` [PATCH 06/10] gpio: rda: Make direction register unreadable Dang Huynh via B4 Relay
2025-09-22 14:20   ` Bartosz Golaszewski
2025-09-18 18:48 ` [PATCH 07/10] clk: Add Clock and Reset Driver for RDA Micro RDA8810PL SoC Dang Huynh via B4 Relay
2025-09-18 18:48 ` [PATCH 08/10] dmaengine: Add RDA IFC driver Dang Huynh via B4 Relay
2025-09-21  4:42   ` kernel test robot
2025-09-18 18:48 ` [PATCH 09/10] mmc: host: Add RDA Micro SD/MMC driver Dang Huynh via B4 Relay
2025-10-17 10:25   ` Ulf Hansson
2025-09-18 18:48 ` [PATCH 10/10] ARM: dts: unisoc: rda8810pl: Add SDMMC controllers Dang Huynh via B4 Relay
2025-09-22 14:17 ` [PATCH 00/10] RDA8810PL SD/MMC support Bartosz Golaszewski
2025-09-23  3:40   ` Dang Huynh
2025-09-23  7:48     ` Bartosz Golaszewski
2025-09-25  5:30       ` Dang Huynh

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