All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement
@ 2026-05-11 20:21 Tanmay Kathpalia
  2026-05-11 20:21 ` [PATCH v1 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Tanmay Kathpalia
                   ` (8 more replies)
  0 siblings, 9 replies; 32+ messages in thread
From: Tanmay Kathpalia @ 2026-05-11 20:21 UTC (permalink / raw)
  To: linux-mmc; +Cc: ulf.hansson, Tanmay Kathpalia

This series adds support for the Cadence SD6HC (sixth-generation) SDHCI
controller and enables it on Altera Agilex5 SoCs.

The SD6HC PHY architecture differs substantially from the SD4HC found on
prior SoCFPGA platforms and requires dedicated handling for PHY timing,
DLL configuration, and speed-mode switching.

Patches 1-2: Update device tree bindings for the Agilex5 reset manager
and the Cadence SDHCI controller to cover the SD6HC compatible strings
and its associated PHY properties.

Patches 3-5: Add device tree nodes for the SD6HC controller on Agilex5,
enabling SD card operation on the SOCDK board and eMMC operation on the
SOCDK eMMC daughter card.

Patches 6-9: Extend the sdhci-cadence driver to support the SD6HC. The
driver is split into sdhci-cadence4.c and sdhci-cadence6.c while
preserving the sdhci-cadence module name. Agilex5 platform-specific
support is registered under the altr,agilex5-sd6hc compatible string.

Tested on Agilex5 SOCDK with SD card (SDR104) and eMMC daughter board
(HS400).

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>

Tanmay Kathpalia (9):
  dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5
  dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties
  arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement
  dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant
  arm64: dts: agilex5: add SOCDK eMMC daughter board support
  mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork
  mmc: sdhci-cadence: refactor driver structure for V6 controller
    support
  mmc: sdhci-cadence: add Cadence SD6HC support
  mmc: sdhci-cadence: add Altera Agilex5 SD6HC support

---
 .../devicetree/bindings/arm/altera.yaml       |    1 +
 .../devicetree/bindings/mmc/cdns,sdhci.yaml   |  107 +-
 MAINTAINERS                                   |    7 +
 arch/arm64/boot/dts/intel/Makefile            |    1 +
 .../arm64/boot/dts/intel/socfpga_agilex5.dtsi |   46 +
 .../boot/dts/intel/socfpga_agilex5_socdk.dts  |   26 +
 .../dts/intel/socfpga_agilex5_socdk_emmc.dts  |  111 ++
 drivers/mmc/host/Makefile                     |    3 +-
 drivers/mmc/host/sdhci-cadence.h              |  113 ++
 .../{sdhci-cadence.c => sdhci-cadence4.c}     |  241 ++--
 drivers/mmc/host/sdhci-cadence6.c             | 1051 +++++++++++++++++
 include/dt-bindings/reset/altr,rst-mgr-s10.h  |    2 +-
 12 files changed, 1623 insertions(+), 86 deletions(-)
 create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
 create mode 100644 drivers/mmc/host/sdhci-cadence.h
 rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence4.c} (73%)
 create mode 100644 drivers/mmc/host/sdhci-cadence6.c

-- 
2.43.7


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

* [PATCH v1 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5
  2026-05-11 20:21 [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
@ 2026-05-11 20:21 ` Tanmay Kathpalia
  2026-05-12 17:33   ` Conor Dooley
  2026-05-11 20:21 ` [PATCH v1 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties Tanmay Kathpalia
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 32+ messages in thread
From: Tanmay Kathpalia @ 2026-05-11 20:21 UTC (permalink / raw)
  To: linux-mmc
  Cc: ulf.hansson, Tanmay Kathpalia, Philipp Zabel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel

Add COMBOPHY_RESET definition at index 38 for the combo PHY reset
control on Altera Agilex5 SoCs. This reset is used by peripherals
such as the SD/eMMC controller that share the combo PHY.

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
 include/dt-bindings/reset/altr,rst-mgr-s10.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/dt-bindings/reset/altr,rst-mgr-s10.h b/include/dt-bindings/reset/altr,rst-mgr-s10.h
index 04c4d0c6fd34..c2505b9eb63e 100644
--- a/include/dt-bindings/reset/altr,rst-mgr-s10.h
+++ b/include/dt-bindings/reset/altr,rst-mgr-s10.h
@@ -22,7 +22,7 @@
 #define USB0_RESET		35
 #define USB1_RESET		36
 #define NAND_RESET		37
-/* 38 is empty */
+#define COMBOPHY_RESET		38
 #define SDMMC_RESET		39
 #define EMAC0_OCP_RESET		40
 #define EMAC1_OCP_RESET		41
-- 
2.43.7


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

* [PATCH v1 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties
  2026-05-11 20:21 [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
  2026-05-11 20:21 ` [PATCH v1 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Tanmay Kathpalia
@ 2026-05-11 20:21 ` Tanmay Kathpalia
  2026-05-12 17:33   ` Conor Dooley
  2026-05-13  0:23   ` sashiko-bot
  2026-05-11 20:21 ` [PATCH v1 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement Tanmay Kathpalia
                   ` (6 subsequent siblings)
  8 siblings, 2 replies; 32+ messages in thread
From: Tanmay Kathpalia @ 2026-05-11 20:21 UTC (permalink / raw)
  To: linux-mmc
  Cc: ulf.hansson, Tanmay Kathpalia, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Masahiro Yamada, devicetree, linux-kernel

Extend the Cadence SDHCI binding to support the sixth-generation
SD6HC controller. Add the cdns,sd6hc and altr,agilex5-sd6hc
compatible strings, clock-names, reset-names, iommus and three
SD6HC-specific PHY timing properties.

Add per-variant conditional blocks so SD6HC and SD4HC each enforce
their own clock, reset, and PHY property constraints independently.

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
 .../devicetree/bindings/mmc/cdns,sdhci.yaml   | 107 ++++++++++++++++--
 1 file changed, 97 insertions(+), 10 deletions(-)

diff --git a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
index 6c7317d13aa6..e483ff83cbc2 100644
--- a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
+++ b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
@@ -4,21 +4,29 @@
 $id: http://devicetree.org/schemas/mmc/cdns,sdhci.yaml#
 $schema: http://devicetree.org/meta-schemas/core.yaml#
 
-title: Cadence SD/SDIO/eMMC Host Controller (SD4HC)
+title: Cadence SD/SDIO/eMMC Host Controller (SD4HC and SD6HC)
 
 maintainers:
   - Masahiro Yamada <yamada.masahiro@socionext.com>
+  - Tanmay Kathpalia <tanmay.kathpalia@altera.com>
 
 properties:
   compatible:
-    items:
-      - enum:
-          - amd,pensando-elba-sd4hc
-          - microchip,mpfs-sd4hc
-          - microchip,pic64gx-sd4hc
-          - mobileye,eyeq-sd4hc
-          - socionext,uniphier-sd4hc
-      - const: cdns,sd4hc
+    oneOf:
+      - description: Cadence SD4HC controller
+        items:
+          - enum:
+              - amd,pensando-elba-sd4hc
+              - microchip,mpfs-sd4hc
+              - microchip,pic64gx-sd4hc
+              - mobileye,eyeq-sd4hc
+              - socionext,uniphier-sd4hc
+          - const: cdns,sd4hc
+      - description: Cadence SD6HC controller
+        items:
+          - enum:
+              - altr,agilex5-sd6hc
+          - const: cdns,sd6hc
 
   reg:
     minItems: 1
@@ -28,9 +36,14 @@ properties:
     maxItems: 1
 
   clocks:
-    maxItems: 1
+    minItems: 1
+    maxItems: 2
 
   resets:
+    minItems: 1
+    maxItems: 3
+
+  iommus:
     maxItems: 1
 
   # PHY DLL input delays:
@@ -115,6 +128,25 @@ properties:
     minimum: 0
     maximum: 0x7f
 
+  # SD6HC PHY timing properties:
+  cdns,iocell-input-delay:
+    description: Input delay across IO cells in picoseconds
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 0
+    maximum: 20000   # 20 ns
+
+  cdns,iocell-output-delay:
+    description: Output delay across IO cells in picoseconds
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 0
+    maximum: 20000   # 20 ns
+
+  cdns,delay-element:
+    description: Delay element size in picoseconds
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 1
+    maximum: 1000    # 1 ns
+
 required:
   - compatible
   - reg
@@ -139,6 +171,61 @@ allOf:
         reg:
           maxItems: 1
 
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: cdns,sd6hc
+    then:
+      description: SD6HC variant - use IO-cell and delay element properties
+      properties:
+        clocks:
+          minItems: 2
+          maxItems: 2
+        clock-names:
+          items:
+            - const: ciu
+            - const: biu
+        resets:
+          minItems: 3
+          maxItems: 3
+        reset-names:
+          items:
+            - const: sdhc-reset
+            - const: combophy
+            - const: sdmmc-ocp
+        cdns,phy-input-delay-sd-highspeed: false
+        cdns,phy-input-delay-legacy: false
+        cdns,phy-input-delay-sd-uhs-sdr12: false
+        cdns,phy-input-delay-sd-uhs-sdr25: false
+        cdns,phy-input-delay-sd-uhs-sdr50: false
+        cdns,phy-input-delay-sd-uhs-ddr50: false
+        cdns,phy-input-delay-mmc-highspeed: false
+        cdns,phy-input-delay-mmc-ddr: false
+        cdns,phy-dll-delay-sdclk: false
+        cdns,phy-dll-delay-sdclk-hsmmc: false
+        cdns,phy-dll-delay-strobe: false
+      required:
+        - resets
+        - clock-names
+        - reset-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: cdns,sd4hc
+    then:
+      description: SD4HC variant - use legacy DLL delay properties
+      properties:
+        clocks:
+          maxItems: 1
+        resets:
+          maxItems: 1
+        cdns,iocell-input-delay: false
+        cdns,iocell-output-delay: false
+        cdns,delay-element: false
+
 unevaluatedProperties: false
 
 examples:
-- 
2.43.7


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

* [PATCH v1 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement
  2026-05-11 20:21 [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
  2026-05-11 20:21 ` [PATCH v1 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Tanmay Kathpalia
  2026-05-11 20:21 ` [PATCH v1 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties Tanmay Kathpalia
@ 2026-05-11 20:21 ` Tanmay Kathpalia
  2026-05-13  0:43   ` sashiko-bot
  2026-05-15  8:38   ` Krzysztof Kozlowski
  2026-05-11 20:21 ` [PATCH v1 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant Tanmay Kathpalia
                   ` (5 subsequent siblings)
  8 siblings, 2 replies; 32+ messages in thread
From: Tanmay Kathpalia @ 2026-05-11 20:21 UTC (permalink / raw)
  To: linux-mmc
  Cc: ulf.hansson, Tanmay Kathpalia, Dinh Nguyen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel

The Agilex5 SoC device tree gains an SD/MMC controller node backed by
the Cadence SD6HC, with IOMMU integration via the system SMMU. Card
power is supplied by a fixed 3.3V regulator and I/O voltage switching
between 1.8V and 3.3V is handled by a GPIO-controlled regulator.

The SOCDK board enables the controller for SD-only operation in 4-bit
bus width with high-speed and SDR104 UHS-I modes at 200 MHz maximum
clock. SDHCI capability overrides clear the SDR50 tuning flag and
override the clock base mask to report 200 MHz.

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
 .../arm64/boot/dts/intel/socfpga_agilex5.dtsi | 38 +++++++++++++++++++
 .../boot/dts/intel/socfpga_agilex5_socdk.dts  | 26 +++++++++++++
 2 files changed, 64 insertions(+)

diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
index 352c96d144a8..7e080f13166f 100644
--- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
+++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
@@ -300,6 +300,44 @@ portb: gpio-controller@0 {
 			};
 		};
 
+		sd_emmc_power: regulator-fixed-3p3v {
+			compatible = "regulator-fixed";
+			regulator-name = "card-power";
+			regulator-min-microvolt = <3300000>;
+			regulator-max-microvolt = <3300000>;
+			regulator-always-on;
+		};
+
+		sd_io_1v8_reg: regulator-1p8v {
+			compatible = "regulator-gpio";
+			regulator-name = "sd-bus-io-power";
+			regulator-min-microvolt = <1800000>;
+			regulator-max-microvolt = <3300000>;
+			states =	<1800000 0x1>,
+					<3300000 0x0>;
+		};
+
+		emmc: mmc@10808000 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			compatible = "altr,agilex5-sd6hc", "cdns,sd6hc";
+			reg = <0x10808000 0x1000>;
+			interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
+			fifo-depth = <0x800>;
+			resets = <&rst SDMMC_RESET>, <&rst COMBOPHY_RESET>, <&rst SDMMC_OCP_RESET>;
+			reset-names = "sdhc-reset", "combophy", "sdmmc-ocp";
+			/*
+			 * "ciu" (SDMCLK) is listed first so it is selected as the
+			 * primary clock by the SDHCI platform layer; the SD6HC PHY
+			 * timing calculations are derived from this clock rate.
+			 */
+			clocks = <&clkmgr AGILEX5_SDMCLK>, <&clkmgr AGILEX5_L4_MP_CLK>;
+			clock-names = "ciu", "biu";
+			iommus = <&smmu 5>;
+			dma-coherent;
+			status = "disabled";
+		};
+
 		nand: nand-controller@10b80000 {
 			compatible = "cdns,hp-nfc";
 			reg = <0x10b80000 0x10000>,
diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk.dts b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk.dts
index 262bb3e8e5c7..a9de824e292a 100644
--- a/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk.dts
+++ b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk.dts
@@ -98,6 +98,32 @@ root: partition@4200000 {
 	};
 };
 
+&sd_io_1v8_reg {
+	gpios = <&portb 3 GPIO_ACTIVE_HIGH>;
+};
+
+&emmc {
+	status = "okay";
+
+	no-mmc;
+	disable-wp;
+	bus-width = <4>;
+	cap-sd-highspeed;
+	sd-uhs-sdr104;
+	vmmc-supply = <&sd_emmc_power>;
+	vqmmc-supply = <&sd_io_1v8_reg>;
+	max-frequency = <200000000>;
+	/*
+	 * SDHCI capability overrides:
+	 *  - caps_mask[0] 0x0000ff00 / caps[0] 0x0000c800:
+	 *      Override SDHCI_CLOCK_BASE_MASK to 0xc8 (200 MHz).
+	 *  - caps_mask[1] 0x00002000 / caps[1] 0:
+	 *      Clear SDHCI_USE_SDR50_TUNING; SDR50 tuning is unsupported.
+	 */
+	sdhci-caps = <0x00000000 0x0000c800>;
+	sdhci-caps-mask = <0x00002000 0x0000ff00>;
+};
+
 &uart0 {
 	status = "okay";
 };
-- 
2.43.7


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

* [PATCH v1 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant
  2026-05-11 20:21 [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
                   ` (2 preceding siblings ...)
  2026-05-11 20:21 ` [PATCH v1 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement Tanmay Kathpalia
@ 2026-05-11 20:21 ` Tanmay Kathpalia
  2026-05-11 20:21 ` [PATCH v1 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support Tanmay Kathpalia
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 32+ messages in thread
From: Tanmay Kathpalia @ 2026-05-11 20:21 UTC (permalink / raw)
  To: linux-mmc
  Cc: ulf.hansson, Tanmay Kathpalia, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Dinh Nguyen, devicetree, linux-kernel

Add "intel,socfpga-agilex5-socdk-emmc" compatible string for the
Agilex5 SOCDK board variant configured with eMMC storage.

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
 Documentation/devicetree/bindings/arm/altera.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/arm/altera.yaml b/Documentation/devicetree/bindings/arm/altera.yaml
index 13a3a9696821..198692878e99 100644
--- a/Documentation/devicetree/bindings/arm/altera.yaml
+++ b/Documentation/devicetree/bindings/arm/altera.yaml
@@ -106,6 +106,7 @@ properties:
               - intel,socfpga-agilex5-socdk
               - intel,socfpga-agilex5-socdk-013b
               - intel,socfpga-agilex5-socdk-nand
+              - intel,socfpga-agilex5-socdk-emmc
           - const: intel,socfpga-agilex5
 
       - description: SoCFPGA VT
-- 
2.43.7


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

* [PATCH v1 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support
  2026-05-11 20:21 [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
                   ` (3 preceding siblings ...)
  2026-05-11 20:21 ` [PATCH v1 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant Tanmay Kathpalia
@ 2026-05-11 20:21 ` Tanmay Kathpalia
  2026-05-13  1:22   ` sashiko-bot
  2026-05-15  8:37   ` Krzysztof Kozlowski
  2026-05-11 20:21 ` [PATCH v1 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork Tanmay Kathpalia
                   ` (3 subsequent siblings)
  8 siblings, 2 replies; 32+ messages in thread
From: Tanmay Kathpalia @ 2026-05-11 20:21 UTC (permalink / raw)
  To: linux-mmc
  Cc: ulf.hansson, Tanmay Kathpalia, Dinh Nguyen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel

Add device tree support for the Agilex5 SOCDK board variant with
eMMC daughter card.

Update the SoC dtsi with a fixed 1.8V regulator for eMMC I/O voltage.

Add socfpga_agilex5_socdk_emmc.dts with eMMC controller configured for:
- 8-bit bus width
- Non-removable eMMC device
- High-speed, HS200, and HS400 modes at 1.8V
- 200MHz maximum frequency with SDHCI clock base capability override

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
 arch/arm64/boot/dts/intel/Makefile            |   1 +
 .../arm64/boot/dts/intel/socfpga_agilex5.dtsi |   8 ++
 .../dts/intel/socfpga_agilex5_socdk_emmc.dts  | 111 ++++++++++++++++++
 3 files changed, 120 insertions(+)
 create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts

diff --git a/arch/arm64/boot/dts/intel/Makefile b/arch/arm64/boot/dts/intel/Makefile
index 33fcc55d0cb9..5bbbcfda1f48 100644
--- a/arch/arm64/boot/dts/intel/Makefile
+++ b/arch/arm64/boot/dts/intel/Makefile
@@ -8,5 +8,6 @@ dtb-$(CONFIG_ARCH_INTEL_SOCFPGA) += socfpga_agilex_n6000.dtb \
 				socfpga_agilex5_socdk_013b.dtb \
 				socfpga_agilex5_socdk_modular.dtb \
 				socfpga_agilex5_socdk_nand.dtb \
+				socfpga_agilex5_socdk_emmc.dtb \
 				socfpga_n5x_socdk.dtb
 dtb-$(CONFIG_ARCH_KEEMBAY) += keembay-evm.dtb
diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
index 7e080f13166f..feb4ccb317a7 100644
--- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
+++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
@@ -317,6 +317,14 @@ sd_io_1v8_reg: regulator-1p8v {
 					<3300000 0x0>;
 		};
 
+		emmc_io_1v8_reg: regulator-fixed-1p8v {
+			compatible = "regulator-fixed";
+			regulator-name = "emmc-io-power";
+			regulator-min-microvolt = <1800000>;
+			regulator-max-microvolt = <1800000>;
+			regulator-always-on;
+		};
+
 		emmc: mmc@10808000 {
 			#address-cells = <1>;
 			#size-cells = <0>;
diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
new file mode 100644
index 000000000000..87bde9fa69c6
--- /dev/null
+++ b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier:     GPL-2.0
+/*
+ * Copyright (C) 2026, Altera Corporation
+ */
+#include "socfpga_agilex5.dtsi"
+
+/ {
+	model = "SoCFPGA Agilex5 SoCDK eMMC daughter board";
+	compatible = "intel,socfpga-agilex5-socdk-emmc", "intel,socfpga-agilex5";
+
+	aliases {
+		serial0 = &uart0;
+		ethernet0 = &gmac0;
+	};
+
+	chosen {
+		stdout-path = "serial0:115200n8";
+	};
+
+	leds {
+		compatible = "gpio-leds";
+		led0 {
+			label = "hps_led0";
+			gpios = <&porta 6 GPIO_ACTIVE_HIGH>;
+		};
+
+		led1 {
+			label = "hps_led1";
+			gpios = <&porta 7 GPIO_ACTIVE_HIGH>;
+		};
+	};
+
+	memory@80000000 {
+		device_type = "memory";
+		/* We expect the bootloader to fill in the reg */
+		reg = <0x0 0x80000000 0x0 0x0>;
+	};
+};
+
+&gmac0 {
+	status = "okay";
+	phy-mode = "rgmii-id";
+	phy-handle = <&emac0_phy0>;
+	max-frame-size = <9000>;
+
+	mdio0 {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		compatible = "snps,dwmac-mdio";
+
+		emac0_phy0: ethernet-phy@0 {
+			reg = <0>;
+		};
+	};
+};
+
+&gpio0 {
+	status = "okay";
+};
+
+&gpio1 {
+	status = "okay";
+};
+
+&i2c0 {
+	status = "okay";
+};
+
+&i3c0 {
+	status = "okay";
+};
+
+&i3c1 {
+	status = "okay";
+};
+
+&emmc {
+	status = "okay";
+
+	no-sd;
+	no-sdio;
+	no-1-8-v;
+	disable-wp;
+	non-removable;
+	cap-mmc-highspeed;
+	mmc-hs400-1_8v;
+	mmc-hs200-1_8v;
+	bus-width = <8>;
+	vmmc-supply = <&sd_emmc_power>;
+	vqmmc-supply = <&emmc_io_1v8_reg>;
+	max-frequency = <200000000>;
+	/*
+	 * SDHCI capability overrides:
+	 *  - caps_mask[0] 0x0000ff00 / caps[0] 0x0000c800:
+	 *      Override SDHCI_CLOCK_BASE_MASK to 0xc8 (200 MHz).
+	 */
+	sdhci-caps = <0x00000000 0x0000c800>;
+	sdhci-caps-mask = <0x00000000 0x0000ff00>;
+};
+
+&osc1 {
+	clock-frequency = <25000000>;
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&watchdog0 {
+	status = "okay";
+};
-- 
2.43.7


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

* [PATCH v1 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork
  2026-05-11 20:21 [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
                   ` (4 preceding siblings ...)
  2026-05-11 20:21 ` [PATCH v1 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support Tanmay Kathpalia
@ 2026-05-11 20:21 ` Tanmay Kathpalia
  2026-05-19 15:29   ` Adrian Hunter
  2026-05-11 20:21 ` [PATCH v1 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support Tanmay Kathpalia
                   ` (2 subsequent siblings)
  8 siblings, 1 reply; 32+ messages in thread
From: Tanmay Kathpalia @ 2026-05-11 20:21 UTC (permalink / raw)
  To: linux-mmc; +Cc: ulf.hansson, Tanmay Kathpalia, Adrian Hunter, linux-kernel

PHY-related functions and data structures in the driver are not
explicitly scoped to the SD4HC (V4) controller, making it unclear
which code is shared and which is version-specific.

Rename them with a "cdns4" prefix to distinguish SD4HC-specific
implementation from the shared driver core, and to avoid naming
conflicts when SD6HC (V6) support is introduced.

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
 drivers/mmc/host/sdhci-cadence.c | 74 ++++++++++++++++----------------
 1 file changed, 37 insertions(+), 37 deletions(-)

diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
index 435603c8c00b..47690a52a221 100644
--- a/drivers/mmc/host/sdhci-cadence.c
+++ b/drivers/mmc/host/sdhci-cadence.c
@@ -78,7 +78,7 @@
  */
 #define SDHCI_CDNS_MAX_TUNING_LOOP	40
 
-struct sdhci_cdns_phy_param {
+struct sdhci_cdns4_phy_param {
 	u8 addr;
 	u8 data;
 };
@@ -91,10 +91,10 @@ struct sdhci_cdns_priv {
 	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
 	struct reset_control *rst_hw;
 	unsigned int nr_phy_params;
-	struct sdhci_cdns_phy_param phy_params[];
+	struct sdhci_cdns4_phy_param phy_params[];
 };
 
-struct sdhci_cdns_phy_cfg {
+struct sdhci_cdns4_phy_cfg {
 	const char *property;
 	u8 addr;
 };
@@ -104,7 +104,7 @@ struct sdhci_cdns_drv_data {
 	const struct sdhci_pltfm_data pltfm_data;
 };
 
-static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = {
+static const struct sdhci_cdns4_phy_cfg sdhci_cdns4_phy_cfgs[] = {
 	{ "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, },
 	{ "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, },
 	{ "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, },
@@ -124,8 +124,8 @@ static inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val,
 	writel(val, reg);
 }
 
-static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
-				    u8 addr, u8 data)
+static int sdhci_cdns4_write_phy_reg(struct sdhci_cdns_priv *priv,
+				     u8 addr, u8 data)
 {
 	void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04;
 	u32 tmp;
@@ -156,44 +156,44 @@ static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
 	return ret;
 }
 
-static unsigned int sdhci_cdns_phy_param_count(struct device_node *np)
+static unsigned int sdhci_cdns4_phy_param_count(struct device_node *np)
 {
 	unsigned int count = 0;
 	int i;
 
-	for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++)
-		if (of_property_present(np, sdhci_cdns_phy_cfgs[i].property))
+	for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++)
+		if (of_property_present(np, sdhci_cdns4_phy_cfgs[i].property))
 			count++;
 
 	return count;
 }
 
-static void sdhci_cdns_phy_param_parse(struct device_node *np,
-				       struct sdhci_cdns_priv *priv)
+static void sdhci_cdns4_phy_param_parse(struct device_node *np,
+					struct sdhci_cdns_priv *priv)
 {
-	struct sdhci_cdns_phy_param *p = priv->phy_params;
+	struct sdhci_cdns4_phy_param *p = priv->phy_params;
 	u32 val;
 	int ret, i;
 
-	for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) {
-		ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property,
+	for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++) {
+		ret = of_property_read_u32(np, sdhci_cdns4_phy_cfgs[i].property,
 					   &val);
 		if (ret)
 			continue;
 
-		p->addr = sdhci_cdns_phy_cfgs[i].addr;
+		p->addr = sdhci_cdns4_phy_cfgs[i].addr;
 		p->data = val;
 		p++;
 	}
 }
 
-static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
+static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
 {
 	int ret, i;
 
 	for (i = 0; i < priv->nr_phy_params; i++) {
-		ret = sdhci_cdns_write_phy_reg(priv, priv->phy_params[i].addr,
-					       priv->phy_params[i].data);
+		ret = sdhci_cdns4_write_phy_reg(priv, priv->phy_params[i].addr,
+						priv->phy_params[i].data);
 		if (ret)
 			return ret;
 	}
@@ -201,7 +201,7 @@ static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
 	return 0;
 }
 
-static void *sdhci_cdns_priv(struct sdhci_host *host)
+static void *sdhci_cdns_get_priv(struct sdhci_host *host)
 {
 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 
@@ -238,7 +238,7 @@ static u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv)
 
 static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
 {
-	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
 	void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06;
 	u32 tmp;
 	int i, ret;
@@ -354,7 +354,7 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
 static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
 					 unsigned int timing)
 {
-	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
 	u32 mode;
 
 	switch (timing) {
@@ -407,12 +407,12 @@ static void elba_priv_writel(struct sdhci_cdns_priv *priv, u32 val,
 
 static void elba_write_l(struct sdhci_host *host, u32 val, int reg)
 {
-	elba_priv_writel(sdhci_cdns_priv(host), val, host->ioaddr + reg);
+	elba_priv_writel(sdhci_cdns_get_priv(host), val, host->ioaddr + reg);
 }
 
 static void elba_write_w(struct sdhci_host *host, u16 val, int reg)
 {
-	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
 	u32 shift = reg & GENMASK(1, 0);
 	unsigned long flags;
 	u32 byte_enables;
@@ -426,7 +426,7 @@ static void elba_write_w(struct sdhci_host *host, u16 val, int reg)
 
 static void elba_write_b(struct sdhci_host *host, u8 val, int reg)
 {
-	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
 	u32 shift = reg & GENMASK(1, 0);
 	unsigned long flags;
 	u32 byte_enables;
@@ -452,7 +452,7 @@ static const struct sdhci_ops sdhci_elba_ops = {
 static int elba_drv_init(struct platform_device *pdev)
 {
 	struct sdhci_host *host = platform_get_drvdata(pdev);
-	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
 	void __iomem *ioaddr;
 
 	host->mmc->caps |= MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA;
@@ -470,7 +470,7 @@ static int elba_drv_init(struct platform_device *pdev)
 	return 0;
 }
 
-static const struct sdhci_ops sdhci_cdns_ops = {
+static const struct sdhci_ops sdhci_cdns4_ops = {
 	.set_clock = sdhci_set_clock,
 	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
 	.set_bus_width = sdhci_set_bus_width,
@@ -481,7 +481,7 @@ static const struct sdhci_ops sdhci_cdns_ops = {
 
 static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
 	.pltfm_data = {
-		.ops = &sdhci_cdns_ops,
+		.ops = &sdhci_cdns4_ops,
 		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
 	},
 };
@@ -495,14 +495,14 @@ static const struct sdhci_cdns_drv_data sdhci_elba_drv_data = {
 
 static const struct sdhci_cdns_drv_data sdhci_eyeq_drv_data = {
 	.pltfm_data = {
-		.ops = &sdhci_cdns_ops,
+		.ops = &sdhci_cdns4_ops,
 		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
 	},
 };
 
-static const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = {
+static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
 	.pltfm_data = {
-		.ops = &sdhci_cdns_ops,
+		.ops = &sdhci_cdns4_ops,
 	},
 };
 
@@ -510,7 +510,7 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
 					     struct mmc_ios *ios)
 {
 	struct sdhci_host *host = mmc_priv(mmc);
-	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
 	u32 mode;
 
 	priv->enhanced_strobe = ios->enhanced_strobe;
@@ -529,7 +529,7 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
 static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc)
 {
 	struct sdhci_host *host = mmc_priv(mmc);
-	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
 
 	dev_dbg(mmc_dev(host->mmc), "emmc hardware reset\n");
 
@@ -560,9 +560,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
 
 	data = of_device_get_match_data(dev);
 	if (!data)
-		data = &sdhci_cdns_drv_data;
+		data = &sdhci_cdns4_drv_data;
 
-	nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node);
+	nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
 	host = sdhci_pltfm_init(pdev, &data->pltfm_data,
 				struct_size(priv, phy_params, nr_phy_params));
 	if (IS_ERR(host))
@@ -593,9 +593,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	sdhci_cdns_phy_param_parse(dev->of_node, priv);
+	sdhci_cdns4_phy_param_parse(dev->of_node, priv);
 
-	ret = sdhci_cdns_phy_init(priv);
+	ret = sdhci_cdns4_phy_init(priv);
 	if (ret)
 		return ret;
 
@@ -622,7 +622,7 @@ static int sdhci_cdns_resume(struct device *dev)
 	if (ret)
 		return ret;
 
-	ret = sdhci_cdns_phy_init(priv);
+	ret = sdhci_cdns4_phy_init(priv);
 	if (ret)
 		goto disable_clk;
 
-- 
2.43.7


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

* [PATCH v1 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support
  2026-05-11 20:21 [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
                   ` (5 preceding siblings ...)
  2026-05-11 20:21 ` [PATCH v1 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork Tanmay Kathpalia
@ 2026-05-11 20:21 ` Tanmay Kathpalia
  2026-05-19 15:29   ` Adrian Hunter
  2026-05-11 20:21 ` [PATCH v1 8/9] mmc: sdhci-cadence: add Cadence SD6HC support Tanmay Kathpalia
  2026-05-11 20:21 ` [PATCH v1 9/9] mmc: sdhci-cadence: add Altera Agilex5 " Tanmay Kathpalia
  8 siblings, 1 reply; 32+ messages in thread
From: Tanmay Kathpalia @ 2026-05-11 20:21 UTC (permalink / raw)
  To: linux-mmc; +Cc: ulf.hansson, Tanmay Kathpalia, Adrian Hunter, linux-kernel

Refactor the sdhci-cadence driver in preparation for adding SD6HC
(V6 controller) support. Separate PHY parameter handling into a
dedicated sdhci_cdns4_phy structure and move PHY initialization
logic into a dedicated sdhci_cdns4_phy_probe() function. This
allows different controller versions to manage their PHY
configurations independently while keeping shared logic in the
main driver.

Each compatible entry now carries its own driver data, so drop the
silent fallback to sdhci_cdns4_drv_data and return an error if
platform data is missing.

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
 drivers/mmc/host/sdhci-cadence.c | 57 ++++++++++++++++++++++----------
 1 file changed, 40 insertions(+), 17 deletions(-)

diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
index 47690a52a221..fe3f7c5109fc 100644
--- a/drivers/mmc/host/sdhci-cadence.c
+++ b/drivers/mmc/host/sdhci-cadence.c
@@ -83,6 +83,11 @@ struct sdhci_cdns4_phy_param {
 	u8 data;
 };
 
+struct sdhci_cdns4_phy {
+	unsigned int nr_phy_params;
+	struct sdhci_cdns4_phy_param phy_params[];
+};
+
 struct sdhci_cdns_priv {
 	void __iomem *hrs_addr;
 	void __iomem *ctl_addr;	/* write control */
@@ -90,8 +95,7 @@ struct sdhci_cdns_priv {
 	bool enhanced_strobe;
 	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
 	struct reset_control *rst_hw;
-	unsigned int nr_phy_params;
-	struct sdhci_cdns4_phy_param phy_params[];
+	struct sdhci_cdns4_phy *phy;
 };
 
 struct sdhci_cdns4_phy_cfg {
@@ -169,9 +173,9 @@ static unsigned int sdhci_cdns4_phy_param_count(struct device_node *np)
 }
 
 static void sdhci_cdns4_phy_param_parse(struct device_node *np,
-					struct sdhci_cdns_priv *priv)
+					struct sdhci_cdns4_phy *phy)
 {
-	struct sdhci_cdns4_phy_param *p = priv->phy_params;
+	struct sdhci_cdns4_phy_param *p = phy->phy_params;
 	u32 val;
 	int ret, i;
 
@@ -190,10 +194,11 @@ static void sdhci_cdns4_phy_param_parse(struct device_node *np,
 static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
 {
 	int ret, i;
+	struct sdhci_cdns4_phy *phy = priv->phy;
 
-	for (i = 0; i < priv->nr_phy_params; i++) {
-		ret = sdhci_cdns4_write_phy_reg(priv, priv->phy_params[i].addr,
-						priv->phy_params[i].data);
+	for (i = 0; i < phy->nr_phy_params; i++) {
+		ret = sdhci_cdns4_write_phy_reg(priv, phy->phy_params[i].addr,
+						phy->phy_params[i].data);
 		if (ret)
 			return ret;
 	}
@@ -542,6 +547,26 @@ static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc)
 	usleep_range(300, 1000);
 }
 
+static int sdhci_cdns4_phy_probe(struct platform_device *pdev,
+				 struct sdhci_cdns_priv *priv)
+{
+	unsigned int nr_phy_params;
+	struct sdhci_cdns4_phy *phy;
+	struct device *dev = &pdev->dev;
+
+	nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
+	phy = devm_kzalloc(dev, struct_size(phy, phy_params, nr_phy_params),
+			   GFP_KERNEL);
+	if (!phy)
+		return -ENOMEM;
+
+	phy->nr_phy_params = nr_phy_params;
+	sdhci_cdns4_phy_param_parse(dev->of_node, phy);
+	priv->phy = phy;
+
+	return sdhci_cdns4_phy_init(priv);
+}
+
 static int sdhci_cdns_probe(struct platform_device *pdev)
 {
 	struct sdhci_host *host;
@@ -549,7 +574,6 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
 	struct sdhci_pltfm_host *pltfm_host;
 	struct sdhci_cdns_priv *priv;
 	struct clk *clk;
-	unsigned int nr_phy_params;
 	int ret;
 	struct device *dev = &pdev->dev;
 	static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT;
@@ -560,11 +584,10 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
 
 	data = of_device_get_match_data(dev);
 	if (!data)
-		data = &sdhci_cdns4_drv_data;
+		return dev_err_probe(dev, -EINVAL,
+				     "missing platform driver data\n");
 
-	nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
-	host = sdhci_pltfm_init(pdev, &data->pltfm_data,
-				struct_size(priv, phy_params, nr_phy_params));
+	host = sdhci_pltfm_init(pdev, &data->pltfm_data, sizeof(*priv));
 	if (IS_ERR(host))
 		return PTR_ERR(host);
 
@@ -572,7 +595,6 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
 	pltfm_host->clk = clk;
 
 	priv = sdhci_pltfm_priv(pltfm_host);
-	priv->nr_phy_params = nr_phy_params;
 	priv->hrs_addr = host->ioaddr;
 	priv->enhanced_strobe = false;
 	priv->priv_writel = cdns_writel;
@@ -593,9 +615,7 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	sdhci_cdns4_phy_param_parse(dev->of_node, priv);
-
-	ret = sdhci_cdns4_phy_init(priv);
+	ret = sdhci_cdns4_phy_probe(pdev, priv);
 	if (ret)
 		return ret;
 
@@ -653,7 +673,10 @@ static const struct of_device_id sdhci_cdns_match[] = {
 		.compatible = "mobileye,eyeq-sd4hc",
 		.data = &sdhci_eyeq_drv_data,
 	},
-	{ .compatible = "cdns,sd4hc" },
+	{
+		.compatible = "cdns,sd4hc",
+		.data = &sdhci_cdns4_drv_data,
+	},
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, sdhci_cdns_match);
-- 
2.43.7


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

* [PATCH v1 8/9] mmc: sdhci-cadence: add Cadence SD6HC support
  2026-05-11 20:21 [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
                   ` (6 preceding siblings ...)
  2026-05-11 20:21 ` [PATCH v1 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support Tanmay Kathpalia
@ 2026-05-11 20:21 ` Tanmay Kathpalia
  2026-05-19 15:29   ` Adrian Hunter
  2026-05-11 20:21 ` [PATCH v1 9/9] mmc: sdhci-cadence: add Altera Agilex5 " Tanmay Kathpalia
  8 siblings, 1 reply; 32+ messages in thread
From: Tanmay Kathpalia @ 2026-05-11 20:21 UTC (permalink / raw)
  To: linux-mmc
  Cc: ulf.hansson, Tanmay Kathpalia, Adrian Hunter, Philipp Zabel,
	linux-kernel

The Cadence SD6HC is the sixth-generation SD Host Controller used in
Agilex5 SoCs. Its PHY differs substantially from the SD4HC: it
requires per-speed-mode IO cell timing parameters and a DLL-based
delay line to achieve correct signal margins across all speed grades
from Default Speed to HS400.

Support is implemented in a new sdhci-cadence6.c alongside the
existing v4 code, now in sdhci-cadence4.c. Shared structures and the
v6 function interface are declared in a new sdhci-cadence.h header.
The common driver paths select between v4 and v6 PHY operations based
on the SDHCI specification version reported by the controller.

The new compatible string "cdns,sd6hc" identifies SD6HC hardware in
device tree.

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
 MAINTAINERS                                   |    7 +
 drivers/mmc/host/Makefile                     |    3 +-
 drivers/mmc/host/sdhci-cadence.h              |  113 ++
 .../{sdhci-cadence.c => sdhci-cadence4.c}     |   92 +-
 drivers/mmc/host/sdhci-cadence6.c             | 1051 +++++++++++++++++
 5 files changed, 1228 insertions(+), 38 deletions(-)
 create mode 100644 drivers/mmc/host/sdhci-cadence.h
 rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence4.c} (91%)
 create mode 100644 drivers/mmc/host/sdhci-cadence6.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 27fefd92744c..4856450bfd36 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23863,6 +23863,13 @@ L:	linux-mmc@vger.kernel.org
 S:	Maintained
 F:	drivers/mmc/host/sdhci-brcmstb*
 
+SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) CADENCE DRIVER
+M:	Tanmay Kathpalia <tanmay.kathpalia@altera.com>
+L:	linux-mmc@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
+F:	drivers/mmc/host/sdhci-cadence*
+
 SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) DRIVER
 M:	Adrian Hunter <adrian.hunter@intel.com>
 L:	linux-mmc@vger.kernel.org
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index ee412e6b84d6..f3b2f43e751e 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -80,7 +80,8 @@ obj-$(CONFIG_MMC_REALTEK_USB)	+= rtsx_usb_sdmmc.o
 
 obj-$(CONFIG_MMC_SDHCI_PLTFM)		+= sdhci-pltfm.o
 obj-$(CONFIG_MMC_SDHCI_CADENCE)		+= sdhci-cadence.o
-obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF)       += sdhci-esdhc-mcf.o
+sdhci-cadence-y				+= sdhci-cadence4.o sdhci-cadence6.o
+obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF)	+= sdhci-esdhc-mcf.o
 obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX)	+= sdhci-esdhc-imx.o
 obj-$(CONFIG_MMC_SDHCI_DOVE)		+= sdhci-dove.o
 obj-$(CONFIG_MMC_SDHCI_TEGRA)		+= sdhci-tegra.o
diff --git a/drivers/mmc/host/sdhci-cadence.h b/drivers/mmc/host/sdhci-cadence.h
new file mode 100644
index 000000000000..05b8d8f16543
--- /dev/null
+++ b/drivers/mmc/host/sdhci-cadence.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Altera Corporation
+ *   Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
+ *
+ * Cadence SD/SDIO/eMMC Host Controller driver - common header
+ * Shared definitions and structures for the Cadence SDHCI driver.
+ * Contains private data and declarations for SD6HC-specific functions
+ * called by the main driver in sdhci-cadence4.c.
+ */
+
+#ifndef _MMC_HOST_SDHCI_CADENCE_H
+#define _MMC_HOST_SDHCI_CADENCE_H
+
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/mmc/host.h>
+
+#include "sdhci-pltfm.h"
+
+/* HRS - Host Register Set (specific to Cadence) */
+#define SDHCI_CDNS_HRS04		0x10		/* PHY access: address port */
+#define SDHCI_CDNS_HRS05		0x14		/* PHY access: data port */
+
+/*
+ * The tuned val register is 6 bit-wide, but not the whole of the range is
+ * available.  The range 0-42 seems to be available (then 43 wraps around to 0)
+ * but I am not quite sure if it is official.  Use only 0 to 39 for safety.
+ */
+#define SDHCI_CDNS_MAX_TUNING_LOOP	40
+
+/**
+ * struct sdhci_cdns_priv - Cadence SDHCI private controller data
+ * @hrs_addr: Base address of Cadence Host Register Set (HRS) registers.
+ * @ctl_addr: Base address for write control registers.
+ *            Used only for "amd,pensando-elba-sd4hc" compatible controllers
+ *            to enable byte-lane writes.
+ * @wrlock: Spinlock for protecting register writes (Elba only).
+ * @enhanced_strobe: Flag indicating if Enhanced Strobe (HS400ES) is enabled.
+ * @priv_writel: Optional SoC-specific write function for register access.
+ *               Used for Elba to ensure correct byte-lane enable.
+ * @rst_hw: Hardware reset control for the controller.
+ * @phy: Opaque pointer to variant-specific PHY data.
+ *       For SD4HC: points to struct sdhci_cdns4_phy.
+ *       For SD6HC: points to struct sdhci_cdns6_phy.
+ */
+struct sdhci_cdns_priv {
+	void __iomem *hrs_addr;
+	void __iomem *ctl_addr; /* write control */
+	spinlock_t wrlock; /* write lock */
+	bool enhanced_strobe;
+	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val,
+			    void __iomem *reg);
+	struct reset_control *rst_hw;
+	void *phy;
+};
+
+/*
+ * sdhci_cdns_get_priv - Helper to retrieve Cadence private data from sdhci_host
+ * @host: Pointer to struct sdhci_host.
+ *
+ * Return: Pointer to struct sdhci_cdns_priv.
+ */
+static inline void *sdhci_cdns_get_priv(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+	return sdhci_pltfm_priv(pltfm_host);
+}
+
+/**
+ * sdhci_cdns6_set_uhs_signaling - Program PHY registers for a specific timing mode.
+ * @host: Pointer to struct sdhci_host.
+ * @timing: MMC timing mode (MMC_TIMING_*).
+ */
+void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing);
+
+/**
+ * sdhci_cdns6_set_tune_val - Set the PHY tuning value.
+ * @host: Pointer to struct sdhci_host.
+ * @val: Tuning value to program.
+ *
+ * Return: 0 on success, -ETIMEDOUT if PHY initialization times out.
+ */
+int sdhci_cdns6_set_tune_val(struct sdhci_host *host, unsigned int val);
+
+/**
+ * sdhci_cdns6_phy_probe - Probe and initialize Cadence SD6HC PHY parameters
+ * @pdev: Platform device pointer
+ * @priv: Pointer to Cadence private data structure
+ *
+ * Return: 0 on success or a negative error code.
+ */
+int sdhci_cdns6_phy_probe(struct platform_device *pdev,
+			  struct sdhci_cdns_priv *priv);
+/**
+ * sdhci_cdns6_hw_reset - Perform hardware reset of the Cadence SDHCI controller.
+ * @host: Pointer to struct sdhci_host.
+ */
+void sdhci_cdns6_hw_reset(struct sdhci_host *host);
+
+/**
+ * sdhci_cdns6_phy_init - Initialize the SD6HC PHY with current settings.
+ * @priv: Pointer to Cadence private data structure.
+ *
+ * Return: 0 on success, -ETIMEDOUT if PHY initialization times out.
+ */
+int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv);
+
+#endif /* _MMC_HOST_SDHCI_CADENCE_H */
diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence4.c
similarity index 91%
rename from drivers/mmc/host/sdhci-cadence.c
rename to drivers/mmc/host/sdhci-cadence4.c
index fe3f7c5109fc..283d22328248 100644
--- a/drivers/mmc/host/sdhci-cadence.c
+++ b/drivers/mmc/host/sdhci-cadence4.c
@@ -2,22 +2,17 @@
 /*
  * Copyright (C) 2016 Socionext Inc.
  *   Author: Masahiro Yamada <yamada.masahiro@socionext.com>
+ * Copyright (C) 2026 Altera Corporation
  */
 
 #include <linux/bitfield.h>
 #include <linux/bits.h>
-#include <linux/iopoll.h>
 #include <linux/module.h>
-#include <linux/mmc/host.h>
-#include <linux/mmc/mmc.h>
-#include <linux/of.h>
-#include <linux/platform_device.h>
-#include <linux/reset.h>
 
-#include "sdhci-pltfm.h"
+#include "sdhci-cadence.h"
 
 /* HRS - Host Register Set (specific to Cadence) */
-#define SDHCI_CDNS_HRS04		0x10		/* PHY access port */
+/* HRS04 (PHY access) bitfields (SD4HC) */
 #define   SDHCI_CDNS_HRS04_ACK			BIT(26)
 #define   SDHCI_CDNS_HRS04_RD			BIT(25)
 #define   SDHCI_CDNS_HRS04_WR			BIT(24)
@@ -71,13 +66,6 @@
 #define SDHCI_CDNS_PHY_DLY_HSMMC	0x0c
 #define SDHCI_CDNS_PHY_DLY_STROBE	0x0d
 
-/*
- * The tuned val register is 6 bit-wide, but not the whole of the range is
- * available.  The range 0-42 seems to be available (then 43 wraps around to 0)
- * but I am not quite sure if it is official.  Use only 0 to 39 for safety.
- */
-#define SDHCI_CDNS_MAX_TUNING_LOOP	40
-
 struct sdhci_cdns4_phy_param {
 	u8 addr;
 	u8 data;
@@ -88,16 +76,6 @@ struct sdhci_cdns4_phy {
 	struct sdhci_cdns4_phy_param phy_params[];
 };
 
-struct sdhci_cdns_priv {
-	void __iomem *hrs_addr;
-	void __iomem *ctl_addr;	/* write control */
-	spinlock_t wrlock;	/* write lock */
-	bool enhanced_strobe;
-	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
-	struct reset_control *rst_hw;
-	struct sdhci_cdns4_phy *phy;
-};
-
 struct sdhci_cdns4_phy_cfg {
 	const char *property;
 	u8 addr;
@@ -206,13 +184,6 @@ static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
 	return 0;
 }
 
-static void *sdhci_cdns_get_priv(struct sdhci_host *host)
-{
-	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
-
-	return sdhci_pltfm_priv(pltfm_host);
-}
-
 static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
 {
 	/*
@@ -248,6 +219,9 @@ static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
 	u32 tmp;
 	int i, ret;
 
+	if (host->version >= SDHCI_SPEC_420)
+		return sdhci_cdns6_set_tune_val(host, val);
+
 	if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val)))
 		return -EINVAL;
 
@@ -328,8 +302,11 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
 	 * The delay is set by probe, based on the DT properties.
 	 */
 	if (host->timing != MMC_TIMING_MMC_HS200 &&
-	    host->timing != MMC_TIMING_UHS_SDR104)
+	    host->timing != MMC_TIMING_UHS_SDR104) {
+		dev_dbg(mmc_dev(host->mmc), "Tuning skipped (timing: %d)\n",
+			host->timing);
 		return 0;
+	}
 
 	for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) {
 		if (sdhci_cdns_set_tune_val(host, i) ||
@@ -353,6 +330,10 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
 	if (ret)
 		return ret;
 
+	/* Block gap tuning is only required for SD4HC, not for SD6HC */
+	if (host->version >= SDHCI_SPEC_420)
+		return 0;
+
 	return sdhci_cdns_tune_blkgap(host->mmc);
 }
 
@@ -388,6 +369,10 @@ static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
 	/* For SD, fall back to the default handler */
 	if (mode == SDHCI_CDNS_HRS06_MODE_SD)
 		sdhci_set_uhs_signaling(host, timing);
+
+	/* For host controller V6, set SDHCI and PHY registers for UHS signaling */
+	if (host->version >= SDHCI_SPEC_420)
+		sdhci_cdns6_set_uhs_signaling(host, timing);
 }
 
 /* Elba control register bits [6:3] are byte-lane enables */
@@ -484,6 +469,16 @@ static const struct sdhci_ops sdhci_cdns4_ops = {
 	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
 };
 
+static const struct sdhci_ops sdhci_cdns6_ops = {
+	.set_clock = sdhci_set_clock,
+	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
+	.set_bus_width = sdhci_set_bus_width,
+	.reset = sdhci_reset,
+	.platform_execute_tuning = sdhci_cdns_execute_tuning,
+	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
+	.hw_reset = sdhci_cdns6_hw_reset,
+};
+
 static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
 	.pltfm_data = {
 		.ops = &sdhci_cdns4_ops,
@@ -511,6 +506,12 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
 	},
 };
 
+static const struct sdhci_cdns_drv_data sdhci_cdns6_drv_data = {
+	.pltfm_data = {
+		.ops = &sdhci_cdns6_ops,
+	},
+};
+
 static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
 					     struct mmc_ios *ios)
 {
@@ -607,15 +608,24 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
 			return ret;
 	}
 	sdhci_enable_v4_mode(host);
-	__sdhci_read_caps(host, &version, NULL, NULL);
-
 	sdhci_get_of_property(pdev);
 
 	ret = mmc_of_parse(host->mmc);
 	if (ret)
 		return ret;
 
-	ret = sdhci_cdns4_phy_probe(pdev, priv);
+	/*
+	 * For SD4HC, read capabilities with fixed version override.
+	 * For SD6HC, sdhci_add_host() will automatically read capabilities
+	 * and version from the host controller registers.
+	 */
+	if (of_device_is_compatible(dev->of_node, "cdns,sd4hc")) {
+		__sdhci_read_caps(host, &version, NULL, NULL);
+		ret = sdhci_cdns4_phy_probe(pdev, priv);
+	} else {
+		ret = sdhci_cdns6_phy_probe(pdev, priv);
+	}
+
 	if (ret)
 		return ret;
 
@@ -642,7 +652,11 @@ static int sdhci_cdns_resume(struct device *dev)
 	if (ret)
 		return ret;
 
-	ret = sdhci_cdns4_phy_init(priv);
+	if (host->version >= SDHCI_SPEC_420)
+		ret = sdhci_cdns6_phy_init(priv);
+	else
+		ret = sdhci_cdns4_phy_init(priv);
+
 	if (ret)
 		goto disable_clk;
 
@@ -677,6 +691,10 @@ static const struct of_device_id sdhci_cdns_match[] = {
 		.compatible = "cdns,sd4hc",
 		.data = &sdhci_cdns4_drv_data,
 	},
+	{
+		.compatible = "cdns,sd6hc",
+		.data = &sdhci_cdns6_drv_data,
+	},
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, sdhci_cdns_match);
diff --git a/drivers/mmc/host/sdhci-cadence6.c b/drivers/mmc/host/sdhci-cadence6.c
new file mode 100644
index 000000000000..34d3570a0bbb
--- /dev/null
+++ b/drivers/mmc/host/sdhci-cadence6.c
@@ -0,0 +1,1051 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PHY and host controller support for Cadence SD6HC SDHCI
+ *
+ * This file provides comprehensive support for Cadence's sixth-generation
+ * SDHCI controller (SD6HC), handling both low-level PHY operations and
+ * high-level host controller programming. The implementation includes PHY
+ * initialization and timing calculations, DLL (Delay-Locked Loop)
+ * management and configuration, host controller register programming for
+ * IO delay compensation and signal timing optimization.
+ *
+ * Copyright (C) 2026 Altera Corporation
+ *   Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
+ */
+
+#include "sdhci-cadence.h"
+
+/* IO Delay Information */
+#define SDHCI_CDNS_HRS07		0x1c
+#define   SDHCI_CDNS_HRS07_RW_COMPENSATE	GENMASK(20, 16)
+#define   SDHCI_CDNS_HRS07_IDELAY_VAL		GENMASK(4, 0)
+
+/* PHY Control and Status */
+#define SDHCI_CDNS_HRS09		0x24
+#define   SDHCI_CDNS_HRS09_RDDATA_EN		BIT(16)
+#define   SDHCI_CDNS_HRS09_RDCMD_EN		BIT(15)
+#define   SDHCI_CDNS_HRS09_EXTENDED_WR_MODE	BIT(3)
+#define   SDHCI_CDNS_HRS09_EXTENDED_RD_MODE	BIT(2)
+#define   SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE	BIT(1)
+#define   SDHCI_CDNS_HRS09_PHY_SW_RESET		BIT(0)
+
+/* SDCLK start point adjustment */
+#define SDHCI_CDNS_HRS10		0x28
+#define   SDHCI_CDNS_HRS10_HCSDCLKADJ		GENMASK(19, 16)
+
+/* eMMC Control */
+#define SDHCI_CDNS_HRS11		0x2c
+#define   SDHCI_CDNS_HRS11_EMMC_RST		BIT(0) /* eMMC reset */
+
+/* CMD/DAT output delay */
+#define SDHCI_CDNS_HRS16		0x40
+#define   SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY	GENMASK(31, 28)
+#define   SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY	GENMASK(27, 24)
+#define   SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY	GENMASK(23, 20)
+#define   SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY	GENMASK(19, 16)
+#define   SDHCI_CDNS_HRS16_WRDATA1_DLY		GENMASK(15, 12)
+#define   SDHCI_CDNS_HRS16_WRDATA0_DLY		GENMASK(11, 8)
+#define   SDHCI_CDNS_HRS16_WRCMD1_DLY		GENMASK(7, 4)
+#define   SDHCI_CDNS_HRS16_WRCMD0_DLY		GENMASK(3, 0)
+
+/* PHY Special Function Registers */
+/* DQ timing */
+#define SDHCI_CDNS6_PHY_DQ_TIMING_REG			0x2000
+#define   SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON		BIT(31)
+#define   SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END			GENMASK(29, 27)
+#define   SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START		GENMASK(26, 24)
+#define   SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END		GENMASK(2, 0)
+
+/* DQS timing */
+#define SDHCI_CDNS6_PHY_DQS_TIMING_REG			0x2004
+#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS		BIT(22)
+#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS		BIT(21)
+#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS		BIT(20)
+#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD		BIT(19)
+
+/* Gate and loopback control */
+#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG		0x2008
+#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD		BIT(31)
+#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SW_HALF_CYCLE_SHIFT	BIT(28)
+#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL		GENMASK(24, 19)
+#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS	BIT(18)
+#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON	BIT(6)
+
+/* Master DLL logic */
+#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_REG		0x200c
+#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_BYPASS_MODE		BIT(23)
+#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_PHASE_DETECT_SEL	GENMASK(22, 20)
+#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM		GENMASK(18, 16)
+#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT	GENMASK(7, 0)
+
+/* Slave DLL logic */
+#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG		0x2010
+#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY	GENMASK(31, 24)
+#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WRDQS_DELAY	GENMASK(23, 16)
+#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WR_DELAY		GENMASK(15, 8)
+#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY		GENMASK(7, 0)
+
+/* Global control settings */
+#define SDHCI_CDNS6_PHY_CTRL_REG			0x2080
+#define   SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING			GENMASK(9, 4)
+
+/* Default PHY settings */
+#define SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY	2500
+#define SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT	24
+#define SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL	52
+#define SDHCI_CDNS6_PHY_DEFAULT_DLL_START	4
+
+/* Scale tuning tap (0..39) to 8-bit PHY DLL delay field (0..255) */
+#define SDHCI_CDNS6_PHY_DLL_FIELD_SIZE		256
+
+struct sdhci_cdns6_phy {
+	/*
+	 * Mode-specific timing constraints (in picoseconds)
+	 * These define valid output/input windows per SD/eMMC spec
+	 */
+	u32 t_cmd_output_min;
+	u32 t_cmd_output_max;
+	u32 t_dat_output_min;
+	u32 t_dat_output_max;
+	u32 t_cmd_input_min;
+	u32 t_cmd_input_max;
+	u32 t_dat_input_min;
+	u32 t_dat_input_max;
+
+	/*
+	 * PHY delay configuration (in picoseconds)
+	 * Derived from clock period and board-level IO cell delays
+	 */
+	u32 phy_sdclk_delay;
+	u32 phy_cmd_o_delay;
+	u32 phy_dat_o_delay;
+	u32 iocell_input_delay;
+	u32 iocell_output_delay;
+	u32 delay_element_org;	/* Original delay element from DT */
+	u32 delay_element;	/* Current delay element (may be doubled) */
+
+	/* PHY_DLL_SLAVE_CTRL register fields */
+	u32 cp_read_dqs_cmd_delay;
+	u32 cp_read_dqs_delay;
+	u32 cp_clk_wr_delay;
+	u32 cp_clk_wrdqs_delay;
+
+	/* PHY_DLL_MASTER_CTRL register fields */
+	u32 cp_dll_bypass_mode;
+	u32 cp_dll_start_point;
+
+	/* PHY_GATE_LPBK_CTRL register fields */
+	u32 cp_gate_cfg_always_on;
+	u32 cp_sync_method;
+	u32 cp_rd_del_sel;
+	u32 cp_sw_half_cycle_shift;
+	u32 cp_underrun_suppress;
+
+	/* PHY_DQ_TIMING register fields */
+	u32 cp_io_mask_always_on;
+	u32 cp_io_mask_end;
+	u32 cp_io_mask_start;
+	u32 cp_data_select_oe_end;
+
+	/* PHY_DQS_TIMING register fields */
+	u32 cp_use_ext_lpbk_dqs;
+	u32 cp_use_lpbk_dqs;
+	u8 cp_use_phony_dqs;
+	u8 cp_use_phony_dqs_cmd;
+
+	/* HRS09 register fields - PHY control and Status */
+	u8 sdhc_extended_rd_mode;
+	u8 sdhc_extended_wr_mode;
+	u32 sdhc_rdcmd_en;
+	u32 sdhc_rddata_en;
+
+	/* HRS10 register - SDCLK start point adjustment */
+	u32 sdhc_hcsdclkadj;
+
+	/* HRS07 register - IO delay Information */
+	u32 sdhc_idelay_val;
+	u32 sdhc_rw_compensate;
+
+	/* HRS16 register fields - CMD/DAT output delay control */
+	u32 sdhc_wrcmd0_dly;
+	u32 sdhc_wrcmd0_sdclk_dly;
+	u32 sdhc_wrcmd1_dly;
+	u32 sdhc_wrcmd1_sdclk_dly;
+	u32 sdhc_wrdata0_dly;
+	u32 sdhc_wrdata0_sdclk_dly;
+	u32 sdhc_wrdata1_dly;
+	u32 sdhc_wrdata1_sdclk_dly;
+
+	/*
+	 * DLL calculation intermediate values
+	 * Used during PHY timing calculations
+	 */
+	u32 t_sdmclk_calc;	/* Calculated SDMCLK period for DLL */
+	u32 dll_max_value;	/* Max DLL delay value (127/255/256) */
+
+	/* Tuning value for HS200/HS400 modes */
+	u32 hs200_tune_val;
+
+	/* Clock periods (in picoseconds) */
+	u32 t_sdmclk;		/* Master clock period */
+	u32 t_sdclk;		/* SD card clock period */
+
+	/* Current operating state */
+	bool strobe_cmd;	/* Enhanced strobe for CMD line */
+	unsigned int mode;	/* Current MMC_TIMING_* mode */
+};
+
+/**
+ * init_ds() - Initialize PHY timing for Default Speed mode (up to 25 MHz).
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_ds(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 5000;
+	phy->t_cmd_output_max = t_sdclk - 5000;
+	phy->t_dat_output_min = 5000;
+	phy->t_dat_output_max = t_sdclk - 5000;
+	phy->t_cmd_input_min = t_sdclk / 2 + 14000;
+	phy->t_cmd_input_max = t_sdclk + t_sdclk / 2;
+	phy->t_dat_input_min = t_sdclk / 2 + 14000;
+	phy->t_dat_input_max = t_sdclk + t_sdclk / 2;
+}
+
+/**
+ * init_hs() - Initialize PHY timing for High Speed mode (up to 50 MHz).
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_hs(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 2000;
+	phy->t_cmd_output_max = t_sdclk - 6000;
+	phy->t_dat_output_min = 2000;
+	phy->t_dat_output_max = t_sdclk - 6000;
+	phy->t_cmd_input_min = 14000;
+	phy->t_cmd_input_max = t_sdclk + 2500;
+	phy->t_dat_input_min = 14000;
+	phy->t_dat_input_max = t_sdclk + 2500;
+}
+
+/**
+ * init_uhs_sdr12() - Initialize PHY timing for UHS SDR12 mode (up to 25 MHz).
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_uhs_sdr12(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 800;
+	phy->t_cmd_output_max = t_sdclk - 3000;
+	phy->t_dat_output_min = 800;
+	phy->t_dat_output_max = t_sdclk - 3000;
+	phy->t_cmd_input_min = 14000;
+	phy->t_cmd_input_max = t_sdclk + 1500;
+	phy->t_dat_input_min = 14000;
+	phy->t_dat_input_max = t_sdclk + 1500;
+}
+
+/**
+ * init_uhs_sdr25() - Initialize PHY timing for UHS SDR25 mode (up to 50 MHz).
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_uhs_sdr25(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 800;
+	phy->t_cmd_output_max = t_sdclk - 3000;
+	phy->t_dat_output_min = 800;
+	phy->t_dat_output_max = t_sdclk - 3000;
+	phy->t_cmd_input_min = 14000;
+	phy->t_cmd_input_max = t_sdclk + 1500;
+	phy->t_dat_input_min = 14000;
+	phy->t_dat_input_max = t_sdclk + 1500;
+}
+
+/**
+ * init_uhs_sdr50() - Initialize PHY timing for UHS SDR50 mode (up to 100 MHz).
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_uhs_sdr50(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 800;
+	phy->t_cmd_output_max = t_sdclk - 3000;
+	phy->t_dat_output_min = 800;
+	phy->t_dat_output_max = t_sdclk - 3000;
+	phy->t_cmd_input_min = 7500;
+	phy->t_cmd_input_max = t_sdclk + 1500;
+	phy->t_dat_input_min = 7500;
+	phy->t_dat_input_max = t_sdclk + 1500;
+}
+
+/**
+ * init_uhs_sdr104() - Initialize PHY timing for UHS SDR104 mode (up to 208 MHz).
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_uhs_sdr104(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 800;
+	phy->t_cmd_output_max = t_sdclk - 1400;
+	phy->t_dat_output_min = 800;
+	phy->t_dat_output_max = t_sdclk - 1400;
+	phy->t_cmd_input_min = 1000;
+	phy->t_cmd_input_max = t_sdclk + 1000;
+	phy->t_dat_input_min = 1000;
+	phy->t_dat_input_max = t_sdclk + 1000;
+}
+
+/**
+ * init_uhs_ddr50() - Initialize PHY timing for UHS DDR50 mode (up to 50 MHz DDR).
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_uhs_ddr50(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 800;
+	phy->t_cmd_output_max = t_sdclk - 3000;
+	phy->t_dat_output_min = 800;
+	phy->t_dat_output_max = t_sdclk - 3000;
+	phy->t_cmd_input_min = 13700;
+	phy->t_cmd_input_max = t_sdclk + 1500;
+	phy->t_dat_input_min = 7000;
+	phy->t_dat_input_max = t_sdclk + 1500;
+}
+
+/**
+ * init_emmc_sdr() - Initialize PHY timing for eMMC legacy/SDR mode.
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_emmc_sdr(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 3000;
+	phy->t_cmd_output_max = t_sdclk - 3000;
+	phy->t_dat_output_min = 3000;
+	phy->t_dat_output_max = t_sdclk - 3000;
+	phy->t_cmd_input_min = 13700;
+	phy->t_cmd_input_max = t_sdclk + 2500;
+	phy->t_dat_input_min = 13700;
+	phy->t_dat_input_max = t_sdclk + 2500;
+}
+
+/**
+ * init_emmc_ddr() - Initialize PHY timing for eMMC DDR52 mode.
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_emmc_ddr(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 3000;
+	phy->t_cmd_output_max = t_sdclk - 3000;
+	phy->t_dat_output_min = 2500;
+	phy->t_dat_output_max = t_sdclk - 2500;
+	phy->t_cmd_input_min = 13700;
+	phy->t_cmd_input_max = t_sdclk + 2500;
+	phy->t_dat_input_min = 7000;
+	phy->t_dat_input_max = t_sdclk + 1500;
+}
+
+/**
+ * init_emmc_hs200() - Initialize PHY timing for eMMC HS200 mode (up to 200 MHz).
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_emmc_hs200(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 800;
+	phy->t_cmd_output_max = t_sdclk - 1400;
+	phy->t_dat_output_min = 800;
+	phy->t_dat_output_max = t_sdclk - 1400;
+	phy->t_cmd_input_min = 1000;
+	phy->t_cmd_input_max = t_sdclk + 1000;
+	phy->t_dat_input_min = 1000;
+	phy->t_dat_input_max = t_sdclk + 1000;
+}
+
+/**
+ * init_emmc_hs400() - Initialize PHY timing for eMMC HS400/HS400ES mode.
+ * @phy: Pointer to SD6HC PHY state.
+ * @t_sdclk: SD clock period in picoseconds.
+ */
+static void init_emmc_hs400(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
+{
+	phy->t_cmd_output_min = 800;
+	phy->t_cmd_output_max = t_sdclk - 1400;
+	phy->t_dat_output_min = 400;
+	phy->t_dat_output_max = t_sdclk - 400;
+	phy->t_cmd_input_min = 1000;
+	phy->t_cmd_input_max = t_sdclk + 1000;
+	phy->t_dat_input_min = 1000;
+	phy->t_dat_input_max = t_sdclk + 1000;
+}
+
+/*
+ * init_timings - PHY timing initializers indexed by MMC_TIMING_* value.
+ *
+ * Each entry corresponds to a MMC_TIMING_* constant and sets the
+ * appropriate cmd/dat output and input timing windows in the PHY
+ * state struct. Must stay in sync with the MMC_TIMING_* definitions
+ * in include/linux/mmc/host.h.
+ */
+static void (*init_timings[])(struct sdhci_cdns6_phy *, u32) = {
+	&init_ds,
+	&init_emmc_sdr,
+	&init_hs,
+	&init_uhs_sdr12,
+	&init_uhs_sdr25,
+	&init_uhs_sdr50,
+	&init_uhs_sdr104,
+	&init_uhs_ddr50,
+	&init_emmc_ddr,
+	&init_emmc_hs200,
+	&init_emmc_hs400,
+};
+
+static unsigned int sdhci_cdns6_read_phy_reg(struct sdhci_cdns_priv *priv,
+					     const u32 address)
+{
+	writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
+	return readl(priv->hrs_addr + SDHCI_CDNS_HRS05);
+}
+
+static void sdhci_cdns6_write_phy_reg(struct sdhci_cdns_priv *priv,
+				      const u32 address, const u32 value)
+{
+	writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
+	writel(value, priv->hrs_addr + SDHCI_CDNS_HRS05);
+}
+
+static int sdhci_cdns6_phy_lock_dll(struct sdhci_cdns6_phy *phy)
+{
+	u32 delay_element = phy->delay_element_org;
+	u32 delay_elements_in_sdmclk;
+
+	delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, delay_element);
+	if (delay_elements_in_sdmclk > 256) {
+		delay_element *= 2;
+		delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk,
+							delay_element);
+
+		if (delay_elements_in_sdmclk > 256)
+			return -EINVAL;
+
+		phy->dll_max_value = 127;
+	} else {
+		phy->dll_max_value = 255;
+	}
+
+	phy->t_sdmclk_calc = delay_element * delay_elements_in_sdmclk;
+	phy->delay_element = delay_element;
+	phy->cp_dll_bypass_mode = 0;
+
+	return 0;
+}
+
+static void sdhci_cdns6_phy_dll_bypass(struct sdhci_cdns6_phy *phy)
+{
+	phy->dll_max_value = 256;
+	phy->cp_dll_bypass_mode = 1;
+}
+
+static void sdhci_cdns6_phy_configure_dll(struct sdhci_cdns6_phy *phy)
+{
+	if (phy->sdhc_extended_wr_mode == 0) {
+		if (sdhci_cdns6_phy_lock_dll(phy) == 0)
+			return;
+	}
+	sdhci_cdns6_phy_dll_bypass(phy);
+}
+
+static void sdhci_cdns6_phy_calc_out(struct sdhci_cdns6_phy *phy,
+				     bool cmd_not_dat)
+{
+	u32 wr0_dly = 0, wr1_dly = 0, output_min, output_max, phy_o_delay,
+	    clk_wr_delay = 0, wr0_sdclk_dly = 0, wr1_sdclk_dly = 0;
+	bool ddr = (phy->mode == MMC_TIMING_UHS_DDR50) ||
+			(phy->mode == MMC_TIMING_MMC_DDR52) ||
+			(phy->mode == MMC_TIMING_MMC_HS400);
+	bool data_ddr = ddr && !cmd_not_dat;
+	int t;
+
+	if (cmd_not_dat) {
+		output_min = phy->t_cmd_output_min;
+		output_max = phy->t_cmd_output_max;
+		phy_o_delay = phy->phy_cmd_o_delay;
+	} else {
+		output_min = phy->t_dat_output_min;
+		output_max = phy->t_dat_output_max;
+		phy_o_delay = phy->phy_dat_o_delay;
+	}
+
+	if (data_ddr) {
+		wr0_sdclk_dly = 1;
+		wr1_sdclk_dly = 1;
+	}
+
+	t = phy_o_delay - phy->phy_sdclk_delay - output_min;
+	if (t < 0 && phy->sdhc_extended_wr_mode == 1) {
+		u32 n_half_cycle = DIV_ROUND_UP(-t * 2, phy->t_sdmclk);
+
+		wr0_dly = (n_half_cycle + 1) / 2;
+		if (data_ddr)
+			wr1_dly = (n_half_cycle + 1) / 2;
+		else
+			wr1_dly = (n_half_cycle + 1) % 2 + wr0_dly - 1;
+	}
+
+	if (phy->sdhc_extended_wr_mode == 0) {
+		u32 out_hold, out_setup, out_hold_margin;
+		u32 n;
+
+		if (!data_ddr)
+			wr0_dly = 1;
+
+		out_setup = output_max;
+		out_hold = output_min;
+		out_hold_margin = DIV_ROUND_UP(out_setup - out_hold, 4);
+		out_hold += out_hold_margin;
+
+		if (phy->cp_dll_bypass_mode == 0)
+			n = DIV_ROUND_UP(256 * out_hold, phy->t_sdmclk_calc);
+		else
+			n = DIV_ROUND_UP(out_hold, phy->delay_element) - 1;
+
+		if (n <= phy->dll_max_value)
+			clk_wr_delay = n;
+		else
+			clk_wr_delay = 255;
+	} else {
+		/*  sdhc_extended_wr_mode = 1 => PHY IO cell work in SDR mode */
+		clk_wr_delay = 0;
+	}
+
+	if (cmd_not_dat) {
+		phy->sdhc_wrcmd0_dly = wr0_dly;
+		phy->sdhc_wrcmd1_dly = wr1_dly;
+		phy->cp_clk_wrdqs_delay = clk_wr_delay;
+		phy->sdhc_wrcmd0_sdclk_dly = wr0_sdclk_dly;
+		phy->sdhc_wrcmd1_sdclk_dly = wr1_sdclk_dly;
+	} else {
+		phy->sdhc_wrdata0_dly = wr0_dly;
+		phy->sdhc_wrdata1_dly = wr1_dly;
+		phy->cp_clk_wr_delay = clk_wr_delay;
+		phy->sdhc_wrdata0_sdclk_dly = wr0_sdclk_dly;
+		phy->sdhc_wrdata1_sdclk_dly = wr1_sdclk_dly;
+	}
+}
+
+static void sdhci_cdns6_phy_calc_cmd_out(struct sdhci_cdns6_phy *phy)
+{
+	sdhci_cdns6_phy_calc_out(phy, true);
+}
+
+static void sdhci_cdns6_phy_calc_cmd_in(struct sdhci_cdns6_phy *phy)
+{
+	phy->cp_io_mask_end =
+		((phy->iocell_output_delay + phy->iocell_input_delay) * 2)
+		/ phy->t_sdmclk;
+
+	/* cp_io_mask_end is a 3-bit field, clamp to max value of 7 */
+	phy->cp_io_mask_end = min_t(u32, phy->cp_io_mask_end, 7);
+
+	if (phy->strobe_cmd && phy->cp_io_mask_end > 0)
+		phy->cp_io_mask_end--;
+
+	if (phy->strobe_cmd) {
+		phy->cp_use_phony_dqs_cmd = 0;
+		phy->cp_read_dqs_cmd_delay = 64;
+	} else {
+		phy->cp_use_phony_dqs_cmd = 1;
+		phy->cp_read_dqs_cmd_delay = 0;
+	}
+
+	if ((phy->mode == MMC_TIMING_MMC_HS400 && !phy->strobe_cmd) ||
+	    phy->mode == MMC_TIMING_MMC_HS200)
+		phy->cp_read_dqs_cmd_delay =
+			phy->hs200_tune_val;
+}
+
+static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy)
+{
+	u32 hcsdclkadj = 0;
+	bool strobe_dat = (phy->mode == MMC_TIMING_MMC_HS400);
+
+	if (strobe_dat) {
+		phy->cp_use_phony_dqs = 0;
+		phy->cp_read_dqs_delay = 64;
+	} else {
+		phy->cp_use_phony_dqs = 1;
+		phy->cp_read_dqs_delay = 0;
+	}
+
+	if (phy->mode == MMC_TIMING_MMC_HS200)
+		phy->cp_read_dqs_delay =
+			phy->hs200_tune_val;
+
+	if (strobe_dat) {
+		/* dqs loopback input via IO cell */
+		hcsdclkadj += phy->iocell_input_delay;
+		/* dfi_dqs_in: mem_dqs -> clean_dqs_mod; delay of hic_dll_dqs_nand2 */
+		hcsdclkadj += phy->delay_element / 2;
+		/* delay line */
+		hcsdclkadj += phy->t_sdclk / 2;
+		/* PHY FIFO write pointer */
+		hcsdclkadj += phy->t_sdclk / 2 + phy->delay_element;
+		/* 1st synchronizer */
+		hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk)
+			* phy->t_sdmclk - hcsdclkadj;
+		/*
+		 * 2nd synchronizer + PHY FIFO read pointer + PHY rddata
+		 * + PHY rddata registered, + FIFO 1st ciu_en
+		 */
+		hcsdclkadj += 5 * phy->t_sdmclk;
+		/* FIFO 2nd ciu_en */
+		hcsdclkadj += phy->t_sdclk;
+
+		hcsdclkadj /= phy->t_sdclk;
+	} else {
+		u32 n;
+
+		/* rebar PHY delay */
+		hcsdclkadj += 2 * phy->t_sdmclk;
+		/* rebar output via IO cell */
+		hcsdclkadj += phy->iocell_output_delay;
+		/* dqs loopback input via IO cell */
+		hcsdclkadj += phy->iocell_input_delay;
+		/* dfi_dqs_in: mem_dqs -> clean_dqs_mod delay of hic_dll_dqs_nand2 */
+		hcsdclkadj += phy->delay_element / 2;
+		/* dll: one delay element between SIGI_0 and SIGO_0 */
+		hcsdclkadj += phy->delay_element;
+		/* dfi_dqs_in: mem_dqs_delayed -> clk_dqs delay of hic_dll_dqs_nand2 */
+		hcsdclkadj += phy->delay_element / 2;
+		/* deskew DLL: clk_dqs -> clk_dqN: one delay element */
+		hcsdclkadj += phy->delay_element;
+
+		if (phy->t_sdclk == phy->t_sdmclk)
+			n = (hcsdclkadj - 2 * phy->t_sdmclk) / phy->t_sdclk;
+		else
+			n = hcsdclkadj / phy->t_sdclk;
+
+		/* phase shift within one t_sdclk clock cycle caused by rebar - lbk dqs delay */
+		hcsdclkadj = hcsdclkadj % phy->t_sdclk;
+		/* PHY FIFO write pointer */
+		hcsdclkadj += phy->t_sdclk / 2;
+		/* 1st synchronizer */
+		hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk)
+			* phy->t_sdmclk - hcsdclkadj;
+		/*
+		 * 2nd synchronizer + PHY FIFO read pointer + PHY rddata
+		 * + PHY rddata registered
+		 */
+		hcsdclkadj += 4 * phy->t_sdmclk;
+
+		if ((phy->t_sdclk / phy->t_sdmclk) > 1) {
+			u32 tmp1, tmp2;
+
+			tmp1 = hcsdclkadj;
+			tmp2 = (hcsdclkadj / phy->t_sdclk) * phy->t_sdclk
+				+ phy->t_sdclk - phy->t_sdmclk;
+			if (tmp1 == tmp2)
+				tmp2 += phy->t_sdclk;
+
+			/* FIFO aligns to clock cycle before ciu_en */
+			hcsdclkadj += tmp2 - tmp1;
+		}
+
+		/* FIFO 1st ciu_en */
+		hcsdclkadj += phy->t_sdmclk;
+		/* FIFO 2nd ciu_en */
+		hcsdclkadj += phy->t_sdclk;
+
+		hcsdclkadj /= phy->t_sdclk;
+
+		hcsdclkadj += n;
+
+		if ((phy->t_sdclk / phy->t_sdmclk) >= 2) {
+			if (phy->mode == MMC_TIMING_UHS_DDR50 ||
+			    phy->mode == MMC_TIMING_MMC_DDR52)
+				hcsdclkadj -= 2;
+			else
+				hcsdclkadj -= 1;
+		} else if ((phy->t_sdclk / phy->t_sdmclk) == 1) {
+			hcsdclkadj += 2;
+		}
+
+		if (phy->mode == MMC_TIMING_UHS_SDR104 || phy->mode == MMC_TIMING_MMC_HS200)
+			hcsdclkadj -= 1;
+	}
+
+	/* hcsdclkadj is a 4-bit field, clamp to max value of 15 */
+	if (hcsdclkadj > 15)
+		hcsdclkadj = 15;
+
+	phy->sdhc_hcsdclkadj = hcsdclkadj;
+}
+
+static void sdhci_cdns6_phy_calc_dat_out(struct sdhci_cdns6_phy *phy)
+{
+	sdhci_cdns6_phy_calc_out(phy, false);
+}
+
+static void sdhci_cdns6_phy_calc_io(struct sdhci_cdns6_phy *phy)
+{
+	u32 rw_compensate;
+
+	rw_compensate = ((phy->iocell_input_delay + phy->iocell_output_delay)
+		/ phy->t_sdmclk) + phy->sdhc_wrdata0_dly + 5 + 3;
+
+	phy->sdhc_idelay_val = (2 * phy->iocell_input_delay)
+		/ phy->t_sdmclk;
+
+	phy->cp_io_mask_start = 0;
+	if (phy->t_sdclk == phy->t_sdmclk && rw_compensate > 10)
+		phy->cp_io_mask_start = 2 * (rw_compensate - 10);
+
+	if (phy->mode == MMC_TIMING_UHS_SDR104)
+		phy->cp_io_mask_start++;
+
+	if (phy->t_sdclk == phy->t_sdmclk && phy->mode == MMC_TIMING_UHS_SDR50)
+		phy->cp_io_mask_start++;
+
+	phy->sdhc_rw_compensate = rw_compensate;
+}
+
+static void sdhci_cdns6_phy_calc_settings(struct sdhci_cdns6_phy *phy)
+{
+	sdhci_cdns6_phy_calc_cmd_out(phy);
+	sdhci_cdns6_phy_calc_cmd_in(phy);
+	sdhci_cdns6_phy_calc_dat_out(phy);
+	sdhci_cdns6_phy_calc_dat_in(phy);
+	sdhci_cdns6_phy_calc_io(phy);
+}
+
+static int sdhci_cdns6_dll_reset(struct sdhci_cdns_priv *priv, bool reset)
+{
+	u32 reg;
+	int ret = 0;
+
+	reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09);
+	if (reset)
+		reg &= ~SDHCI_CDNS_HRS09_PHY_SW_RESET;
+	else
+		reg |= SDHCI_CDNS_HRS09_PHY_SW_RESET;
+
+	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09);
+
+	/* After reset, wait until HRS09.PHY_INIT_COMPLETE is set to 1 within 3000us*/
+	if (!reset) {
+		ret = readl_poll_timeout(priv->hrs_addr + SDHCI_CDNS_HRS09, reg,
+					 (reg & SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE),
+					 0, 3000);
+	}
+
+	return ret;
+}
+
+int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv)
+{
+	int ret;
+	u32 reg;
+	struct sdhci_cdns6_phy *phy = priv->phy;
+
+	sdhci_cdns6_dll_reset(priv, true);
+
+	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG);
+	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS;
+	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS;
+	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS;
+	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD;
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS,
+			phy->cp_use_ext_lpbk_dqs);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS,
+			phy->cp_use_lpbk_dqs);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS,
+			  phy->cp_use_phony_dqs);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD,
+			  phy->cp_use_phony_dqs_cmd);
+	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG, reg);
+
+	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG);
+	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD;
+	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SW_HALF_CYCLE_SHIFT;
+	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL;
+	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS;
+	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON;
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD,
+			phy->cp_sync_method);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SW_HALF_CYCLE_SHIFT,
+			phy->cp_sw_half_cycle_shift);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL,
+			phy->cp_rd_del_sel);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS,
+			phy->cp_underrun_suppress);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON,
+			phy->cp_gate_cfg_always_on);
+	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG, reg);
+
+	reg = FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_BYPASS_MODE,
+			 phy->cp_dll_bypass_mode);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_PHASE_DETECT_SEL, 2);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM, 0);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT,
+			phy->cp_dll_start_point);
+	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_REG, reg);
+
+	reg = FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY,
+			 phy->cp_read_dqs_cmd_delay);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WRDQS_DELAY,
+			  phy->cp_clk_wrdqs_delay);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WR_DELAY,
+			  phy->cp_clk_wr_delay);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY,
+			  phy->cp_read_dqs_delay);
+	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG, reg);
+
+	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG);
+	reg &= ~SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING;
+	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG, reg);
+
+	/*
+	 * Ensure all preceding PHY register writes complete and reach the
+	 * controller before releasing the PHY from reset. Without this,
+	 * SDR104 has been observed to fail intermittently on some boards.
+	 */
+	wmb();
+
+	ret = sdhci_cdns6_dll_reset(priv, false);
+	if (ret)
+		return ret;
+
+	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DQ_TIMING_REG);
+	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON;
+	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END;
+	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START;
+	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END;
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON,
+			phy->cp_io_mask_always_on);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END,
+			  phy->cp_io_mask_end);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START,
+			  phy->cp_io_mask_start);
+	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END,
+			phy->cp_data_select_oe_end);
+	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQ_TIMING_REG, reg);
+
+	/* Ensure DQ timing programming is visible before HRS09 follow-up writes */
+	wmb();
+
+	reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09);
+	if (phy->sdhc_extended_wr_mode)
+		reg |= SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
+	else
+		reg &= ~SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
+
+	if (phy->sdhc_extended_rd_mode)
+		reg |= SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
+	else
+		reg &= ~SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
+
+	if (phy->sdhc_rddata_en)
+		reg |= SDHCI_CDNS_HRS09_RDDATA_EN;
+	else
+		reg &= ~SDHCI_CDNS_HRS09_RDDATA_EN;
+
+	if (phy->sdhc_rdcmd_en)
+		reg |= SDHCI_CDNS_HRS09_RDCMD_EN;
+	else
+		reg &= ~SDHCI_CDNS_HRS09_RDCMD_EN;
+
+	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09);
+
+	reg = FIELD_PREP(SDHCI_CDNS_HRS10_HCSDCLKADJ, phy->sdhc_hcsdclkadj);
+	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS10);
+
+	reg = FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY,
+			 phy->sdhc_wrdata1_sdclk_dly);
+	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY,
+			  phy->sdhc_wrdata0_sdclk_dly);
+	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY,
+			  phy->sdhc_wrcmd1_sdclk_dly);
+	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY,
+			  phy->sdhc_wrcmd0_sdclk_dly);
+	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_DLY,
+			  phy->sdhc_wrdata1_dly);
+	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_DLY,
+			  phy->sdhc_wrdata0_dly);
+	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_DLY,
+			  phy->sdhc_wrcmd1_dly);
+	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_DLY,
+			  phy->sdhc_wrcmd0_dly);
+	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS16);
+
+	reg = FIELD_PREP(SDHCI_CDNS_HRS07_RW_COMPENSATE,
+			 phy->sdhc_rw_compensate);
+	reg |= FIELD_PREP(SDHCI_CDNS_HRS07_IDELAY_VAL,
+			  phy->sdhc_idelay_val);
+	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS07);
+
+	/* Allow 5ms for clock and PHY signals to stabilize after configuration */
+	usleep_range(5000, 5500);
+
+	return 0;
+}
+
+int sdhci_cdns6_set_tune_val(struct sdhci_host *host,
+			     unsigned int val)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
+	struct sdhci_cdns6_phy *phy = priv->phy;
+	u32 tuneval;
+
+	/*
+	 * Scale tuning tap (val in [0, SDHCI_CDNS_MAX_TUNING_LOOP-1]) to the
+	 * 8-bit PHY DLL slave delay field [0, 255]. With MAX_TUNING_LOOP=40
+	 * and FIELD_SIZE=256, the result fits in 8 bits.
+	 */
+	tuneval = (val * SDHCI_CDNS6_PHY_DLL_FIELD_SIZE) /
+		  SDHCI_CDNS_MAX_TUNING_LOOP;
+
+	phy->hs200_tune_val = tuneval;
+	phy->cp_read_dqs_cmd_delay = tuneval;
+	phy->cp_read_dqs_delay = tuneval;
+
+	return sdhci_cdns6_phy_init(priv);
+}
+
+static int sdhci_cdns6_phy_update_timings(struct sdhci_host *host)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
+	struct sdhci_cdns6_phy *phy = priv->phy;
+	u32 t_sdmclk = phy->t_sdmclk;
+
+	/* Validate mode is within supported range */
+	if (phy->mode >= ARRAY_SIZE(init_timings))
+		return -EINVAL;
+
+	/* initialize input */
+	init_timings[phy->mode](phy, phy->t_sdclk);
+
+	phy->strobe_cmd = false;
+
+	if (priv->enhanced_strobe)
+		phy->strobe_cmd = true;
+
+	phy->phy_sdclk_delay = 2 * t_sdmclk;
+
+	/*
+	 * CMD and DAT output delays are currently identical, but kept separate
+	 * to allow independent tuning for specific modes (e.g., HS400) or
+	 * board-specific optimizations in the future.
+	 */
+	phy->phy_cmd_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
+	phy->phy_dat_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
+
+	if (phy->t_sdclk == phy->t_sdmclk) {
+		phy->sdhc_extended_wr_mode = 0;
+		phy->sdhc_extended_rd_mode = 0;
+	} else {
+		phy->sdhc_extended_wr_mode = 1;
+		phy->sdhc_extended_rd_mode = 1;
+	}
+
+	phy->cp_gate_cfg_always_on = 1;
+
+	sdhci_cdns6_phy_configure_dll(phy);
+
+	sdhci_cdns6_phy_calc_settings(phy);
+
+	return 0;
+}
+
+int sdhci_cdns6_phy_probe(struct platform_device *pdev,
+			  struct sdhci_cdns_priv *priv)
+{
+	struct device *dev = &pdev->dev;
+	struct sdhci_host *host = dev_get_drvdata(dev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_cdns6_phy *phy;
+	unsigned long val;
+	int ret;
+
+	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+	if (!phy)
+		return -ENOMEM;
+
+	val = clk_get_rate(pltfm_host->clk);
+	if (!val)
+		return dev_err_probe(dev, -EINVAL,
+				     "failed to get controller clock rate\n");
+
+	phy->t_sdmclk = DIV_ROUND_DOWN_ULL(1000000000000ULL, val);
+
+	ret = of_property_read_u32(dev->of_node, "cdns,iocell-input-delay",
+				   &phy->iocell_input_delay);
+	if (ret)
+		phy->iocell_input_delay = SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY;
+
+	ret = of_property_read_u32(dev->of_node, "cdns,iocell-output-delay",
+				   &phy->iocell_output_delay);
+	if (ret)
+		phy->iocell_output_delay = SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY;
+
+	ret = of_property_read_u32(dev->of_node, "cdns,delay-element",
+				   &phy->delay_element);
+	if (ret)
+		phy->delay_element = SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT;
+
+	phy->delay_element_org = phy->delay_element;
+
+	priv->phy = phy;
+
+	/* default settings */
+	phy->sdhc_rdcmd_en = 1;
+	phy->sdhc_rddata_en = 1;
+	phy->cp_use_ext_lpbk_dqs = 1;
+	phy->cp_use_lpbk_dqs = 1;
+	phy->cp_sync_method = 1;
+	phy->cp_rd_del_sel = SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL;
+	phy->cp_dll_start_point = SDHCI_CDNS6_PHY_DEFAULT_DLL_START;
+	phy->cp_data_select_oe_end = 1;
+	phy->cp_io_mask_always_on = 0;
+	phy->cp_underrun_suppress = 1;
+
+	return 0;
+}
+
+void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
+	struct sdhci_cdns6_phy *phy = priv->phy;
+
+	/* Clock may be 0 during initial ios setup; skip PHY update */
+	if (!host->mmc->ios.clock)
+		return;
+
+	phy->t_sdclk = DIV_ROUND_DOWN_ULL(1000000000000ULL,
+					  host->mmc->ios.clock);
+	phy->mode = timing;
+
+	if (sdhci_cdns6_phy_update_timings(host))
+		dev_warn(mmc_dev(host->mmc), "%s: update timings failed\n",
+			 __func__);
+
+	if (sdhci_cdns6_phy_init(priv))
+		dev_warn(mmc_dev(host->mmc), "%s: phy init failed\n",
+			 __func__);
+}
+
+void sdhci_cdns6_hw_reset(struct sdhci_host *host)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
+	void __iomem *reg;
+
+	reg = priv->hrs_addr + SDHCI_CDNS_HRS11;
+	writel(SDHCI_CDNS_HRS11_EMMC_RST, reg);
+	/* eMMC HW reset assertion: spec requires >= 1us, give margin */
+	usleep_range(10, 20);
+	writel(0, reg);
+	usleep_range(300, 1000);
+}
-- 
2.43.7


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

* [PATCH v1 9/9] mmc: sdhci-cadence: add Altera Agilex5 SD6HC support
  2026-05-11 20:21 [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
                   ` (7 preceding siblings ...)
  2026-05-11 20:21 ` [PATCH v1 8/9] mmc: sdhci-cadence: add Cadence SD6HC support Tanmay Kathpalia
@ 2026-05-11 20:21 ` Tanmay Kathpalia
  2026-05-27 21:01   ` Zach Miller
  8 siblings, 1 reply; 32+ messages in thread
From: Tanmay Kathpalia @ 2026-05-11 20:21 UTC (permalink / raw)
  To: linux-mmc; +Cc: ulf.hansson, Tanmay Kathpalia, Adrian Hunter, linux-kernel

The Altera Agilex5 SoC integrates a Cadence SD6HC controller that needs
platform-specific configuration to operate correctly. Its SMMU exposes
DRAM within a 40-bit address space, requiring the DMA mask to be capped
at 40 bits to prevent address allocation beyond the controller's reach.

The silicon also has hardware limitations that require the
MULTIBLOCK_READ_ACMD12, PRESET_VALUE_BROKEN, and ACMD23_BROKEN quirks.
Support is registered under the "altr,agilex5-sd6hc" compatible string
with Agilex5-specific ops and platform data.

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
 drivers/mmc/host/sdhci-cadence4.c | 52 +++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/drivers/mmc/host/sdhci-cadence4.c b/drivers/mmc/host/sdhci-cadence4.c
index 283d22328248..b6d35c165673 100644
--- a/drivers/mmc/host/sdhci-cadence4.c
+++ b/drivers/mmc/host/sdhci-cadence4.c
@@ -7,6 +7,7 @@
 
 #include <linux/bitfield.h>
 #include <linux/bits.h>
+#include <linux/dma-mapping.h>
 #include <linux/module.h>
 
 #include "sdhci-cadence.h"
@@ -84,6 +85,7 @@ struct sdhci_cdns4_phy_cfg {
 struct sdhci_cdns_drv_data {
 	int (*init)(struct platform_device *pdev);
 	const struct sdhci_pltfm_data pltfm_data;
+	u64 dma_mask;
 };
 
 static const struct sdhci_cdns4_phy_cfg sdhci_cdns4_phy_cfgs[] = {
@@ -193,6 +195,31 @@ static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
 	return host->max_clk;
 }
 
+/**
+ * sdhci_cdns_set_dma_mask - Set platform-specific DMA mask
+ * @host: SDHCI host controller
+ *
+ * Configure DMA mask based on platform capabilities to avoid IOMMU
+ * address allocation beyond controller's reach or unnecessary bounce
+ * buffers.
+ */
+static int sdhci_cdns_set_dma_mask(struct sdhci_host *host)
+{
+	struct mmc_host *mmc = host->mmc;
+	struct device *dev = mmc_dev(mmc);
+	const struct sdhci_cdns_drv_data *data = of_device_get_match_data(dev);
+	int ret;
+
+	if (!data->dma_mask)
+		return 0;
+
+	ret = dma_set_mask_and_coherent(dev, data->dma_mask);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to set DMA mask\n");
+
+	return 0;
+}
+
 static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode)
 {
 	u32 tmp;
@@ -479,6 +506,17 @@ static const struct sdhci_ops sdhci_cdns6_ops = {
 	.hw_reset = sdhci_cdns6_hw_reset,
 };
 
+static const struct sdhci_ops sdhci_cdns6_agilex5_ops = {
+	.set_clock = sdhci_set_clock,
+	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
+	.set_bus_width = sdhci_set_bus_width,
+	.reset = sdhci_reset,
+	.platform_execute_tuning = sdhci_cdns_execute_tuning,
+	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
+	.hw_reset = sdhci_cdns6_hw_reset,
+	.set_dma_mask = sdhci_cdns_set_dma_mask,
+};
+
 static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
 	.pltfm_data = {
 		.ops = &sdhci_cdns4_ops,
@@ -506,6 +544,16 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
 	},
 };
 
+static const struct sdhci_cdns_drv_data sdhci_cdns6_agilex5_drv_data = {
+	.pltfm_data = {
+		.ops = &sdhci_cdns6_agilex5_ops,
+		.quirks = SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12,
+		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+			   SDHCI_QUIRK2_ACMD23_BROKEN,
+	},
+	.dma_mask = DMA_BIT_MASK(40),
+};
+
 static const struct sdhci_cdns_drv_data sdhci_cdns6_drv_data = {
 	.pltfm_data = {
 		.ops = &sdhci_cdns6_ops,
@@ -691,6 +739,10 @@ static const struct of_device_id sdhci_cdns_match[] = {
 		.compatible = "cdns,sd4hc",
 		.data = &sdhci_cdns4_drv_data,
 	},
+	{
+		.compatible = "altr,agilex5-sd6hc",
+		.data = &sdhci_cdns6_agilex5_drv_data,
+	},
 	{
 		.compatible = "cdns,sd6hc",
 		.data = &sdhci_cdns6_drv_data,
-- 
2.43.7


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

* Re: [PATCH v1 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties
  2026-05-11 20:21 ` [PATCH v1 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties Tanmay Kathpalia
@ 2026-05-12 17:33   ` Conor Dooley
  2026-05-13  0:23   ` sashiko-bot
  1 sibling, 0 replies; 32+ messages in thread
From: Conor Dooley @ 2026-05-12 17:33 UTC (permalink / raw)
  To: Tanmay Kathpalia
  Cc: linux-mmc, ulf.hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Masahiro Yamada, devicetree, linux-kernel

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

On Mon, May 11, 2026 at 01:21:24PM -0700, Tanmay Kathpalia wrote:
> Extend the Cadence SDHCI binding to support the sixth-generation
> SD6HC controller. Add the cdns,sd6hc and altr,agilex5-sd6hc
> compatible strings, clock-names, reset-names, iommus and three
> SD6HC-specific PHY timing properties.
> 
> Add per-variant conditional blocks so SD6HC and SD4HC each enforce
> their own clock, reset, and PHY property constraints independently.
> 
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>

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

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

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

* Re: [PATCH v1 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5
  2026-05-11 20:21 ` [PATCH v1 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Tanmay Kathpalia
@ 2026-05-12 17:33   ` Conor Dooley
  0 siblings, 0 replies; 32+ messages in thread
From: Conor Dooley @ 2026-05-12 17:33 UTC (permalink / raw)
  To: Tanmay Kathpalia
  Cc: linux-mmc, ulf.hansson, Philipp Zabel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel

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

On Mon, May 11, 2026 at 01:21:23PM -0700, Tanmay Kathpalia wrote:
> Add COMBOPHY_RESET definition at index 38 for the combo PHY reset
> control on Altera Agilex5 SoCs. This reset is used by peripherals
> such as the SD/eMMC controller that share the combo PHY.
> 
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>

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

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

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

* Re: [PATCH v1 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties
  2026-05-11 20:21 ` [PATCH v1 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties Tanmay Kathpalia
  2026-05-12 17:33   ` Conor Dooley
@ 2026-05-13  0:23   ` sashiko-bot
  1 sibling, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-05-13  0:23 UTC (permalink / raw)
  To: Tanmay Kathpalia; +Cc: robh, krzk+dt, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] clock-names and reset-names are defined exclusively inside the if:then: conditional block instead of the top-level properties block.
--

commit f399abc006829d70cb476a2bacc2fc4f092408b9
Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>

dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties

Extend the Cadence SDHCI binding to support the sixth-generation SD6HC
controller. Add new compatible strings, properties, and per-variant
conditional blocks to enforce hardware constraints.

> diff --git a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
> --- a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
> +++ b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml

[ ... ]

> @@ -141,6 +173,61 @@ allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: cdns,sd6hc
> +    then:
> +      description: SD6HC variant - use IO-cell and delay element properties
> +      properties:
> +        clocks:
> +          minItems: 2
> +          maxItems: 2
> +        clock-names:
> +          items:
> +            - const: ciu
> +            - const: biu
> +        resets:
> +          minItems: 3
> +          maxItems: 3
> +        reset-names:
> +          items:
> +            - const: sdhc-reset
> +            - const: combophy
> +            - const: sdmmc-ocp

This isn't a functional bug, but does defining these properties directly
inside the conditional block violate the device tree writing schema
guidelines?

The documentation in writing-schema.rst typically requires properties to be
declared with their broadest constraints in the top-level properties block,
rather than exclusively inside an if:then: block.

Should clock-names and reset-names be added to the top-level properties
dictionary before narrowing their constraints here?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260511202132.5597-1-tanmay.kathpalia@altera.com?part=2

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

* Re: [PATCH v1 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement
  2026-05-11 20:21 ` [PATCH v1 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement Tanmay Kathpalia
@ 2026-05-13  0:43   ` sashiko-bot
  2026-05-13  6:34     ` Kathpalia, Tanmay
  2026-05-15  8:38   ` Krzysztof Kozlowski
  1 sibling, 1 reply; 32+ messages in thread
From: sashiko-bot @ 2026-05-13  0:43 UTC (permalink / raw)
  To: Tanmay Kathpalia; +Cc: robh, krzk+dt, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Board-specific regulators are incorrectly defined in the generic SoC .dtsi file and placed inside a simple-bus.
- [Low] The Cadence SD6HC node includes an undocumented and unsupported `fifo-depth` property.
--

commit cd8114ca6cbb7c0bc2b1d26651ea445b3eef24ec
Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>

arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement

This commit adds an SD/MMC controller node backed by the Cadence SD6HC
to the Agilex5 SoC device tree. It also enables the controller on the
SOCDK board with high-speed and UHS-I modes.

> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
[ ... ]
> @@ -300,6 +300,44 @@ portb: gpio-controller@0 {
>  			};
>  		};
>  
> +		sd_emmc_power: regulator-fixed-3p3v {
> +			compatible = "regulator-fixed";
> +			regulator-name = "card-power";
> +			regulator-min-microvolt = <3300000>;
> +			regulator-max-microvolt = <3300000>;
> +			regulator-always-on;
> +		};
> +
> +		sd_io_1v8_reg: regulator-1p8v {
> +			compatible = "regulator-gpio";
> +			regulator-name = "sd-bus-io-power";
> +			regulator-min-microvolt = <1800000>;
> +			regulator-max-microvolt = <3300000>;
> +			states =	<1800000 0x1>,
> +					<3300000 0x0>;
> +		};

Should these board-specific regulators be defined in the generic SoC
dtsi file instead of the board dts file?

Placing them here forces these external board-level circuitry
definitions onto all future boards using this SoC.

Additionally, because they are placed inside a simple-bus without a reg
property, it looks like they will trigger simple_bus_reg compiler
warnings. The sd_io_1v8_reg node is also missing the mandatory gpios
property in the dtsi, which might cause dtbs_check schema validation
failures for any board that does not explicitly provide one.

> +
> +		emmc: mmc@10808000 {
> +			#address-cells = <1>;
> +			#size-cells = <0>;
> +			compatible = "altr,agilex5-sd6hc", "cdns,sd6hc";
> +			reg = <0x10808000 0x1000>;
> +			interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
> +			fifo-depth = <0x800>;

Is the fifo-depth property supported by the Cadence SDHC binding?

The Cadence SDHC binding specifies unevaluatedProperties: false and
does not appear to define or support fifo-depth, which seems to be a
leftover from Synopsys DesignWare MMC bindings.

While the driver will ignore it, its presence might cause a dtbs_check
schema validation failure.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260511202132.5597-1-tanmay.kathpalia@altera.com?part=3

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

* Re: [PATCH v1 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support
  2026-05-11 20:21 ` [PATCH v1 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support Tanmay Kathpalia
@ 2026-05-13  1:22   ` sashiko-bot
  2026-05-13  6:25     ` Kathpalia, Tanmay
  2026-05-15  8:37   ` Krzysztof Kozlowski
  1 sibling, 1 reply; 32+ messages in thread
From: sashiko-bot @ 2026-05-13  1:22 UTC (permalink / raw)
  To: Tanmay Kathpalia; +Cc: krzk+dt, devicetree, conor+dt, robh

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The `no-1-8-v` property in the `&emmc` node explicitly disables the 1.8V HS200 and HS400 modes, contradicting the commit intent.
- [Medium] A board-specific fixed regulator (`emmc_io_1v8_reg`) is improperly defined in the shared SoC-level `.dtsi` file and placed under a memory-mapped bus.
--

commit 832d2f01748902348b5ee8ddc2a87d8dfab30896
Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>

arm64: dts: agilex5: add SOCDK eMMC daughter board support

This commit adds device tree support for the Agilex5 SOCDK eMMC daughter
board. It introduces the new board dts file and adds a fixed 1.8V regulator
for the eMMC I/O voltage to the common SoC dtsi.

> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> index 7e080f13166f2..feb4ccb317a71 100644
> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
[ ... ]
> @@ -317,6 +317,14 @@ sd_io_1v8_reg: regulator-1p8v {
>  					<3300000 0x0>;
>  		};
>  
> +		emmc_io_1v8_reg: regulator-fixed-1p8v {
> +			compatible = "regulator-fixed";
> +			regulator-name = "emmc-io-power";
> +			regulator-min-microvolt = <1800000>;
> +			regulator-max-microvolt = <1800000>;
> +			regulator-always-on;
> +		};
> +

Will this cause every Agilex 5 board to inadvertently instantiate and enable
this regulator, even if they do not have the eMMC daughter card attached?
Since this appears to be a board-specific regulator, would it be better
placed at the root level of the board-specific socfpga_agilex5_socdk_emmc.dts
file?

Additionally, does placing this node inside a memory-mapped bus without a reg
property trigger device tree compiler warnings?

> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
> new file mode 100644
> index 0000000000000..87bde9fa69c6c
> --- /dev/null
> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
[ ... ]
> +&emmc {
> +	status = "okay";
> +
> +	no-sd;
> +	no-sdio;
> +	no-1-8-v;

The commit message states the eMMC controller is configured for HS200 and
HS400 modes at 1.8V. Does the no-1-8-v property prevent these high-speed
modes from functioning?

In the SDHCI core, parsing no-1-8-v sets the SDHCI_QUIRK2_NO_1_8_V flag,
which strips out the MMC_CAP2_HSX00_1_8V and MMC_CAP2_HS400_ES capabilities
during host setup. Since the eMMC IO is explicitly supplied by a 1.8V
regulator, shouldn't this property be removed to allow HS200 and HS400
negotiation?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260511202132.5597-1-tanmay.kathpalia@altera.com?part=5

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

* Re: [PATCH v1 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support
  2026-05-13  1:22   ` sashiko-bot
@ 2026-05-13  6:25     ` Kathpalia, Tanmay
  0 siblings, 0 replies; 32+ messages in thread
From: Kathpalia, Tanmay @ 2026-05-13  6:25 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: krzk+dt, devicetree, conor+dt, robh


On 5/13/2026 6:52 AM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] The `no-1-8-v` property in the `&emmc` node explicitly disables the 1.8V HS200 and HS400 modes, contradicting the commit intent.
> - [Medium] A board-specific fixed regulator (`emmc_io_1v8_reg`) is improperly defined in the shared SoC-level `.dtsi` file and placed under a memory-mapped bus.
> --
>
> commit 832d2f01748902348b5ee8ddc2a87d8dfab30896
> Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>
> arm64: dts: agilex5: add SOCDK eMMC daughter board support
>
> This commit adds device tree support for the Agilex5 SOCDK eMMC daughter
> board. It introduces the new board dts file and adds a fixed 1.8V regulator
> for the eMMC I/O voltage to the common SoC dtsi.
>
>> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> index 7e080f13166f2..feb4ccb317a71 100644
>> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> [ ... ]
>> @@ -317,6 +317,14 @@ sd_io_1v8_reg: regulator-1p8v {
>>   					<3300000 0x0>;
>>   		};
>>   
>> +		emmc_io_1v8_reg: regulator-fixed-1p8v {
>> +			compatible = "regulator-fixed";
>> +			regulator-name = "emmc-io-power";
>> +			regulator-min-microvolt = <1800000>;
>> +			regulator-max-microvolt = <1800000>;
>> +			regulator-always-on;
>> +		};
>> +
> Will this cause every Agilex 5 board to inadvertently instantiate and enable
> this regulator, even if they do not have the eMMC daughter card attached?
> Since this appears to be a board-specific regulator, would it be better
> placed at the root level of the board-specific socfpga_agilex5_socdk_emmc.dts
> file?
>
> Additionally, does placing this node inside a memory-mapped bus without a reg
> property trigger device tree compiler warnings?

Agreed, this is board-specific and should not be in the shared SoC dtsi.
Will be moved to socfpga_agilex5_socdk_emmc.dts in v2.
>
>> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
>> new file mode 100644
>> index 0000000000000..87bde9fa69c6c
>> --- /dev/null
>> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
> [ ... ]
>> +&emmc {
>> +	status = "okay";
>> +
>> +	no-sd;
>> +	no-sdio;
>> +	no-1-8-v;
> The commit message states the eMMC controller is configured for HS200 and
> HS400 modes at 1.8V. Does the no-1-8-v property prevent these high-speed
> modes from functioning?
>
> In the SDHCI core, parsing no-1-8-v sets the SDHCI_QUIRK2_NO_1_8_V flag,
> which strips out the MMC_CAP2_HSX00_1_8V and MMC_CAP2_HS400_ES capabilities
> during host setup. Since the eMMC IO is explicitly supplied by a 1.8V
> regulator, shouldn't this property be removed to allow HS200 and HS400
> negotiation?


no-1-8-v sets SDHCI_QUIRK2_NO_1_8_V which prevents HS200/HS400
negotiation — contradicting the intent. Will be removed in v2.


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

* Re: [PATCH v1 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement
  2026-05-13  0:43   ` sashiko-bot
@ 2026-05-13  6:34     ` Kathpalia, Tanmay
  0 siblings, 0 replies; 32+ messages in thread
From: Kathpalia, Tanmay @ 2026-05-13  6:34 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: robh, krzk+dt, devicetree, conor+dt


On 5/13/2026 6:13 AM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] Board-specific regulators are incorrectly defined in the generic SoC .dtsi file and placed inside a simple-bus.
> - [Low] The Cadence SD6HC node includes an undocumented and unsupported `fifo-depth` property.
> --
>
> commit cd8114ca6cbb7c0bc2b1d26651ea445b3eef24ec
> Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>
> arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement
>
> This commit adds an SD/MMC controller node backed by the Cadence SD6HC
> to the Agilex5 SoC device tree. It also enables the controller on the
> SOCDK board with high-speed and UHS-I modes.
>
>> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> [ ... ]
>> @@ -300,6 +300,44 @@ portb: gpio-controller@0 {
>>   			};
>>   		};
>>   
>> +		sd_emmc_power: regulator-fixed-3p3v {
>> +			compatible = "regulator-fixed";
>> +			regulator-name = "card-power";
>> +			regulator-min-microvolt = <3300000>;
>> +			regulator-max-microvolt = <3300000>;
>> +			regulator-always-on;
>> +		};
>> +
>> +		sd_io_1v8_reg: regulator-1p8v {
>> +			compatible = "regulator-gpio";
>> +			regulator-name = "sd-bus-io-power";
>> +			regulator-min-microvolt = <1800000>;
>> +			regulator-max-microvolt = <3300000>;
>> +			states =	<1800000 0x1>,
>> +					<3300000 0x0>;
>> +		};
> Should these board-specific regulators be defined in the generic SoC
> dtsi file instead of the board dts file?
>
> Placing them here forces these external board-level circuitry
> definitions onto all future boards using this SoC.
>
> Additionally, because they are placed inside a simple-bus without a reg
> property, it looks like they will trigger simple_bus_reg compiler
> warnings. The sd_io_1v8_reg node is also missing the mandatory gpios
> property in the dtsi, which might cause dtbs_check schema validation
> failures for any board that does not explicitly provide one.

The sd_emmc_power and sd_io_1v8_reg regulators are
board-specific and will be moved to the board DTS file in v2. I
will also run dtbs_check schema validation to catch any
remaining issues before sending v2.
>
>> +
>> +		emmc: mmc@10808000 {
>> +			#address-cells = <1>;
>> +			#size-cells = <0>;
>> +			compatible = "altr,agilex5-sd6hc", "cdns,sd6hc";
>> +			reg = <0x10808000 0x1000>;
>> +			interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
>> +			fifo-depth = <0x800>;
> Is the fifo-depth property supported by the Cadence SDHC binding?
>
> The Cadence SDHC binding specifies unevaluatedProperties: false and
> does not appear to define or support fifo-depth, which seems to be a
> leftover from Synopsys DesignWare MMC bindings.
>
> While the driver will ignore it, its presence might cause a dtbs_check
> schema validation failure.

fifo-depth is not supported by the Cadence SD6HC binding. It will
be removed in v2. I will verify with dtbs_check schema
validation before sending.

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

* Re: [PATCH v1 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support
  2026-05-11 20:21 ` [PATCH v1 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support Tanmay Kathpalia
  2026-05-13  1:22   ` sashiko-bot
@ 2026-05-15  8:37   ` Krzysztof Kozlowski
  2026-05-22  6:30     ` Kathpalia, Tanmay
  1 sibling, 1 reply; 32+ messages in thread
From: Krzysztof Kozlowski @ 2026-05-15  8:37 UTC (permalink / raw)
  To: Tanmay Kathpalia
  Cc: linux-mmc, ulf.hansson, Dinh Nguyen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel

On Mon, May 11, 2026 at 01:21:27PM -0700, Tanmay Kathpalia wrote:
> Add device tree support for the Agilex5 SOCDK board variant with
> eMMC daughter card.
> 
> Update the SoC dtsi with a fixed 1.8V regulator for eMMC I/O voltage.
> 
> Add socfpga_agilex5_socdk_emmc.dts with eMMC controller configured for:
> - 8-bit bus width
> - Non-removable eMMC device
> - High-speed, HS200, and HS400 modes at 1.8V
> - 200MHz maximum frequency with SDHCI clock base capability override
> 
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> ---
>  arch/arm64/boot/dts/intel/Makefile            |   1 +
>  .../arm64/boot/dts/intel/socfpga_agilex5.dtsi |   8 ++
>  .../dts/intel/socfpga_agilex5_socdk_emmc.dts  | 111 ++++++++++++++++++
>  3 files changed, 120 insertions(+)
>  create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
> 
> diff --git a/arch/arm64/boot/dts/intel/Makefile b/arch/arm64/boot/dts/intel/Makefile
> index 33fcc55d0cb9..5bbbcfda1f48 100644
> --- a/arch/arm64/boot/dts/intel/Makefile
> +++ b/arch/arm64/boot/dts/intel/Makefile
> @@ -8,5 +8,6 @@ dtb-$(CONFIG_ARCH_INTEL_SOCFPGA) += socfpga_agilex_n6000.dtb \
>  				socfpga_agilex5_socdk_013b.dtb \
>  				socfpga_agilex5_socdk_modular.dtb \
>  				socfpga_agilex5_socdk_nand.dtb \
> +				socfpga_agilex5_socdk_emmc.dtb \
>  				socfpga_n5x_socdk.dtb
>  dtb-$(CONFIG_ARCH_KEEMBAY) += keembay-evm.dtb
> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> index 7e080f13166f..feb4ccb317a7 100644
> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> @@ -317,6 +317,14 @@ sd_io_1v8_reg: regulator-1p8v {
>  					<3300000 0x0>;
>  		};
>  
> +		emmc_io_1v8_reg: regulator-fixed-1p8v {

How is this property of a SoC? How SoC itself has a fixed regulator for
EMMC? Really?

Best regards,
Krzysztof


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

* Re: [PATCH v1 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement
  2026-05-11 20:21 ` [PATCH v1 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement Tanmay Kathpalia
  2026-05-13  0:43   ` sashiko-bot
@ 2026-05-15  8:38   ` Krzysztof Kozlowski
  2026-05-21 17:56     ` Kathpalia, Tanmay
  1 sibling, 1 reply; 32+ messages in thread
From: Krzysztof Kozlowski @ 2026-05-15  8:38 UTC (permalink / raw)
  To: Tanmay Kathpalia
  Cc: linux-mmc, ulf.hansson, Dinh Nguyen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel

On Mon, May 11, 2026 at 01:21:25PM -0700, Tanmay Kathpalia wrote:
> The Agilex5 SoC device tree gains an SD/MMC controller node backed by
> the Cadence SD6HC, with IOMMU integration via the system SMMU. Card
> power is supplied by a fixed 3.3V regulator and I/O voltage switching
> between 1.8V and 3.3V is handled by a GPIO-controlled regulator.
> 
> The SOCDK board enables the controller for SD-only operation in 4-bit
> bus width with high-speed and SDR104 UHS-I modes at 200 MHz maximum
> clock. SDHCI capability overrides clear the SDR50 tuning flag and
> override the clock base mask to report 200 MHz.
> 
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> ---
>  .../arm64/boot/dts/intel/socfpga_agilex5.dtsi | 38 +++++++++++++++++++
>  .../boot/dts/intel/socfpga_agilex5_socdk.dts  | 26 +++++++++++++
>  2 files changed, 64 insertions(+)
> 
> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> index 352c96d144a8..7e080f13166f 100644
> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> @@ -300,6 +300,44 @@ portb: gpio-controller@0 {
>  			};
>  		};
>  
> +		sd_emmc_power: regulator-fixed-3p3v {

NAK, this fails basic rules of organizing DTS/DTSI and the nodes. This
is simple-bus, so how could you have here a regulator which is non MMIO?

Plus, explain me how these regulators managed to appear on the SoC
die/silicon?

Best regards,
Krzysztof


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

* Re: [PATCH v1 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork
  2026-05-11 20:21 ` [PATCH v1 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork Tanmay Kathpalia
@ 2026-05-19 15:29   ` Adrian Hunter
  2026-05-21 15:53     ` Kathpalia, Tanmay
  0 siblings, 1 reply; 32+ messages in thread
From: Adrian Hunter @ 2026-05-19 15:29 UTC (permalink / raw)
  To: Tanmay Kathpalia, linux-mmc; +Cc: ulf.hansson, linux-kernel

On 11/05/2026 23:21, Tanmay Kathpalia wrote:
> PHY-related functions and data structures in the driver are not
> explicitly scoped to the SD4HC (V4) controller, making it unclear
> which code is shared and which is version-specific.
> 
> Rename them with a "cdns4" prefix to distinguish SD4HC-specific
> implementation from the shared driver core, and to avoid naming
> conflicts when SD6HC (V6) support is introduced.
> 
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>

One minor comment below

> ---
>  drivers/mmc/host/sdhci-cadence.c | 74 ++++++++++++++++----------------
>  1 file changed, 37 insertions(+), 37 deletions(-)
> 
> diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
> index 435603c8c00b..47690a52a221 100644
> --- a/drivers/mmc/host/sdhci-cadence.c
> +++ b/drivers/mmc/host/sdhci-cadence.c
> @@ -78,7 +78,7 @@
>   */
>  #define SDHCI_CDNS_MAX_TUNING_LOOP	40
>  
> -struct sdhci_cdns_phy_param {
> +struct sdhci_cdns4_phy_param {
>  	u8 addr;
>  	u8 data;
>  };
> @@ -91,10 +91,10 @@ struct sdhci_cdns_priv {
>  	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
>  	struct reset_control *rst_hw;
>  	unsigned int nr_phy_params;
> -	struct sdhci_cdns_phy_param phy_params[];
> +	struct sdhci_cdns4_phy_param phy_params[];
>  };
>  
> -struct sdhci_cdns_phy_cfg {
> +struct sdhci_cdns4_phy_cfg {
>  	const char *property;
>  	u8 addr;
>  };
> @@ -104,7 +104,7 @@ struct sdhci_cdns_drv_data {
>  	const struct sdhci_pltfm_data pltfm_data;
>  };
>  
> -static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = {
> +static const struct sdhci_cdns4_phy_cfg sdhci_cdns4_phy_cfgs[] = {
>  	{ "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, },
>  	{ "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, },
>  	{ "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, },
> @@ -124,8 +124,8 @@ static inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val,
>  	writel(val, reg);
>  }
>  
> -static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
> -				    u8 addr, u8 data)
> +static int sdhci_cdns4_write_phy_reg(struct sdhci_cdns_priv *priv,
> +				     u8 addr, u8 data)
>  {
>  	void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04;
>  	u32 tmp;
> @@ -156,44 +156,44 @@ static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
>  	return ret;
>  }
>  
> -static unsigned int sdhci_cdns_phy_param_count(struct device_node *np)
> +static unsigned int sdhci_cdns4_phy_param_count(struct device_node *np)
>  {
>  	unsigned int count = 0;
>  	int i;
>  
> -	for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++)
> -		if (of_property_present(np, sdhci_cdns_phy_cfgs[i].property))
> +	for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++)
> +		if (of_property_present(np, sdhci_cdns4_phy_cfgs[i].property))
>  			count++;
>  
>  	return count;
>  }
>  
> -static void sdhci_cdns_phy_param_parse(struct device_node *np,
> -				       struct sdhci_cdns_priv *priv)
> +static void sdhci_cdns4_phy_param_parse(struct device_node *np,
> +					struct sdhci_cdns_priv *priv)
>  {
> -	struct sdhci_cdns_phy_param *p = priv->phy_params;
> +	struct sdhci_cdns4_phy_param *p = priv->phy_params;
>  	u32 val;
>  	int ret, i;
>  
> -	for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) {
> -		ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property,
> +	for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++) {
> +		ret = of_property_read_u32(np, sdhci_cdns4_phy_cfgs[i].property,
>  					   &val);
>  		if (ret)
>  			continue;
>  
> -		p->addr = sdhci_cdns_phy_cfgs[i].addr;
> +		p->addr = sdhci_cdns4_phy_cfgs[i].addr;
>  		p->data = val;
>  		p++;
>  	}
>  }
>  
> -static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
> +static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
>  {
>  	int ret, i;
>  
>  	for (i = 0; i < priv->nr_phy_params; i++) {
> -		ret = sdhci_cdns_write_phy_reg(priv, priv->phy_params[i].addr,
> -					       priv->phy_params[i].data);
> +		ret = sdhci_cdns4_write_phy_reg(priv, priv->phy_params[i].addr,
> +						priv->phy_params[i].data);
>  		if (ret)
>  			return ret;
>  	}
> @@ -201,7 +201,7 @@ static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
>  	return 0;
>  }
>  
> -static void *sdhci_cdns_priv(struct sdhci_host *host)
> +static void *sdhci_cdns_get_priv(struct sdhci_host *host)

Why rename this?  It seems to just create churn.

>  {
>  	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>  
> @@ -238,7 +238,7 @@ static u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv)
>  
>  static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
>  {
> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>  	void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06;
>  	u32 tmp;
>  	int i, ret;
> @@ -354,7 +354,7 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
>  static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
>  					 unsigned int timing)
>  {
> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>  	u32 mode;
>  
>  	switch (timing) {
> @@ -407,12 +407,12 @@ static void elba_priv_writel(struct sdhci_cdns_priv *priv, u32 val,
>  
>  static void elba_write_l(struct sdhci_host *host, u32 val, int reg)
>  {
> -	elba_priv_writel(sdhci_cdns_priv(host), val, host->ioaddr + reg);
> +	elba_priv_writel(sdhci_cdns_get_priv(host), val, host->ioaddr + reg);
>  }
>  
>  static void elba_write_w(struct sdhci_host *host, u16 val, int reg)
>  {
> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>  	u32 shift = reg & GENMASK(1, 0);
>  	unsigned long flags;
>  	u32 byte_enables;
> @@ -426,7 +426,7 @@ static void elba_write_w(struct sdhci_host *host, u16 val, int reg)
>  
>  static void elba_write_b(struct sdhci_host *host, u8 val, int reg)
>  {
> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>  	u32 shift = reg & GENMASK(1, 0);
>  	unsigned long flags;
>  	u32 byte_enables;
> @@ -452,7 +452,7 @@ static const struct sdhci_ops sdhci_elba_ops = {
>  static int elba_drv_init(struct platform_device *pdev)
>  {
>  	struct sdhci_host *host = platform_get_drvdata(pdev);
> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>  	void __iomem *ioaddr;
>  
>  	host->mmc->caps |= MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA;
> @@ -470,7 +470,7 @@ static int elba_drv_init(struct platform_device *pdev)
>  	return 0;
>  }
>  
> -static const struct sdhci_ops sdhci_cdns_ops = {
> +static const struct sdhci_ops sdhci_cdns4_ops = {
>  	.set_clock = sdhci_set_clock,
>  	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
>  	.set_bus_width = sdhci_set_bus_width,
> @@ -481,7 +481,7 @@ static const struct sdhci_ops sdhci_cdns_ops = {
>  
>  static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
>  	.pltfm_data = {
> -		.ops = &sdhci_cdns_ops,
> +		.ops = &sdhci_cdns4_ops,
>  		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
>  	},
>  };
> @@ -495,14 +495,14 @@ static const struct sdhci_cdns_drv_data sdhci_elba_drv_data = {
>  
>  static const struct sdhci_cdns_drv_data sdhci_eyeq_drv_data = {
>  	.pltfm_data = {
> -		.ops = &sdhci_cdns_ops,
> +		.ops = &sdhci_cdns4_ops,
>  		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
>  	},
>  };
>  
> -static const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = {
> +static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
>  	.pltfm_data = {
> -		.ops = &sdhci_cdns_ops,
> +		.ops = &sdhci_cdns4_ops,
>  	},
>  };
>  
> @@ -510,7 +510,7 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
>  					     struct mmc_ios *ios)
>  {
>  	struct sdhci_host *host = mmc_priv(mmc);
> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>  	u32 mode;
>  
>  	priv->enhanced_strobe = ios->enhanced_strobe;
> @@ -529,7 +529,7 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
>  static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc)
>  {
>  	struct sdhci_host *host = mmc_priv(mmc);
> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>  
>  	dev_dbg(mmc_dev(host->mmc), "emmc hardware reset\n");
>  
> @@ -560,9 +560,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>  
>  	data = of_device_get_match_data(dev);
>  	if (!data)
> -		data = &sdhci_cdns_drv_data;
> +		data = &sdhci_cdns4_drv_data;
>  
> -	nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node);
> +	nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
>  	host = sdhci_pltfm_init(pdev, &data->pltfm_data,
>  				struct_size(priv, phy_params, nr_phy_params));
>  	if (IS_ERR(host))
> @@ -593,9 +593,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>  	if (ret)
>  		return ret;
>  
> -	sdhci_cdns_phy_param_parse(dev->of_node, priv);
> +	sdhci_cdns4_phy_param_parse(dev->of_node, priv);
>  
> -	ret = sdhci_cdns_phy_init(priv);
> +	ret = sdhci_cdns4_phy_init(priv);
>  	if (ret)
>  		return ret;
>  
> @@ -622,7 +622,7 @@ static int sdhci_cdns_resume(struct device *dev)
>  	if (ret)
>  		return ret;
>  
> -	ret = sdhci_cdns_phy_init(priv);
> +	ret = sdhci_cdns4_phy_init(priv);
>  	if (ret)
>  		goto disable_clk;
>  


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

* Re: [PATCH v1 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support
  2026-05-11 20:21 ` [PATCH v1 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support Tanmay Kathpalia
@ 2026-05-19 15:29   ` Adrian Hunter
  2026-05-21 17:41     ` Kathpalia, Tanmay
  0 siblings, 1 reply; 32+ messages in thread
From: Adrian Hunter @ 2026-05-19 15:29 UTC (permalink / raw)
  To: Tanmay Kathpalia, linux-mmc; +Cc: ulf.hansson, linux-kernel

On 11/05/2026 23:21, Tanmay Kathpalia wrote:
> Refactor the sdhci-cadence driver in preparation for adding SD6HC
> (V6 controller) support. Separate PHY parameter handling into a
> dedicated sdhci_cdns4_phy structure and move PHY initialization
> logic into a dedicated sdhci_cdns4_phy_probe() function. This
> allows different controller versions to manage their PHY
> configurations independently while keeping shared logic in the
> main driver.
> 
> Each compatible entry now carries its own driver data, so drop the
> silent fallback to sdhci_cdns4_drv_data and return an error if
> platform data is missing.
> 
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>

A few minor style comments below

> ---
>  drivers/mmc/host/sdhci-cadence.c | 57 ++++++++++++++++++++++----------
>  1 file changed, 40 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
> index 47690a52a221..fe3f7c5109fc 100644
> --- a/drivers/mmc/host/sdhci-cadence.c
> +++ b/drivers/mmc/host/sdhci-cadence.c
> @@ -83,6 +83,11 @@ struct sdhci_cdns4_phy_param {
>  	u8 data;
>  };
>  
> +struct sdhci_cdns4_phy {
> +	unsigned int nr_phy_params;
> +	struct sdhci_cdns4_phy_param phy_params[];
> +};
> +
>  struct sdhci_cdns_priv {
>  	void __iomem *hrs_addr;
>  	void __iomem *ctl_addr;	/* write control */
> @@ -90,8 +95,7 @@ struct sdhci_cdns_priv {
>  	bool enhanced_strobe;
>  	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
>  	struct reset_control *rst_hw;
> -	unsigned int nr_phy_params;
> -	struct sdhci_cdns4_phy_param phy_params[];
> +	struct sdhci_cdns4_phy *phy;
>  };
>  
>  struct sdhci_cdns4_phy_cfg {
> @@ -169,9 +173,9 @@ static unsigned int sdhci_cdns4_phy_param_count(struct device_node *np)
>  }
>  
>  static void sdhci_cdns4_phy_param_parse(struct device_node *np,
> -					struct sdhci_cdns_priv *priv)
> +					struct sdhci_cdns4_phy *phy)
>  {
> -	struct sdhci_cdns4_phy_param *p = priv->phy_params;
> +	struct sdhci_cdns4_phy_param *p = phy->phy_params;
>  	u32 val;
>  	int ret, i;
>  
> @@ -190,10 +194,11 @@ static void sdhci_cdns4_phy_param_parse(struct device_node *np,
>  static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
>  {
>  	int ret, i;
> +	struct sdhci_cdns4_phy *phy = priv->phy;

Nicer to place locals in reverse order of line length e.g.

+	struct sdhci_cdns4_phy *phy = priv->phy;
	int ret, i;

>  
> -	for (i = 0; i < priv->nr_phy_params; i++) {
> -		ret = sdhci_cdns4_write_phy_reg(priv, priv->phy_params[i].addr,
> -						priv->phy_params[i].data);
> +	for (i = 0; i < phy->nr_phy_params; i++) {
> +		ret = sdhci_cdns4_write_phy_reg(priv, phy->phy_params[i].addr,
> +						phy->phy_params[i].data);
>  		if (ret)
>  			return ret;
>  	}
> @@ -542,6 +547,26 @@ static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc)
>  	usleep_range(300, 1000);
>  }
>  
> +static int sdhci_cdns4_phy_probe(struct platform_device *pdev,
> +				 struct sdhci_cdns_priv *priv)

Please wrap after 100 columns not 80

> +{
> +	unsigned int nr_phy_params;
> +	struct sdhci_cdns4_phy *phy;
> +	struct device *dev = &pdev->dev;

Nicer to place locals in reverse order of line length e.g.

+	struct device *dev = &pdev->dev;
+	struct sdhci_cdns4_phy *phy;
+	unsigned int nr_phy_params;

> +
> +	nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
> +	phy = devm_kzalloc(dev, struct_size(phy, phy_params, nr_phy_params),
> +			   GFP_KERNEL);

Please wrap after 100 columns not 80

> +	if (!phy)
> +		return -ENOMEM;
> +
> +	phy->nr_phy_params = nr_phy_params;
> +	sdhci_cdns4_phy_param_parse(dev->of_node, phy);
> +	priv->phy = phy;
> +
> +	return sdhci_cdns4_phy_init(priv);
> +}
> +
>  static int sdhci_cdns_probe(struct platform_device *pdev)
>  {
>  	struct sdhci_host *host;
> @@ -549,7 +574,6 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>  	struct sdhci_pltfm_host *pltfm_host;
>  	struct sdhci_cdns_priv *priv;
>  	struct clk *clk;
> -	unsigned int nr_phy_params;
>  	int ret;
>  	struct device *dev = &pdev->dev;
>  	static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT;
> @@ -560,11 +584,10 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>  
>  	data = of_device_get_match_data(dev);
>  	if (!data)
> -		data = &sdhci_cdns4_drv_data;
> +		return dev_err_probe(dev, -EINVAL,
> +				     "missing platform driver data\n");

Please wrap after 100 columns not 80

>  
> -	nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
> -	host = sdhci_pltfm_init(pdev, &data->pltfm_data,
> -				struct_size(priv, phy_params, nr_phy_params));
> +	host = sdhci_pltfm_init(pdev, &data->pltfm_data, sizeof(*priv));
>  	if (IS_ERR(host))
>  		return PTR_ERR(host);
>  
> @@ -572,7 +595,6 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>  	pltfm_host->clk = clk;
>  
>  	priv = sdhci_pltfm_priv(pltfm_host);
> -	priv->nr_phy_params = nr_phy_params;
>  	priv->hrs_addr = host->ioaddr;
>  	priv->enhanced_strobe = false;
>  	priv->priv_writel = cdns_writel;
> @@ -593,9 +615,7 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>  	if (ret)
>  		return ret;
>  
> -	sdhci_cdns4_phy_param_parse(dev->of_node, priv);
> -
> -	ret = sdhci_cdns4_phy_init(priv);
> +	ret = sdhci_cdns4_phy_probe(pdev, priv);
>  	if (ret)
>  		return ret;
>  
> @@ -653,7 +673,10 @@ static const struct of_device_id sdhci_cdns_match[] = {
>  		.compatible = "mobileye,eyeq-sd4hc",
>  		.data = &sdhci_eyeq_drv_data,
>  	},
> -	{ .compatible = "cdns,sd4hc" },
> +	{
> +		.compatible = "cdns,sd4hc",
> +		.data = &sdhci_cdns4_drv_data,
> +	},
>  	{ /* sentinel */ }
>  };
>  MODULE_DEVICE_TABLE(of, sdhci_cdns_match);


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

* Re: [PATCH v1 8/9] mmc: sdhci-cadence: add Cadence SD6HC support
  2026-05-11 20:21 ` [PATCH v1 8/9] mmc: sdhci-cadence: add Cadence SD6HC support Tanmay Kathpalia
@ 2026-05-19 15:29   ` Adrian Hunter
  2026-05-21 17:28     ` Kathpalia, Tanmay
  0 siblings, 1 reply; 32+ messages in thread
From: Adrian Hunter @ 2026-05-19 15:29 UTC (permalink / raw)
  To: Tanmay Kathpalia, linux-mmc; +Cc: ulf.hansson, Philipp Zabel, linux-kernel

On 11/05/2026 23:21, Tanmay Kathpalia wrote:
> The Cadence SD6HC is the sixth-generation SD Host Controller used in
> Agilex5 SoCs. Its PHY differs substantially from the SD4HC: it
> requires per-speed-mode IO cell timing parameters and a DLL-based
> delay line to achieve correct signal margins across all speed grades
> from Default Speed to HS400.
> 
> Support is implemented in a new sdhci-cadence6.c alongside the
> existing v4 code, now in sdhci-cadence4.c. Shared structures and the
> v6 function interface are declared in a new sdhci-cadence.h header.
> The common driver paths select between v4 and v6 PHY operations based
> on the SDHCI specification version reported by the controller.
> 
> The new compatible string "cdns,sd6hc" identifies SD6HC hardware in
> device tree.
> 
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> ---
>  MAINTAINERS                                   |    7 +
>  drivers/mmc/host/Makefile                     |    3 +-
>  drivers/mmc/host/sdhci-cadence.h              |  113 ++
>  .../{sdhci-cadence.c => sdhci-cadence4.c}     |   92 +-
>  drivers/mmc/host/sdhci-cadence6.c             | 1051 +++++++++++++++++
>  5 files changed, 1228 insertions(+), 38 deletions(-)
>  create mode 100644 drivers/mmc/host/sdhci-cadence.h
>  rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence4.c} (91%)
>  create mode 100644 drivers/mmc/host/sdhci-cadence6.c

The new file names seem slightly misleading since sdhci-cadence4.c
is the main driver as well as supporting V4, whereas sdhci-cadence6.c
is most of V6 support.

There doesn't seem much to gain from having a separate sdhci-cadence6.c.
Isn't it simpler to just put it all in 1 source file.

> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 27fefd92744c..4856450bfd36 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23863,6 +23863,13 @@ L:	linux-mmc@vger.kernel.org
>  S:	Maintained
>  F:	drivers/mmc/host/sdhci-brcmstb*
>  
> +SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) CADENCE DRIVER
> +M:	Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> +L:	linux-mmc@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
> +F:	drivers/mmc/host/sdhci-cadence*
> +
>  SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) DRIVER
>  M:	Adrian Hunter <adrian.hunter@intel.com>
>  L:	linux-mmc@vger.kernel.org
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index ee412e6b84d6..f3b2f43e751e 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -80,7 +80,8 @@ obj-$(CONFIG_MMC_REALTEK_USB)	+= rtsx_usb_sdmmc.o
>  
>  obj-$(CONFIG_MMC_SDHCI_PLTFM)		+= sdhci-pltfm.o
>  obj-$(CONFIG_MMC_SDHCI_CADENCE)		+= sdhci-cadence.o
> -obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF)       += sdhci-esdhc-mcf.o
> +sdhci-cadence-y				+= sdhci-cadence4.o sdhci-cadence6.o
> +obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF)	+= sdhci-esdhc-mcf.o
>  obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX)	+= sdhci-esdhc-imx.o
>  obj-$(CONFIG_MMC_SDHCI_DOVE)		+= sdhci-dove.o
>  obj-$(CONFIG_MMC_SDHCI_TEGRA)		+= sdhci-tegra.o
> diff --git a/drivers/mmc/host/sdhci-cadence.h b/drivers/mmc/host/sdhci-cadence.h
> new file mode 100644
> index 000000000000..05b8d8f16543
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-cadence.h
> @@ -0,0 +1,113 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2026 Altera Corporation
> + *   Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> + *
> + * Cadence SD/SDIO/eMMC Host Controller driver - common header
> + * Shared definitions and structures for the Cadence SDHCI driver.
> + * Contains private data and declarations for SD6HC-specific functions
> + * called by the main driver in sdhci-cadence4.c.
> + */
> +
> +#ifndef _MMC_HOST_SDHCI_CADENCE_H
> +#define _MMC_HOST_SDHCI_CADENCE_H
> +
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +#include <linux/clk.h>
> +#include <linux/iopoll.h>
> +#include <linux/mmc/host.h>
> +
> +#include "sdhci-pltfm.h"
> +
> +/* HRS - Host Register Set (specific to Cadence) */
> +#define SDHCI_CDNS_HRS04		0x10		/* PHY access: address port */
> +#define SDHCI_CDNS_HRS05		0x14		/* PHY access: data port */
> +
> +/*
> + * The tuned val register is 6 bit-wide, but not the whole of the range is
> + * available.  The range 0-42 seems to be available (then 43 wraps around to 0)
> + * but I am not quite sure if it is official.  Use only 0 to 39 for safety.
> + */
> +#define SDHCI_CDNS_MAX_TUNING_LOOP	40
> +
> +/**
> + * struct sdhci_cdns_priv - Cadence SDHCI private controller data
> + * @hrs_addr: Base address of Cadence Host Register Set (HRS) registers.
> + * @ctl_addr: Base address for write control registers.
> + *            Used only for "amd,pensando-elba-sd4hc" compatible controllers
> + *            to enable byte-lane writes.
> + * @wrlock: Spinlock for protecting register writes (Elba only).
> + * @enhanced_strobe: Flag indicating if Enhanced Strobe (HS400ES) is enabled.
> + * @priv_writel: Optional SoC-specific write function for register access.
> + *               Used for Elba to ensure correct byte-lane enable.
> + * @rst_hw: Hardware reset control for the controller.
> + * @phy: Opaque pointer to variant-specific PHY data.
> + *       For SD4HC: points to struct sdhci_cdns4_phy.
> + *       For SD6HC: points to struct sdhci_cdns6_phy.
> + */
> +struct sdhci_cdns_priv {
> +	void __iomem *hrs_addr;
> +	void __iomem *ctl_addr; /* write control */
> +	spinlock_t wrlock; /* write lock */
> +	bool enhanced_strobe;
> +	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val,
> +			    void __iomem *reg);
> +	struct reset_control *rst_hw;
> +	void *phy;
> +};
> +
> +/*
> + * sdhci_cdns_get_priv - Helper to retrieve Cadence private data from sdhci_host
> + * @host: Pointer to struct sdhci_host.
> + *
> + * Return: Pointer to struct sdhci_cdns_priv.
> + */
> +static inline void *sdhci_cdns_get_priv(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +
> +	return sdhci_pltfm_priv(pltfm_host);
> +}
> +
> +/**
> + * sdhci_cdns6_set_uhs_signaling - Program PHY registers for a specific timing mode.
> + * @host: Pointer to struct sdhci_host.
> + * @timing: MMC timing mode (MMC_TIMING_*).
> + */
> +void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing);
> +
> +/**
> + * sdhci_cdns6_set_tune_val - Set the PHY tuning value.
> + * @host: Pointer to struct sdhci_host.
> + * @val: Tuning value to program.
> + *
> + * Return: 0 on success, -ETIMEDOUT if PHY initialization times out.
> + */
> +int sdhci_cdns6_set_tune_val(struct sdhci_host *host, unsigned int val);
> +
> +/**
> + * sdhci_cdns6_phy_probe - Probe and initialize Cadence SD6HC PHY parameters
> + * @pdev: Platform device pointer
> + * @priv: Pointer to Cadence private data structure
> + *
> + * Return: 0 on success or a negative error code.
> + */
> +int sdhci_cdns6_phy_probe(struct platform_device *pdev,
> +			  struct sdhci_cdns_priv *priv);
> +/**
> + * sdhci_cdns6_hw_reset - Perform hardware reset of the Cadence SDHCI controller.
> + * @host: Pointer to struct sdhci_host.
> + */
> +void sdhci_cdns6_hw_reset(struct sdhci_host *host);
> +
> +/**
> + * sdhci_cdns6_phy_init - Initialize the SD6HC PHY with current settings.
> + * @priv: Pointer to Cadence private data structure.
> + *
> + * Return: 0 on success, -ETIMEDOUT if PHY initialization times out.
> + */
> +int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv);
> +
> +#endif /* _MMC_HOST_SDHCI_CADENCE_H */
> diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence4.c
> similarity index 91%
> rename from drivers/mmc/host/sdhci-cadence.c
> rename to drivers/mmc/host/sdhci-cadence4.c
> index fe3f7c5109fc..283d22328248 100644
> --- a/drivers/mmc/host/sdhci-cadence.c
> +++ b/drivers/mmc/host/sdhci-cadence4.c
> @@ -2,22 +2,17 @@
>  /*
>   * Copyright (C) 2016 Socionext Inc.
>   *   Author: Masahiro Yamada <yamada.masahiro@socionext.com>
> + * Copyright (C) 2026 Altera Corporation
>   */
>  
>  #include <linux/bitfield.h>
>  #include <linux/bits.h>
> -#include <linux/iopoll.h>
>  #include <linux/module.h>
> -#include <linux/mmc/host.h>
> -#include <linux/mmc/mmc.h>
> -#include <linux/of.h>
> -#include <linux/platform_device.h>
> -#include <linux/reset.h>
>  
> -#include "sdhci-pltfm.h"
> +#include "sdhci-cadence.h"
>  
>  /* HRS - Host Register Set (specific to Cadence) */
> -#define SDHCI_CDNS_HRS04		0x10		/* PHY access port */
> +/* HRS04 (PHY access) bitfields (SD4HC) */
>  #define   SDHCI_CDNS_HRS04_ACK			BIT(26)
>  #define   SDHCI_CDNS_HRS04_RD			BIT(25)
>  #define   SDHCI_CDNS_HRS04_WR			BIT(24)
> @@ -71,13 +66,6 @@
>  #define SDHCI_CDNS_PHY_DLY_HSMMC	0x0c
>  #define SDHCI_CDNS_PHY_DLY_STROBE	0x0d
>  
> -/*
> - * The tuned val register is 6 bit-wide, but not the whole of the range is
> - * available.  The range 0-42 seems to be available (then 43 wraps around to 0)
> - * but I am not quite sure if it is official.  Use only 0 to 39 for safety.
> - */
> -#define SDHCI_CDNS_MAX_TUNING_LOOP	40
> -
>  struct sdhci_cdns4_phy_param {
>  	u8 addr;
>  	u8 data;
> @@ -88,16 +76,6 @@ struct sdhci_cdns4_phy {
>  	struct sdhci_cdns4_phy_param phy_params[];
>  };
>  
> -struct sdhci_cdns_priv {
> -	void __iomem *hrs_addr;
> -	void __iomem *ctl_addr;	/* write control */
> -	spinlock_t wrlock;	/* write lock */
> -	bool enhanced_strobe;
> -	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
> -	struct reset_control *rst_hw;
> -	struct sdhci_cdns4_phy *phy;
> -};
> -
>  struct sdhci_cdns4_phy_cfg {
>  	const char *property;
>  	u8 addr;
> @@ -206,13 +184,6 @@ static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
>  	return 0;
>  }
>  
> -static void *sdhci_cdns_get_priv(struct sdhci_host *host)
> -{
> -	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> -
> -	return sdhci_pltfm_priv(pltfm_host);
> -}
> -
>  static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
>  {
>  	/*
> @@ -248,6 +219,9 @@ static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
>  	u32 tmp;
>  	int i, ret;
>  
> +	if (host->version >= SDHCI_SPEC_420)
> +		return sdhci_cdns6_set_tune_val(host, val);
> +
>  	if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val)))
>  		return -EINVAL;
>  
> @@ -328,8 +302,11 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
>  	 * The delay is set by probe, based on the DT properties.
>  	 */
>  	if (host->timing != MMC_TIMING_MMC_HS200 &&
> -	    host->timing != MMC_TIMING_UHS_SDR104)
> +	    host->timing != MMC_TIMING_UHS_SDR104) {
> +		dev_dbg(mmc_dev(host->mmc), "Tuning skipped (timing: %d)\n",
> +			host->timing);
>  		return 0;
> +	}
>  
>  	for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) {
>  		if (sdhci_cdns_set_tune_val(host, i) ||
> @@ -353,6 +330,10 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
>  	if (ret)
>  		return ret;
>  
> +	/* Block gap tuning is only required for SD4HC, not for SD6HC */
> +	if (host->version >= SDHCI_SPEC_420)
> +		return 0;
> +
>  	return sdhci_cdns_tune_blkgap(host->mmc);
>  }
>  
> @@ -388,6 +369,10 @@ static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
>  	/* For SD, fall back to the default handler */
>  	if (mode == SDHCI_CDNS_HRS06_MODE_SD)
>  		sdhci_set_uhs_signaling(host, timing);
> +
> +	/* For host controller V6, set SDHCI and PHY registers for UHS signaling */
> +	if (host->version >= SDHCI_SPEC_420)
> +		sdhci_cdns6_set_uhs_signaling(host, timing);
>  }
>  
>  /* Elba control register bits [6:3] are byte-lane enables */
> @@ -484,6 +469,16 @@ static const struct sdhci_ops sdhci_cdns4_ops = {
>  	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
>  };
>  
> +static const struct sdhci_ops sdhci_cdns6_ops = {
> +	.set_clock = sdhci_set_clock,
> +	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
> +	.set_bus_width = sdhci_set_bus_width,
> +	.reset = sdhci_reset,
> +	.platform_execute_tuning = sdhci_cdns_execute_tuning,
> +	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
> +	.hw_reset = sdhci_cdns6_hw_reset,
> +};
> +
>  static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
>  	.pltfm_data = {
>  		.ops = &sdhci_cdns4_ops,
> @@ -511,6 +506,12 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
>  	},
>  };
>  
> +static const struct sdhci_cdns_drv_data sdhci_cdns6_drv_data = {
> +	.pltfm_data = {
> +		.ops = &sdhci_cdns6_ops,
> +	},
> +};
> +
>  static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
>  					     struct mmc_ios *ios)
>  {
> @@ -607,15 +608,24 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>  			return ret;
>  	}
>  	sdhci_enable_v4_mode(host);
> -	__sdhci_read_caps(host, &version, NULL, NULL);
> -
>  	sdhci_get_of_property(pdev);
>  
>  	ret = mmc_of_parse(host->mmc);
>  	if (ret)
>  		return ret;
>  
> -	ret = sdhci_cdns4_phy_probe(pdev, priv);
> +	/*
> +	 * For SD4HC, read capabilities with fixed version override.
> +	 * For SD6HC, sdhci_add_host() will automatically read capabilities
> +	 * and version from the host controller registers.
> +	 */
> +	if (of_device_is_compatible(dev->of_node, "cdns,sd4hc")) {
> +		__sdhci_read_caps(host, &version, NULL, NULL);
> +		ret = sdhci_cdns4_phy_probe(pdev, priv);
> +	} else {
> +		ret = sdhci_cdns6_phy_probe(pdev, priv);
> +	}
> +
>  	if (ret)
>  		return ret;
>  
> @@ -642,7 +652,11 @@ static int sdhci_cdns_resume(struct device *dev)
>  	if (ret)
>  		return ret;
>  
> -	ret = sdhci_cdns4_phy_init(priv);
> +	if (host->version >= SDHCI_SPEC_420)
> +		ret = sdhci_cdns6_phy_init(priv);
> +	else
> +		ret = sdhci_cdns4_phy_init(priv);
> +
>  	if (ret)
>  		goto disable_clk;
>  
> @@ -677,6 +691,10 @@ static const struct of_device_id sdhci_cdns_match[] = {
>  		.compatible = "cdns,sd4hc",
>  		.data = &sdhci_cdns4_drv_data,
>  	},
> +	{
> +		.compatible = "cdns,sd6hc",
> +		.data = &sdhci_cdns6_drv_data,
> +	},
>  	{ /* sentinel */ }
>  };
>  MODULE_DEVICE_TABLE(of, sdhci_cdns_match);
> diff --git a/drivers/mmc/host/sdhci-cadence6.c b/drivers/mmc/host/sdhci-cadence6.c
> new file mode 100644
> index 000000000000..34d3570a0bbb
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-cadence6.c
> @@ -0,0 +1,1051 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * PHY and host controller support for Cadence SD6HC SDHCI
> + *
> + * This file provides comprehensive support for Cadence's sixth-generation
> + * SDHCI controller (SD6HC), handling both low-level PHY operations and
> + * high-level host controller programming. The implementation includes PHY
> + * initialization and timing calculations, DLL (Delay-Locked Loop)
> + * management and configuration, host controller register programming for
> + * IO delay compensation and signal timing optimization.
> + *
> + * Copyright (C) 2026 Altera Corporation
> + *   Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> + */
> +
> +#include "sdhci-cadence.h"
> +
> +/* IO Delay Information */
> +#define SDHCI_CDNS_HRS07		0x1c
> +#define   SDHCI_CDNS_HRS07_RW_COMPENSATE	GENMASK(20, 16)
> +#define   SDHCI_CDNS_HRS07_IDELAY_VAL		GENMASK(4, 0)
> +
> +/* PHY Control and Status */
> +#define SDHCI_CDNS_HRS09		0x24
> +#define   SDHCI_CDNS_HRS09_RDDATA_EN		BIT(16)
> +#define   SDHCI_CDNS_HRS09_RDCMD_EN		BIT(15)
> +#define   SDHCI_CDNS_HRS09_EXTENDED_WR_MODE	BIT(3)
> +#define   SDHCI_CDNS_HRS09_EXTENDED_RD_MODE	BIT(2)
> +#define   SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE	BIT(1)
> +#define   SDHCI_CDNS_HRS09_PHY_SW_RESET		BIT(0)
> +
> +/* SDCLK start point adjustment */
> +#define SDHCI_CDNS_HRS10		0x28
> +#define   SDHCI_CDNS_HRS10_HCSDCLKADJ		GENMASK(19, 16)
> +
> +/* eMMC Control */
> +#define SDHCI_CDNS_HRS11		0x2c
> +#define   SDHCI_CDNS_HRS11_EMMC_RST		BIT(0) /* eMMC reset */
> +
> +/* CMD/DAT output delay */
> +#define SDHCI_CDNS_HRS16		0x40
> +#define   SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY	GENMASK(31, 28)
> +#define   SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY	GENMASK(27, 24)
> +#define   SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY	GENMASK(23, 20)
> +#define   SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY	GENMASK(19, 16)
> +#define   SDHCI_CDNS_HRS16_WRDATA1_DLY		GENMASK(15, 12)
> +#define   SDHCI_CDNS_HRS16_WRDATA0_DLY		GENMASK(11, 8)
> +#define   SDHCI_CDNS_HRS16_WRCMD1_DLY		GENMASK(7, 4)
> +#define   SDHCI_CDNS_HRS16_WRCMD0_DLY		GENMASK(3, 0)
> +
> +/* PHY Special Function Registers */
> +/* DQ timing */
> +#define SDHCI_CDNS6_PHY_DQ_TIMING_REG			0x2000
> +#define   SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON		BIT(31)
> +#define   SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END			GENMASK(29, 27)
> +#define   SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START		GENMASK(26, 24)
> +#define   SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END		GENMASK(2, 0)
> +
> +/* DQS timing */
> +#define SDHCI_CDNS6_PHY_DQS_TIMING_REG			0x2004
> +#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS		BIT(22)
> +#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS		BIT(21)
> +#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS		BIT(20)
> +#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD		BIT(19)
> +
> +/* Gate and loopback control */
> +#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG		0x2008
> +#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD		BIT(31)
> +#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SW_HALF_CYCLE_SHIFT	BIT(28)
> +#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL		GENMASK(24, 19)
> +#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS	BIT(18)
> +#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON	BIT(6)
> +
> +/* Master DLL logic */
> +#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_REG		0x200c
> +#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_BYPASS_MODE		BIT(23)
> +#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_PHASE_DETECT_SEL	GENMASK(22, 20)
> +#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM		GENMASK(18, 16)
> +#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT	GENMASK(7, 0)
> +
> +/* Slave DLL logic */
> +#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG		0x2010
> +#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY	GENMASK(31, 24)
> +#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WRDQS_DELAY	GENMASK(23, 16)
> +#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WR_DELAY		GENMASK(15, 8)
> +#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY		GENMASK(7, 0)
> +
> +/* Global control settings */
> +#define SDHCI_CDNS6_PHY_CTRL_REG			0x2080
> +#define   SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING			GENMASK(9, 4)
> +
> +/* Default PHY settings */
> +#define SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY	2500
> +#define SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT	24
> +#define SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL	52
> +#define SDHCI_CDNS6_PHY_DEFAULT_DLL_START	4
> +
> +/* Scale tuning tap (0..39) to 8-bit PHY DLL delay field (0..255) */
> +#define SDHCI_CDNS6_PHY_DLL_FIELD_SIZE		256
> +
> +struct sdhci_cdns6_phy {
> +	/*
> +	 * Mode-specific timing constraints (in picoseconds)
> +	 * These define valid output/input windows per SD/eMMC spec
> +	 */
> +	u32 t_cmd_output_min;
> +	u32 t_cmd_output_max;
> +	u32 t_dat_output_min;
> +	u32 t_dat_output_max;
> +	u32 t_cmd_input_min;
> +	u32 t_cmd_input_max;
> +	u32 t_dat_input_min;
> +	u32 t_dat_input_max;
> +
> +	/*
> +	 * PHY delay configuration (in picoseconds)
> +	 * Derived from clock period and board-level IO cell delays
> +	 */
> +	u32 phy_sdclk_delay;
> +	u32 phy_cmd_o_delay;
> +	u32 phy_dat_o_delay;
> +	u32 iocell_input_delay;
> +	u32 iocell_output_delay;
> +	u32 delay_element_org;	/* Original delay element from DT */
> +	u32 delay_element;	/* Current delay element (may be doubled) */
> +
> +	/* PHY_DLL_SLAVE_CTRL register fields */
> +	u32 cp_read_dqs_cmd_delay;
> +	u32 cp_read_dqs_delay;
> +	u32 cp_clk_wr_delay;
> +	u32 cp_clk_wrdqs_delay;
> +
> +	/* PHY_DLL_MASTER_CTRL register fields */
> +	u32 cp_dll_bypass_mode;
> +	u32 cp_dll_start_point;
> +
> +	/* PHY_GATE_LPBK_CTRL register fields */
> +	u32 cp_gate_cfg_always_on;
> +	u32 cp_sync_method;
> +	u32 cp_rd_del_sel;
> +	u32 cp_sw_half_cycle_shift;
> +	u32 cp_underrun_suppress;
> +
> +	/* PHY_DQ_TIMING register fields */
> +	u32 cp_io_mask_always_on;
> +	u32 cp_io_mask_end;
> +	u32 cp_io_mask_start;
> +	u32 cp_data_select_oe_end;
> +
> +	/* PHY_DQS_TIMING register fields */
> +	u32 cp_use_ext_lpbk_dqs;
> +	u32 cp_use_lpbk_dqs;
> +	u8 cp_use_phony_dqs;
> +	u8 cp_use_phony_dqs_cmd;
> +
> +	/* HRS09 register fields - PHY control and Status */
> +	u8 sdhc_extended_rd_mode;
> +	u8 sdhc_extended_wr_mode;
> +	u32 sdhc_rdcmd_en;
> +	u32 sdhc_rddata_en;
> +
> +	/* HRS10 register - SDCLK start point adjustment */
> +	u32 sdhc_hcsdclkadj;
> +
> +	/* HRS07 register - IO delay Information */
> +	u32 sdhc_idelay_val;
> +	u32 sdhc_rw_compensate;
> +
> +	/* HRS16 register fields - CMD/DAT output delay control */
> +	u32 sdhc_wrcmd0_dly;
> +	u32 sdhc_wrcmd0_sdclk_dly;
> +	u32 sdhc_wrcmd1_dly;
> +	u32 sdhc_wrcmd1_sdclk_dly;
> +	u32 sdhc_wrdata0_dly;
> +	u32 sdhc_wrdata0_sdclk_dly;
> +	u32 sdhc_wrdata1_dly;
> +	u32 sdhc_wrdata1_sdclk_dly;
> +
> +	/*
> +	 * DLL calculation intermediate values
> +	 * Used during PHY timing calculations
> +	 */
> +	u32 t_sdmclk_calc;	/* Calculated SDMCLK period for DLL */
> +	u32 dll_max_value;	/* Max DLL delay value (127/255/256) */
> +
> +	/* Tuning value for HS200/HS400 modes */
> +	u32 hs200_tune_val;
> +
> +	/* Clock periods (in picoseconds) */
> +	u32 t_sdmclk;		/* Master clock period */
> +	u32 t_sdclk;		/* SD card clock period */
> +
> +	/* Current operating state */
> +	bool strobe_cmd;	/* Enhanced strobe for CMD line */
> +	unsigned int mode;	/* Current MMC_TIMING_* mode */
> +};
> +
> +/**
> + * init_ds() - Initialize PHY timing for Default Speed mode (up to 25 MHz).
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_ds(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 5000;
> +	phy->t_cmd_output_max = t_sdclk - 5000;
> +	phy->t_dat_output_min = 5000;
> +	phy->t_dat_output_max = t_sdclk - 5000;
> +	phy->t_cmd_input_min = t_sdclk / 2 + 14000;
> +	phy->t_cmd_input_max = t_sdclk + t_sdclk / 2;
> +	phy->t_dat_input_min = t_sdclk / 2 + 14000;
> +	phy->t_dat_input_max = t_sdclk + t_sdclk / 2;
> +}
> +
> +/**
> + * init_hs() - Initialize PHY timing for High Speed mode (up to 50 MHz).
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_hs(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 2000;
> +	phy->t_cmd_output_max = t_sdclk - 6000;
> +	phy->t_dat_output_min = 2000;
> +	phy->t_dat_output_max = t_sdclk - 6000;
> +	phy->t_cmd_input_min = 14000;
> +	phy->t_cmd_input_max = t_sdclk + 2500;
> +	phy->t_dat_input_min = 14000;
> +	phy->t_dat_input_max = t_sdclk + 2500;
> +}
> +
> +/**
> + * init_uhs_sdr12() - Initialize PHY timing for UHS SDR12 mode (up to 25 MHz).
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_uhs_sdr12(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 800;
> +	phy->t_cmd_output_max = t_sdclk - 3000;
> +	phy->t_dat_output_min = 800;
> +	phy->t_dat_output_max = t_sdclk - 3000;
> +	phy->t_cmd_input_min = 14000;
> +	phy->t_cmd_input_max = t_sdclk + 1500;
> +	phy->t_dat_input_min = 14000;
> +	phy->t_dat_input_max = t_sdclk + 1500;
> +}
> +
> +/**
> + * init_uhs_sdr25() - Initialize PHY timing for UHS SDR25 mode (up to 50 MHz).
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_uhs_sdr25(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 800;
> +	phy->t_cmd_output_max = t_sdclk - 3000;
> +	phy->t_dat_output_min = 800;
> +	phy->t_dat_output_max = t_sdclk - 3000;
> +	phy->t_cmd_input_min = 14000;
> +	phy->t_cmd_input_max = t_sdclk + 1500;
> +	phy->t_dat_input_min = 14000;
> +	phy->t_dat_input_max = t_sdclk + 1500;
> +}
> +
> +/**
> + * init_uhs_sdr50() - Initialize PHY timing for UHS SDR50 mode (up to 100 MHz).
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_uhs_sdr50(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 800;
> +	phy->t_cmd_output_max = t_sdclk - 3000;
> +	phy->t_dat_output_min = 800;
> +	phy->t_dat_output_max = t_sdclk - 3000;
> +	phy->t_cmd_input_min = 7500;
> +	phy->t_cmd_input_max = t_sdclk + 1500;
> +	phy->t_dat_input_min = 7500;
> +	phy->t_dat_input_max = t_sdclk + 1500;
> +}
> +
> +/**
> + * init_uhs_sdr104() - Initialize PHY timing for UHS SDR104 mode (up to 208 MHz).
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_uhs_sdr104(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 800;
> +	phy->t_cmd_output_max = t_sdclk - 1400;
> +	phy->t_dat_output_min = 800;
> +	phy->t_dat_output_max = t_sdclk - 1400;
> +	phy->t_cmd_input_min = 1000;
> +	phy->t_cmd_input_max = t_sdclk + 1000;
> +	phy->t_dat_input_min = 1000;
> +	phy->t_dat_input_max = t_sdclk + 1000;
> +}
> +
> +/**
> + * init_uhs_ddr50() - Initialize PHY timing for UHS DDR50 mode (up to 50 MHz DDR).
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_uhs_ddr50(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 800;
> +	phy->t_cmd_output_max = t_sdclk - 3000;
> +	phy->t_dat_output_min = 800;
> +	phy->t_dat_output_max = t_sdclk - 3000;
> +	phy->t_cmd_input_min = 13700;
> +	phy->t_cmd_input_max = t_sdclk + 1500;
> +	phy->t_dat_input_min = 7000;
> +	phy->t_dat_input_max = t_sdclk + 1500;
> +}
> +
> +/**
> + * init_emmc_sdr() - Initialize PHY timing for eMMC legacy/SDR mode.
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_emmc_sdr(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 3000;
> +	phy->t_cmd_output_max = t_sdclk - 3000;
> +	phy->t_dat_output_min = 3000;
> +	phy->t_dat_output_max = t_sdclk - 3000;
> +	phy->t_cmd_input_min = 13700;
> +	phy->t_cmd_input_max = t_sdclk + 2500;
> +	phy->t_dat_input_min = 13700;
> +	phy->t_dat_input_max = t_sdclk + 2500;
> +}
> +
> +/**
> + * init_emmc_ddr() - Initialize PHY timing for eMMC DDR52 mode.
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_emmc_ddr(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 3000;
> +	phy->t_cmd_output_max = t_sdclk - 3000;
> +	phy->t_dat_output_min = 2500;
> +	phy->t_dat_output_max = t_sdclk - 2500;
> +	phy->t_cmd_input_min = 13700;
> +	phy->t_cmd_input_max = t_sdclk + 2500;
> +	phy->t_dat_input_min = 7000;
> +	phy->t_dat_input_max = t_sdclk + 1500;
> +}
> +
> +/**
> + * init_emmc_hs200() - Initialize PHY timing for eMMC HS200 mode (up to 200 MHz).
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_emmc_hs200(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 800;
> +	phy->t_cmd_output_max = t_sdclk - 1400;
> +	phy->t_dat_output_min = 800;
> +	phy->t_dat_output_max = t_sdclk - 1400;
> +	phy->t_cmd_input_min = 1000;
> +	phy->t_cmd_input_max = t_sdclk + 1000;
> +	phy->t_dat_input_min = 1000;
> +	phy->t_dat_input_max = t_sdclk + 1000;
> +}
> +
> +/**
> + * init_emmc_hs400() - Initialize PHY timing for eMMC HS400/HS400ES mode.
> + * @phy: Pointer to SD6HC PHY state.
> + * @t_sdclk: SD clock period in picoseconds.
> + */
> +static void init_emmc_hs400(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
> +{
> +	phy->t_cmd_output_min = 800;
> +	phy->t_cmd_output_max = t_sdclk - 1400;
> +	phy->t_dat_output_min = 400;
> +	phy->t_dat_output_max = t_sdclk - 400;
> +	phy->t_cmd_input_min = 1000;
> +	phy->t_cmd_input_max = t_sdclk + 1000;
> +	phy->t_dat_input_min = 1000;
> +	phy->t_dat_input_max = t_sdclk + 1000;
> +}
> +
> +/*
> + * init_timings - PHY timing initializers indexed by MMC_TIMING_* value.
> + *
> + * Each entry corresponds to a MMC_TIMING_* constant and sets the
> + * appropriate cmd/dat output and input timing windows in the PHY
> + * state struct. Must stay in sync with the MMC_TIMING_* definitions
> + * in include/linux/mmc/host.h.
> + */
> +static void (*init_timings[])(struct sdhci_cdns6_phy *, u32) = {
> +	&init_ds,
> +	&init_emmc_sdr,
> +	&init_hs,
> +	&init_uhs_sdr12,
> +	&init_uhs_sdr25,
> +	&init_uhs_sdr50,
> +	&init_uhs_sdr104,
> +	&init_uhs_ddr50,
> +	&init_emmc_ddr,
> +	&init_emmc_hs200,
> +	&init_emmc_hs400,
> +};
> +
> +static unsigned int sdhci_cdns6_read_phy_reg(struct sdhci_cdns_priv *priv,
> +					     const u32 address)
> +{
> +	writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
> +	return readl(priv->hrs_addr + SDHCI_CDNS_HRS05);
> +}
> +
> +static void sdhci_cdns6_write_phy_reg(struct sdhci_cdns_priv *priv,
> +				      const u32 address, const u32 value)
> +{
> +	writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
> +	writel(value, priv->hrs_addr + SDHCI_CDNS_HRS05);
> +}
> +
> +static int sdhci_cdns6_phy_lock_dll(struct sdhci_cdns6_phy *phy)
> +{
> +	u32 delay_element = phy->delay_element_org;
> +	u32 delay_elements_in_sdmclk;
> +
> +	delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, delay_element);
> +	if (delay_elements_in_sdmclk > 256) {
> +		delay_element *= 2;
> +		delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk,
> +							delay_element);
> +
> +		if (delay_elements_in_sdmclk > 256)
> +			return -EINVAL;
> +
> +		phy->dll_max_value = 127;
> +	} else {
> +		phy->dll_max_value = 255;
> +	}
> +
> +	phy->t_sdmclk_calc = delay_element * delay_elements_in_sdmclk;
> +	phy->delay_element = delay_element;
> +	phy->cp_dll_bypass_mode = 0;
> +
> +	return 0;
> +}
> +
> +static void sdhci_cdns6_phy_dll_bypass(struct sdhci_cdns6_phy *phy)
> +{
> +	phy->dll_max_value = 256;
> +	phy->cp_dll_bypass_mode = 1;
> +}
> +
> +static void sdhci_cdns6_phy_configure_dll(struct sdhci_cdns6_phy *phy)
> +{
> +	if (phy->sdhc_extended_wr_mode == 0) {
> +		if (sdhci_cdns6_phy_lock_dll(phy) == 0)
> +			return;
> +	}
> +	sdhci_cdns6_phy_dll_bypass(phy);
> +}
> +
> +static void sdhci_cdns6_phy_calc_out(struct sdhci_cdns6_phy *phy,
> +				     bool cmd_not_dat)
> +{
> +	u32 wr0_dly = 0, wr1_dly = 0, output_min, output_max, phy_o_delay,
> +	    clk_wr_delay = 0, wr0_sdclk_dly = 0, wr1_sdclk_dly = 0;
> +	bool ddr = (phy->mode == MMC_TIMING_UHS_DDR50) ||
> +			(phy->mode == MMC_TIMING_MMC_DDR52) ||
> +			(phy->mode == MMC_TIMING_MMC_HS400);
> +	bool data_ddr = ddr && !cmd_not_dat;
> +	int t;
> +
> +	if (cmd_not_dat) {
> +		output_min = phy->t_cmd_output_min;
> +		output_max = phy->t_cmd_output_max;
> +		phy_o_delay = phy->phy_cmd_o_delay;
> +	} else {
> +		output_min = phy->t_dat_output_min;
> +		output_max = phy->t_dat_output_max;
> +		phy_o_delay = phy->phy_dat_o_delay;
> +	}
> +
> +	if (data_ddr) {
> +		wr0_sdclk_dly = 1;
> +		wr1_sdclk_dly = 1;
> +	}
> +
> +	t = phy_o_delay - phy->phy_sdclk_delay - output_min;
> +	if (t < 0 && phy->sdhc_extended_wr_mode == 1) {
> +		u32 n_half_cycle = DIV_ROUND_UP(-t * 2, phy->t_sdmclk);
> +
> +		wr0_dly = (n_half_cycle + 1) / 2;
> +		if (data_ddr)
> +			wr1_dly = (n_half_cycle + 1) / 2;
> +		else
> +			wr1_dly = (n_half_cycle + 1) % 2 + wr0_dly - 1;
> +	}
> +
> +	if (phy->sdhc_extended_wr_mode == 0) {
> +		u32 out_hold, out_setup, out_hold_margin;
> +		u32 n;
> +
> +		if (!data_ddr)
> +			wr0_dly = 1;
> +
> +		out_setup = output_max;
> +		out_hold = output_min;
> +		out_hold_margin = DIV_ROUND_UP(out_setup - out_hold, 4);
> +		out_hold += out_hold_margin;
> +
> +		if (phy->cp_dll_bypass_mode == 0)
> +			n = DIV_ROUND_UP(256 * out_hold, phy->t_sdmclk_calc);
> +		else
> +			n = DIV_ROUND_UP(out_hold, phy->delay_element) - 1;
> +
> +		if (n <= phy->dll_max_value)
> +			clk_wr_delay = n;
> +		else
> +			clk_wr_delay = 255;
> +	} else {
> +		/*  sdhc_extended_wr_mode = 1 => PHY IO cell work in SDR mode */
> +		clk_wr_delay = 0;
> +	}
> +
> +	if (cmd_not_dat) {
> +		phy->sdhc_wrcmd0_dly = wr0_dly;
> +		phy->sdhc_wrcmd1_dly = wr1_dly;
> +		phy->cp_clk_wrdqs_delay = clk_wr_delay;
> +		phy->sdhc_wrcmd0_sdclk_dly = wr0_sdclk_dly;
> +		phy->sdhc_wrcmd1_sdclk_dly = wr1_sdclk_dly;
> +	} else {
> +		phy->sdhc_wrdata0_dly = wr0_dly;
> +		phy->sdhc_wrdata1_dly = wr1_dly;
> +		phy->cp_clk_wr_delay = clk_wr_delay;
> +		phy->sdhc_wrdata0_sdclk_dly = wr0_sdclk_dly;
> +		phy->sdhc_wrdata1_sdclk_dly = wr1_sdclk_dly;
> +	}
> +}
> +
> +static void sdhci_cdns6_phy_calc_cmd_out(struct sdhci_cdns6_phy *phy)
> +{
> +	sdhci_cdns6_phy_calc_out(phy, true);
> +}
> +
> +static void sdhci_cdns6_phy_calc_cmd_in(struct sdhci_cdns6_phy *phy)
> +{
> +	phy->cp_io_mask_end =
> +		((phy->iocell_output_delay + phy->iocell_input_delay) * 2)
> +		/ phy->t_sdmclk;
> +
> +	/* cp_io_mask_end is a 3-bit field, clamp to max value of 7 */
> +	phy->cp_io_mask_end = min_t(u32, phy->cp_io_mask_end, 7);
> +
> +	if (phy->strobe_cmd && phy->cp_io_mask_end > 0)
> +		phy->cp_io_mask_end--;
> +
> +	if (phy->strobe_cmd) {
> +		phy->cp_use_phony_dqs_cmd = 0;
> +		phy->cp_read_dqs_cmd_delay = 64;
> +	} else {
> +		phy->cp_use_phony_dqs_cmd = 1;
> +		phy->cp_read_dqs_cmd_delay = 0;
> +	}
> +
> +	if ((phy->mode == MMC_TIMING_MMC_HS400 && !phy->strobe_cmd) ||
> +	    phy->mode == MMC_TIMING_MMC_HS200)
> +		phy->cp_read_dqs_cmd_delay =
> +			phy->hs200_tune_val;
> +}
> +
> +static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy)
> +{
> +	u32 hcsdclkadj = 0;
> +	bool strobe_dat = (phy->mode == MMC_TIMING_MMC_HS400);
> +
> +	if (strobe_dat) {
> +		phy->cp_use_phony_dqs = 0;
> +		phy->cp_read_dqs_delay = 64;
> +	} else {
> +		phy->cp_use_phony_dqs = 1;
> +		phy->cp_read_dqs_delay = 0;
> +	}
> +
> +	if (phy->mode == MMC_TIMING_MMC_HS200)
> +		phy->cp_read_dqs_delay =
> +			phy->hs200_tune_val;
> +
> +	if (strobe_dat) {
> +		/* dqs loopback input via IO cell */
> +		hcsdclkadj += phy->iocell_input_delay;
> +		/* dfi_dqs_in: mem_dqs -> clean_dqs_mod; delay of hic_dll_dqs_nand2 */
> +		hcsdclkadj += phy->delay_element / 2;
> +		/* delay line */
> +		hcsdclkadj += phy->t_sdclk / 2;
> +		/* PHY FIFO write pointer */
> +		hcsdclkadj += phy->t_sdclk / 2 + phy->delay_element;
> +		/* 1st synchronizer */
> +		hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk)
> +			* phy->t_sdmclk - hcsdclkadj;
> +		/*
> +		 * 2nd synchronizer + PHY FIFO read pointer + PHY rddata
> +		 * + PHY rddata registered, + FIFO 1st ciu_en
> +		 */
> +		hcsdclkadj += 5 * phy->t_sdmclk;
> +		/* FIFO 2nd ciu_en */
> +		hcsdclkadj += phy->t_sdclk;
> +
> +		hcsdclkadj /= phy->t_sdclk;
> +	} else {
> +		u32 n;
> +
> +		/* rebar PHY delay */
> +		hcsdclkadj += 2 * phy->t_sdmclk;
> +		/* rebar output via IO cell */
> +		hcsdclkadj += phy->iocell_output_delay;
> +		/* dqs loopback input via IO cell */
> +		hcsdclkadj += phy->iocell_input_delay;
> +		/* dfi_dqs_in: mem_dqs -> clean_dqs_mod delay of hic_dll_dqs_nand2 */
> +		hcsdclkadj += phy->delay_element / 2;
> +		/* dll: one delay element between SIGI_0 and SIGO_0 */
> +		hcsdclkadj += phy->delay_element;
> +		/* dfi_dqs_in: mem_dqs_delayed -> clk_dqs delay of hic_dll_dqs_nand2 */
> +		hcsdclkadj += phy->delay_element / 2;
> +		/* deskew DLL: clk_dqs -> clk_dqN: one delay element */
> +		hcsdclkadj += phy->delay_element;
> +
> +		if (phy->t_sdclk == phy->t_sdmclk)
> +			n = (hcsdclkadj - 2 * phy->t_sdmclk) / phy->t_sdclk;
> +		else
> +			n = hcsdclkadj / phy->t_sdclk;
> +
> +		/* phase shift within one t_sdclk clock cycle caused by rebar - lbk dqs delay */
> +		hcsdclkadj = hcsdclkadj % phy->t_sdclk;
> +		/* PHY FIFO write pointer */
> +		hcsdclkadj += phy->t_sdclk / 2;
> +		/* 1st synchronizer */
> +		hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk)
> +			* phy->t_sdmclk - hcsdclkadj;
> +		/*
> +		 * 2nd synchronizer + PHY FIFO read pointer + PHY rddata
> +		 * + PHY rddata registered
> +		 */
> +		hcsdclkadj += 4 * phy->t_sdmclk;
> +
> +		if ((phy->t_sdclk / phy->t_sdmclk) > 1) {
> +			u32 tmp1, tmp2;
> +
> +			tmp1 = hcsdclkadj;
> +			tmp2 = (hcsdclkadj / phy->t_sdclk) * phy->t_sdclk
> +				+ phy->t_sdclk - phy->t_sdmclk;
> +			if (tmp1 == tmp2)
> +				tmp2 += phy->t_sdclk;
> +
> +			/* FIFO aligns to clock cycle before ciu_en */
> +			hcsdclkadj += tmp2 - tmp1;
> +		}
> +
> +		/* FIFO 1st ciu_en */
> +		hcsdclkadj += phy->t_sdmclk;
> +		/* FIFO 2nd ciu_en */
> +		hcsdclkadj += phy->t_sdclk;
> +
> +		hcsdclkadj /= phy->t_sdclk;
> +
> +		hcsdclkadj += n;
> +
> +		if ((phy->t_sdclk / phy->t_sdmclk) >= 2) {
> +			if (phy->mode == MMC_TIMING_UHS_DDR50 ||
> +			    phy->mode == MMC_TIMING_MMC_DDR52)
> +				hcsdclkadj -= 2;
> +			else
> +				hcsdclkadj -= 1;
> +		} else if ((phy->t_sdclk / phy->t_sdmclk) == 1) {
> +			hcsdclkadj += 2;
> +		}
> +
> +		if (phy->mode == MMC_TIMING_UHS_SDR104 || phy->mode == MMC_TIMING_MMC_HS200)
> +			hcsdclkadj -= 1;
> +	}
> +
> +	/* hcsdclkadj is a 4-bit field, clamp to max value of 15 */
> +	if (hcsdclkadj > 15)
> +		hcsdclkadj = 15;
> +
> +	phy->sdhc_hcsdclkadj = hcsdclkadj;
> +}
> +
> +static void sdhci_cdns6_phy_calc_dat_out(struct sdhci_cdns6_phy *phy)
> +{
> +	sdhci_cdns6_phy_calc_out(phy, false);
> +}
> +
> +static void sdhci_cdns6_phy_calc_io(struct sdhci_cdns6_phy *phy)
> +{
> +	u32 rw_compensate;
> +
> +	rw_compensate = ((phy->iocell_input_delay + phy->iocell_output_delay)
> +		/ phy->t_sdmclk) + phy->sdhc_wrdata0_dly + 5 + 3;
> +
> +	phy->sdhc_idelay_val = (2 * phy->iocell_input_delay)
> +		/ phy->t_sdmclk;
> +
> +	phy->cp_io_mask_start = 0;
> +	if (phy->t_sdclk == phy->t_sdmclk && rw_compensate > 10)
> +		phy->cp_io_mask_start = 2 * (rw_compensate - 10);
> +
> +	if (phy->mode == MMC_TIMING_UHS_SDR104)
> +		phy->cp_io_mask_start++;
> +
> +	if (phy->t_sdclk == phy->t_sdmclk && phy->mode == MMC_TIMING_UHS_SDR50)
> +		phy->cp_io_mask_start++;
> +
> +	phy->sdhc_rw_compensate = rw_compensate;
> +}
> +
> +static void sdhci_cdns6_phy_calc_settings(struct sdhci_cdns6_phy *phy)
> +{
> +	sdhci_cdns6_phy_calc_cmd_out(phy);
> +	sdhci_cdns6_phy_calc_cmd_in(phy);
> +	sdhci_cdns6_phy_calc_dat_out(phy);
> +	sdhci_cdns6_phy_calc_dat_in(phy);
> +	sdhci_cdns6_phy_calc_io(phy);
> +}
> +
> +static int sdhci_cdns6_dll_reset(struct sdhci_cdns_priv *priv, bool reset)
> +{
> +	u32 reg;
> +	int ret = 0;
> +
> +	reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09);
> +	if (reset)
> +		reg &= ~SDHCI_CDNS_HRS09_PHY_SW_RESET;
> +	else
> +		reg |= SDHCI_CDNS_HRS09_PHY_SW_RESET;
> +
> +	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09);
> +
> +	/* After reset, wait until HRS09.PHY_INIT_COMPLETE is set to 1 within 3000us*/
> +	if (!reset) {
> +		ret = readl_poll_timeout(priv->hrs_addr + SDHCI_CDNS_HRS09, reg,
> +					 (reg & SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE),
> +					 0, 3000);
> +	}
> +
> +	return ret;
> +}
> +
> +int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv)
> +{
> +	int ret;
> +	u32 reg;
> +	struct sdhci_cdns6_phy *phy = priv->phy;
> +
> +	sdhci_cdns6_dll_reset(priv, true);
> +
> +	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG);
> +	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS;
> +	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS;
> +	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS;
> +	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD;
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS,
> +			phy->cp_use_ext_lpbk_dqs);

drivers/mmc/host/sdhci-cadence6.c: In function ‘sdhci_cdns6_phy_init’:
drivers/mmc/host/sdhci-cadence6.c:761:16: error: implicit declaration of function ‘FIELD_PREP’ [-Wimplicit-function-declaration]
  761 |         reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS,
      |                ^~~~~~~~~~

> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS,
> +			phy->cp_use_lpbk_dqs);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS,
> +			  phy->cp_use_phony_dqs);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD,
> +			  phy->cp_use_phony_dqs_cmd);
> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG, reg);
> +
> +	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG);
> +	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD;
> +	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SW_HALF_CYCLE_SHIFT;
> +	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL;
> +	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS;
> +	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON;
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD,
> +			phy->cp_sync_method);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SW_HALF_CYCLE_SHIFT,
> +			phy->cp_sw_half_cycle_shift);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL,
> +			phy->cp_rd_del_sel);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS,
> +			phy->cp_underrun_suppress);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON,
> +			phy->cp_gate_cfg_always_on);
> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG, reg);
> +
> +	reg = FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_BYPASS_MODE,
> +			 phy->cp_dll_bypass_mode);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_PHASE_DETECT_SEL, 2);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM, 0);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT,
> +			phy->cp_dll_start_point);
> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_REG, reg);
> +
> +	reg = FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY,
> +			 phy->cp_read_dqs_cmd_delay);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WRDQS_DELAY,
> +			  phy->cp_clk_wrdqs_delay);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WR_DELAY,
> +			  phy->cp_clk_wr_delay);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY,
> +			  phy->cp_read_dqs_delay);
> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG, reg);
> +
> +	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG);
> +	reg &= ~SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING;
> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG, reg);
> +
> +	/*
> +	 * Ensure all preceding PHY register writes complete and reach the
> +	 * controller before releasing the PHY from reset. Without this,
> +	 * SDR104 has been observed to fail intermittently on some boards.
> +	 */
> +	wmb();
> +
> +	ret = sdhci_cdns6_dll_reset(priv, false);
> +	if (ret)
> +		return ret;
> +
> +	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DQ_TIMING_REG);
> +	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON;
> +	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END;
> +	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START;
> +	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END;
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON,
> +			phy->cp_io_mask_always_on);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END,
> +			  phy->cp_io_mask_end);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START,
> +			  phy->cp_io_mask_start);
> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END,
> +			phy->cp_data_select_oe_end);
> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQ_TIMING_REG, reg);
> +
> +	/* Ensure DQ timing programming is visible before HRS09 follow-up writes */
> +	wmb();
> +
> +	reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09);
> +	if (phy->sdhc_extended_wr_mode)
> +		reg |= SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
> +	else
> +		reg &= ~SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
> +
> +	if (phy->sdhc_extended_rd_mode)
> +		reg |= SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
> +	else
> +		reg &= ~SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
> +
> +	if (phy->sdhc_rddata_en)
> +		reg |= SDHCI_CDNS_HRS09_RDDATA_EN;
> +	else
> +		reg &= ~SDHCI_CDNS_HRS09_RDDATA_EN;
> +
> +	if (phy->sdhc_rdcmd_en)
> +		reg |= SDHCI_CDNS_HRS09_RDCMD_EN;
> +	else
> +		reg &= ~SDHCI_CDNS_HRS09_RDCMD_EN;
> +
> +	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09);
> +
> +	reg = FIELD_PREP(SDHCI_CDNS_HRS10_HCSDCLKADJ, phy->sdhc_hcsdclkadj);
> +	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS10);
> +
> +	reg = FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY,
> +			 phy->sdhc_wrdata1_sdclk_dly);
> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY,
> +			  phy->sdhc_wrdata0_sdclk_dly);
> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY,
> +			  phy->sdhc_wrcmd1_sdclk_dly);
> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY,
> +			  phy->sdhc_wrcmd0_sdclk_dly);
> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_DLY,
> +			  phy->sdhc_wrdata1_dly);
> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_DLY,
> +			  phy->sdhc_wrdata0_dly);
> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_DLY,
> +			  phy->sdhc_wrcmd1_dly);
> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_DLY,
> +			  phy->sdhc_wrcmd0_dly);
> +	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS16);
> +
> +	reg = FIELD_PREP(SDHCI_CDNS_HRS07_RW_COMPENSATE,
> +			 phy->sdhc_rw_compensate);
> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS07_IDELAY_VAL,
> +			  phy->sdhc_idelay_val);
> +	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS07);
> +
> +	/* Allow 5ms for clock and PHY signals to stabilize after configuration */
> +	usleep_range(5000, 5500);
> +
> +	return 0;
> +}
> +
> +int sdhci_cdns6_set_tune_val(struct sdhci_host *host,
> +			     unsigned int val)
> +{
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
> +	struct sdhci_cdns6_phy *phy = priv->phy;
> +	u32 tuneval;
> +
> +	/*
> +	 * Scale tuning tap (val in [0, SDHCI_CDNS_MAX_TUNING_LOOP-1]) to the
> +	 * 8-bit PHY DLL slave delay field [0, 255]. With MAX_TUNING_LOOP=40
> +	 * and FIELD_SIZE=256, the result fits in 8 bits.
> +	 */
> +	tuneval = (val * SDHCI_CDNS6_PHY_DLL_FIELD_SIZE) /
> +		  SDHCI_CDNS_MAX_TUNING_LOOP;
> +
> +	phy->hs200_tune_val = tuneval;
> +	phy->cp_read_dqs_cmd_delay = tuneval;
> +	phy->cp_read_dqs_delay = tuneval;
> +
> +	return sdhci_cdns6_phy_init(priv);
> +}
> +
> +static int sdhci_cdns6_phy_update_timings(struct sdhci_host *host)
> +{
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
> +	struct sdhci_cdns6_phy *phy = priv->phy;
> +	u32 t_sdmclk = phy->t_sdmclk;
> +
> +	/* Validate mode is within supported range */
> +	if (phy->mode >= ARRAY_SIZE(init_timings))
> +		return -EINVAL;
> +
> +	/* initialize input */
> +	init_timings[phy->mode](phy, phy->t_sdclk);
> +
> +	phy->strobe_cmd = false;
> +
> +	if (priv->enhanced_strobe)
> +		phy->strobe_cmd = true;
> +
> +	phy->phy_sdclk_delay = 2 * t_sdmclk;
> +
> +	/*
> +	 * CMD and DAT output delays are currently identical, but kept separate
> +	 * to allow independent tuning for specific modes (e.g., HS400) or
> +	 * board-specific optimizations in the future.
> +	 */
> +	phy->phy_cmd_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
> +	phy->phy_dat_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
> +
> +	if (phy->t_sdclk == phy->t_sdmclk) {
> +		phy->sdhc_extended_wr_mode = 0;
> +		phy->sdhc_extended_rd_mode = 0;
> +	} else {
> +		phy->sdhc_extended_wr_mode = 1;
> +		phy->sdhc_extended_rd_mode = 1;
> +	}
> +
> +	phy->cp_gate_cfg_always_on = 1;
> +
> +	sdhci_cdns6_phy_configure_dll(phy);
> +
> +	sdhci_cdns6_phy_calc_settings(phy);
> +
> +	return 0;
> +}
> +
> +int sdhci_cdns6_phy_probe(struct platform_device *pdev,
> +			  struct sdhci_cdns_priv *priv)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct sdhci_host *host = dev_get_drvdata(dev);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_cdns6_phy *phy;
> +	unsigned long val;
> +	int ret;
> +
> +	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
> +	if (!phy)
> +		return -ENOMEM;
> +
> +	val = clk_get_rate(pltfm_host->clk);
> +	if (!val)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "failed to get controller clock rate\n");
> +
> +	phy->t_sdmclk = DIV_ROUND_DOWN_ULL(1000000000000ULL, val);
> +
> +	ret = of_property_read_u32(dev->of_node, "cdns,iocell-input-delay",
> +				   &phy->iocell_input_delay);
> +	if (ret)
> +		phy->iocell_input_delay = SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY;
> +
> +	ret = of_property_read_u32(dev->of_node, "cdns,iocell-output-delay",
> +				   &phy->iocell_output_delay);
> +	if (ret)
> +		phy->iocell_output_delay = SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY;
> +
> +	ret = of_property_read_u32(dev->of_node, "cdns,delay-element",
> +				   &phy->delay_element);
> +	if (ret)
> +		phy->delay_element = SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT;
> +
> +	phy->delay_element_org = phy->delay_element;
> +
> +	priv->phy = phy;
> +
> +	/* default settings */
> +	phy->sdhc_rdcmd_en = 1;
> +	phy->sdhc_rddata_en = 1;
> +	phy->cp_use_ext_lpbk_dqs = 1;
> +	phy->cp_use_lpbk_dqs = 1;
> +	phy->cp_sync_method = 1;
> +	phy->cp_rd_del_sel = SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL;
> +	phy->cp_dll_start_point = SDHCI_CDNS6_PHY_DEFAULT_DLL_START;
> +	phy->cp_data_select_oe_end = 1;
> +	phy->cp_io_mask_always_on = 0;
> +	phy->cp_underrun_suppress = 1;
> +
> +	return 0;
> +}
> +
> +void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing)
> +{
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
> +	struct sdhci_cdns6_phy *phy = priv->phy;
> +
> +	/* Clock may be 0 during initial ios setup; skip PHY update */
> +	if (!host->mmc->ios.clock)
> +		return;
> +
> +	phy->t_sdclk = DIV_ROUND_DOWN_ULL(1000000000000ULL,
> +					  host->mmc->ios.clock);
> +	phy->mode = timing;
> +
> +	if (sdhci_cdns6_phy_update_timings(host))
> +		dev_warn(mmc_dev(host->mmc), "%s: update timings failed\n",
> +			 __func__);
> +
> +	if (sdhci_cdns6_phy_init(priv))
> +		dev_warn(mmc_dev(host->mmc), "%s: phy init failed\n",
> +			 __func__);
> +}
> +
> +void sdhci_cdns6_hw_reset(struct sdhci_host *host)
> +{
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
> +	void __iomem *reg;
> +
> +	reg = priv->hrs_addr + SDHCI_CDNS_HRS11;
> +	writel(SDHCI_CDNS_HRS11_EMMC_RST, reg);
> +	/* eMMC HW reset assertion: spec requires >= 1us, give margin */
> +	usleep_range(10, 20);
> +	writel(0, reg);
> +	usleep_range(300, 1000);
> +}


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

* Re: [PATCH v1 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork
  2026-05-19 15:29   ` Adrian Hunter
@ 2026-05-21 15:53     ` Kathpalia, Tanmay
  2026-05-21 16:16       ` Adrian Hunter
  0 siblings, 1 reply; 32+ messages in thread
From: Kathpalia, Tanmay @ 2026-05-21 15:53 UTC (permalink / raw)
  To: Adrian Hunter, linux-mmc; +Cc: ulf.hansson, linux-kernel

Hi Adrian,

On 5/19/2026 8:59 PM, Adrian Hunter wrote:
> On 11/05/2026 23:21, Tanmay Kathpalia wrote:
>> PHY-related functions and data structures in the driver are not
>> explicitly scoped to the SD4HC (V4) controller, making it unclear
>> which code is shared and which is version-specific.
>>
>> Rename them with a "cdns4" prefix to distinguish SD4HC-specific
>> implementation from the shared driver core, and to avoid naming
>> conflicts when SD6HC (V6) support is introduced.
>>
>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> One minor comment below

Thanks for reviewing.

>
>> ---
>>   drivers/mmc/host/sdhci-cadence.c | 74 ++++++++++++++++----------------
>>   1 file changed, 37 insertions(+), 37 deletions(-)
>>
>> diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
>> index 435603c8c00b..47690a52a221 100644
>> --- a/drivers/mmc/host/sdhci-cadence.c
>> +++ b/drivers/mmc/host/sdhci-cadence.c
>> @@ -78,7 +78,7 @@
>>    */
>>   #define SDHCI_CDNS_MAX_TUNING_LOOP	40
>>   
>> -struct sdhci_cdns_phy_param {
>> +struct sdhci_cdns4_phy_param {
>>   	u8 addr;
>>   	u8 data;
>>   };
>> @@ -91,10 +91,10 @@ struct sdhci_cdns_priv {
>>   	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
>>   	struct reset_control *rst_hw;
>>   	unsigned int nr_phy_params;
>> -	struct sdhci_cdns_phy_param phy_params[];
>> +	struct sdhci_cdns4_phy_param phy_params[];
>>   };
>>   
>> -struct sdhci_cdns_phy_cfg {
>> +struct sdhci_cdns4_phy_cfg {
>>   	const char *property;
>>   	u8 addr;
>>   };
>> @@ -104,7 +104,7 @@ struct sdhci_cdns_drv_data {
>>   	const struct sdhci_pltfm_data pltfm_data;
>>   };
>>   
>> -static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = {
>> +static const struct sdhci_cdns4_phy_cfg sdhci_cdns4_phy_cfgs[] = {
>>   	{ "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, },
>>   	{ "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, },
>>   	{ "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, },
>> @@ -124,8 +124,8 @@ static inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val,
>>   	writel(val, reg);
>>   }
>>   
>> -static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
>> -				    u8 addr, u8 data)
>> +static int sdhci_cdns4_write_phy_reg(struct sdhci_cdns_priv *priv,
>> +				     u8 addr, u8 data)
>>   {
>>   	void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04;
>>   	u32 tmp;
>> @@ -156,44 +156,44 @@ static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
>>   	return ret;
>>   }
>>   
>> -static unsigned int sdhci_cdns_phy_param_count(struct device_node *np)
>> +static unsigned int sdhci_cdns4_phy_param_count(struct device_node *np)
>>   {
>>   	unsigned int count = 0;
>>   	int i;
>>   
>> -	for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++)
>> -		if (of_property_present(np, sdhci_cdns_phy_cfgs[i].property))
>> +	for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++)
>> +		if (of_property_present(np, sdhci_cdns4_phy_cfgs[i].property))
>>   			count++;
>>   
>>   	return count;
>>   }
>>   
>> -static void sdhci_cdns_phy_param_parse(struct device_node *np,
>> -				       struct sdhci_cdns_priv *priv)
>> +static void sdhci_cdns4_phy_param_parse(struct device_node *np,
>> +					struct sdhci_cdns_priv *priv)
>>   {
>> -	struct sdhci_cdns_phy_param *p = priv->phy_params;
>> +	struct sdhci_cdns4_phy_param *p = priv->phy_params;
>>   	u32 val;
>>   	int ret, i;
>>   
>> -	for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) {
>> -		ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property,
>> +	for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++) {
>> +		ret = of_property_read_u32(np, sdhci_cdns4_phy_cfgs[i].property,
>>   					   &val);
>>   		if (ret)
>>   			continue;
>>   
>> -		p->addr = sdhci_cdns_phy_cfgs[i].addr;
>> +		p->addr = sdhci_cdns4_phy_cfgs[i].addr;
>>   		p->data = val;
>>   		p++;
>>   	}
>>   }
>>   
>> -static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
>> +static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
>>   {
>>   	int ret, i;
>>   
>>   	for (i = 0; i < priv->nr_phy_params; i++) {
>> -		ret = sdhci_cdns_write_phy_reg(priv, priv->phy_params[i].addr,
>> -					       priv->phy_params[i].data);
>> +		ret = sdhci_cdns4_write_phy_reg(priv, priv->phy_params[i].addr,
>> +						priv->phy_params[i].data);
>>   		if (ret)
>>   			return ret;
>>   	}
>> @@ -201,7 +201,7 @@ static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
>>   	return 0;
>>   }
>>   
>> -static void *sdhci_cdns_priv(struct sdhci_host *host)
>> +static void *sdhci_cdns_get_priv(struct sdhci_host *host)
> Why rename this?  It seems to just create churn.

The reason was to resolve a name collision: in the original code
both the data structure and the accessor function were named
sdhci_cdns_priv, which can reduce readability. Renaming the
accessor to sdhci_cdns_get_priv() makes the intent clearer and
avoids this ambiguity.
If you would prefer to keep the original name and avoid the churn,
I will revert this rename in v2.

>
>>   {
>>   	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>   
>> @@ -238,7 +238,7 @@ static u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv)
>>   
>>   static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
>>   {
>> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>   	void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06;
>>   	u32 tmp;
>>   	int i, ret;
>> @@ -354,7 +354,7 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
>>   static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
>>   					 unsigned int timing)
>>   {
>> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>   	u32 mode;
>>   
>>   	switch (timing) {
>> @@ -407,12 +407,12 @@ static void elba_priv_writel(struct sdhci_cdns_priv *priv, u32 val,
>>   
>>   static void elba_write_l(struct sdhci_host *host, u32 val, int reg)
>>   {
>> -	elba_priv_writel(sdhci_cdns_priv(host), val, host->ioaddr + reg);
>> +	elba_priv_writel(sdhci_cdns_get_priv(host), val, host->ioaddr + reg);
>>   }
>>   
>>   static void elba_write_w(struct sdhci_host *host, u16 val, int reg)
>>   {
>> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>   	u32 shift = reg & GENMASK(1, 0);
>>   	unsigned long flags;
>>   	u32 byte_enables;
>> @@ -426,7 +426,7 @@ static void elba_write_w(struct sdhci_host *host, u16 val, int reg)
>>   
>>   static void elba_write_b(struct sdhci_host *host, u8 val, int reg)
>>   {
>> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>   	u32 shift = reg & GENMASK(1, 0);
>>   	unsigned long flags;
>>   	u32 byte_enables;
>> @@ -452,7 +452,7 @@ static const struct sdhci_ops sdhci_elba_ops = {
>>   static int elba_drv_init(struct platform_device *pdev)
>>   {
>>   	struct sdhci_host *host = platform_get_drvdata(pdev);
>> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>   	void __iomem *ioaddr;
>>   
>>   	host->mmc->caps |= MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA;
>> @@ -470,7 +470,7 @@ static int elba_drv_init(struct platform_device *pdev)
>>   	return 0;
>>   }
>>   
>> -static const struct sdhci_ops sdhci_cdns_ops = {
>> +static const struct sdhci_ops sdhci_cdns4_ops = {
>>   	.set_clock = sdhci_set_clock,
>>   	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
>>   	.set_bus_width = sdhci_set_bus_width,
>> @@ -481,7 +481,7 @@ static const struct sdhci_ops sdhci_cdns_ops = {
>>   
>>   static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
>>   	.pltfm_data = {
>> -		.ops = &sdhci_cdns_ops,
>> +		.ops = &sdhci_cdns4_ops,
>>   		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
>>   	},
>>   };
>> @@ -495,14 +495,14 @@ static const struct sdhci_cdns_drv_data sdhci_elba_drv_data = {
>>   
>>   static const struct sdhci_cdns_drv_data sdhci_eyeq_drv_data = {
>>   	.pltfm_data = {
>> -		.ops = &sdhci_cdns_ops,
>> +		.ops = &sdhci_cdns4_ops,
>>   		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
>>   	},
>>   };
>>   
>> -static const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = {
>> +static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
>>   	.pltfm_data = {
>> -		.ops = &sdhci_cdns_ops,
>> +		.ops = &sdhci_cdns4_ops,
>>   	},
>>   };
>>   
>> @@ -510,7 +510,7 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
>>   					     struct mmc_ios *ios)
>>   {
>>   	struct sdhci_host *host = mmc_priv(mmc);
>> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>   	u32 mode;
>>   
>>   	priv->enhanced_strobe = ios->enhanced_strobe;
>> @@ -529,7 +529,7 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
>>   static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc)
>>   {
>>   	struct sdhci_host *host = mmc_priv(mmc);
>> -	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>   
>>   	dev_dbg(mmc_dev(host->mmc), "emmc hardware reset\n");
>>   
>> @@ -560,9 +560,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>>   
>>   	data = of_device_get_match_data(dev);
>>   	if (!data)
>> -		data = &sdhci_cdns_drv_data;
>> +		data = &sdhci_cdns4_drv_data;
>>   
>> -	nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node);
>> +	nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
>>   	host = sdhci_pltfm_init(pdev, &data->pltfm_data,
>>   				struct_size(priv, phy_params, nr_phy_params));
>>   	if (IS_ERR(host))
>> @@ -593,9 +593,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>>   	if (ret)
>>   		return ret;
>>   
>> -	sdhci_cdns_phy_param_parse(dev->of_node, priv);
>> +	sdhci_cdns4_phy_param_parse(dev->of_node, priv);
>>   
>> -	ret = sdhci_cdns_phy_init(priv);
>> +	ret = sdhci_cdns4_phy_init(priv);
>>   	if (ret)
>>   		return ret;
>>   
>> @@ -622,7 +622,7 @@ static int sdhci_cdns_resume(struct device *dev)
>>   	if (ret)
>>   		return ret;
>>   
>> -	ret = sdhci_cdns_phy_init(priv);
>> +	ret = sdhci_cdns4_phy_init(priv);
>>   	if (ret)
>>   		goto disable_clk;
>>   

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

* Re: [PATCH v1 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork
  2026-05-21 15:53     ` Kathpalia, Tanmay
@ 2026-05-21 16:16       ` Adrian Hunter
  0 siblings, 0 replies; 32+ messages in thread
From: Adrian Hunter @ 2026-05-21 16:16 UTC (permalink / raw)
  To: Kathpalia, Tanmay, linux-mmc; +Cc: ulfh, linux-kernel

On 21/05/2026 18:53, Kathpalia, Tanmay wrote:
> Hi Adrian,
> 
> On 5/19/2026 8:59 PM, Adrian Hunter wrote:
>> On 11/05/2026 23:21, Tanmay Kathpalia wrote:
>>> PHY-related functions and data structures in the driver are not
>>> explicitly scoped to the SD4HC (V4) controller, making it unclear
>>> which code is shared and which is version-specific.
>>>
>>> Rename them with a "cdns4" prefix to distinguish SD4HC-specific
>>> implementation from the shared driver core, and to avoid naming
>>> conflicts when SD6HC (V6) support is introduced.
>>>
>>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>> One minor comment below
> 
> Thanks for reviewing.
> 
>>
>>> ---
>>>   drivers/mmc/host/sdhci-cadence.c | 74 ++++++++++++++++----------------
>>>   1 file changed, 37 insertions(+), 37 deletions(-)
>>>
>>> diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
>>> index 435603c8c00b..47690a52a221 100644
>>> --- a/drivers/mmc/host/sdhci-cadence.c
>>> +++ b/drivers/mmc/host/sdhci-cadence.c
>>> @@ -78,7 +78,7 @@
>>>    */
>>>   #define SDHCI_CDNS_MAX_TUNING_LOOP    40
>>>   -struct sdhci_cdns_phy_param {
>>> +struct sdhci_cdns4_phy_param {
>>>       u8 addr;
>>>       u8 data;
>>>   };
>>> @@ -91,10 +91,10 @@ struct sdhci_cdns_priv {
>>>       void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
>>>       struct reset_control *rst_hw;
>>>       unsigned int nr_phy_params;
>>> -    struct sdhci_cdns_phy_param phy_params[];
>>> +    struct sdhci_cdns4_phy_param phy_params[];
>>>   };
>>>   -struct sdhci_cdns_phy_cfg {
>>> +struct sdhci_cdns4_phy_cfg {
>>>       const char *property;
>>>       u8 addr;
>>>   };
>>> @@ -104,7 +104,7 @@ struct sdhci_cdns_drv_data {
>>>       const struct sdhci_pltfm_data pltfm_data;
>>>   };
>>>   -static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = {
>>> +static const struct sdhci_cdns4_phy_cfg sdhci_cdns4_phy_cfgs[] = {
>>>       { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, },
>>>       { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, },
>>>       { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, },
>>> @@ -124,8 +124,8 @@ static inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val,
>>>       writel(val, reg);
>>>   }
>>>   -static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
>>> -                    u8 addr, u8 data)
>>> +static int sdhci_cdns4_write_phy_reg(struct sdhci_cdns_priv *priv,
>>> +                     u8 addr, u8 data)
>>>   {
>>>       void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04;
>>>       u32 tmp;
>>> @@ -156,44 +156,44 @@ static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
>>>       return ret;
>>>   }
>>>   -static unsigned int sdhci_cdns_phy_param_count(struct device_node *np)
>>> +static unsigned int sdhci_cdns4_phy_param_count(struct device_node *np)
>>>   {
>>>       unsigned int count = 0;
>>>       int i;
>>>   -    for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++)
>>> -        if (of_property_present(np, sdhci_cdns_phy_cfgs[i].property))
>>> +    for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++)
>>> +        if (of_property_present(np, sdhci_cdns4_phy_cfgs[i].property))
>>>               count++;
>>>         return count;
>>>   }
>>>   -static void sdhci_cdns_phy_param_parse(struct device_node *np,
>>> -                       struct sdhci_cdns_priv *priv)
>>> +static void sdhci_cdns4_phy_param_parse(struct device_node *np,
>>> +                    struct sdhci_cdns_priv *priv)
>>>   {
>>> -    struct sdhci_cdns_phy_param *p = priv->phy_params;
>>> +    struct sdhci_cdns4_phy_param *p = priv->phy_params;
>>>       u32 val;
>>>       int ret, i;
>>>   -    for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) {
>>> -        ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property,
>>> +    for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++) {
>>> +        ret = of_property_read_u32(np, sdhci_cdns4_phy_cfgs[i].property,
>>>                          &val);
>>>           if (ret)
>>>               continue;
>>>   -        p->addr = sdhci_cdns_phy_cfgs[i].addr;
>>> +        p->addr = sdhci_cdns4_phy_cfgs[i].addr;
>>>           p->data = val;
>>>           p++;
>>>       }
>>>   }
>>>   -static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
>>> +static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
>>>   {
>>>       int ret, i;
>>>         for (i = 0; i < priv->nr_phy_params; i++) {
>>> -        ret = sdhci_cdns_write_phy_reg(priv, priv->phy_params[i].addr,
>>> -                           priv->phy_params[i].data);
>>> +        ret = sdhci_cdns4_write_phy_reg(priv, priv->phy_params[i].addr,
>>> +                        priv->phy_params[i].data);
>>>           if (ret)
>>>               return ret;
>>>       }
>>> @@ -201,7 +201,7 @@ static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
>>>       return 0;
>>>   }
>>>   -static void *sdhci_cdns_priv(struct sdhci_host *host)
>>> +static void *sdhci_cdns_get_priv(struct sdhci_host *host)
>> Why rename this?  It seems to just create churn.
> 
> The reason was to resolve a name collision: in the original code
> both the data structure and the accessor function were named
> sdhci_cdns_priv, which can reduce readability. Renaming the
> accessor to sdhci_cdns_get_priv() makes the intent clearer and
> avoids this ambiguity.
> If you would prefer to keep the original name and avoid the churn,
> I will revert this rename in v2.

Yes please

> 
>>
>>>   {
>>>       struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>   @@ -238,7 +238,7 @@ static u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv)
>>>     static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
>>>   {
>>> -    struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>>> +    struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>>       void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06;
>>>       u32 tmp;
>>>       int i, ret;
>>> @@ -354,7 +354,7 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
>>>   static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
>>>                        unsigned int timing)
>>>   {
>>> -    struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>>> +    struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>>       u32 mode;
>>>         switch (timing) {
>>> @@ -407,12 +407,12 @@ static void elba_priv_writel(struct sdhci_cdns_priv *priv, u32 val,
>>>     static void elba_write_l(struct sdhci_host *host, u32 val, int reg)
>>>   {
>>> -    elba_priv_writel(sdhci_cdns_priv(host), val, host->ioaddr + reg);
>>> +    elba_priv_writel(sdhci_cdns_get_priv(host), val, host->ioaddr + reg);
>>>   }
>>>     static void elba_write_w(struct sdhci_host *host, u16 val, int reg)
>>>   {
>>> -    struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>>> +    struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>>       u32 shift = reg & GENMASK(1, 0);
>>>       unsigned long flags;
>>>       u32 byte_enables;
>>> @@ -426,7 +426,7 @@ static void elba_write_w(struct sdhci_host *host, u16 val, int reg)
>>>     static void elba_write_b(struct sdhci_host *host, u8 val, int reg)
>>>   {
>>> -    struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>>> +    struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>>       u32 shift = reg & GENMASK(1, 0);
>>>       unsigned long flags;
>>>       u32 byte_enables;
>>> @@ -452,7 +452,7 @@ static const struct sdhci_ops sdhci_elba_ops = {
>>>   static int elba_drv_init(struct platform_device *pdev)
>>>   {
>>>       struct sdhci_host *host = platform_get_drvdata(pdev);
>>> -    struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>>> +    struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>>       void __iomem *ioaddr;
>>>         host->mmc->caps |= MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA;
>>> @@ -470,7 +470,7 @@ static int elba_drv_init(struct platform_device *pdev)
>>>       return 0;
>>>   }
>>>   -static const struct sdhci_ops sdhci_cdns_ops = {
>>> +static const struct sdhci_ops sdhci_cdns4_ops = {
>>>       .set_clock = sdhci_set_clock,
>>>       .get_timeout_clock = sdhci_cdns_get_timeout_clock,
>>>       .set_bus_width = sdhci_set_bus_width,
>>> @@ -481,7 +481,7 @@ static const struct sdhci_ops sdhci_cdns_ops = {
>>>     static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
>>>       .pltfm_data = {
>>> -        .ops = &sdhci_cdns_ops,
>>> +        .ops = &sdhci_cdns4_ops,
>>>           .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
>>>       },
>>>   };
>>> @@ -495,14 +495,14 @@ static const struct sdhci_cdns_drv_data sdhci_elba_drv_data = {
>>>     static const struct sdhci_cdns_drv_data sdhci_eyeq_drv_data = {
>>>       .pltfm_data = {
>>> -        .ops = &sdhci_cdns_ops,
>>> +        .ops = &sdhci_cdns4_ops,
>>>           .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
>>>       },
>>>   };
>>>   -static const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = {
>>> +static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
>>>       .pltfm_data = {
>>> -        .ops = &sdhci_cdns_ops,
>>> +        .ops = &sdhci_cdns4_ops,
>>>       },
>>>   };
>>>   @@ -510,7 +510,7 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
>>>                            struct mmc_ios *ios)
>>>   {
>>>       struct sdhci_host *host = mmc_priv(mmc);
>>> -    struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>>> +    struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>>       u32 mode;
>>>         priv->enhanced_strobe = ios->enhanced_strobe;
>>> @@ -529,7 +529,7 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
>>>   static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc)
>>>   {
>>>       struct sdhci_host *host = mmc_priv(mmc);
>>> -    struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
>>> +    struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>>>         dev_dbg(mmc_dev(host->mmc), "emmc hardware reset\n");
>>>   @@ -560,9 +560,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>>>         data = of_device_get_match_data(dev);
>>>       if (!data)
>>> -        data = &sdhci_cdns_drv_data;
>>> +        data = &sdhci_cdns4_drv_data;
>>>   -    nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node);
>>> +    nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
>>>       host = sdhci_pltfm_init(pdev, &data->pltfm_data,
>>>                   struct_size(priv, phy_params, nr_phy_params));
>>>       if (IS_ERR(host))
>>> @@ -593,9 +593,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>>>       if (ret)
>>>           return ret;
>>>   -    sdhci_cdns_phy_param_parse(dev->of_node, priv);
>>> +    sdhci_cdns4_phy_param_parse(dev->of_node, priv);
>>>   -    ret = sdhci_cdns_phy_init(priv);
>>> +    ret = sdhci_cdns4_phy_init(priv);
>>>       if (ret)
>>>           return ret;
>>>   @@ -622,7 +622,7 @@ static int sdhci_cdns_resume(struct device *dev)
>>>       if (ret)
>>>           return ret;
>>>   -    ret = sdhci_cdns_phy_init(priv);
>>> +    ret = sdhci_cdns4_phy_init(priv);
>>>       if (ret)
>>>           goto disable_clk;
>>>   


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

* Re: [PATCH v1 8/9] mmc: sdhci-cadence: add Cadence SD6HC support
  2026-05-19 15:29   ` Adrian Hunter
@ 2026-05-21 17:28     ` Kathpalia, Tanmay
  2026-05-22  5:32       ` Adrian Hunter
  0 siblings, 1 reply; 32+ messages in thread
From: Kathpalia, Tanmay @ 2026-05-21 17:28 UTC (permalink / raw)
  To: Adrian Hunter, linux-mmc; +Cc: ulf.hansson, Philipp Zabel, linux-kernel

Hi Adrian,

Thanks for your feedback.

On 5/19/2026 8:59 PM, Adrian Hunter wrote:
> On 11/05/2026 23:21, Tanmay Kathpalia wrote:
>> The Cadence SD6HC is the sixth-generation SD Host Controller used in
>> Agilex5 SoCs. Its PHY differs substantially from the SD4HC: it
>> requires per-speed-mode IO cell timing parameters and a DLL-based
>> delay line to achieve correct signal margins across all speed grades
>> from Default Speed to HS400.
>>
>> Support is implemented in a new sdhci-cadence6.c alongside the
>> existing v4 code, now in sdhci-cadence4.c. Shared structures and the
>> v6 function interface are declared in a new sdhci-cadence.h header.
>> The common driver paths select between v4 and v6 PHY operations based
>> on the SDHCI specification version reported by the controller.
>>
>> The new compatible string "cdns,sd6hc" identifies SD6HC hardware in
>> device tree.
>>
>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>> ---
>>   MAINTAINERS                                   |    7 +
>>   drivers/mmc/host/Makefile                     |    3 +-
>>   drivers/mmc/host/sdhci-cadence.h              |  113 ++
>>   .../{sdhci-cadence.c => sdhci-cadence4.c}     |   92 +-
>>   drivers/mmc/host/sdhci-cadence6.c             | 1051 +++++++++++++++++
>>   5 files changed, 1228 insertions(+), 38 deletions(-)
>>   create mode 100644 drivers/mmc/host/sdhci-cadence.h
>>   rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence4.c} (91%)
>>   create mode 100644 drivers/mmc/host/sdhci-cadence6.c
> The new file names seem slightly misleading since sdhci-cadence4.c
> is the main driver as well as supporting V4, whereas sdhci-cadence6.c
> is most of V6 support.
>
> There doesn't seem much to gain from having a separate sdhci-cadence6.c.
> Isn't it simpler to just put it all in 1 source file.

I believe keeping them separate improves maintainability for the
following reasons:

1. V4 and V6 PHY programming models are fundamentally different:
     a. SD4HC reads pre-computed delay values directly from DT and
     writes them one-to-one into PHY registers.
     b. SD6HC takes only a few timing parameters (IO cell delay, delay
     element size) from DT and derives all PHY and SDHC register
     programming values inside the driver through a calibration
     sequence.
2. The V6 PHY logic alone is more than 1000 lines. Merging it into
the existing file would make sdhci-cadence4.c significantly harder
to review, and maintain.
3. Keep code structure aligned with U-Boot, where V6 handling is
also separated.

That said, I am open to merging into one file if you think that is
better for maintainability in this driver. I can do that in v2.

>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 27fefd92744c..4856450bfd36 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -23863,6 +23863,13 @@ L:	linux-mmc@vger.kernel.org
>>   S:	Maintained
>>   F:	drivers/mmc/host/sdhci-brcmstb*
>>   
>> +SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) CADENCE DRIVER
>> +M:	Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>> +L:	linux-mmc@vger.kernel.org
>> +S:	Maintained
>> +F:	Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
>> +F:	drivers/mmc/host/sdhci-cadence*
>> +
>>   SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) DRIVER
>>   M:	Adrian Hunter <adrian.hunter@intel.com>
>>   L:	linux-mmc@vger.kernel.org
>> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
>> index ee412e6b84d6..f3b2f43e751e 100644
>> --- a/drivers/mmc/host/Makefile
>> +++ b/drivers/mmc/host/Makefile
>> @@ -80,7 +80,8 @@ obj-$(CONFIG_MMC_REALTEK_USB)	+= rtsx_usb_sdmmc.o
>>   
>>   obj-$(CONFIG_MMC_SDHCI_PLTFM)		+= sdhci-pltfm.o
>>   obj-$(CONFIG_MMC_SDHCI_CADENCE)		+= sdhci-cadence.o
>> -obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF)       += sdhci-esdhc-mcf.o
>> +sdhci-cadence-y				+= sdhci-cadence4.o sdhci-cadence6.o
>> +obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF)	+= sdhci-esdhc-mcf.o
>>   obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX)	+= sdhci-esdhc-imx.o
>>   obj-$(CONFIG_MMC_SDHCI_DOVE)		+= sdhci-dove.o
>>   obj-$(CONFIG_MMC_SDHCI_TEGRA)		+= sdhci-tegra.o
>> diff --git a/drivers/mmc/host/sdhci-cadence.h b/drivers/mmc/host/sdhci-cadence.h
>> new file mode 100644
>> index 000000000000..05b8d8f16543
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-cadence.h
>> @@ -0,0 +1,113 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2026 Altera Corporation
>> + *   Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>> + *
>> + * Cadence SD/SDIO/eMMC Host Controller driver - common header
>> + * Shared definitions and structures for the Cadence SDHCI driver.
>> + * Contains private data and declarations for SD6HC-specific functions
>> + * called by the main driver in sdhci-cadence4.c.
>> + */
>> +
>> +#ifndef _MMC_HOST_SDHCI_CADENCE_H
>> +#define _MMC_HOST_SDHCI_CADENCE_H
>> +
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/reset.h>
>> +#include <linux/clk.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/mmc/host.h>
>> +
>> +#include "sdhci-pltfm.h"
>> +
>> +/* HRS - Host Register Set (specific to Cadence) */
>> +#define SDHCI_CDNS_HRS04		0x10		/* PHY access: address port */
>> +#define SDHCI_CDNS_HRS05		0x14		/* PHY access: data port */
>> +
>> +/*
>> + * The tuned val register is 6 bit-wide, but not the whole of the range is
>> + * available.  The range 0-42 seems to be available (then 43 wraps around to 0)
>> + * but I am not quite sure if it is official.  Use only 0 to 39 for safety.
>> + */
>> +#define SDHCI_CDNS_MAX_TUNING_LOOP	40
>> +
>> +/**
>> + * struct sdhci_cdns_priv - Cadence SDHCI private controller data
>> + * @hrs_addr: Base address of Cadence Host Register Set (HRS) registers.
>> + * @ctl_addr: Base address for write control registers.
>> + *            Used only for "amd,pensando-elba-sd4hc" compatible controllers
>> + *            to enable byte-lane writes.
>> + * @wrlock: Spinlock for protecting register writes (Elba only).
>> + * @enhanced_strobe: Flag indicating if Enhanced Strobe (HS400ES) is enabled.
>> + * @priv_writel: Optional SoC-specific write function for register access.
>> + *               Used for Elba to ensure correct byte-lane enable.
>> + * @rst_hw: Hardware reset control for the controller.
>> + * @phy: Opaque pointer to variant-specific PHY data.
>> + *       For SD4HC: points to struct sdhci_cdns4_phy.
>> + *       For SD6HC: points to struct sdhci_cdns6_phy.
>> + */
>> +struct sdhci_cdns_priv {
>> +	void __iomem *hrs_addr;
>> +	void __iomem *ctl_addr; /* write control */
>> +	spinlock_t wrlock; /* write lock */
>> +	bool enhanced_strobe;
>> +	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val,
>> +			    void __iomem *reg);
>> +	struct reset_control *rst_hw;
>> +	void *phy;
>> +};
>> +
>> +/*
>> + * sdhci_cdns_get_priv - Helper to retrieve Cadence private data from sdhci_host
>> + * @host: Pointer to struct sdhci_host.
>> + *
>> + * Return: Pointer to struct sdhci_cdns_priv.
>> + */
>> +static inline void *sdhci_cdns_get_priv(struct sdhci_host *host)
>> +{
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +
>> +	return sdhci_pltfm_priv(pltfm_host);
>> +}
>> +
>> +/**
>> + * sdhci_cdns6_set_uhs_signaling - Program PHY registers for a specific timing mode.
>> + * @host: Pointer to struct sdhci_host.
>> + * @timing: MMC timing mode (MMC_TIMING_*).
>> + */
>> +void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing);
>> +
>> +/**
>> + * sdhci_cdns6_set_tune_val - Set the PHY tuning value.
>> + * @host: Pointer to struct sdhci_host.
>> + * @val: Tuning value to program.
>> + *
>> + * Return: 0 on success, -ETIMEDOUT if PHY initialization times out.
>> + */
>> +int sdhci_cdns6_set_tune_val(struct sdhci_host *host, unsigned int val);
>> +
>> +/**
>> + * sdhci_cdns6_phy_probe - Probe and initialize Cadence SD6HC PHY parameters
>> + * @pdev: Platform device pointer
>> + * @priv: Pointer to Cadence private data structure
>> + *
>> + * Return: 0 on success or a negative error code.
>> + */
>> +int sdhci_cdns6_phy_probe(struct platform_device *pdev,
>> +			  struct sdhci_cdns_priv *priv);
>> +/**
>> + * sdhci_cdns6_hw_reset - Perform hardware reset of the Cadence SDHCI controller.
>> + * @host: Pointer to struct sdhci_host.
>> + */
>> +void sdhci_cdns6_hw_reset(struct sdhci_host *host);
>> +
>> +/**
>> + * sdhci_cdns6_phy_init - Initialize the SD6HC PHY with current settings.
>> + * @priv: Pointer to Cadence private data structure.
>> + *
>> + * Return: 0 on success, -ETIMEDOUT if PHY initialization times out.
>> + */
>> +int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv);
>> +
>> +#endif /* _MMC_HOST_SDHCI_CADENCE_H */
>> diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence4.c
>> similarity index 91%
>> rename from drivers/mmc/host/sdhci-cadence.c
>> rename to drivers/mmc/host/sdhci-cadence4.c
>> index fe3f7c5109fc..283d22328248 100644
>> --- a/drivers/mmc/host/sdhci-cadence.c
>> +++ b/drivers/mmc/host/sdhci-cadence4.c
>> @@ -2,22 +2,17 @@
>>   /*
>>    * Copyright (C) 2016 Socionext Inc.
>>    *   Author: Masahiro Yamada <yamada.masahiro@socionext.com>
>> + * Copyright (C) 2026 Altera Corporation
>>    */
>>   
>>   #include <linux/bitfield.h>
>>   #include <linux/bits.h>
>> -#include <linux/iopoll.h>
>>   #include <linux/module.h>
>> -#include <linux/mmc/host.h>
>> -#include <linux/mmc/mmc.h>
>> -#include <linux/of.h>
>> -#include <linux/platform_device.h>
>> -#include <linux/reset.h>
>>   
>> -#include "sdhci-pltfm.h"
>> +#include "sdhci-cadence.h"
>>   
>>   /* HRS - Host Register Set (specific to Cadence) */
>> -#define SDHCI_CDNS_HRS04		0x10		/* PHY access port */
>> +/* HRS04 (PHY access) bitfields (SD4HC) */
>>   #define   SDHCI_CDNS_HRS04_ACK			BIT(26)
>>   #define   SDHCI_CDNS_HRS04_RD			BIT(25)
>>   #define   SDHCI_CDNS_HRS04_WR			BIT(24)
>> @@ -71,13 +66,6 @@
>>   #define SDHCI_CDNS_PHY_DLY_HSMMC	0x0c
>>   #define SDHCI_CDNS_PHY_DLY_STROBE	0x0d
>>   
>> -/*
>> - * The tuned val register is 6 bit-wide, but not the whole of the range is
>> - * available.  The range 0-42 seems to be available (then 43 wraps around to 0)
>> - * but I am not quite sure if it is official.  Use only 0 to 39 for safety.
>> - */
>> -#define SDHCI_CDNS_MAX_TUNING_LOOP	40
>> -
>>   struct sdhci_cdns4_phy_param {
>>   	u8 addr;
>>   	u8 data;
>> @@ -88,16 +76,6 @@ struct sdhci_cdns4_phy {
>>   	struct sdhci_cdns4_phy_param phy_params[];
>>   };
>>   
>> -struct sdhci_cdns_priv {
>> -	void __iomem *hrs_addr;
>> -	void __iomem *ctl_addr;	/* write control */
>> -	spinlock_t wrlock;	/* write lock */
>> -	bool enhanced_strobe;
>> -	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
>> -	struct reset_control *rst_hw;
>> -	struct sdhci_cdns4_phy *phy;
>> -};
>> -
>>   struct sdhci_cdns4_phy_cfg {
>>   	const char *property;
>>   	u8 addr;
>> @@ -206,13 +184,6 @@ static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
>>   	return 0;
>>   }
>>   
>> -static void *sdhci_cdns_get_priv(struct sdhci_host *host)
>> -{
>> -	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> -
>> -	return sdhci_pltfm_priv(pltfm_host);
>> -}
>> -
>>   static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
>>   {
>>   	/*
>> @@ -248,6 +219,9 @@ static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
>>   	u32 tmp;
>>   	int i, ret;
>>   
>> +	if (host->version >= SDHCI_SPEC_420)
>> +		return sdhci_cdns6_set_tune_val(host, val);
>> +
>>   	if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val)))
>>   		return -EINVAL;
>>   
>> @@ -328,8 +302,11 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
>>   	 * The delay is set by probe, based on the DT properties.
>>   	 */
>>   	if (host->timing != MMC_TIMING_MMC_HS200 &&
>> -	    host->timing != MMC_TIMING_UHS_SDR104)
>> +	    host->timing != MMC_TIMING_UHS_SDR104) {
>> +		dev_dbg(mmc_dev(host->mmc), "Tuning skipped (timing: %d)\n",
>> +			host->timing);
>>   		return 0;
>> +	}
>>   
>>   	for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) {
>>   		if (sdhci_cdns_set_tune_val(host, i) ||
>> @@ -353,6 +330,10 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
>>   	if (ret)
>>   		return ret;
>>   
>> +	/* Block gap tuning is only required for SD4HC, not for SD6HC */
>> +	if (host->version >= SDHCI_SPEC_420)
>> +		return 0;
>> +
>>   	return sdhci_cdns_tune_blkgap(host->mmc);
>>   }
>>   
>> @@ -388,6 +369,10 @@ static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
>>   	/* For SD, fall back to the default handler */
>>   	if (mode == SDHCI_CDNS_HRS06_MODE_SD)
>>   		sdhci_set_uhs_signaling(host, timing);
>> +
>> +	/* For host controller V6, set SDHCI and PHY registers for UHS signaling */
>> +	if (host->version >= SDHCI_SPEC_420)
>> +		sdhci_cdns6_set_uhs_signaling(host, timing);
>>   }
>>   
>>   /* Elba control register bits [6:3] are byte-lane enables */
>> @@ -484,6 +469,16 @@ static const struct sdhci_ops sdhci_cdns4_ops = {
>>   	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
>>   };
>>   
>> +static const struct sdhci_ops sdhci_cdns6_ops = {
>> +	.set_clock = sdhci_set_clock,
>> +	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
>> +	.set_bus_width = sdhci_set_bus_width,
>> +	.reset = sdhci_reset,
>> +	.platform_execute_tuning = sdhci_cdns_execute_tuning,
>> +	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
>> +	.hw_reset = sdhci_cdns6_hw_reset,
>> +};
>> +
>>   static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
>>   	.pltfm_data = {
>>   		.ops = &sdhci_cdns4_ops,
>> @@ -511,6 +506,12 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
>>   	},
>>   };
>>   
>> +static const struct sdhci_cdns_drv_data sdhci_cdns6_drv_data = {
>> +	.pltfm_data = {
>> +		.ops = &sdhci_cdns6_ops,
>> +	},
>> +};
>> +
>>   static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
>>   					     struct mmc_ios *ios)
>>   {
>> @@ -607,15 +608,24 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>>   			return ret;
>>   	}
>>   	sdhci_enable_v4_mode(host);
>> -	__sdhci_read_caps(host, &version, NULL, NULL);
>> -
>>   	sdhci_get_of_property(pdev);
>>   
>>   	ret = mmc_of_parse(host->mmc);
>>   	if (ret)
>>   		return ret;
>>   
>> -	ret = sdhci_cdns4_phy_probe(pdev, priv);
>> +	/*
>> +	 * For SD4HC, read capabilities with fixed version override.
>> +	 * For SD6HC, sdhci_add_host() will automatically read capabilities
>> +	 * and version from the host controller registers.
>> +	 */
>> +	if (of_device_is_compatible(dev->of_node, "cdns,sd4hc")) {
>> +		__sdhci_read_caps(host, &version, NULL, NULL);
>> +		ret = sdhci_cdns4_phy_probe(pdev, priv);
>> +	} else {
>> +		ret = sdhci_cdns6_phy_probe(pdev, priv);
>> +	}
>> +
>>   	if (ret)
>>   		return ret;
>>   
>> @@ -642,7 +652,11 @@ static int sdhci_cdns_resume(struct device *dev)
>>   	if (ret)
>>   		return ret;
>>   
>> -	ret = sdhci_cdns4_phy_init(priv);
>> +	if (host->version >= SDHCI_SPEC_420)
>> +		ret = sdhci_cdns6_phy_init(priv);
>> +	else
>> +		ret = sdhci_cdns4_phy_init(priv);
>> +
>>   	if (ret)
>>   		goto disable_clk;
>>   
>> @@ -677,6 +691,10 @@ static const struct of_device_id sdhci_cdns_match[] = {
>>   		.compatible = "cdns,sd4hc",
>>   		.data = &sdhci_cdns4_drv_data,
>>   	},
>> +	{
>> +		.compatible = "cdns,sd6hc",
>> +		.data = &sdhci_cdns6_drv_data,
>> +	},
>>   	{ /* sentinel */ }
>>   };
>>   MODULE_DEVICE_TABLE(of, sdhci_cdns_match);
>> diff --git a/drivers/mmc/host/sdhci-cadence6.c b/drivers/mmc/host/sdhci-cadence6.c
>> new file mode 100644
>> index 000000000000..34d3570a0bbb
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-cadence6.c
>> @@ -0,0 +1,1051 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * PHY and host controller support for Cadence SD6HC SDHCI
>> + *
>> + * This file provides comprehensive support for Cadence's sixth-generation
>> + * SDHCI controller (SD6HC), handling both low-level PHY operations and
>> + * high-level host controller programming. The implementation includes PHY
>> + * initialization and timing calculations, DLL (Delay-Locked Loop)
>> + * management and configuration, host controller register programming for
>> + * IO delay compensation and signal timing optimization.
>> + *
>> + * Copyright (C) 2026 Altera Corporation
>> + *   Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>> + */
>> +
>> +#include "sdhci-cadence.h"
>> +
>> +/* IO Delay Information */
>> +#define SDHCI_CDNS_HRS07		0x1c
>> +#define   SDHCI_CDNS_HRS07_RW_COMPENSATE	GENMASK(20, 16)
>> +#define   SDHCI_CDNS_HRS07_IDELAY_VAL		GENMASK(4, 0)
>> +
>> +/* PHY Control and Status */
>> +#define SDHCI_CDNS_HRS09		0x24
>> +#define   SDHCI_CDNS_HRS09_RDDATA_EN		BIT(16)
>> +#define   SDHCI_CDNS_HRS09_RDCMD_EN		BIT(15)
>> +#define   SDHCI_CDNS_HRS09_EXTENDED_WR_MODE	BIT(3)
>> +#define   SDHCI_CDNS_HRS09_EXTENDED_RD_MODE	BIT(2)
>> +#define   SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE	BIT(1)
>> +#define   SDHCI_CDNS_HRS09_PHY_SW_RESET		BIT(0)
>> +
>> +/* SDCLK start point adjustment */
>> +#define SDHCI_CDNS_HRS10		0x28
>> +#define   SDHCI_CDNS_HRS10_HCSDCLKADJ		GENMASK(19, 16)
>> +
>> +/* eMMC Control */
>> +#define SDHCI_CDNS_HRS11		0x2c
>> +#define   SDHCI_CDNS_HRS11_EMMC_RST		BIT(0) /* eMMC reset */
>> +
>> +/* CMD/DAT output delay */
>> +#define SDHCI_CDNS_HRS16		0x40
>> +#define   SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY	GENMASK(31, 28)
>> +#define   SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY	GENMASK(27, 24)
>> +#define   SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY	GENMASK(23, 20)
>> +#define   SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY	GENMASK(19, 16)
>> +#define   SDHCI_CDNS_HRS16_WRDATA1_DLY		GENMASK(15, 12)
>> +#define   SDHCI_CDNS_HRS16_WRDATA0_DLY		GENMASK(11, 8)
>> +#define   SDHCI_CDNS_HRS16_WRCMD1_DLY		GENMASK(7, 4)
>> +#define   SDHCI_CDNS_HRS16_WRCMD0_DLY		GENMASK(3, 0)
>> +
>> +/* PHY Special Function Registers */
>> +/* DQ timing */
>> +#define SDHCI_CDNS6_PHY_DQ_TIMING_REG			0x2000
>> +#define   SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON		BIT(31)
>> +#define   SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END			GENMASK(29, 27)
>> +#define   SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START		GENMASK(26, 24)
>> +#define   SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END		GENMASK(2, 0)
>> +
>> +/* DQS timing */
>> +#define SDHCI_CDNS6_PHY_DQS_TIMING_REG			0x2004
>> +#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS		BIT(22)
>> +#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS		BIT(21)
>> +#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS		BIT(20)
>> +#define   SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD		BIT(19)
>> +
>> +/* Gate and loopback control */
>> +#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG		0x2008
>> +#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD		BIT(31)
>> +#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SW_HALF_CYCLE_SHIFT	BIT(28)
>> +#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL		GENMASK(24, 19)
>> +#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS	BIT(18)
>> +#define   SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON	BIT(6)
>> +
>> +/* Master DLL logic */
>> +#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_REG		0x200c
>> +#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_BYPASS_MODE		BIT(23)
>> +#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_PHASE_DETECT_SEL	GENMASK(22, 20)
>> +#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM		GENMASK(18, 16)
>> +#define   SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT	GENMASK(7, 0)
>> +
>> +/* Slave DLL logic */
>> +#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG		0x2010
>> +#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY	GENMASK(31, 24)
>> +#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WRDQS_DELAY	GENMASK(23, 16)
>> +#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WR_DELAY		GENMASK(15, 8)
>> +#define   SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY		GENMASK(7, 0)
>> +
>> +/* Global control settings */
>> +#define SDHCI_CDNS6_PHY_CTRL_REG			0x2080
>> +#define   SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING			GENMASK(9, 4)
>> +
>> +/* Default PHY settings */
>> +#define SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY	2500
>> +#define SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT	24
>> +#define SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL	52
>> +#define SDHCI_CDNS6_PHY_DEFAULT_DLL_START	4
>> +
>> +/* Scale tuning tap (0..39) to 8-bit PHY DLL delay field (0..255) */
>> +#define SDHCI_CDNS6_PHY_DLL_FIELD_SIZE		256
>> +
>> +struct sdhci_cdns6_phy {
>> +	/*
>> +	 * Mode-specific timing constraints (in picoseconds)
>> +	 * These define valid output/input windows per SD/eMMC spec
>> +	 */
>> +	u32 t_cmd_output_min;
>> +	u32 t_cmd_output_max;
>> +	u32 t_dat_output_min;
>> +	u32 t_dat_output_max;
>> +	u32 t_cmd_input_min;
>> +	u32 t_cmd_input_max;
>> +	u32 t_dat_input_min;
>> +	u32 t_dat_input_max;
>> +
>> +	/*
>> +	 * PHY delay configuration (in picoseconds)
>> +	 * Derived from clock period and board-level IO cell delays
>> +	 */
>> +	u32 phy_sdclk_delay;
>> +	u32 phy_cmd_o_delay;
>> +	u32 phy_dat_o_delay;
>> +	u32 iocell_input_delay;
>> +	u32 iocell_output_delay;
>> +	u32 delay_element_org;	/* Original delay element from DT */
>> +	u32 delay_element;	/* Current delay element (may be doubled) */
>> +
>> +	/* PHY_DLL_SLAVE_CTRL register fields */
>> +	u32 cp_read_dqs_cmd_delay;
>> +	u32 cp_read_dqs_delay;
>> +	u32 cp_clk_wr_delay;
>> +	u32 cp_clk_wrdqs_delay;
>> +
>> +	/* PHY_DLL_MASTER_CTRL register fields */
>> +	u32 cp_dll_bypass_mode;
>> +	u32 cp_dll_start_point;
>> +
>> +	/* PHY_GATE_LPBK_CTRL register fields */
>> +	u32 cp_gate_cfg_always_on;
>> +	u32 cp_sync_method;
>> +	u32 cp_rd_del_sel;
>> +	u32 cp_sw_half_cycle_shift;
>> +	u32 cp_underrun_suppress;
>> +
>> +	/* PHY_DQ_TIMING register fields */
>> +	u32 cp_io_mask_always_on;
>> +	u32 cp_io_mask_end;
>> +	u32 cp_io_mask_start;
>> +	u32 cp_data_select_oe_end;
>> +
>> +	/* PHY_DQS_TIMING register fields */
>> +	u32 cp_use_ext_lpbk_dqs;
>> +	u32 cp_use_lpbk_dqs;
>> +	u8 cp_use_phony_dqs;
>> +	u8 cp_use_phony_dqs_cmd;
>> +
>> +	/* HRS09 register fields - PHY control and Status */
>> +	u8 sdhc_extended_rd_mode;
>> +	u8 sdhc_extended_wr_mode;
>> +	u32 sdhc_rdcmd_en;
>> +	u32 sdhc_rddata_en;
>> +
>> +	/* HRS10 register - SDCLK start point adjustment */
>> +	u32 sdhc_hcsdclkadj;
>> +
>> +	/* HRS07 register - IO delay Information */
>> +	u32 sdhc_idelay_val;
>> +	u32 sdhc_rw_compensate;
>> +
>> +	/* HRS16 register fields - CMD/DAT output delay control */
>> +	u32 sdhc_wrcmd0_dly;
>> +	u32 sdhc_wrcmd0_sdclk_dly;
>> +	u32 sdhc_wrcmd1_dly;
>> +	u32 sdhc_wrcmd1_sdclk_dly;
>> +	u32 sdhc_wrdata0_dly;
>> +	u32 sdhc_wrdata0_sdclk_dly;
>> +	u32 sdhc_wrdata1_dly;
>> +	u32 sdhc_wrdata1_sdclk_dly;
>> +
>> +	/*
>> +	 * DLL calculation intermediate values
>> +	 * Used during PHY timing calculations
>> +	 */
>> +	u32 t_sdmclk_calc;	/* Calculated SDMCLK period for DLL */
>> +	u32 dll_max_value;	/* Max DLL delay value (127/255/256) */
>> +
>> +	/* Tuning value for HS200/HS400 modes */
>> +	u32 hs200_tune_val;
>> +
>> +	/* Clock periods (in picoseconds) */
>> +	u32 t_sdmclk;		/* Master clock period */
>> +	u32 t_sdclk;		/* SD card clock period */
>> +
>> +	/* Current operating state */
>> +	bool strobe_cmd;	/* Enhanced strobe for CMD line */
>> +	unsigned int mode;	/* Current MMC_TIMING_* mode */
>> +};
>> +
>> +/**
>> + * init_ds() - Initialize PHY timing for Default Speed mode (up to 25 MHz).
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_ds(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 5000;
>> +	phy->t_cmd_output_max = t_sdclk - 5000;
>> +	phy->t_dat_output_min = 5000;
>> +	phy->t_dat_output_max = t_sdclk - 5000;
>> +	phy->t_cmd_input_min = t_sdclk / 2 + 14000;
>> +	phy->t_cmd_input_max = t_sdclk + t_sdclk / 2;
>> +	phy->t_dat_input_min = t_sdclk / 2 + 14000;
>> +	phy->t_dat_input_max = t_sdclk + t_sdclk / 2;
>> +}
>> +
>> +/**
>> + * init_hs() - Initialize PHY timing for High Speed mode (up to 50 MHz).
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_hs(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 2000;
>> +	phy->t_cmd_output_max = t_sdclk - 6000;
>> +	phy->t_dat_output_min = 2000;
>> +	phy->t_dat_output_max = t_sdclk - 6000;
>> +	phy->t_cmd_input_min = 14000;
>> +	phy->t_cmd_input_max = t_sdclk + 2500;
>> +	phy->t_dat_input_min = 14000;
>> +	phy->t_dat_input_max = t_sdclk + 2500;
>> +}
>> +
>> +/**
>> + * init_uhs_sdr12() - Initialize PHY timing for UHS SDR12 mode (up to 25 MHz).
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_uhs_sdr12(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 800;
>> +	phy->t_cmd_output_max = t_sdclk - 3000;
>> +	phy->t_dat_output_min = 800;
>> +	phy->t_dat_output_max = t_sdclk - 3000;
>> +	phy->t_cmd_input_min = 14000;
>> +	phy->t_cmd_input_max = t_sdclk + 1500;
>> +	phy->t_dat_input_min = 14000;
>> +	phy->t_dat_input_max = t_sdclk + 1500;
>> +}
>> +
>> +/**
>> + * init_uhs_sdr25() - Initialize PHY timing for UHS SDR25 mode (up to 50 MHz).
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_uhs_sdr25(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 800;
>> +	phy->t_cmd_output_max = t_sdclk - 3000;
>> +	phy->t_dat_output_min = 800;
>> +	phy->t_dat_output_max = t_sdclk - 3000;
>> +	phy->t_cmd_input_min = 14000;
>> +	phy->t_cmd_input_max = t_sdclk + 1500;
>> +	phy->t_dat_input_min = 14000;
>> +	phy->t_dat_input_max = t_sdclk + 1500;
>> +}
>> +
>> +/**
>> + * init_uhs_sdr50() - Initialize PHY timing for UHS SDR50 mode (up to 100 MHz).
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_uhs_sdr50(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 800;
>> +	phy->t_cmd_output_max = t_sdclk - 3000;
>> +	phy->t_dat_output_min = 800;
>> +	phy->t_dat_output_max = t_sdclk - 3000;
>> +	phy->t_cmd_input_min = 7500;
>> +	phy->t_cmd_input_max = t_sdclk + 1500;
>> +	phy->t_dat_input_min = 7500;
>> +	phy->t_dat_input_max = t_sdclk + 1500;
>> +}
>> +
>> +/**
>> + * init_uhs_sdr104() - Initialize PHY timing for UHS SDR104 mode (up to 208 MHz).
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_uhs_sdr104(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 800;
>> +	phy->t_cmd_output_max = t_sdclk - 1400;
>> +	phy->t_dat_output_min = 800;
>> +	phy->t_dat_output_max = t_sdclk - 1400;
>> +	phy->t_cmd_input_min = 1000;
>> +	phy->t_cmd_input_max = t_sdclk + 1000;
>> +	phy->t_dat_input_min = 1000;
>> +	phy->t_dat_input_max = t_sdclk + 1000;
>> +}
>> +
>> +/**
>> + * init_uhs_ddr50() - Initialize PHY timing for UHS DDR50 mode (up to 50 MHz DDR).
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_uhs_ddr50(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 800;
>> +	phy->t_cmd_output_max = t_sdclk - 3000;
>> +	phy->t_dat_output_min = 800;
>> +	phy->t_dat_output_max = t_sdclk - 3000;
>> +	phy->t_cmd_input_min = 13700;
>> +	phy->t_cmd_input_max = t_sdclk + 1500;
>> +	phy->t_dat_input_min = 7000;
>> +	phy->t_dat_input_max = t_sdclk + 1500;
>> +}
>> +
>> +/**
>> + * init_emmc_sdr() - Initialize PHY timing for eMMC legacy/SDR mode.
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_emmc_sdr(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 3000;
>> +	phy->t_cmd_output_max = t_sdclk - 3000;
>> +	phy->t_dat_output_min = 3000;
>> +	phy->t_dat_output_max = t_sdclk - 3000;
>> +	phy->t_cmd_input_min = 13700;
>> +	phy->t_cmd_input_max = t_sdclk + 2500;
>> +	phy->t_dat_input_min = 13700;
>> +	phy->t_dat_input_max = t_sdclk + 2500;
>> +}
>> +
>> +/**
>> + * init_emmc_ddr() - Initialize PHY timing for eMMC DDR52 mode.
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_emmc_ddr(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 3000;
>> +	phy->t_cmd_output_max = t_sdclk - 3000;
>> +	phy->t_dat_output_min = 2500;
>> +	phy->t_dat_output_max = t_sdclk - 2500;
>> +	phy->t_cmd_input_min = 13700;
>> +	phy->t_cmd_input_max = t_sdclk + 2500;
>> +	phy->t_dat_input_min = 7000;
>> +	phy->t_dat_input_max = t_sdclk + 1500;
>> +}
>> +
>> +/**
>> + * init_emmc_hs200() - Initialize PHY timing for eMMC HS200 mode (up to 200 MHz).
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_emmc_hs200(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 800;
>> +	phy->t_cmd_output_max = t_sdclk - 1400;
>> +	phy->t_dat_output_min = 800;
>> +	phy->t_dat_output_max = t_sdclk - 1400;
>> +	phy->t_cmd_input_min = 1000;
>> +	phy->t_cmd_input_max = t_sdclk + 1000;
>> +	phy->t_dat_input_min = 1000;
>> +	phy->t_dat_input_max = t_sdclk + 1000;
>> +}
>> +
>> +/**
>> + * init_emmc_hs400() - Initialize PHY timing for eMMC HS400/HS400ES mode.
>> + * @phy: Pointer to SD6HC PHY state.
>> + * @t_sdclk: SD clock period in picoseconds.
>> + */
>> +static void init_emmc_hs400(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
>> +{
>> +	phy->t_cmd_output_min = 800;
>> +	phy->t_cmd_output_max = t_sdclk - 1400;
>> +	phy->t_dat_output_min = 400;
>> +	phy->t_dat_output_max = t_sdclk - 400;
>> +	phy->t_cmd_input_min = 1000;
>> +	phy->t_cmd_input_max = t_sdclk + 1000;
>> +	phy->t_dat_input_min = 1000;
>> +	phy->t_dat_input_max = t_sdclk + 1000;
>> +}
>> +
>> +/*
>> + * init_timings - PHY timing initializers indexed by MMC_TIMING_* value.
>> + *
>> + * Each entry corresponds to a MMC_TIMING_* constant and sets the
>> + * appropriate cmd/dat output and input timing windows in the PHY
>> + * state struct. Must stay in sync with the MMC_TIMING_* definitions
>> + * in include/linux/mmc/host.h.
>> + */
>> +static void (*init_timings[])(struct sdhci_cdns6_phy *, u32) = {
>> +	&init_ds,
>> +	&init_emmc_sdr,
>> +	&init_hs,
>> +	&init_uhs_sdr12,
>> +	&init_uhs_sdr25,
>> +	&init_uhs_sdr50,
>> +	&init_uhs_sdr104,
>> +	&init_uhs_ddr50,
>> +	&init_emmc_ddr,
>> +	&init_emmc_hs200,
>> +	&init_emmc_hs400,
>> +};
>> +
>> +static unsigned int sdhci_cdns6_read_phy_reg(struct sdhci_cdns_priv *priv,
>> +					     const u32 address)
>> +{
>> +	writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
>> +	return readl(priv->hrs_addr + SDHCI_CDNS_HRS05);
>> +}
>> +
>> +static void sdhci_cdns6_write_phy_reg(struct sdhci_cdns_priv *priv,
>> +				      const u32 address, const u32 value)
>> +{
>> +	writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
>> +	writel(value, priv->hrs_addr + SDHCI_CDNS_HRS05);
>> +}
>> +
>> +static int sdhci_cdns6_phy_lock_dll(struct sdhci_cdns6_phy *phy)
>> +{
>> +	u32 delay_element = phy->delay_element_org;
>> +	u32 delay_elements_in_sdmclk;
>> +
>> +	delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, delay_element);
>> +	if (delay_elements_in_sdmclk > 256) {
>> +		delay_element *= 2;
>> +		delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk,
>> +							delay_element);
>> +
>> +		if (delay_elements_in_sdmclk > 256)
>> +			return -EINVAL;
>> +
>> +		phy->dll_max_value = 127;
>> +	} else {
>> +		phy->dll_max_value = 255;
>> +	}
>> +
>> +	phy->t_sdmclk_calc = delay_element * delay_elements_in_sdmclk;
>> +	phy->delay_element = delay_element;
>> +	phy->cp_dll_bypass_mode = 0;
>> +
>> +	return 0;
>> +}
>> +
>> +static void sdhci_cdns6_phy_dll_bypass(struct sdhci_cdns6_phy *phy)
>> +{
>> +	phy->dll_max_value = 256;
>> +	phy->cp_dll_bypass_mode = 1;
>> +}
>> +
>> +static void sdhci_cdns6_phy_configure_dll(struct sdhci_cdns6_phy *phy)
>> +{
>> +	if (phy->sdhc_extended_wr_mode == 0) {
>> +		if (sdhci_cdns6_phy_lock_dll(phy) == 0)
>> +			return;
>> +	}
>> +	sdhci_cdns6_phy_dll_bypass(phy);
>> +}
>> +
>> +static void sdhci_cdns6_phy_calc_out(struct sdhci_cdns6_phy *phy,
>> +				     bool cmd_not_dat)
>> +{
>> +	u32 wr0_dly = 0, wr1_dly = 0, output_min, output_max, phy_o_delay,
>> +	    clk_wr_delay = 0, wr0_sdclk_dly = 0, wr1_sdclk_dly = 0;
>> +	bool ddr = (phy->mode == MMC_TIMING_UHS_DDR50) ||
>> +			(phy->mode == MMC_TIMING_MMC_DDR52) ||
>> +			(phy->mode == MMC_TIMING_MMC_HS400);
>> +	bool data_ddr = ddr && !cmd_not_dat;
>> +	int t;
>> +
>> +	if (cmd_not_dat) {
>> +		output_min = phy->t_cmd_output_min;
>> +		output_max = phy->t_cmd_output_max;
>> +		phy_o_delay = phy->phy_cmd_o_delay;
>> +	} else {
>> +		output_min = phy->t_dat_output_min;
>> +		output_max = phy->t_dat_output_max;
>> +		phy_o_delay = phy->phy_dat_o_delay;
>> +	}
>> +
>> +	if (data_ddr) {
>> +		wr0_sdclk_dly = 1;
>> +		wr1_sdclk_dly = 1;
>> +	}
>> +
>> +	t = phy_o_delay - phy->phy_sdclk_delay - output_min;
>> +	if (t < 0 && phy->sdhc_extended_wr_mode == 1) {
>> +		u32 n_half_cycle = DIV_ROUND_UP(-t * 2, phy->t_sdmclk);
>> +
>> +		wr0_dly = (n_half_cycle + 1) / 2;
>> +		if (data_ddr)
>> +			wr1_dly = (n_half_cycle + 1) / 2;
>> +		else
>> +			wr1_dly = (n_half_cycle + 1) % 2 + wr0_dly - 1;
>> +	}
>> +
>> +	if (phy->sdhc_extended_wr_mode == 0) {
>> +		u32 out_hold, out_setup, out_hold_margin;
>> +		u32 n;
>> +
>> +		if (!data_ddr)
>> +			wr0_dly = 1;
>> +
>> +		out_setup = output_max;
>> +		out_hold = output_min;
>> +		out_hold_margin = DIV_ROUND_UP(out_setup - out_hold, 4);
>> +		out_hold += out_hold_margin;
>> +
>> +		if (phy->cp_dll_bypass_mode == 0)
>> +			n = DIV_ROUND_UP(256 * out_hold, phy->t_sdmclk_calc);
>> +		else
>> +			n = DIV_ROUND_UP(out_hold, phy->delay_element) - 1;
>> +
>> +		if (n <= phy->dll_max_value)
>> +			clk_wr_delay = n;
>> +		else
>> +			clk_wr_delay = 255;
>> +	} else {
>> +		/*  sdhc_extended_wr_mode = 1 => PHY IO cell work in SDR mode */
>> +		clk_wr_delay = 0;
>> +	}
>> +
>> +	if (cmd_not_dat) {
>> +		phy->sdhc_wrcmd0_dly = wr0_dly;
>> +		phy->sdhc_wrcmd1_dly = wr1_dly;
>> +		phy->cp_clk_wrdqs_delay = clk_wr_delay;
>> +		phy->sdhc_wrcmd0_sdclk_dly = wr0_sdclk_dly;
>> +		phy->sdhc_wrcmd1_sdclk_dly = wr1_sdclk_dly;
>> +	} else {
>> +		phy->sdhc_wrdata0_dly = wr0_dly;
>> +		phy->sdhc_wrdata1_dly = wr1_dly;
>> +		phy->cp_clk_wr_delay = clk_wr_delay;
>> +		phy->sdhc_wrdata0_sdclk_dly = wr0_sdclk_dly;
>> +		phy->sdhc_wrdata1_sdclk_dly = wr1_sdclk_dly;
>> +	}
>> +}
>> +
>> +static void sdhci_cdns6_phy_calc_cmd_out(struct sdhci_cdns6_phy *phy)
>> +{
>> +	sdhci_cdns6_phy_calc_out(phy, true);
>> +}
>> +
>> +static void sdhci_cdns6_phy_calc_cmd_in(struct sdhci_cdns6_phy *phy)
>> +{
>> +	phy->cp_io_mask_end =
>> +		((phy->iocell_output_delay + phy->iocell_input_delay) * 2)
>> +		/ phy->t_sdmclk;
>> +
>> +	/* cp_io_mask_end is a 3-bit field, clamp to max value of 7 */
>> +	phy->cp_io_mask_end = min_t(u32, phy->cp_io_mask_end, 7);
>> +
>> +	if (phy->strobe_cmd && phy->cp_io_mask_end > 0)
>> +		phy->cp_io_mask_end--;
>> +
>> +	if (phy->strobe_cmd) {
>> +		phy->cp_use_phony_dqs_cmd = 0;
>> +		phy->cp_read_dqs_cmd_delay = 64;
>> +	} else {
>> +		phy->cp_use_phony_dqs_cmd = 1;
>> +		phy->cp_read_dqs_cmd_delay = 0;
>> +	}
>> +
>> +	if ((phy->mode == MMC_TIMING_MMC_HS400 && !phy->strobe_cmd) ||
>> +	    phy->mode == MMC_TIMING_MMC_HS200)
>> +		phy->cp_read_dqs_cmd_delay =
>> +			phy->hs200_tune_val;
>> +}
>> +
>> +static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy)
>> +{
>> +	u32 hcsdclkadj = 0;
>> +	bool strobe_dat = (phy->mode == MMC_TIMING_MMC_HS400);
>> +
>> +	if (strobe_dat) {
>> +		phy->cp_use_phony_dqs = 0;
>> +		phy->cp_read_dqs_delay = 64;
>> +	} else {
>> +		phy->cp_use_phony_dqs = 1;
>> +		phy->cp_read_dqs_delay = 0;
>> +	}
>> +
>> +	if (phy->mode == MMC_TIMING_MMC_HS200)
>> +		phy->cp_read_dqs_delay =
>> +			phy->hs200_tune_val;
>> +
>> +	if (strobe_dat) {
>> +		/* dqs loopback input via IO cell */
>> +		hcsdclkadj += phy->iocell_input_delay;
>> +		/* dfi_dqs_in: mem_dqs -> clean_dqs_mod; delay of hic_dll_dqs_nand2 */
>> +		hcsdclkadj += phy->delay_element / 2;
>> +		/* delay line */
>> +		hcsdclkadj += phy->t_sdclk / 2;
>> +		/* PHY FIFO write pointer */
>> +		hcsdclkadj += phy->t_sdclk / 2 + phy->delay_element;
>> +		/* 1st synchronizer */
>> +		hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk)
>> +			* phy->t_sdmclk - hcsdclkadj;
>> +		/*
>> +		 * 2nd synchronizer + PHY FIFO read pointer + PHY rddata
>> +		 * + PHY rddata registered, + FIFO 1st ciu_en
>> +		 */
>> +		hcsdclkadj += 5 * phy->t_sdmclk;
>> +		/* FIFO 2nd ciu_en */
>> +		hcsdclkadj += phy->t_sdclk;
>> +
>> +		hcsdclkadj /= phy->t_sdclk;
>> +	} else {
>> +		u32 n;
>> +
>> +		/* rebar PHY delay */
>> +		hcsdclkadj += 2 * phy->t_sdmclk;
>> +		/* rebar output via IO cell */
>> +		hcsdclkadj += phy->iocell_output_delay;
>> +		/* dqs loopback input via IO cell */
>> +		hcsdclkadj += phy->iocell_input_delay;
>> +		/* dfi_dqs_in: mem_dqs -> clean_dqs_mod delay of hic_dll_dqs_nand2 */
>> +		hcsdclkadj += phy->delay_element / 2;
>> +		/* dll: one delay element between SIGI_0 and SIGO_0 */
>> +		hcsdclkadj += phy->delay_element;
>> +		/* dfi_dqs_in: mem_dqs_delayed -> clk_dqs delay of hic_dll_dqs_nand2 */
>> +		hcsdclkadj += phy->delay_element / 2;
>> +		/* deskew DLL: clk_dqs -> clk_dqN: one delay element */
>> +		hcsdclkadj += phy->delay_element;
>> +
>> +		if (phy->t_sdclk == phy->t_sdmclk)
>> +			n = (hcsdclkadj - 2 * phy->t_sdmclk) / phy->t_sdclk;
>> +		else
>> +			n = hcsdclkadj / phy->t_sdclk;
>> +
>> +		/* phase shift within one t_sdclk clock cycle caused by rebar - lbk dqs delay */
>> +		hcsdclkadj = hcsdclkadj % phy->t_sdclk;
>> +		/* PHY FIFO write pointer */
>> +		hcsdclkadj += phy->t_sdclk / 2;
>> +		/* 1st synchronizer */
>> +		hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk)
>> +			* phy->t_sdmclk - hcsdclkadj;
>> +		/*
>> +		 * 2nd synchronizer + PHY FIFO read pointer + PHY rddata
>> +		 * + PHY rddata registered
>> +		 */
>> +		hcsdclkadj += 4 * phy->t_sdmclk;
>> +
>> +		if ((phy->t_sdclk / phy->t_sdmclk) > 1) {
>> +			u32 tmp1, tmp2;
>> +
>> +			tmp1 = hcsdclkadj;
>> +			tmp2 = (hcsdclkadj / phy->t_sdclk) * phy->t_sdclk
>> +				+ phy->t_sdclk - phy->t_sdmclk;
>> +			if (tmp1 == tmp2)
>> +				tmp2 += phy->t_sdclk;
>> +
>> +			/* FIFO aligns to clock cycle before ciu_en */
>> +			hcsdclkadj += tmp2 - tmp1;
>> +		}
>> +
>> +		/* FIFO 1st ciu_en */
>> +		hcsdclkadj += phy->t_sdmclk;
>> +		/* FIFO 2nd ciu_en */
>> +		hcsdclkadj += phy->t_sdclk;
>> +
>> +		hcsdclkadj /= phy->t_sdclk;
>> +
>> +		hcsdclkadj += n;
>> +
>> +		if ((phy->t_sdclk / phy->t_sdmclk) >= 2) {
>> +			if (phy->mode == MMC_TIMING_UHS_DDR50 ||
>> +			    phy->mode == MMC_TIMING_MMC_DDR52)
>> +				hcsdclkadj -= 2;
>> +			else
>> +				hcsdclkadj -= 1;
>> +		} else if ((phy->t_sdclk / phy->t_sdmclk) == 1) {
>> +			hcsdclkadj += 2;
>> +		}
>> +
>> +		if (phy->mode == MMC_TIMING_UHS_SDR104 || phy->mode == MMC_TIMING_MMC_HS200)
>> +			hcsdclkadj -= 1;
>> +	}
>> +
>> +	/* hcsdclkadj is a 4-bit field, clamp to max value of 15 */
>> +	if (hcsdclkadj > 15)
>> +		hcsdclkadj = 15;
>> +
>> +	phy->sdhc_hcsdclkadj = hcsdclkadj;
>> +}
>> +
>> +static void sdhci_cdns6_phy_calc_dat_out(struct sdhci_cdns6_phy *phy)
>> +{
>> +	sdhci_cdns6_phy_calc_out(phy, false);
>> +}
>> +
>> +static void sdhci_cdns6_phy_calc_io(struct sdhci_cdns6_phy *phy)
>> +{
>> +	u32 rw_compensate;
>> +
>> +	rw_compensate = ((phy->iocell_input_delay + phy->iocell_output_delay)
>> +		/ phy->t_sdmclk) + phy->sdhc_wrdata0_dly + 5 + 3;
>> +
>> +	phy->sdhc_idelay_val = (2 * phy->iocell_input_delay)
>> +		/ phy->t_sdmclk;
>> +
>> +	phy->cp_io_mask_start = 0;
>> +	if (phy->t_sdclk == phy->t_sdmclk && rw_compensate > 10)
>> +		phy->cp_io_mask_start = 2 * (rw_compensate - 10);
>> +
>> +	if (phy->mode == MMC_TIMING_UHS_SDR104)
>> +		phy->cp_io_mask_start++;
>> +
>> +	if (phy->t_sdclk == phy->t_sdmclk && phy->mode == MMC_TIMING_UHS_SDR50)
>> +		phy->cp_io_mask_start++;
>> +
>> +	phy->sdhc_rw_compensate = rw_compensate;
>> +}
>> +
>> +static void sdhci_cdns6_phy_calc_settings(struct sdhci_cdns6_phy *phy)
>> +{
>> +	sdhci_cdns6_phy_calc_cmd_out(phy);
>> +	sdhci_cdns6_phy_calc_cmd_in(phy);
>> +	sdhci_cdns6_phy_calc_dat_out(phy);
>> +	sdhci_cdns6_phy_calc_dat_in(phy);
>> +	sdhci_cdns6_phy_calc_io(phy);
>> +}
>> +
>> +static int sdhci_cdns6_dll_reset(struct sdhci_cdns_priv *priv, bool reset)
>> +{
>> +	u32 reg;
>> +	int ret = 0;
>> +
>> +	reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09);
>> +	if (reset)
>> +		reg &= ~SDHCI_CDNS_HRS09_PHY_SW_RESET;
>> +	else
>> +		reg |= SDHCI_CDNS_HRS09_PHY_SW_RESET;
>> +
>> +	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09);
>> +
>> +	/* After reset, wait until HRS09.PHY_INIT_COMPLETE is set to 1 within 3000us*/
>> +	if (!reset) {
>> +		ret = readl_poll_timeout(priv->hrs_addr + SDHCI_CDNS_HRS09, reg,
>> +					 (reg & SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE),
>> +					 0, 3000);
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv)
>> +{
>> +	int ret;
>> +	u32 reg;
>> +	struct sdhci_cdns6_phy *phy = priv->phy;
>> +
>> +	sdhci_cdns6_dll_reset(priv, true);
>> +
>> +	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG);
>> +	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS;
>> +	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS;
>> +	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS;
>> +	reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD;
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS,
>> +			phy->cp_use_ext_lpbk_dqs);
> drivers/mmc/host/sdhci-cadence6.c: In function ‘sdhci_cdns6_phy_init’:
> drivers/mmc/host/sdhci-cadence6.c:761:16: error: implicit declaration of function ‘FIELD_PREP’ [-Wimplicit-function-declaration]
>    761 |         reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS,
>        |                ^~~~~~~~~~

I will fix this in V2 by adding the required include and rerun build 
validation
before re-sending.
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS,
>> +			phy->cp_use_lpbk_dqs);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS,
>> +			  phy->cp_use_phony_dqs);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD,
>> +			  phy->cp_use_phony_dqs_cmd);
>> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG, reg);
>> +
>> +	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG);
>> +	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD;
>> +	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SW_HALF_CYCLE_SHIFT;
>> +	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL;
>> +	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS;
>> +	reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON;
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD,
>> +			phy->cp_sync_method);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SW_HALF_CYCLE_SHIFT,
>> +			phy->cp_sw_half_cycle_shift);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL,
>> +			phy->cp_rd_del_sel);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS,
>> +			phy->cp_underrun_suppress);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON,
>> +			phy->cp_gate_cfg_always_on);
>> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG, reg);
>> +
>> +	reg = FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_BYPASS_MODE,
>> +			 phy->cp_dll_bypass_mode);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_PHASE_DETECT_SEL, 2);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM, 0);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT,
>> +			phy->cp_dll_start_point);
>> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_REG, reg);
>> +
>> +	reg = FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY,
>> +			 phy->cp_read_dqs_cmd_delay);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WRDQS_DELAY,
>> +			  phy->cp_clk_wrdqs_delay);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WR_DELAY,
>> +			  phy->cp_clk_wr_delay);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY,
>> +			  phy->cp_read_dqs_delay);
>> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG, reg);
>> +
>> +	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG);
>> +	reg &= ~SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING;
>> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG, reg);
>> +
>> +	/*
>> +	 * Ensure all preceding PHY register writes complete and reach the
>> +	 * controller before releasing the PHY from reset. Without this,
>> +	 * SDR104 has been observed to fail intermittently on some boards.
>> +	 */
>> +	wmb();
>> +
>> +	ret = sdhci_cdns6_dll_reset(priv, false);
>> +	if (ret)
>> +		return ret;
>> +
>> +	reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DQ_TIMING_REG);
>> +	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON;
>> +	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END;
>> +	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START;
>> +	reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END;
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON,
>> +			phy->cp_io_mask_always_on);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END,
>> +			  phy->cp_io_mask_end);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START,
>> +			  phy->cp_io_mask_start);
>> +	reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END,
>> +			phy->cp_data_select_oe_end);
>> +	sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQ_TIMING_REG, reg);
>> +
>> +	/* Ensure DQ timing programming is visible before HRS09 follow-up writes */
>> +	wmb();
>> +
>> +	reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09);
>> +	if (phy->sdhc_extended_wr_mode)
>> +		reg |= SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
>> +	else
>> +		reg &= ~SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
>> +
>> +	if (phy->sdhc_extended_rd_mode)
>> +		reg |= SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
>> +	else
>> +		reg &= ~SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
>> +
>> +	if (phy->sdhc_rddata_en)
>> +		reg |= SDHCI_CDNS_HRS09_RDDATA_EN;
>> +	else
>> +		reg &= ~SDHCI_CDNS_HRS09_RDDATA_EN;
>> +
>> +	if (phy->sdhc_rdcmd_en)
>> +		reg |= SDHCI_CDNS_HRS09_RDCMD_EN;
>> +	else
>> +		reg &= ~SDHCI_CDNS_HRS09_RDCMD_EN;
>> +
>> +	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09);
>> +
>> +	reg = FIELD_PREP(SDHCI_CDNS_HRS10_HCSDCLKADJ, phy->sdhc_hcsdclkadj);
>> +	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS10);
>> +
>> +	reg = FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY,
>> +			 phy->sdhc_wrdata1_sdclk_dly);
>> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY,
>> +			  phy->sdhc_wrdata0_sdclk_dly);
>> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY,
>> +			  phy->sdhc_wrcmd1_sdclk_dly);
>> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY,
>> +			  phy->sdhc_wrcmd0_sdclk_dly);
>> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_DLY,
>> +			  phy->sdhc_wrdata1_dly);
>> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_DLY,
>> +			  phy->sdhc_wrdata0_dly);
>> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_DLY,
>> +			  phy->sdhc_wrcmd1_dly);
>> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_DLY,
>> +			  phy->sdhc_wrcmd0_dly);
>> +	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS16);
>> +
>> +	reg = FIELD_PREP(SDHCI_CDNS_HRS07_RW_COMPENSATE,
>> +			 phy->sdhc_rw_compensate);
>> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS07_IDELAY_VAL,
>> +			  phy->sdhc_idelay_val);
>> +	writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS07);
>> +
>> +	/* Allow 5ms for clock and PHY signals to stabilize after configuration */
>> +	usleep_range(5000, 5500);
>> +
>> +	return 0;
>> +}
>> +
>> +int sdhci_cdns6_set_tune_val(struct sdhci_host *host,
>> +			     unsigned int val)
>> +{
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>> +	struct sdhci_cdns6_phy *phy = priv->phy;
>> +	u32 tuneval;
>> +
>> +	/*
>> +	 * Scale tuning tap (val in [0, SDHCI_CDNS_MAX_TUNING_LOOP-1]) to the
>> +	 * 8-bit PHY DLL slave delay field [0, 255]. With MAX_TUNING_LOOP=40
>> +	 * and FIELD_SIZE=256, the result fits in 8 bits.
>> +	 */
>> +	tuneval = (val * SDHCI_CDNS6_PHY_DLL_FIELD_SIZE) /
>> +		  SDHCI_CDNS_MAX_TUNING_LOOP;
>> +
>> +	phy->hs200_tune_val = tuneval;
>> +	phy->cp_read_dqs_cmd_delay = tuneval;
>> +	phy->cp_read_dqs_delay = tuneval;
>> +
>> +	return sdhci_cdns6_phy_init(priv);
>> +}
>> +
>> +static int sdhci_cdns6_phy_update_timings(struct sdhci_host *host)
>> +{
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>> +	struct sdhci_cdns6_phy *phy = priv->phy;
>> +	u32 t_sdmclk = phy->t_sdmclk;
>> +
>> +	/* Validate mode is within supported range */
>> +	if (phy->mode >= ARRAY_SIZE(init_timings))
>> +		return -EINVAL;
>> +
>> +	/* initialize input */
>> +	init_timings[phy->mode](phy, phy->t_sdclk);
>> +
>> +	phy->strobe_cmd = false;
>> +
>> +	if (priv->enhanced_strobe)
>> +		phy->strobe_cmd = true;
>> +
>> +	phy->phy_sdclk_delay = 2 * t_sdmclk;
>> +
>> +	/*
>> +	 * CMD and DAT output delays are currently identical, but kept separate
>> +	 * to allow independent tuning for specific modes (e.g., HS400) or
>> +	 * board-specific optimizations in the future.
>> +	 */
>> +	phy->phy_cmd_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
>> +	phy->phy_dat_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
>> +
>> +	if (phy->t_sdclk == phy->t_sdmclk) {
>> +		phy->sdhc_extended_wr_mode = 0;
>> +		phy->sdhc_extended_rd_mode = 0;
>> +	} else {
>> +		phy->sdhc_extended_wr_mode = 1;
>> +		phy->sdhc_extended_rd_mode = 1;
>> +	}
>> +
>> +	phy->cp_gate_cfg_always_on = 1;
>> +
>> +	sdhci_cdns6_phy_configure_dll(phy);
>> +
>> +	sdhci_cdns6_phy_calc_settings(phy);
>> +
>> +	return 0;
>> +}
>> +
>> +int sdhci_cdns6_phy_probe(struct platform_device *pdev,
>> +			  struct sdhci_cdns_priv *priv)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct sdhci_host *host = dev_get_drvdata(dev);
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_cdns6_phy *phy;
>> +	unsigned long val;
>> +	int ret;
>> +
>> +	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
>> +	if (!phy)
>> +		return -ENOMEM;
>> +
>> +	val = clk_get_rate(pltfm_host->clk);
>> +	if (!val)
>> +		return dev_err_probe(dev, -EINVAL,
>> +				     "failed to get controller clock rate\n");
>> +
>> +	phy->t_sdmclk = DIV_ROUND_DOWN_ULL(1000000000000ULL, val);
>> +
>> +	ret = of_property_read_u32(dev->of_node, "cdns,iocell-input-delay",
>> +				   &phy->iocell_input_delay);
>> +	if (ret)
>> +		phy->iocell_input_delay = SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY;
>> +
>> +	ret = of_property_read_u32(dev->of_node, "cdns,iocell-output-delay",
>> +				   &phy->iocell_output_delay);
>> +	if (ret)
>> +		phy->iocell_output_delay = SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY;
>> +
>> +	ret = of_property_read_u32(dev->of_node, "cdns,delay-element",
>> +				   &phy->delay_element);
>> +	if (ret)
>> +		phy->delay_element = SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT;
>> +
>> +	phy->delay_element_org = phy->delay_element;
>> +
>> +	priv->phy = phy;
>> +
>> +	/* default settings */
>> +	phy->sdhc_rdcmd_en = 1;
>> +	phy->sdhc_rddata_en = 1;
>> +	phy->cp_use_ext_lpbk_dqs = 1;
>> +	phy->cp_use_lpbk_dqs = 1;
>> +	phy->cp_sync_method = 1;
>> +	phy->cp_rd_del_sel = SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL;
>> +	phy->cp_dll_start_point = SDHCI_CDNS6_PHY_DEFAULT_DLL_START;
>> +	phy->cp_data_select_oe_end = 1;
>> +	phy->cp_io_mask_always_on = 0;
>> +	phy->cp_underrun_suppress = 1;
>> +
>> +	return 0;
>> +}
>> +
>> +void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing)
>> +{
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>> +	struct sdhci_cdns6_phy *phy = priv->phy;
>> +
>> +	/* Clock may be 0 during initial ios setup; skip PHY update */
>> +	if (!host->mmc->ios.clock)
>> +		return;
>> +
>> +	phy->t_sdclk = DIV_ROUND_DOWN_ULL(1000000000000ULL,
>> +					  host->mmc->ios.clock);
>> +	phy->mode = timing;
>> +
>> +	if (sdhci_cdns6_phy_update_timings(host))
>> +		dev_warn(mmc_dev(host->mmc), "%s: update timings failed\n",
>> +			 __func__);
>> +
>> +	if (sdhci_cdns6_phy_init(priv))
>> +		dev_warn(mmc_dev(host->mmc), "%s: phy init failed\n",
>> +			 __func__);
>> +}
>> +
>> +void sdhci_cdns6_hw_reset(struct sdhci_host *host)
>> +{
>> +	struct sdhci_cdns_priv *priv = sdhci_cdns_get_priv(host);
>> +	void __iomem *reg;
>> +
>> +	reg = priv->hrs_addr + SDHCI_CDNS_HRS11;
>> +	writel(SDHCI_CDNS_HRS11_EMMC_RST, reg);
>> +	/* eMMC HW reset assertion: spec requires >= 1us, give margin */
>> +	usleep_range(10, 20);
>> +	writel(0, reg);
>> +	usleep_range(300, 1000);
>> +}

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

* Re: [PATCH v1 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support
  2026-05-19 15:29   ` Adrian Hunter
@ 2026-05-21 17:41     ` Kathpalia, Tanmay
  0 siblings, 0 replies; 32+ messages in thread
From: Kathpalia, Tanmay @ 2026-05-21 17:41 UTC (permalink / raw)
  To: Adrian Hunter, linux-mmc; +Cc: ulf.hansson, linux-kernel

Hi Adrian,

Thanks for your feedback. I will fix all of the below in v2.

On 5/19/2026 8:59 PM, Adrian Hunter wrote:
> On 11/05/2026 23:21, Tanmay Kathpalia wrote:
>> Refactor the sdhci-cadence driver in preparation for adding SD6HC
>> (V6 controller) support. Separate PHY parameter handling into a
>> dedicated sdhci_cdns4_phy structure and move PHY initialization
>> logic into a dedicated sdhci_cdns4_phy_probe() function. This
>> allows different controller versions to manage their PHY
>> configurations independently while keeping shared logic in the
>> main driver.
>>
>> Each compatible entry now carries its own driver data, so drop the
>> silent fallback to sdhci_cdns4_drv_data and return an error if
>> platform data is missing.
>>
>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> A few minor style comments below
>
>> ---
>>   drivers/mmc/host/sdhci-cadence.c | 57 ++++++++++++++++++++++----------
>>   1 file changed, 40 insertions(+), 17 deletions(-)
>>
>> diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
>> index 47690a52a221..fe3f7c5109fc 100644
>> --- a/drivers/mmc/host/sdhci-cadence.c
>> +++ b/drivers/mmc/host/sdhci-cadence.c
>> @@ -83,6 +83,11 @@ struct sdhci_cdns4_phy_param {
>>   	u8 data;
>>   };
>>   
>> +struct sdhci_cdns4_phy {
>> +	unsigned int nr_phy_params;
>> +	struct sdhci_cdns4_phy_param phy_params[];
>> +};
>> +
>>   struct sdhci_cdns_priv {
>>   	void __iomem *hrs_addr;
>>   	void __iomem *ctl_addr;	/* write control */
>> @@ -90,8 +95,7 @@ struct sdhci_cdns_priv {
>>   	bool enhanced_strobe;
>>   	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
>>   	struct reset_control *rst_hw;
>> -	unsigned int nr_phy_params;
>> -	struct sdhci_cdns4_phy_param phy_params[];
>> +	struct sdhci_cdns4_phy *phy;
>>   };
>>   
>>   struct sdhci_cdns4_phy_cfg {
>> @@ -169,9 +173,9 @@ static unsigned int sdhci_cdns4_phy_param_count(struct device_node *np)
>>   }
>>   
>>   static void sdhci_cdns4_phy_param_parse(struct device_node *np,
>> -					struct sdhci_cdns_priv *priv)
>> +					struct sdhci_cdns4_phy *phy)
>>   {
>> -	struct sdhci_cdns4_phy_param *p = priv->phy_params;
>> +	struct sdhci_cdns4_phy_param *p = phy->phy_params;
>>   	u32 val;
>>   	int ret, i;
>>   
>> @@ -190,10 +194,11 @@ static void sdhci_cdns4_phy_param_parse(struct device_node *np,
>>   static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
>>   {
>>   	int ret, i;
>> +	struct sdhci_cdns4_phy *phy = priv->phy;
> Nicer to place locals in reverse order of line length e.g.
>
> +	struct sdhci_cdns4_phy *phy = priv->phy;
> 	int ret, i;
>
>>   
>> -	for (i = 0; i < priv->nr_phy_params; i++) {
>> -		ret = sdhci_cdns4_write_phy_reg(priv, priv->phy_params[i].addr,
>> -						priv->phy_params[i].data);
>> +	for (i = 0; i < phy->nr_phy_params; i++) {
>> +		ret = sdhci_cdns4_write_phy_reg(priv, phy->phy_params[i].addr,
>> +						phy->phy_params[i].data);
>>   		if (ret)
>>   			return ret;
>>   	}
>> @@ -542,6 +547,26 @@ static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc)
>>   	usleep_range(300, 1000);
>>   }
>>   
>> +static int sdhci_cdns4_phy_probe(struct platform_device *pdev,
>> +				 struct sdhci_cdns_priv *priv)
> Please wrap after 100 columns not 80
>
>> +{
>> +	unsigned int nr_phy_params;
>> +	struct sdhci_cdns4_phy *phy;
>> +	struct device *dev = &pdev->dev;
> Nicer to place locals in reverse order of line length e.g.
>
> +	struct device *dev = &pdev->dev;
> +	struct sdhci_cdns4_phy *phy;
> +	unsigned int nr_phy_params;
>
>> +
>> +	nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
>> +	phy = devm_kzalloc(dev, struct_size(phy, phy_params, nr_phy_params),
>> +			   GFP_KERNEL);
> Please wrap after 100 columns not 80
>
>> +	if (!phy)
>> +		return -ENOMEM;
>> +
>> +	phy->nr_phy_params = nr_phy_params;
>> +	sdhci_cdns4_phy_param_parse(dev->of_node, phy);
>> +	priv->phy = phy;
>> +
>> +	return sdhci_cdns4_phy_init(priv);
>> +}
>> +
>>   static int sdhci_cdns_probe(struct platform_device *pdev)
>>   {
>>   	struct sdhci_host *host;
>> @@ -549,7 +574,6 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>>   	struct sdhci_pltfm_host *pltfm_host;
>>   	struct sdhci_cdns_priv *priv;
>>   	struct clk *clk;
>> -	unsigned int nr_phy_params;
>>   	int ret;
>>   	struct device *dev = &pdev->dev;
>>   	static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT;
>> @@ -560,11 +584,10 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>>   
>>   	data = of_device_get_match_data(dev);
>>   	if (!data)
>> -		data = &sdhci_cdns4_drv_data;
>> +		return dev_err_probe(dev, -EINVAL,
>> +				     "missing platform driver data\n");
> Please wrap after 100 columns not 80
>
>>   
>> -	nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
>> -	host = sdhci_pltfm_init(pdev, &data->pltfm_data,
>> -				struct_size(priv, phy_params, nr_phy_params));
>> +	host = sdhci_pltfm_init(pdev, &data->pltfm_data, sizeof(*priv));
>>   	if (IS_ERR(host))
>>   		return PTR_ERR(host);
>>   
>> @@ -572,7 +595,6 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>>   	pltfm_host->clk = clk;
>>   
>>   	priv = sdhci_pltfm_priv(pltfm_host);
>> -	priv->nr_phy_params = nr_phy_params;
>>   	priv->hrs_addr = host->ioaddr;
>>   	priv->enhanced_strobe = false;
>>   	priv->priv_writel = cdns_writel;
>> @@ -593,9 +615,7 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
>>   	if (ret)
>>   		return ret;
>>   
>> -	sdhci_cdns4_phy_param_parse(dev->of_node, priv);
>> -
>> -	ret = sdhci_cdns4_phy_init(priv);
>> +	ret = sdhci_cdns4_phy_probe(pdev, priv);
>>   	if (ret)
>>   		return ret;
>>   
>> @@ -653,7 +673,10 @@ static const struct of_device_id sdhci_cdns_match[] = {
>>   		.compatible = "mobileye,eyeq-sd4hc",
>>   		.data = &sdhci_eyeq_drv_data,
>>   	},
>> -	{ .compatible = "cdns,sd4hc" },
>> +	{
>> +		.compatible = "cdns,sd4hc",
>> +		.data = &sdhci_cdns4_drv_data,
>> +	},
>>   	{ /* sentinel */ }
>>   };
>>   MODULE_DEVICE_TABLE(of, sdhci_cdns_match);

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

* Re: [PATCH v1 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement
  2026-05-15  8:38   ` Krzysztof Kozlowski
@ 2026-05-21 17:56     ` Kathpalia, Tanmay
  0 siblings, 0 replies; 32+ messages in thread
From: Kathpalia, Tanmay @ 2026-05-21 17:56 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: linux-mmc, ulf.hansson, Dinh Nguyen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel

Hi K

Thanks for your feedback.

On 5/15/2026 2:08 PM, Krzysztof Kozlowski wrote:
> On Mon, May 11, 2026 at 01:21:25PM -0700, Tanmay Kathpalia wrote:
>> The Agilex5 SoC device tree gains an SD/MMC controller node backed by
>> the Cadence SD6HC, with IOMMU integration via the system SMMU. Card
>> power is supplied by a fixed 3.3V regulator and I/O voltage switching
>> between 1.8V and 3.3V is handled by a GPIO-controlled regulator.
>>
>> The SOCDK board enables the controller for SD-only operation in 4-bit
>> bus width with high-speed and SDR104 UHS-I modes at 200 MHz maximum
>> clock. SDHCI capability overrides clear the SDR50 tuning flag and
>> override the clock base mask to report 200 MHz.
>>
>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>> ---
>>   .../arm64/boot/dts/intel/socfpga_agilex5.dtsi | 38 +++++++++++++++++++
>>   .../boot/dts/intel/socfpga_agilex5_socdk.dts  | 26 +++++++++++++
>>   2 files changed, 64 insertions(+)
>>
>> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> index 352c96d144a8..7e080f13166f 100644
>> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> @@ -300,6 +300,44 @@ portb: gpio-controller@0 {
>>   			};
>>   		};
>>   
>> +		sd_emmc_power: regulator-fixed-3p3v {
> NAK, this fails basic rules of organizing DTS/DTSI and the nodes. This
> is simple-bus, so how could you have here a regulator which is non MMIO?
>
> Plus, explain me how these regulators managed to appear on the SoC
> die/silicon?
>
> Best regards,
> Krzysztof


Apologies, you are right on both counts.

In v2 this will be fixed:
1. sd_emmc_power and sd_io_1v8_reg will be moved to the root level
of socfpga_agilex5_socdk.dts where they belong.
2. emmc_io_1v8_reg will be moved to the root level of
socfpga_agilex5_socdk_emmc.dts.
3. The shared socfpga_agilex5.dtsi will carry only the SoC-level
emmc controller node with no board-specific regulators.

Regards,
Tanmay

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

* Re: [PATCH v1 8/9] mmc: sdhci-cadence: add Cadence SD6HC support
  2026-05-21 17:28     ` Kathpalia, Tanmay
@ 2026-05-22  5:32       ` Adrian Hunter
  2026-05-22  6:34         ` Kathpalia, Tanmay
  0 siblings, 1 reply; 32+ messages in thread
From: Adrian Hunter @ 2026-05-22  5:32 UTC (permalink / raw)
  To: Kathpalia, Tanmay, linux-mmc; +Cc: ulfh, Philipp Zabel, linux-kernel

On 21/05/2026 20:28, Kathpalia, Tanmay wrote:
> Hi Adrian,
> 
> Thanks for your feedback.
> 
> On 5/19/2026 8:59 PM, Adrian Hunter wrote:
>> On 11/05/2026 23:21, Tanmay Kathpalia wrote:
>>> The Cadence SD6HC is the sixth-generation SD Host Controller used in
>>> Agilex5 SoCs. Its PHY differs substantially from the SD4HC: it
>>> requires per-speed-mode IO cell timing parameters and a DLL-based
>>> delay line to achieve correct signal margins across all speed grades
>>> from Default Speed to HS400.
>>>
>>> Support is implemented in a new sdhci-cadence6.c alongside the
>>> existing v4 code, now in sdhci-cadence4.c. Shared structures and the
>>> v6 function interface are declared in a new sdhci-cadence.h header.
>>> The common driver paths select between v4 and v6 PHY operations based
>>> on the SDHCI specification version reported by the controller.
>>>
>>> The new compatible string "cdns,sd6hc" identifies SD6HC hardware in
>>> device tree.
>>>
>>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>>> ---
>>>   MAINTAINERS                                   |    7 +
>>>   drivers/mmc/host/Makefile                     |    3 +-
>>>   drivers/mmc/host/sdhci-cadence.h              |  113 ++
>>>   .../{sdhci-cadence.c => sdhci-cadence4.c}     |   92 +-
>>>   drivers/mmc/host/sdhci-cadence6.c             | 1051 +++++++++++++++++
>>>   5 files changed, 1228 insertions(+), 38 deletions(-)
>>>   create mode 100644 drivers/mmc/host/sdhci-cadence.h
>>>   rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence4.c} (91%)
>>>   create mode 100644 drivers/mmc/host/sdhci-cadence6.c
>> The new file names seem slightly misleading since sdhci-cadence4.c
>> is the main driver as well as supporting V4, whereas sdhci-cadence6.c
>> is most of V6 support.
>>
>> There doesn't seem much to gain from having a separate sdhci-cadence6.c.
>> Isn't it simpler to just put it all in 1 source file.
> 
> I believe keeping them separate improves maintainability for the
> following reasons:
> 
> 1. V4 and V6 PHY programming models are fundamentally different:
>     a. SD4HC reads pre-computed delay values directly from DT and
>     writes them one-to-one into PHY registers.
>     b. SD6HC takes only a few timing parameters (IO cell delay, delay
>     element size) from DT and derives all PHY and SDHC register
>     programming values inside the driver through a calibration
>     sequence.
> 2. The V6 PHY logic alone is more than 1000 lines. Merging it into
> the existing file would make sdhci-cadence4.c significantly harder
> to review, and maintain.
> 3. Keep code structure aligned with U-Boot, where V6 handling is
> also separated.
> 
> That said, I am open to merging into one file if you think that is
> better for maintainability in this driver. I can do that in v2.

At the very least, the file names need to be more explicit:

  The core part of the driver:	sdhci-cadence-core.c
  The V6 PHY implementation:	sdhci-cadence-phy-v6.c


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

* Re: [PATCH v1 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support
  2026-05-15  8:37   ` Krzysztof Kozlowski
@ 2026-05-22  6:30     ` Kathpalia, Tanmay
  0 siblings, 0 replies; 32+ messages in thread
From: Kathpalia, Tanmay @ 2026-05-22  6:30 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: linux-mmc, ulf.hansson, Dinh Nguyen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel

[Re-sending in plain text due to HTML rejection by the mailing list.]

Hi Krzyztof,

Thanks for your feedback.

On 5/15/2026 2:07 PM, Krzysztof Kozlowski wrote:
> On Mon, May 11, 2026 at 01:21:27PM -0700, Tanmay Kathpalia wrote:
>> Add device tree support for the Agilex5 SOCDK board variant with
>> eMMC daughter card.
>>
>> Update the SoC dtsi with a fixed 1.8V regulator for eMMC I/O voltage.
>>
>> Add socfpga_agilex5_socdk_emmc.dts with eMMC controller configured for:
>> - 8-bit bus width
>> - Non-removable eMMC device
>> - High-speed, HS200, and HS400 modes at 1.8V
>> - 200MHz maximum frequency with SDHCI clock base capability override
>>
>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>> ---
>>   arch/arm64/boot/dts/intel/Makefile            |   1 +
>>   .../arm64/boot/dts/intel/socfpga_agilex5.dtsi |   8 ++
>>   .../dts/intel/socfpga_agilex5_socdk_emmc.dts  | 111 ++++++++++++++++++
>>   3 files changed, 120 insertions(+)
>>   create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
>>
>> diff --git a/arch/arm64/boot/dts/intel/Makefile b/arch/arm64/boot/dts/intel/Makefile
>> index 33fcc55d0cb9..5bbbcfda1f48 100644
>> --- a/arch/arm64/boot/dts/intel/Makefile
>> +++ b/arch/arm64/boot/dts/intel/Makefile
>> @@ -8,5 +8,6 @@ dtb-$(CONFIG_ARCH_INTEL_SOCFPGA) += socfpga_agilex_n6000.dtb \
>>   				socfpga_agilex5_socdk_013b.dtb \
>>   				socfpga_agilex5_socdk_modular.dtb \
>>   				socfpga_agilex5_socdk_nand.dtb \
>> +				socfpga_agilex5_socdk_emmc.dtb \
>>   				socfpga_n5x_socdk.dtb
>>   dtb-$(CONFIG_ARCH_KEEMBAY) += keembay-evm.dtb
>> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> index 7e080f13166f..feb4ccb317a7 100644
>> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> @@ -317,6 +317,14 @@ sd_io_1v8_reg: regulator-1p8v {
>>   					<3300000 0x0>;
>>   		};
>>   
>> +		emmc_io_1v8_reg: regulator-fixed-1p8v {
> How is this property of a SoC? How SoC itself has a fixed regulator for
> EMMC? Really?
>
> Best regards,
> Krzysztof

Apologies, you are absolutely right.

The emmc_io_1v8_reg regulator is a fixed 1.8v rail on the eMMC
daughter card, not a property of the SoC silicon.

In v2 this will be fixed:
emmc_io_1v8_reg will be moved to the root level of
socfpga_agilex5_socdk_emmc.dts where it correctly describes
board-level hardware.

Regards,
Tanmay


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

* Re: [PATCH v1 8/9] mmc: sdhci-cadence: add Cadence SD6HC support
  2026-05-22  5:32       ` Adrian Hunter
@ 2026-05-22  6:34         ` Kathpalia, Tanmay
  0 siblings, 0 replies; 32+ messages in thread
From: Kathpalia, Tanmay @ 2026-05-22  6:34 UTC (permalink / raw)
  To: Adrian Hunter, linux-mmc; +Cc: ulfh, Philipp Zabel, linux-kernel


On 5/22/2026 11:02 AM, Adrian Hunter wrote:
> On 21/05/2026 20:28, Kathpalia, Tanmay wrote:
>> Hi Adrian,
>>
>> Thanks for your feedback.
>>
>> On 5/19/2026 8:59 PM, Adrian Hunter wrote:
>>> On 11/05/2026 23:21, Tanmay Kathpalia wrote:
>>>> The Cadence SD6HC is the sixth-generation SD Host Controller used in
>>>> Agilex5 SoCs. Its PHY differs substantially from the SD4HC: it
>>>> requires per-speed-mode IO cell timing parameters and a DLL-based
>>>> delay line to achieve correct signal margins across all speed grades
>>>> from Default Speed to HS400.
>>>>
>>>> Support is implemented in a new sdhci-cadence6.c alongside the
>>>> existing v4 code, now in sdhci-cadence4.c. Shared structures and the
>>>> v6 function interface are declared in a new sdhci-cadence.h header.
>>>> The common driver paths select between v4 and v6 PHY operations based
>>>> on the SDHCI specification version reported by the controller.
>>>>
>>>> The new compatible string "cdns,sd6hc" identifies SD6HC hardware in
>>>> device tree.
>>>>
>>>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>>>> ---
>>>>    MAINTAINERS                                   |    7 +
>>>>    drivers/mmc/host/Makefile                     |    3 +-
>>>>    drivers/mmc/host/sdhci-cadence.h              |  113 ++
>>>>    .../{sdhci-cadence.c => sdhci-cadence4.c}     |   92 +-
>>>>    drivers/mmc/host/sdhci-cadence6.c             | 1051 +++++++++++++++++
>>>>    5 files changed, 1228 insertions(+), 38 deletions(-)
>>>>    create mode 100644 drivers/mmc/host/sdhci-cadence.h
>>>>    rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence4.c} (91%)
>>>>    create mode 100644 drivers/mmc/host/sdhci-cadence6.c
>>> The new file names seem slightly misleading since sdhci-cadence4.c
>>> is the main driver as well as supporting V4, whereas sdhci-cadence6.c
>>> is most of V6 support.
>>>
>>> There doesn't seem much to gain from having a separate sdhci-cadence6.c.
>>> Isn't it simpler to just put it all in 1 source file.
>> I believe keeping them separate improves maintainability for the
>> following reasons:
>>
>> 1. V4 and V6 PHY programming models are fundamentally different:
>>      a. SD4HC reads pre-computed delay values directly from DT and
>>      writes them one-to-one into PHY registers.
>>      b. SD6HC takes only a few timing parameters (IO cell delay, delay
>>      element size) from DT and derives all PHY and SDHC register
>>      programming values inside the driver through a calibration
>>      sequence.
>> 2. The V6 PHY logic alone is more than 1000 lines. Merging it into
>> the existing file would make sdhci-cadence4.c significantly harder
>> to review, and maintain.
>> 3. Keep code structure aligned with U-Boot, where V6 handling is
>> also separated.
>>
>> That said, I am open to merging into one file if you think that is
>> better for maintainability in this driver. I can do that in v2.
> At the very least, the file names need to be more explicit:
>
>    The core part of the driver:	sdhci-cadence-core.c
>    The V6 PHY implementation:	sdhci-cadence-phy-v6.c

Ack. I will rename the files accordingly in V2.


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

* Re: [PATCH v1 9/9] mmc: sdhci-cadence: add Altera Agilex5 SD6HC support
  2026-05-11 20:21 ` [PATCH v1 9/9] mmc: sdhci-cadence: add Altera Agilex5 " Tanmay Kathpalia
@ 2026-05-27 21:01   ` Zach Miller
  2026-06-01 11:54     ` Kathpalia, Tanmay
  0 siblings, 1 reply; 32+ messages in thread
From: Zach Miller @ 2026-05-27 21:01 UTC (permalink / raw)
  To: Tanmay Kathpalia; +Cc: linux-mmc, ulf.hansson, Adrian Hunter, linux-kernel

On Mon, May 11, 2026 at 01:21:31PM -0700, Tanmay Kathpalia wrote:
> +static const struct sdhci_ops sdhci_cdns6_agilex5_ops = {
> +	.set_clock = sdhci_set_clock,
> +	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
> +	.set_bus_width = sdhci_set_bus_width,
> +	.reset = sdhci_reset,
> +	.platform_execute_tuning = sdhci_cdns_execute_tuning,
> +	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
> +	.hw_reset = sdhci_cdns6_hw_reset,
> +	.set_dma_mask = sdhci_cdns_set_dma_mask,
> +};
> +
>  static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
>  	.pltfm_data = {
>  		.ops = &sdhci_cdns4_ops,
> @@ -506,6 +544,16 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
>  	},
>  };
>  
> +static const struct sdhci_cdns_drv_data sdhci_cdns6_agilex5_drv_data = {
> +	.pltfm_data = {
> +		.ops = &sdhci_cdns6_agilex5_ops,
> +		.quirks = SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12,
> +		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
> +			   SDHCI_QUIRK2_ACMD23_BROKEN,
> +	},
> +	.dma_mask = DMA_BIT_MASK(40),
> +};

Would it make sense to add SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN and set
sdhci_pltfm_clk_get_max_clock() as the get_max_clock() op?

The clock rate that function returns is already the one being overridden
via sdhci-caps in device tree in patches 3 and 5. Since the
infrastructure is there, it seems nicer to have this managed by the
driver rather than requiring users to do it in device tree, especially
because the clock rate is user-selectable via their FPGA design (so it's
also a bit different from other caps which may need to be overridden).

Best regards,
Zach

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

* Re: [PATCH v1 9/9] mmc: sdhci-cadence: add Altera Agilex5 SD6HC support
  2026-05-27 21:01   ` Zach Miller
@ 2026-06-01 11:54     ` Kathpalia, Tanmay
  0 siblings, 0 replies; 32+ messages in thread
From: Kathpalia, Tanmay @ 2026-06-01 11:54 UTC (permalink / raw)
  To: Zach Miller; +Cc: linux-mmc, ulf.hansson, Adrian Hunter, linux-kernel

Hi Zach,

Thanks for your feedback.

On 5/28/2026 2:31 AM, Zach Miller wrote:
> On Mon, May 11, 2026 at 01:21:31PM -0700, Tanmay Kathpalia wrote:
>> +static const struct sdhci_ops sdhci_cdns6_agilex5_ops = {
>> +	.set_clock = sdhci_set_clock,
>> +	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
>> +	.set_bus_width = sdhci_set_bus_width,
>> +	.reset = sdhci_reset,
>> +	.platform_execute_tuning = sdhci_cdns_execute_tuning,
>> +	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
>> +	.hw_reset = sdhci_cdns6_hw_reset,
>> +	.set_dma_mask = sdhci_cdns_set_dma_mask,
>> +};
>> +
>>   static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
>>   	.pltfm_data = {
>>   		.ops = &sdhci_cdns4_ops,
>> @@ -506,6 +544,16 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
>>   	},
>>   };
>>   
>> +static const struct sdhci_cdns_drv_data sdhci_cdns6_agilex5_drv_data = {
>> +	.pltfm_data = {
>> +		.ops = &sdhci_cdns6_agilex5_ops,
>> +		.quirks = SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12,
>> +		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
>> +			   SDHCI_QUIRK2_ACMD23_BROKEN,
>> +	},
>> +	.dma_mask = DMA_BIT_MASK(40),
>> +};
> Would it make sense to add SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN and set
> sdhci_pltfm_clk_get_max_clock() as the get_max_clock() op?
>
> The clock rate that function returns is already the one being overridden
> via sdhci-caps in device tree in patches 3 and 5. Since the
> infrastructure is there, it seems nicer to have this managed by the
> driver rather than requiring users to do it in device tree, especially
> because the clock rate is user-selectable via their FPGA design (so it's
> also a bit different from other caps which may need to be overridden).
>

Agreed. I've added SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN and wired up
.get_max_clock = sdhci_pltfm_clk_get_max_clock in sdhci_cdns6_agilex5_ops,
so the driver now owns the clock-base handling directly. The sdhci-caps 
clock-base
overrides in both socfpga_agilex5_socdk.dts and 
socfpga_agilex5_socdk_emmc.dts
have been dropped accordingly.

Regards,
Tanmay

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

end of thread, other threads:[~2026-06-01 11:54 UTC | newest]

Thread overview: 32+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-11 20:21 [PATCH v1 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
2026-05-11 20:21 ` [PATCH v1 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Tanmay Kathpalia
2026-05-12 17:33   ` Conor Dooley
2026-05-11 20:21 ` [PATCH v1 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties Tanmay Kathpalia
2026-05-12 17:33   ` Conor Dooley
2026-05-13  0:23   ` sashiko-bot
2026-05-11 20:21 ` [PATCH v1 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement Tanmay Kathpalia
2026-05-13  0:43   ` sashiko-bot
2026-05-13  6:34     ` Kathpalia, Tanmay
2026-05-15  8:38   ` Krzysztof Kozlowski
2026-05-21 17:56     ` Kathpalia, Tanmay
2026-05-11 20:21 ` [PATCH v1 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant Tanmay Kathpalia
2026-05-11 20:21 ` [PATCH v1 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support Tanmay Kathpalia
2026-05-13  1:22   ` sashiko-bot
2026-05-13  6:25     ` Kathpalia, Tanmay
2026-05-15  8:37   ` Krzysztof Kozlowski
2026-05-22  6:30     ` Kathpalia, Tanmay
2026-05-11 20:21 ` [PATCH v1 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork Tanmay Kathpalia
2026-05-19 15:29   ` Adrian Hunter
2026-05-21 15:53     ` Kathpalia, Tanmay
2026-05-21 16:16       ` Adrian Hunter
2026-05-11 20:21 ` [PATCH v1 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support Tanmay Kathpalia
2026-05-19 15:29   ` Adrian Hunter
2026-05-21 17:41     ` Kathpalia, Tanmay
2026-05-11 20:21 ` [PATCH v1 8/9] mmc: sdhci-cadence: add Cadence SD6HC support Tanmay Kathpalia
2026-05-19 15:29   ` Adrian Hunter
2026-05-21 17:28     ` Kathpalia, Tanmay
2026-05-22  5:32       ` Adrian Hunter
2026-05-22  6:34         ` Kathpalia, Tanmay
2026-05-11 20:21 ` [PATCH v1 9/9] mmc: sdhci-cadence: add Altera Agilex5 " Tanmay Kathpalia
2026-05-27 21:01   ` Zach Miller
2026-06-01 11:54     ` Kathpalia, Tanmay

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.