* [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement
@ 2026-06-27 20:14 Tanmay Kathpalia
2026-06-27 20:14 ` [PATCH v2 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Tanmay Kathpalia
` (8 more replies)
0 siblings, 9 replies; 24+ messages in thread
From: Tanmay Kathpalia @ 2026-06-27 20:14 UTC (permalink / raw)
To: linux-mmc; +Cc: ulf.hansson, Tanmay Kathpalia, Philipp Zabel
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: it
requires dedicated 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 through HS400. These are programmed through a
new sdhci-cadence-phy-v6.c file; shared driver infrastructure lives in
sdhci-cadence-core.c.
Patches 1-2: DT bindings
Patch 1 adds COMBOPHY_RESET to the Agilex5 reset manager binding.
Patch 2 extends cdns,sdhci.yaml with SD6HC compatible strings, clock,
reset and IOMMU constraints, and three PHY timing properties; per-
variant allOf conditionals enforce which properties apply to sd4hc vs.
sd6hc hardware.
Patches 3-5: Device tree
Patch 3 adds the SD6HC controller node to the Agilex5 SoC DTSI and
enables SD card operation (4-bit, SDR104, 200 MHz) on the SOCDK board
with GPIO-switched IO-voltage regulation.
Patch 4 registers the intel,socfpga-agilex5-socdk-emmc board variant
in the arm/altera binding.
Patch 5 adds socfpga_agilex5_socdk_emmc.dts for eMMC-only operation
(8-bit, HS200/HS400, 1.8 V IO, 200 MHz).
Patches 6-9: Driver
Patch 6 renames SD4HC-specific functions and structures with a "cdns4"
prefix to separate them from shared driver paths.
Patch 7 encapsulates SD4HC PHY probing in sdhci_cdns4_phy_probe() and
makes every of_device_id entry carry explicit platform data, removing
the silent fallback.
Patch 8 introduces the SD6HC PHY driver (sdhci-cadence-phy-v6.c): DLL
lock/bypass, per-speed-mode IO cell timing, tuning, and HW reset. The
common driver core selects between v4 and v6 PHY operations based on
the SDHCI specification version reported by the controller.
Patch 9 adds the Agilex5 platform overlay under altr,agilex5-sd6hc:
40-bit DMA mask for the SMMU address space, quirks for
CAP_CLOCK_BASE_BROKEN, PRESET_VALUE_BROKEN, ACMD23_BROKEN and
MULTIBLOCK_READ_ACMD12, .get_max_clock set to
sdhci_pltfm_clk_get_max_clock to supply the base clock directly from
the platform clock since CAP_CLOCK_BASE_BROKEN prevents reading it
from the capabilities register, and an init hook that sequences the
three named resets correctly.
Tested on Agilex5 SOCDK:
- SD card: Default Speed, High Speed, SDR25, SDR50, SDR104
- eMMC daughter board: HS200, HS400
Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
Changes in v2:
[drivers]
- Rename sdhci-cadence4.c -> sdhci-cadence-core.c and
sdhci-cadence6.c -> sdhci-cadence-phy-v6.c to better reflect each
file's role (patch 8).
- Add SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN to Agilex5 platform quirks
and set .get_max_clock to sdhci_pltfm_clk_get_max_clock so the base
clock is supplied directly from the platform clock rather than the
broken capabilities register; this replaces the sdhci-caps-mask/
sdhci-caps DT workaround used in v1 (patches 3, 9).
- Add sdhci_cdns6_agilex5_init() to handle Agilex5-specific reset
sequencing: the combophy reset is shared with other peripheral
(NAND) and is obtained with
devm_reset_control_get_shared_deasserted(); sdhc-reset and sdmmc-ocp
are asserted together before being released so both clock domains
cross the reset boundary simultaneously (patch 9).
- Convert init_timings[] from positional to designated initializers
([MMC_TIMING_LEGACY] = ...) so the table is robust against any future
reordering of MMC_TIMING_* values (patch 8).
- Add min_t(u8, cp_io_mask_start, 7) clamp to prevent overflow of the
3-bit GENMASK(26,24) hardware field (patch 8).
- Fix trailing whitespace in sdhci-cadence-phy-v6.c (patch 8).
- Fix declaration ordering in sdhci_cdns_set_dma_mask() to follow the
reverse-xmas-tree convention; drop the unused mmc intermediate
variable (patch 9).
[DT]
- Move board-specific regulators (vmmc, vqmmc-gpio) out of
socfpga_agilex5.dtsi into the board-level DTS files; regulator nodes
describe board circuitry, not SoC IP (patch 3).
- Remove fifo-depth from the SD6HC controller DTSI node; the controller
operates in DMA mode and has no software FIFO (patch 3).
- Remove sdhci-caps-mask and sdhci-caps from the SOCDK board node; the
clock override is now handled by the CAP_CLOCK_BASE_BROKEN driver
quirk (patches 3, 9).
Link: https://lore.kernel.org/linux-mmc/20260511202132.5597-1-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 | 122 ++-
MAINTAINERS | 7 +
arch/arm64/boot/dts/intel/Makefile | 1 +
.../arm64/boot/dts/intel/socfpga_agilex5.dtsi | 25 +
.../boot/dts/intel/socfpga_agilex5_socdk.dts | 31 +
.../dts/intel/socfpga_agilex5_socdk_emmc.dts | 120 +++
drivers/mmc/host/Makefile | 3 +-
.../{sdhci-cadence.c => sdhci-cadence-core.c} | 318 ++++--
drivers/mmc/host/sdhci-cadence-phy-v6.c | 965 ++++++++++++++++++
drivers/mmc/host/sdhci-cadence.h | 114 +++
include/dt-bindings/reset/altr,rst-mgr-s10.h | 2 +-
12 files changed, 1620 insertions(+), 89 deletions(-)
create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence-core.c} (66%)
create mode 100644 drivers/mmc/host/sdhci-cadence-phy-v6.c
create mode 100644 drivers/mmc/host/sdhci-cadence.h
--
2.43.7
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v2 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5
2026-06-27 20:14 [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
@ 2026-06-27 20:14 ` Tanmay Kathpalia
2026-07-02 15:53 ` (subset) " Philipp Zabel
2026-06-27 20:14 ` [PATCH v2 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties Tanmay Kathpalia
` (7 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Tanmay Kathpalia @ 2026-06-27 20:14 UTC (permalink / raw)
To: linux-mmc
Cc: ulf.hansson, Tanmay Kathpalia, Conor Dooley, 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>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de>
---
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] 24+ messages in thread
* [PATCH v2 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties
2026-06-27 20:14 [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
2026-06-27 20:14 ` [PATCH v2 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Tanmay Kathpalia
@ 2026-06-27 20:14 ` Tanmay Kathpalia
2026-06-29 7:04 ` Krzysztof Kozlowski
2026-06-27 20:14 ` [PATCH v2 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement Tanmay Kathpalia
` (6 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Tanmay Kathpalia @ 2026-06-27 20:14 UTC (permalink / raw)
To: linux-mmc
Cc: ulf.hansson, Tanmay Kathpalia, Ulf Hansson, 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 compatible string with two named clocks
(ciu and biu) and three SD6HC-specific PHY timing properties for iocell
input/output delay and delay element size.
Add the altr,agilex5-sd6hc compatible string with three named reset
lines from the Altera HPS Reset Manager. Introduce per-variant
constraints so SD6HC and SD4HC each enforce their own clock, reset, and
PHY property requirements independently.
Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
.../devicetree/bindings/mmc/cdns,sdhci.yaml | 122 ++++++++++++++++--
1 file changed, 111 insertions(+), 11 deletions(-)
diff --git a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
index 6c7317d13aa6..edd96e1d2bdc 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,10 +36,12 @@ properties:
maxItems: 1
clocks:
- maxItems: 1
+ minItems: 1
+ maxItems: 2
resets:
- maxItems: 1
+ minItems: 1
+ maxItems: 3
# PHY DLL input delays:
# They are used to delay the data valid window, and align the window to
@@ -115,6 +125,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 +168,77 @@ 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
+ dma-coherent: true
+ iommus:
+ maxItems: 1
+ 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:
+ - clock-names
+
+ # The Cadence SD6HC IP has per-clock-domain reset inputs, but the specific
+ # reset lines exposed here ("sdhc-reset", "combophy", "sdmmc-ocp") are
+ # provided by the Altera HPS Reset Manager and reflect the Agilex5 SoC
+ # integration.
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: altr,agilex5-sd6hc
+ then:
+ properties:
+ resets:
+ minItems: 3
+ maxItems: 3
+ reset-names:
+ items:
+ - const: sdhc-reset
+ - const: combophy
+ - const: sdmmc-ocp
+ required:
+ - resets
+ - 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] 24+ messages in thread
* [PATCH v2 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement
2026-06-27 20:14 [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
2026-06-27 20:14 ` [PATCH v2 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Tanmay Kathpalia
2026-06-27 20:14 ` [PATCH v2 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties Tanmay Kathpalia
@ 2026-06-27 20:14 ` Tanmay Kathpalia
2026-06-29 7:06 ` Krzysztof Kozlowski
2026-06-27 20:14 ` [PATCH v2 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant Tanmay Kathpalia
` (5 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Tanmay Kathpalia @ 2026-06-27 20:14 UTC (permalink / raw)
To: linux-mmc
Cc: ulf.hansson, Tanmay Kathpalia, Dinh Nguyen, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
Add the Cadence SD6HC controller node to the Agilex5 SoC DTSI as a
shared SD/eMMC node, disabled by default. The controller integrates
with the system SMMU for IOMMU support and uses SDMCLK as the primary
clock source for PHY timing.
On the SOCDK board, add a fixed 3.3V regulator for card power and a
GPIO-controlled regulator for I/O voltage switching between 1.8V and
3.3V. Enable the controller for SD-only operation in 4-bit bus width
with high-speed and SDR104 UHS-I modes at 200 MHz.
Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
.../arm64/boot/dts/intel/socfpga_agilex5.dtsi | 25 +++++++++++++++
.../boot/dts/intel/socfpga_agilex5_socdk.dts | 31 +++++++++++++++++++
2 files changed, 56 insertions(+)
diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
index 02e62d954e94..f552aa0c1faa 100644
--- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
+++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
@@ -300,6 +300,31 @@ portb: gpio-controller@0 {
};
};
+ /*
+ * Shared SD/eMMC controller node. On the SOCDK OOBE daughter-card
+ * this is used for SD card operation; on the SOCDK eMMC daughter-card
+ * it is configured for eMMC.
+ */
+ 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>;
+ 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..c56f46721bb0 100644
--- a/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk.dts
+++ b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk.dts
@@ -34,6 +34,24 @@ memory@80000000 {
/* We expect the bootloader to fill in the reg */
reg = <0x0 0x80000000 0x0 0x0>;
};
+
+ vmmc_reg: regulator-fixed-3p3v {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc-sd";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-always-on;
+ };
+
+ vqmmc_io_reg: regulator-1p8v {
+ compatible = "regulator-gpio";
+ regulator-name = "vqmmc-io";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <3300000>;
+ states = <1800000 0x1>,
+ <3300000 0x0>;
+ gpios = <&portb 3 GPIO_ACTIVE_HIGH>;
+ };
};
&gpio0 {
@@ -98,6 +116,19 @@ root: partition@4200000 {
};
};
+&emmc {
+ status = "okay";
+
+ no-mmc;
+ disable-wp;
+ bus-width = <4>;
+ cap-sd-highspeed;
+ sd-uhs-sdr104;
+ vmmc-supply = <&vmmc_reg>;
+ vqmmc-supply = <&vqmmc_io_reg>;
+ max-frequency = <200000000>;
+};
+
&uart0 {
status = "okay";
};
--
2.43.7
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant
2026-06-27 20:14 [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
` (2 preceding siblings ...)
2026-06-27 20:14 ` [PATCH v2 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement Tanmay Kathpalia
@ 2026-06-27 20:14 ` Tanmay Kathpalia
2026-06-29 7:06 ` Krzysztof Kozlowski
2026-06-27 20:14 ` [PATCH v2 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support Tanmay Kathpalia
` (4 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Tanmay Kathpalia @ 2026-06-27 20:14 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 206686f3eebc..f5efcbc381b8 100644
--- a/Documentation/devicetree/bindings/arm/altera.yaml
+++ b/Documentation/devicetree/bindings/arm/altera.yaml
@@ -113,6 +113,7 @@ properties:
- intel,socfpga-agilex5-socdk-013b
- intel,socfpga-agilex5-socdk-modular
- 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] 24+ messages in thread
* [PATCH v2 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support
2026-06-27 20:14 [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
` (3 preceding siblings ...)
2026-06-27 20:14 ` [PATCH v2 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant Tanmay Kathpalia
@ 2026-06-27 20:14 ` Tanmay Kathpalia
2026-06-29 7:07 ` Krzysztof Kozlowski
2026-06-27 20:14 ` [PATCH v2 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork Tanmay Kathpalia
` (3 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Tanmay Kathpalia @ 2026-06-27 20:14 UTC (permalink / raw)
To: linux-mmc
Cc: ulf.hansson, Tanmay Kathpalia, Dinh Nguyen, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
Add socfpga_agilex5_socdk_emmc.dts for the Agilex5 SoCDK eMMC daughter
board variant. Define board-specific regulators at the DTS root: a
fixed 3.3V supply for card power and a fixed 1.8V supply for eMMC I/O
voltage.
Enable the shared SD/eMMC controller for eMMC-only operation with an
8-bit bus, HS200 and HS400 modes at 1.8V signaling, and a 200 MHz
maximum clock frequency.
Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
arch/arm64/boot/dts/intel/Makefile | 1 +
.../dts/intel/socfpga_agilex5_socdk_emmc.dts | 120 ++++++++++++++++++
2 files changed, 121 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_socdk_emmc.dts b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
new file mode 100644
index 000000000000..455808db32bb
--- /dev/null
+++ b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 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";
+
+ led-0 {
+ label = "hps_led0";
+ gpios = <&porta 6 GPIO_ACTIVE_HIGH>;
+ };
+
+ led-1 {
+ 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>;
+ };
+
+ vmmc_reg: regulator-fixed-3p3v {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc-emmc";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-always-on;
+ };
+
+ vqmmc_io_reg: regulator-fixed-1p8v {
+ compatible = "regulator-fixed";
+ regulator-name = "vqmmc-io";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-always-on;
+ };
+};
+
+&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;
+ disable-wp;
+ non-removable;
+ cap-mmc-highspeed;
+ mmc-hs200-1_8v;
+ mmc-hs400-1_8v;
+ bus-width = <8>;
+ vmmc-supply = <&vmmc_reg>;
+ vqmmc-supply = <&vqmmc_io_reg>;
+ max-frequency = <200000000>;
+};
+
+&osc1 {
+ clock-frequency = <25000000>;
+};
+
+&uart0 {
+ status = "okay";
+};
+
+&watchdog0 {
+ status = "okay";
+};
--
2.43.7
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork
2026-06-27 20:14 [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
` (4 preceding siblings ...)
2026-06-27 20:14 ` [PATCH v2 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support Tanmay Kathpalia
@ 2026-06-27 20:14 ` Tanmay Kathpalia
2026-07-04 11:17 ` Adrian Hunter
2026-06-27 20:14 ` [PATCH v2 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support Tanmay Kathpalia
` (2 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Tanmay Kathpalia @ 2026-06-27 20:14 UTC (permalink / raw)
To: linux-mmc
Cc: ulf.hansson, Tanmay Kathpalia, Adrian Hunter, Ulf Hansson,
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 | 56 ++++++++++++++++----------------
1 file changed, 28 insertions(+), 28 deletions(-)
diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
index 435603c8c00b..b00dc2eec922 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;
}
@@ -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,
},
};
@@ -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] 24+ messages in thread
* [PATCH v2 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support
2026-06-27 20:14 [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
` (5 preceding siblings ...)
2026-06-27 20:14 ` [PATCH v2 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork Tanmay Kathpalia
@ 2026-06-27 20:14 ` Tanmay Kathpalia
2026-06-27 20:14 ` [PATCH v2 8/9] mmc: sdhci-cadence: add Cadence SD6HC support Tanmay Kathpalia
2026-06-27 20:14 ` [PATCH v2 9/9] mmc: sdhci-cadence: add Altera Agilex5 " Tanmay Kathpalia
8 siblings, 0 replies; 24+ messages in thread
From: Tanmay Kathpalia @ 2026-06-27 20:14 UTC (permalink / raw)
To: linux-mmc
Cc: ulf.hansson, Tanmay Kathpalia, Adrian Hunter, Ulf Hansson,
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 | 54 ++++++++++++++++++++++----------
1 file changed, 37 insertions(+), 17 deletions(-)
diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
index b00dc2eec922..ab5dfae12732 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;
@@ -189,11 +193,12 @@ static void sdhci_cdns4_phy_param_parse(struct device_node *np,
static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
{
+ 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,24 @@ 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)
+{
+ 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);
+ 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 +572,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 +582,9 @@ 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 +592,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 +612,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 +670,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] 24+ messages in thread
* [PATCH v2 8/9] mmc: sdhci-cadence: add Cadence SD6HC support
2026-06-27 20:14 [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
` (6 preceding siblings ...)
2026-06-27 20:14 ` [PATCH v2 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support Tanmay Kathpalia
@ 2026-06-27 20:14 ` Tanmay Kathpalia
2026-07-04 11:18 ` Adrian Hunter
2026-06-27 20:14 ` [PATCH v2 9/9] mmc: sdhci-cadence: add Altera Agilex5 " Tanmay Kathpalia
8 siblings, 1 reply; 24+ messages in thread
From: Tanmay Kathpalia @ 2026-06-27 20:14 UTC (permalink / raw)
To: linux-mmc
Cc: ulf.hansson, Tanmay Kathpalia, Ulf Hansson, Adrian Hunter,
Philipp Zabel, linux-kernel
The Cadence SD6HC is the sixth-generation SD/SDIO/eMMC host controller
with an integrated hard combo-PHY. Unlike SD4HC, the SD6HC PHY requires
timing values derived from the current speed mode, clock period, and
board-level IO cell delays to achieve correct signal margins across all
speed grades from Default Speed to HS400.
SD6HC nodes require two named clocks, "ciu" for the controller and
"biu" for the bus interface unit. eMMC hardware reset is handled via an
internal controller register rather than an external reset line. The new
"cdns,sd6hc" compatible string identifies generic SD6HC hardware in
device tree.
Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
MAINTAINERS | 7 +
drivers/mmc/host/Makefile | 3 +-
.../{sdhci-cadence.c => sdhci-cadence-core.c} | 126 ++-
drivers/mmc/host/sdhci-cadence-phy-v6.c | 965 ++++++++++++++++++
drivers/mmc/host/sdhci-cadence.h | 114 +++
5 files changed, 1168 insertions(+), 47 deletions(-)
rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence-core.c} (87%)
create mode 100644 drivers/mmc/host/sdhci-cadence-phy-v6.c
create mode 100644 drivers/mmc/host/sdhci-cadence.h
diff --git a/MAINTAINERS b/MAINTAINERS
index b2040011a386..25c3eb17b3c7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24104,6 +24104,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..83ce3358d8d4 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-cadence-core.o sdhci-cadence-phy-v6.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.c b/drivers/mmc/host/sdhci-cadence-core.c
similarity index 87%
rename from drivers/mmc/host/sdhci-cadence.c
rename to drivers/mmc/host/sdhci-cadence-core.c
index ab5dfae12732..5b8a83c9a0aa 100644
--- a/drivers/mmc/host/sdhci-cadence.c
+++ b/drivers/mmc/host/sdhci-cadence-core.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_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)
{
@@ -572,6 +573,7 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_cdns_priv *priv;
struct clk *clk;
+ struct clk *biu_clk;
int ret;
struct device *dev = &pdev->dev;
static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT;
@@ -580,6 +582,14 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
if (IS_ERR(clk))
return PTR_ERR(clk);
+ /* SD6HC requires a second clock, "biu", for the bus interface unit. */
+ if (of_device_is_compatible(dev->of_node, "cdns,sd6hc")) {
+ biu_clk = devm_clk_get_enabled(dev, "biu");
+ if (IS_ERR(biu_clk))
+ return dev_err_probe(dev, PTR_ERR(biu_clk),
+ "failed to enable biu clock\n");
+ }
+
data = of_device_get_match_data(dev);
if (!data)
return dev_err_probe(dev, -EINVAL, "missing platform driver data\n");
@@ -604,30 +614,46 @@ 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);
- if (ret)
- return ret;
+ /*
+ * For SD4HC, read capabilities with fixed version override and set up
+ * the optional eMMC card RST_n reset control.
+ * 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);
+ if (ret)
+ return ret;
- if (host->mmc->caps & MMC_CAP_HW_RESET) {
- priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL);
- if (IS_ERR(priv->rst_hw))
- return dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw),
- "reset controller error\n");
- if (priv->rst_hw)
- host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset;
+ if (host->mmc->caps & MMC_CAP_HW_RESET) {
+ priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(priv->rst_hw))
+ return dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw),
+ "reset controller error\n");
+ if (priv->rst_hw)
+ host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset;
+ }
+ } else {
+ ret = sdhci_cdns6_phy_probe(pdev, priv);
+ if (ret)
+ return ret;
}
return sdhci_add_host(host);
}
+/*
+ * Only the CIU clock is gated on suspend. The SD6HC "biu" clock is not
+ * toggled here as it may be a shared bus clock; a dedicated biu clock
+ * would need explicit PM gating added here.
+ */
static int sdhci_cdns_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
@@ -639,7 +665,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;
@@ -674,6 +704,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-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c
new file mode 100644
index 000000000000..f3d2a5d16630
--- /dev/null
+++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c
@@ -0,0 +1,965 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PHY and host controller support for Cadence SD6HC SDHCI
+ *
+ * This file provides support for Cadence's sixth-generation SDHCI controller (SD6HC).
+ * Implements PHY initialization, DLL management, per-speed-mode timing calculations,
+ * and host controller register programming for the SD6HC integrated combo-PHY.
+ *
+ * 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_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
+#define SDHCI_CDNS6_PHY_DEFAULT_PHASE_DETECT_SEL 2
+#define SDHCI_CDNS6_PHY_DEFAULT_DLL_LOCK_NUM 0
+#define SDHCI_CDNS6_PHY_DEFAULT_DATA_SELECT_OE_END 1
+
+/* 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;
+ /* Configured delay element (ps); preserved across clock changes */
+ u32 delay_element_org;
+ /* Active delay element (ps); doubled when one SDMCLK requires > 256 steps */
+ u32 delay_element;
+
+ /* PHY_DLL_SLAVE_CTRL register fields */
+ u8 cp_read_dqs_cmd_delay; /* bits [31:24] */
+ u8 cp_clk_wrdqs_delay; /* bits [23:16] */
+ u8 cp_clk_wr_delay; /* bits [15:8] */
+ u8 cp_read_dqs_delay; /* bits [7:0] */
+
+ /* PHY_DLL_MASTER_CTRL register fields */
+ bool cp_dll_bypass_mode; /* bit [23] */
+
+ /* PHY_DQ_TIMING register fields */
+ u8 cp_io_mask_end; /* bits [29:27] */
+ u8 cp_io_mask_start; /* bits [26:24] */
+
+ /* PHY_DQS_TIMING register fields */
+ bool cp_use_phony_dqs; /* bit [20] */
+ bool cp_use_phony_dqs_cmd; /* bit [19] */
+
+ /* HRS07 register - IO delay Information */
+ u8 sdhc_rw_compensate; /* bits [20:16] */
+ u8 sdhc_idelay_val; /* bits [4:0] */
+
+ /* HRS09 register - PHY control and Status */
+ bool sdhc_extended_wr_mode; /* bit [3] */
+ bool sdhc_extended_rd_mode; /* bit [2] */
+
+ /* HRS10 register - SDCLK start point adjustment */
+ u8 sdhc_hcsdclkadj; /* bits [19:16] */
+
+ /* HRS16 register fields - CMD/DAT output delay control */
+ u8 sdhc_wrdata1_sdclk_dly; /* bits [31:28] */
+ u8 sdhc_wrdata0_sdclk_dly; /* bits [27:24] */
+ u8 sdhc_wrcmd1_sdclk_dly; /* bits [23:20] */
+ u8 sdhc_wrcmd0_sdclk_dly; /* bits [19:16] */
+ u8 sdhc_wrdata1_dly; /* bits [15:12] */
+ u8 sdhc_wrdata0_dly; /* bits [11:8] */
+ u8 sdhc_wrcmd1_dly; /* bits [7:4] */
+ u8 sdhc_wrcmd0_dly; /* bits [3:0] */
+
+ /* DLL calculation intermediate values, used during PHY timing calculations */
+ u32 t_sdmclk_calc; /* DLL-quantized SDMCLK period */
+ u32 dll_max_value; /* DLL delay field ceiling */
+
+ /* Tuning value for HS200/HS400 modes */
+ u8 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 (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 (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 (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 (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 (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 (200 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 (50 MHz).
+ * @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 (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.
+ */
+static void (* const init_timings[])(struct sdhci_cdns6_phy *, u32) = {
+ [MMC_TIMING_LEGACY] = init_ds,
+ [MMC_TIMING_MMC_HS] = init_emmc_sdr,
+ [MMC_TIMING_SD_HS] = init_hs,
+ [MMC_TIMING_UHS_SDR12] = init_uhs_sdr12,
+ [MMC_TIMING_UHS_SDR25] = init_uhs_sdr25,
+ [MMC_TIMING_UHS_SDR50] = init_uhs_sdr50,
+ [MMC_TIMING_UHS_SDR104] = init_uhs_sdr104,
+ [MMC_TIMING_UHS_DDR50] = init_uhs_ddr50,
+ [MMC_TIMING_MMC_DDR52] = init_emmc_ddr,
+ [MMC_TIMING_MMC_HS200] = init_emmc_hs200,
+ [MMC_TIMING_MMC_HS400] = 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 = false;
+
+ return 0;
+}
+
+static void sdhci_cdns6_phy_dll_bypass(struct sdhci_cdns6_phy *phy)
+{
+ phy->dll_max_value = 256;
+ phy->cp_dll_bypass_mode = true;
+}
+
+static void sdhci_cdns6_phy_configure_dll(struct sdhci_cdns6_phy *phy)
+{
+ if (!phy->sdhc_extended_wr_mode) {
+ 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) {
+ 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) {
+ 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)
+ 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 set => 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(u8, 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 = false;
+ phy->cp_read_dqs_cmd_delay = 64;
+ } else {
+ phy->cp_use_phony_dqs_cmd = true;
+ 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 = false;
+ phy->cp_read_dqs_delay = 64;
+ } else {
+ phy->cp_use_phony_dqs = true;
+ 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++;
+
+ /* cp_io_mask_start is a 3-bit field, clamp to max value of 7 */
+ phy->cp_io_mask_start = min_t(u8, phy->cp_io_mask_start, 7);
+
+ 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 releasing PHY from reset, wait until PHY_INIT_COMPLETE is set 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)
+{
+ struct sdhci_cdns6_phy *phy = priv->phy;
+ u32 reg;
+ int ret;
+
+ 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_PHONY_DQS;
+ reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD;
+ reg |= SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS;
+ reg |= SDHCI_CDNS6_PHY_DQS_TIMING_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_RD_DEL_SEL;
+ reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS;
+ reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON;
+ reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD;
+ reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL,
+ SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL);
+ 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,
+ SDHCI_CDNS6_PHY_DEFAULT_PHASE_DETECT_SEL);
+ reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM,
+ SDHCI_CDNS6_PHY_DEFAULT_DLL_LOCK_NUM);
+ reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT,
+ SDHCI_CDNS6_PHY_DEFAULT_DLL_START);
+ 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_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,
+ SDHCI_CDNS6_PHY_DEFAULT_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;
+
+ reg |= SDHCI_CDNS_HRS09_RDDATA_EN;
+ 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 5 to 5.5 ms 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_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_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 = false;
+ phy->sdhc_extended_rd_mode = false;
+ } else {
+ phy->sdhc_extended_wr_mode = true;
+ phy->sdhc_extended_rd_mode = true;
+ }
+
+ 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;
+
+ return 0;
+}
+
+void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing)
+{
+ struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+ struct sdhci_cdns6_phy *phy = priv->phy;
+ int ret;
+
+ /* 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;
+
+ ret = sdhci_cdns6_phy_update_timings(host);
+ if (ret) {
+ dev_warn(mmc_dev(host->mmc), "%s: update timings failed: %d\n", __func__, ret);
+ return;
+ }
+
+ ret = sdhci_cdns6_phy_init(priv);
+ if (ret)
+ dev_warn(mmc_dev(host->mmc), "%s: phy init failed: %d\n", __func__, ret);
+}
+
+void sdhci_cdns6_hw_reset(struct sdhci_host *host)
+{
+ struct sdhci_cdns_priv *priv = sdhci_cdns_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);
+ /* For eMMC, minimum is 200us but give it 300us for good measure */
+ usleep_range(300, 1000);
+}
diff --git a/drivers/mmc/host/sdhci-cadence.h b/drivers/mmc/host/sdhci-cadence.h
new file mode 100644
index 000000000000..82780c86e98d
--- /dev/null
+++ b/drivers/mmc/host/sdhci-cadence.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * 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-cadence-core.c.
+ */
+
+#ifndef _MMC_HOST_SDHCI_CADENCE_H
+#define _MMC_HOST_SDHCI_CADENCE_H
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.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 eMMC card RST_n pin (SD4HC only).
+ * @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_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_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 */
--
2.43.7
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 9/9] mmc: sdhci-cadence: add Altera Agilex5 SD6HC support
2026-06-27 20:14 [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
` (7 preceding siblings ...)
2026-06-27 20:14 ` [PATCH v2 8/9] mmc: sdhci-cadence: add Cadence SD6HC support Tanmay Kathpalia
@ 2026-06-27 20:14 ` Tanmay Kathpalia
2026-06-29 7:08 ` Krzysztof Kozlowski
2026-07-04 11:18 ` Adrian Hunter
8 siblings, 2 replies; 24+ messages in thread
From: Tanmay Kathpalia @ 2026-06-27 20:14 UTC (permalink / raw)
To: linux-mmc
Cc: ulf.hansson, Tanmay Kathpalia, Adrian Hunter, Ulf Hansson,
Philipp Zabel, linux-kernel
The Altera Agilex5 SoC integrates a Cadence SD6HC controller that needs
platform-specific configuration to operate correctly.
The SoC requires three named resets: "sdhc-reset" and "sdmmc-ocp" are
exclusive and must be asserted together before being released, so both
clock domains cross the reset boundary simultaneously. "combophy" is
shared with NAND and must only be deasserted, not toggled.
The IOMMU maps DMA addresses within a 40-bit physical address space, so
the DMA mask is capped at 40 bits to prevent allocation beyond the
controller's reach.
The silicon requires the MULTIBLOCK_READ_ACMD12, CAP_CLOCK_BASE_BROKEN,
PRESET_VALUE_BROKEN, and ACMD23_BROKEN quirks. Since
CAP_CLOCK_BASE_BROKEN prevents reading the base clock from the
capabilities register, the maximum clock is supplied from the platform
clock instead.
Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
---
drivers/mmc/host/sdhci-cadence-core.c | 112 ++++++++++++++++++++++++++
1 file changed, 112 insertions(+)
diff --git a/drivers/mmc/host/sdhci-cadence-core.c b/drivers/mmc/host/sdhci-cadence-core.c
index 5b8a83c9a0aa..0f9b9dd2d2c4 100644
--- a/drivers/mmc/host/sdhci-cadence-core.c
+++ b/drivers/mmc/host/sdhci-cadence-core.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,29 @@ 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
+ *
+ * Limit DMA addresses to the physical range the controller can reach.
+ */
+static int sdhci_cdns_set_dma_mask(struct sdhci_host *host)
+{
+ const struct sdhci_cdns_drv_data *data;
+ struct device *dev = mmc_dev(host->mmc);
+ int ret;
+
+ data = of_device_get_match_data(dev);
+ if (!data || !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;
@@ -460,6 +485,65 @@ static int elba_drv_init(struct platform_device *pdev)
return 0;
}
+static int sdhci_cdns6_agilex5_init(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct reset_control *rst_sdhc;
+ struct reset_control *rst_ocp;
+ struct reset_control *rst_combophy;
+ int ret;
+
+ /*
+ * The combo PHY reset is shared with other peripheral (NAND).
+ * Use the _deasserted variant so devres calls assert + put on driver
+ * detach, keeping the shared deassert reference count balanced across
+ * probe/unbind cycles.
+ */
+ rst_combophy = devm_reset_control_get_shared_deasserted(dev, "combophy");
+ if (IS_ERR(rst_combophy))
+ return dev_err_probe(dev, PTR_ERR(rst_combophy),
+ "failed to get combophy reset\n");
+
+ /*
+ * Assert SDHCI core and SDMMC OCP/AXI bus resets together so their
+ * active periods overlap before both clock domains are released.
+ */
+ rst_sdhc = devm_reset_control_get_exclusive(dev, "sdhc-reset");
+ if (IS_ERR(rst_sdhc))
+ return dev_err_probe(dev, PTR_ERR(rst_sdhc),
+ "failed to get sdhc-reset\n");
+
+ rst_ocp = devm_reset_control_get_exclusive(dev, "sdmmc-ocp");
+ if (IS_ERR(rst_ocp))
+ return dev_err_probe(dev, PTR_ERR(rst_ocp),
+ "failed to get sdmmc-ocp reset\n");
+
+ ret = reset_control_assert(rst_sdhc);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to assert sdhc-reset\n");
+
+ ret = reset_control_assert(rst_ocp);
+ if (ret) {
+ reset_control_deassert(rst_sdhc);
+ return dev_err_probe(dev, ret, "failed to assert sdmmc-ocp reset\n");
+ }
+
+ /* Hold resets asserted long enough for all clock domains to capture. */
+ usleep_range(10, 20);
+
+ ret = reset_control_deassert(rst_sdhc);
+ if (ret) {
+ reset_control_deassert(rst_ocp);
+ return dev_err_probe(dev, ret, "failed to deassert sdhc-reset\n");
+ }
+
+ ret = reset_control_deassert(rst_ocp);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to deassert sdmmc-ocp reset\n");
+
+ return 0;
+}
+
static const struct sdhci_ops sdhci_cdns4_ops = {
.set_clock = sdhci_set_clock,
.get_timeout_clock = sdhci_cdns_get_timeout_clock,
@@ -479,6 +563,18 @@ 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_max_clock = sdhci_pltfm_clk_get_max_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 +602,18 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
},
};
+static const struct sdhci_cdns_drv_data sdhci_cdns6_agilex5_drv_data = {
+ .init = sdhci_cdns6_agilex5_init,
+ .pltfm_data = {
+ .ops = &sdhci_cdns6_agilex5_ops,
+ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+ 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,
@@ -704,6 +812,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] 24+ messages in thread
* Re: [PATCH v2 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties
2026-06-27 20:14 ` [PATCH v2 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties Tanmay Kathpalia
@ 2026-06-29 7:04 ` Krzysztof Kozlowski
2026-07-02 8:58 ` Kathpalia, Tanmay
0 siblings, 1 reply; 24+ messages in thread
From: Krzysztof Kozlowski @ 2026-06-29 7:04 UTC (permalink / raw)
To: Tanmay Kathpalia
Cc: linux-mmc, ulf.hansson, Ulf Hansson, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Masahiro Yamada, devicetree,
linux-kernel
On Sat, Jun 27, 2026 at 01:14:47PM -0700, Tanmay Kathpalia wrote:
> Extend the Cadence SDHCI binding to support the sixth-generation SD6HC
> controller. Add the cdns,sd6hc compatible string with two named clocks
> (ciu and biu) and three SD6HC-specific PHY timing properties for iocell
> input/output delay and delay element size.
>
> Add the altr,agilex5-sd6hc compatible string with three named reset
> lines from the Altera HPS Reset Manager. Introduce per-variant
> constraints so SD6HC and SD4HC each enforce their own clock, reset, and
> PHY property requirements independently.
You just repeated the diff. Instead describe the hardware.
>
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> ---
> .../devicetree/bindings/mmc/cdns,sdhci.yaml | 122 ++++++++++++++++--
> 1 file changed, 111 insertions(+), 11 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
> index 6c7317d13aa6..edd96e1d2bdc 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
Drop description, you repeat the fallback compatible, so this is obvious.
> + 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
Same here
> + items:
> + - enum:
> + - altr,agilex5-sd6hc
> + - const: cdns,sd6hc
>
> reg:
> minItems: 1
> @@ -28,10 +36,12 @@ properties:
> maxItems: 1
>
> clocks:
> - maxItems: 1
> + minItems: 1
> + maxItems: 2
>
> resets:
> - maxItems: 1
> + minItems: 1
> + maxItems: 3
>
> # PHY DLL input delays:
> # They are used to delay the data valid window, and align the window to
> @@ -115,6 +125,25 @@ properties:
> minimum: 0
> maximum: 0x7f
>
> + # SD6HC PHY timing properties:
> + cdns,iocell-input-delay:
Use standard unit suffixes from dtschema. I am pretty sure we have
picoseconds.
> + 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
None of these are deducible from the compatible? IOW, they differ in
each board with the same SoC?
> + $ref: /schemas/types.yaml#/definitions/uint32
> + minimum: 1
> + maximum: 1000 # 1 ns
> +
> required:
> - compatible
> - reg
> @@ -139,6 +168,77 @@ 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
There is no property like clock-names. Look at the schema/binding.
> + dma-coherent: true
> + iommus:
> + maxItems: 1a
Do not define properties in conditional block, but top level.
> + 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:
> + - clock-names
All this clearly suggests you should have own binding file.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement
2026-06-27 20:14 ` [PATCH v2 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement Tanmay Kathpalia
@ 2026-06-29 7:06 ` Krzysztof Kozlowski
2026-07-02 9:01 ` Kathpalia, Tanmay
0 siblings, 1 reply; 24+ messages in thread
From: Krzysztof Kozlowski @ 2026-06-29 7:06 UTC (permalink / raw)
To: Tanmay Kathpalia
Cc: linux-mmc, ulf.hansson, Dinh Nguyen, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
On Sat, Jun 27, 2026 at 01:14:48PM -0700, Tanmay Kathpalia wrote:
> Add the Cadence SD6HC controller node to the Agilex5 SoC DTSI as a
> shared SD/eMMC node, disabled by default. The controller integrates
> with the system SMMU for IOMMU support and uses SDMCLK as the primary
> clock source for PHY timing.
>
> On the SOCDK board, add a fixed 3.3V regulator for card power and a
> GPIO-controlled regulator for I/O voltage switching between 1.8V and
> 3.3V. Enable the controller for SD-only operation in 4-bit bus width
> with high-speed and SDR104 UHS-I modes at 200 MHz.
>
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> ---
> .../arm64/boot/dts/intel/socfpga_agilex5.dtsi | 25 +++++++++++++++
> .../boot/dts/intel/socfpga_agilex5_socdk.dts | 31 +++++++++++++++++++
> 2 files changed, 56 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> index 02e62d954e94..f552aa0c1faa 100644
> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> @@ -300,6 +300,31 @@ portb: gpio-controller@0 {
> };
> };
>
> + /*
> + * Shared SD/eMMC controller node. On the SOCDK OOBE daughter-card
> + * this is used for SD card operation; on the SOCDK eMMC daughter-card
> + * it is configured for eMMC.
> + */
> + emmc: mmc@10808000 {
> + #address-cells = <1>;
> + #size-cells = <0>;
Please follow DTS coding style for new code.
> + compatible = "altr,agilex5-sd6hc", "cdns,sd6hc";
> + reg = <0x10808000 0x1000>;
> + interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
> + 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..c56f46721bb0 100644
> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk.dts
> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk.dts
> @@ -34,6 +34,24 @@ memory@80000000 {
> /* We expect the bootloader to fill in the reg */
> reg = <0x0 0x80000000 0x0 0x0>;
> };
> +
> + vmmc_reg: regulator-fixed-3p3v {
> + compatible = "regulator-fixed";
> + regulator-name = "vcc-sd";
> + regulator-min-microvolt = <3300000>;
> + regulator-max-microvolt = <3300000>;
> + regulator-always-on;
> + };
> +
> + vqmmc_io_reg: regulator-1p8v {
> + compatible = "regulator-gpio";
> + regulator-name = "vqmmc-io";
> + regulator-min-microvolt = <1800000>;
> + regulator-max-microvolt = <3300000>;
> + states = <1800000 0x1>,
There is only one space after '='.
> + <3300000 0x0>;
> + gpios = <&portb 3 GPIO_ACTIVE_HIGH>;
> + };
> };
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant
2026-06-27 20:14 ` [PATCH v2 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant Tanmay Kathpalia
@ 2026-06-29 7:06 ` Krzysztof Kozlowski
2026-07-02 9:07 ` Kathpalia, Tanmay
0 siblings, 1 reply; 24+ messages in thread
From: Krzysztof Kozlowski @ 2026-06-29 7:06 UTC (permalink / raw)
To: Tanmay Kathpalia
Cc: linux-mmc, ulf.hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Dinh Nguyen, devicetree, linux-kernel
On Sat, Jun 27, 2026 at 01:14:49PM -0700, Tanmay Kathpalia wrote:
> 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 206686f3eebc..f5efcbc381b8 100644
> --- a/Documentation/devicetree/bindings/arm/altera.yaml
> +++ b/Documentation/devicetree/bindings/arm/altera.yaml
> @@ -113,6 +113,7 @@ properties:
> - intel,socfpga-agilex5-socdk-013b
> - intel,socfpga-agilex5-socdk-modular
> - intel,socfpga-agilex5-socdk-nand
> + - intel,socfpga-agilex5-socdk-emmc
Do not break the order of entries.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support
2026-06-27 20:14 ` [PATCH v2 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support Tanmay Kathpalia
@ 2026-06-29 7:07 ` Krzysztof Kozlowski
2026-07-02 9:07 ` Kathpalia, Tanmay
0 siblings, 1 reply; 24+ messages in thread
From: Krzysztof Kozlowski @ 2026-06-29 7:07 UTC (permalink / raw)
To: Tanmay Kathpalia
Cc: linux-mmc, ulf.hansson, Dinh Nguyen, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
On Sat, Jun 27, 2026 at 01:14:50PM -0700, Tanmay Kathpalia wrote:
> Add socfpga_agilex5_socdk_emmc.dts for the Agilex5 SoCDK eMMC daughter
> board variant. Define board-specific regulators at the DTS root: a
> fixed 3.3V supply for card power and a fixed 1.8V supply for eMMC I/O
> voltage.
>
> Enable the shared SD/eMMC controller for eMMC-only operation with an
> 8-bit bus, HS200 and HS400 modes at 1.8V signaling, and a 200 MHz
> maximum clock frequency.
>
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> ---
> arch/arm64/boot/dts/intel/Makefile | 1 +
> .../dts/intel/socfpga_agilex5_socdk_emmc.dts | 120 ++++++++++++++++++
> 2 files changed, 121 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 \
Same mistakes... Do not introduce random order.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 9/9] mmc: sdhci-cadence: add Altera Agilex5 SD6HC support
2026-06-27 20:14 ` [PATCH v2 9/9] mmc: sdhci-cadence: add Altera Agilex5 " Tanmay Kathpalia
@ 2026-06-29 7:08 ` Krzysztof Kozlowski
2026-07-02 9:04 ` Kathpalia, Tanmay
2026-07-04 11:18 ` Adrian Hunter
1 sibling, 1 reply; 24+ messages in thread
From: Krzysztof Kozlowski @ 2026-06-29 7:08 UTC (permalink / raw)
To: Tanmay Kathpalia
Cc: linux-mmc, ulf.hansson, Adrian Hunter, Ulf Hansson, Philipp Zabel,
linux-kernel
On Sat, Jun 27, 2026 at 01:14:54PM -0700, Tanmay Kathpalia wrote:
> + rst_combophy = devm_reset_control_get_shared_deasserted(dev, "combophy");
> + if (IS_ERR(rst_combophy))
> + return dev_err_probe(dev, PTR_ERR(rst_combophy),
> + "failed to get combophy reset\n");
> +
> + /*
> + * Assert SDHCI core and SDMMC OCP/AXI bus resets together so their
> + * active periods overlap before both clock domains are released.
> + */
> + rst_sdhc = devm_reset_control_get_exclusive(dev, "sdhc-reset");
Binding has no reset-names property (just open the binding!), thus you
cannot use this.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties
2026-06-29 7:04 ` Krzysztof Kozlowski
@ 2026-07-02 8:58 ` Kathpalia, Tanmay
0 siblings, 0 replies; 24+ messages in thread
From: Kathpalia, Tanmay @ 2026-07-02 8:58 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: linux-mmc, ulf.hansson, Ulf Hansson, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Masahiro Yamada, devicetree,
linux-kernel
Hi Krzysztof,
Thanks for the review.
On 6/29/2026 12:34 PM, Krzysztof Kozlowski wrote:
> On Sat, Jun 27, 2026 at 01:14:47PM -0700, Tanmay Kathpalia wrote:
>> Extend the Cadence SDHCI binding to support the sixth-generation SD6HC
>> controller. Add the cdns,sd6hc compatible string with two named clocks
>> (ciu and biu) and three SD6HC-specific PHY timing properties for iocell
>> input/output delay and delay element size.
>>
>> Add the altr,agilex5-sd6hc compatible string with three named reset
>> lines from the Altera HPS Reset Manager. Introduce per-variant
>> constraints so SD6HC and SD4HC each enforce their own clock, reset, and
>> PHY property requirements independently.
> You just repeated the diff. Instead describe the hardware.
Ack, I'll update the commit message to describe the SD6HC hardware
instead of summarizing the changes.
>
>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>> ---
>> .../devicetree/bindings/mmc/cdns,sdhci.yaml | 122 ++++++++++++++++--
>> 1 file changed, 111 insertions(+), 11 deletions(-)
>>
>> diff --git a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml
>> index 6c7317d13aa6..edd96e1d2bdc 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
> Drop description, you repeat the fallback compatible, so this is obvious.
I'll remove the redundant descriptions.
>
>> + 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
> Same here
Ack.
>
>> + items:
>> + - enum:
>> + - altr,agilex5-sd6hc
>> + - const: cdns,sd6hc
>>
>> reg:
>> minItems: 1
>> @@ -28,10 +36,12 @@ properties:
>> maxItems: 1
>>
>> clocks:
>> - maxItems: 1
>> + minItems: 1
>> + maxItems: 2
>>
>> resets:
>> - maxItems: 1
>> + minItems: 1
>> + maxItems: 3
>>
>> # PHY DLL input delays:
>> # They are used to delay the data valid window, and align the window to
>> @@ -115,6 +125,25 @@ properties:
>> minimum: 0
>> maximum: 0x7f
>>
>> + # SD6HC PHY timing properties:
>> + cdns,iocell-input-delay:
> Use standard unit suffixes from dtschema. I am pretty sure we have
> picoseconds.
I'll rename these properties to use the standard dtschema unit
suffixes.
>
>> + 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
> None of these are deducible from the compatible? IOW, they differ in
> each board with the same SoC?
These values are board/platform dependent and are provided by
the platform integration rather than being fixed by the controller
compatible.
>
>> + $ref: /schemas/types.yaml#/definitions/uint32
>> + minimum: 1
>> + maximum: 1000 # 1 ns
>> +
>> required:
>> - compatible
>> - reg
>> @@ -139,6 +168,77 @@ 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
> There is no property like clock-names. Look at the schema/binding.
Ack, I'll fix this.
>
>> + dma-coherent: true
>> + iommus:
>> + maxItems: 1a
> Do not define properties in conditional block, but top level.
I'll move them to the top level as suggested.
>
>> + 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:
>> + - clock-names
> All this clearly suggests you should have own binding file.
Agreed. I'll split the SD6HC support into a separate binding
file and address the above comments in v3.
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement
2026-06-29 7:06 ` Krzysztof Kozlowski
@ 2026-07-02 9:01 ` Kathpalia, Tanmay
0 siblings, 0 replies; 24+ messages in thread
From: Kathpalia, Tanmay @ 2026-07-02 9:01 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: linux-mmc, ulf.hansson, Dinh Nguyen, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
Hi Krzysztof,
Thanks for the review.
On 6/29/2026 12:36 PM, Krzysztof Kozlowski wrote:
> On Sat, Jun 27, 2026 at 01:14:48PM -0700, Tanmay Kathpalia wrote:
>> Add the Cadence SD6HC controller node to the Agilex5 SoC DTSI as a
>> shared SD/eMMC node, disabled by default. The controller integrates
>> with the system SMMU for IOMMU support and uses SDMCLK as the primary
>> clock source for PHY timing.
>>
>> On the SOCDK board, add a fixed 3.3V regulator for card power and a
>> GPIO-controlled regulator for I/O voltage switching between 1.8V and
>> 3.3V. Enable the controller for SD-only operation in 4-bit bus width
>> with high-speed and SDR104 UHS-I modes at 200 MHz.
>>
>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>> ---
>> .../arm64/boot/dts/intel/socfpga_agilex5.dtsi | 25 +++++++++++++++
>> .../boot/dts/intel/socfpga_agilex5_socdk.dts | 31 +++++++++++++++++++
>> 2 files changed, 56 insertions(+)
>>
>> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> index 02e62d954e94..f552aa0c1faa 100644
>> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
>> @@ -300,6 +300,31 @@ portb: gpio-controller@0 {
>> };
>> };
>>
>> + /*
>> + * Shared SD/eMMC controller node. On the SOCDK OOBE daughter-card
>> + * this is used for SD card operation; on the SOCDK eMMC daughter-card
>> + * it is configured for eMMC.
>> + */
>> + emmc: mmc@10808000 {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
> Please follow DTS coding style for new code.
Ack, I'll update the DTS formatting to follow the coding style in v3.
>
>> + compatible = "altr,agilex5-sd6hc", "cdns,sd6hc";
>> + reg = <0x10808000 0x1000>;
>> + interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
>> + 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..c56f46721bb0 100644
>> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk.dts
>> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk.dts
>> @@ -34,6 +34,24 @@ memory@80000000 {
>> /* We expect the bootloader to fill in the reg */
>> reg = <0x0 0x80000000 0x0 0x0>;
>> };
>> +
>> + vmmc_reg: regulator-fixed-3p3v {
>> + compatible = "regulator-fixed";
>> + regulator-name = "vcc-sd";
>> + regulator-min-microvolt = <3300000>;
>> + regulator-max-microvolt = <3300000>;
>> + regulator-always-on;
>> + };
>> +
>> + vqmmc_io_reg: regulator-1p8v {
>> + compatible = "regulator-gpio";
>> + regulator-name = "vqmmc-io";
>> + regulator-min-microvolt = <1800000>;
>> + regulator-max-microvolt = <3300000>;
>> + states = <1800000 0x1>,
> There is only one space after '='.
I'll fix the formatting in v3.
>
>> + <3300000 0x0>;
>> + gpios = <&portb 3 GPIO_ACTIVE_HIGH>;
>> + };
>> };
> Best regards,
> Krzysztof
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 9/9] mmc: sdhci-cadence: add Altera Agilex5 SD6HC support
2026-06-29 7:08 ` Krzysztof Kozlowski
@ 2026-07-02 9:04 ` Kathpalia, Tanmay
0 siblings, 0 replies; 24+ messages in thread
From: Kathpalia, Tanmay @ 2026-07-02 9:04 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: linux-mmc, ulf.hansson, Adrian Hunter, Ulf Hansson, Philipp Zabel,
linux-kernel
Hi Krzysztof,
Thanks for the review.
On 6/29/2026 12:38 PM, Krzysztof Kozlowski wrote:
> On Sat, Jun 27, 2026 at 01:14:54PM -0700, Tanmay Kathpalia wrote:
>> + rst_combophy = devm_reset_control_get_shared_deasserted(dev, "combophy");
>> + if (IS_ERR(rst_combophy))
>> + return dev_err_probe(dev, PTR_ERR(rst_combophy),
>> + "failed to get combophy reset\n");
>> +
>> + /*
>> + * Assert SDHCI core and SDMMC OCP/AXI bus resets together so their
>> + * active periods overlap before both clock domains are released.
>> + */
>> + rst_sdhc = devm_reset_control_get_exclusive(dev, "sdhc-reset");
> Binding has no reset-names property (just open the binding!), thus you
> cannot use this.
In patch 2 of this series I had added the reset-names property to the
SD6HC binding. Based on your earlier feedback, I'll be splitting the
SD6HC support into a separate binding file in v3, and reset-names
will be defined there accordingly.
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant
2026-06-29 7:06 ` Krzysztof Kozlowski
@ 2026-07-02 9:07 ` Kathpalia, Tanmay
0 siblings, 0 replies; 24+ messages in thread
From: Kathpalia, Tanmay @ 2026-07-02 9:07 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: linux-mmc, ulf.hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Dinh Nguyen, devicetree, linux-kernel
On 6/29/2026 12:36 PM, Krzysztof Kozlowski wrote:
> On Sat, Jun 27, 2026 at 01:14:49PM -0700, Tanmay Kathpalia wrote:
>> 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 206686f3eebc..f5efcbc381b8 100644
>> --- a/Documentation/devicetree/bindings/arm/altera.yaml
>> +++ b/Documentation/devicetree/bindings/arm/altera.yaml
>> @@ -113,6 +113,7 @@ properties:
>> - intel,socfpga-agilex5-socdk-013b
>> - intel,socfpga-agilex5-socdk-modular
>> - intel,socfpga-agilex5-socdk-nand
>> + - intel,socfpga-agilex5-socdk-emmc
> Do not break the order of entries.
Ack.
>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support
2026-06-29 7:07 ` Krzysztof Kozlowski
@ 2026-07-02 9:07 ` Kathpalia, Tanmay
0 siblings, 0 replies; 24+ messages in thread
From: Kathpalia, Tanmay @ 2026-07-02 9:07 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: linux-mmc, ulf.hansson, Dinh Nguyen, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
On 6/29/2026 12:37 PM, Krzysztof Kozlowski wrote:
> On Sat, Jun 27, 2026 at 01:14:50PM -0700, Tanmay Kathpalia wrote:
>> Add socfpga_agilex5_socdk_emmc.dts for the Agilex5 SoCDK eMMC daughter
>> board variant. Define board-specific regulators at the DTS root: a
>> fixed 3.3V supply for card power and a fixed 1.8V supply for eMMC I/O
>> voltage.
>>
>> Enable the shared SD/eMMC controller for eMMC-only operation with an
>> 8-bit bus, HS200 and HS400 modes at 1.8V signaling, and a 200 MHz
>> maximum clock frequency.
>>
>> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
>> ---
>> arch/arm64/boot/dts/intel/Makefile | 1 +
>> .../dts/intel/socfpga_agilex5_socdk_emmc.dts | 120 ++++++++++++++++++
>> 2 files changed, 121 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 \
> Same mistakes... Do not introduce random order.
Ack.
>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: (subset) [PATCH v2 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5
2026-06-27 20:14 ` [PATCH v2 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Tanmay Kathpalia
@ 2026-07-02 15:53 ` Philipp Zabel
0 siblings, 0 replies; 24+ messages in thread
From: Philipp Zabel @ 2026-07-02 15:53 UTC (permalink / raw)
To: Tanmay Kathpalia, linux-mmc
Cc: ulf.hansson, Conor Dooley, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, devicetree, linux-kernel
On Sa, 2026-06-27 at 13:14 -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.
[...]
Applied to reset/fixes, thanks!
[1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5
https://git.kernel.org/pub/scm/linux/kernel/git/pza/linux.git/commit/?id=ab45ecfab540
regards
Philipp
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork
2026-06-27 20:14 ` [PATCH v2 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork Tanmay Kathpalia
@ 2026-07-04 11:17 ` Adrian Hunter
0 siblings, 0 replies; 24+ messages in thread
From: Adrian Hunter @ 2026-07-04 11:17 UTC (permalink / raw)
To: Tanmay Kathpalia, linux-mmc; +Cc: ulf.hansson, Ulf Hansson, linux-kernel
On 27/06/2026 23:14, 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>
> ---
> drivers/mmc/host/sdhci-cadence.c | 56 ++++++++++++++++----------------
> 1 file changed, 28 insertions(+), 28 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
> index 435603c8c00b..b00dc2eec922 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)
Could be all one line
> {
> 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)
Could be all one line
> {
> - 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);
Could be all one line
> 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;
> }
> @@ -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,
> },
> };
>
> @@ -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] 24+ messages in thread
* Re: [PATCH v2 8/9] mmc: sdhci-cadence: add Cadence SD6HC support
2026-06-27 20:14 ` [PATCH v2 8/9] mmc: sdhci-cadence: add Cadence SD6HC support Tanmay Kathpalia
@ 2026-07-04 11:18 ` Adrian Hunter
0 siblings, 0 replies; 24+ messages in thread
From: Adrian Hunter @ 2026-07-04 11:18 UTC (permalink / raw)
To: Tanmay Kathpalia, linux-mmc
Cc: ulf.hansson, Ulf Hansson, Philipp Zabel, linux-kernel
On 27/06/2026 23:14, Tanmay Kathpalia wrote:
> The Cadence SD6HC is the sixth-generation SD/SDIO/eMMC host controller
> with an integrated hard combo-PHY. Unlike SD4HC, the SD6HC PHY requires
> timing values derived from the current speed mode, clock period, and
> board-level IO cell delays to achieve correct signal margins across all
> speed grades from Default Speed to HS400.
>
> SD6HC nodes require two named clocks, "ciu" for the controller and
> "biu" for the bus interface unit. eMMC hardware reset is handled via an
> internal controller register rather than an external reset line. The new
> "cdns,sd6hc" compatible string identifies generic SD6HC hardware in
> device tree.
>
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> ---
> MAINTAINERS | 7 +
> drivers/mmc/host/Makefile | 3 +-
> .../{sdhci-cadence.c => sdhci-cadence-core.c} | 126 ++-
> drivers/mmc/host/sdhci-cadence-phy-v6.c | 965 ++++++++++++++++++
> drivers/mmc/host/sdhci-cadence.h | 114 +++
> 5 files changed, 1168 insertions(+), 47 deletions(-)
> rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence-core.c} (87%)
> create mode 100644 drivers/mmc/host/sdhci-cadence-phy-v6.c
> create mode 100644 drivers/mmc/host/sdhci-cadence.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b2040011a386..25c3eb17b3c7 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -24104,6 +24104,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
"Maintained" or "Supported" ?
> +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..83ce3358d8d4 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-cadence-core.o sdhci-cadence-phy-v6.o
> +obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF) += sdhci-esdhc-mcf.o
Do not amend CONFIG_MMC_SDHCI_ESDHC_MCF line
> 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.c b/drivers/mmc/host/sdhci-cadence-core.c
> similarity index 87%
> rename from drivers/mmc/host/sdhci-cadence.c
> rename to drivers/mmc/host/sdhci-cadence-core.c
> index ab5dfae12732..5b8a83c9a0aa 100644
> --- a/drivers/mmc/host/sdhci-cadence.c
> +++ b/drivers/mmc/host/sdhci-cadence-core.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"
If "sdhci-cadence.h" includes only what it uses (see further below),
then some of these includes will still be needed
>
> /* 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_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);
Could be all one line
> 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)
> {
> @@ -572,6 +573,7 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
> struct sdhci_pltfm_host *pltfm_host;
> struct sdhci_cdns_priv *priv;
> struct clk *clk;
> + struct clk *biu_clk;
> int ret;
> struct device *dev = &pdev->dev;
> static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT;
> @@ -580,6 +582,14 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
> if (IS_ERR(clk))
> return PTR_ERR(clk);
>
> + /* SD6HC requires a second clock, "biu", for the bus interface unit. */
> + if (of_device_is_compatible(dev->of_node, "cdns,sd6hc")) {
> + biu_clk = devm_clk_get_enabled(dev, "biu");
> + if (IS_ERR(biu_clk))
> + return dev_err_probe(dev, PTR_ERR(biu_clk),
> + "failed to enable biu clock\n");
Could be all one line
> + }
> +
> data = of_device_get_match_data(dev);
> if (!data)
> return dev_err_probe(dev, -EINVAL, "missing platform driver data\n");
> @@ -604,30 +614,46 @@ 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);
> - if (ret)
> - return ret;
> + /*
> + * For SD4HC, read capabilities with fixed version override and set up
> + * the optional eMMC card RST_n reset control.
> + * 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);
> + if (ret)
> + return ret;
>
> - if (host->mmc->caps & MMC_CAP_HW_RESET) {
> - priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL);
> - if (IS_ERR(priv->rst_hw))
> - return dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw),
> - "reset controller error\n");
> - if (priv->rst_hw)
> - host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset;
> + if (host->mmc->caps & MMC_CAP_HW_RESET) {
> + priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL);
> + if (IS_ERR(priv->rst_hw))
> + return dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw),
> + "reset controller error\n");
> + if (priv->rst_hw)
> + host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset;
> + }
> + } else {
> + ret = sdhci_cdns6_phy_probe(pdev, priv);
> + if (ret)
> + return ret;
> }
>
> return sdhci_add_host(host);
> }
>
> +/*
> + * Only the CIU clock is gated on suspend. The SD6HC "biu" clock is not
> + * toggled here as it may be a shared bus clock; a dedicated biu clock
> + * would need explicit PM gating added here.
> + */
> static int sdhci_cdns_resume(struct device *dev)
> {
> struct sdhci_host *host = dev_get_drvdata(dev);
> @@ -639,7 +665,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;
>
> @@ -674,6 +704,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-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> new file mode 100644
> index 000000000000..f3d2a5d16630
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> @@ -0,0 +1,965 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * PHY and host controller support for Cadence SD6HC SDHCI
> + *
> + * This file provides support for Cadence's sixth-generation SDHCI controller (SD6HC).
> + * Implements PHY initialization, DLL management, per-speed-mode timing calculations,
> + * and host controller register programming for the SD6HC integrated combo-PHY.
> + *
> + * Copyright (C) 2026 Altera Corporation
> + * Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> + */
> +
> +#include "sdhci-cadence.h"
It is better if a file includes what it uses. See comment
about sdhci-cadence.h further below
> +
> +/* 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_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
> +#define SDHCI_CDNS6_PHY_DEFAULT_PHASE_DETECT_SEL 2
> +#define SDHCI_CDNS6_PHY_DEFAULT_DLL_LOCK_NUM 0
> +#define SDHCI_CDNS6_PHY_DEFAULT_DATA_SELECT_OE_END 1
> +
> +/* 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;
Above four are assigned but never used
> +
> + /*
> + * 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;
> + /* Configured delay element (ps); preserved across clock changes */
> + u32 delay_element_org;
> + /* Active delay element (ps); doubled when one SDMCLK requires > 256 steps */
> + u32 delay_element;
> +
> + /* PHY_DLL_SLAVE_CTRL register fields */
> + u8 cp_read_dqs_cmd_delay; /* bits [31:24] */
> + u8 cp_clk_wrdqs_delay; /* bits [23:16] */
> + u8 cp_clk_wr_delay; /* bits [15:8] */
> + u8 cp_read_dqs_delay; /* bits [7:0] */
> +
> + /* PHY_DLL_MASTER_CTRL register fields */
> + bool cp_dll_bypass_mode; /* bit [23] */
> +
> + /* PHY_DQ_TIMING register fields */
> + u8 cp_io_mask_end; /* bits [29:27] */
> + u8 cp_io_mask_start; /* bits [26:24] */
> +
> + /* PHY_DQS_TIMING register fields */
> + bool cp_use_phony_dqs; /* bit [20] */
> + bool cp_use_phony_dqs_cmd; /* bit [19] */
> +
> + /* HRS07 register - IO delay Information */
> + u8 sdhc_rw_compensate; /* bits [20:16] */
> + u8 sdhc_idelay_val; /* bits [4:0] */
> +
> + /* HRS09 register - PHY control and Status */
> + bool sdhc_extended_wr_mode; /* bit [3] */
> + bool sdhc_extended_rd_mode; /* bit [2] */
> +
> + /* HRS10 register - SDCLK start point adjustment */
> + u8 sdhc_hcsdclkadj; /* bits [19:16] */
> +
> + /* HRS16 register fields - CMD/DAT output delay control */
> + u8 sdhc_wrdata1_sdclk_dly; /* bits [31:28] */
> + u8 sdhc_wrdata0_sdclk_dly; /* bits [27:24] */
> + u8 sdhc_wrcmd1_sdclk_dly; /* bits [23:20] */
> + u8 sdhc_wrcmd0_sdclk_dly; /* bits [19:16] */
> + u8 sdhc_wrdata1_dly; /* bits [15:12] */
> + u8 sdhc_wrdata0_dly; /* bits [11:8] */
> + u8 sdhc_wrcmd1_dly; /* bits [7:4] */
> + u8 sdhc_wrcmd0_dly; /* bits [3:0] */
> +
> + /* DLL calculation intermediate values, used during PHY timing calculations */
> + u32 t_sdmclk_calc; /* DLL-quantized SDMCLK period */
> + u32 dll_max_value; /* DLL delay field ceiling */
> +
> + /* Tuning value for HS200/HS400 modes */
> + u8 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 (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 (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 (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 (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 (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 (200 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 (50 MHz).
> + * @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 (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.
> + */
> +static void (* const init_timings[])(struct sdhci_cdns6_phy *, u32) = {
> + [MMC_TIMING_LEGACY] = init_ds,
> + [MMC_TIMING_MMC_HS] = init_emmc_sdr,
> + [MMC_TIMING_SD_HS] = init_hs,
> + [MMC_TIMING_UHS_SDR12] = init_uhs_sdr12,
> + [MMC_TIMING_UHS_SDR25] = init_uhs_sdr25,
> + [MMC_TIMING_UHS_SDR50] = init_uhs_sdr50,
> + [MMC_TIMING_UHS_SDR104] = init_uhs_sdr104,
> + [MMC_TIMING_UHS_DDR50] = init_uhs_ddr50,
> + [MMC_TIMING_MMC_DDR52] = init_emmc_ddr,
> + [MMC_TIMING_MMC_HS200] = init_emmc_hs200,
> + [MMC_TIMING_MMC_HS400] = 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 = false;
> +
> + return 0;
> +}
> +
> +static void sdhci_cdns6_phy_dll_bypass(struct sdhci_cdns6_phy *phy)
> +{
> + phy->dll_max_value = 256;
> + phy->cp_dll_bypass_mode = true;
> +}
> +
> +static void sdhci_cdns6_phy_configure_dll(struct sdhci_cdns6_phy *phy)
> +{
> + if (!phy->sdhc_extended_wr_mode) {
> + 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) {
> + 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) {
> + 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)
> + 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 set => 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(u8, 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 = false;
> + phy->cp_read_dqs_cmd_delay = 64;
> + } else {
> + phy->cp_use_phony_dqs_cmd = true;
> + 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;
Unnecessary line wrap
> +}
> +
> +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 = false;
> + phy->cp_read_dqs_delay = 64;
> + } else {
> + phy->cp_use_phony_dqs = true;
> + 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++;
> +
> + /* cp_io_mask_start is a 3-bit field, clamp to max value of 7 */
> + phy->cp_io_mask_start = min_t(u8, phy->cp_io_mask_start, 7);
> +
> + 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 releasing PHY from reset, wait until PHY_INIT_COMPLETE is set 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)
> +{
> + struct sdhci_cdns6_phy *phy = priv->phy;
> + u32 reg;
> + int ret;
> +
> + 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_PHONY_DQS;
> + reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD;
> + reg |= SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS;
> + reg |= SDHCI_CDNS6_PHY_DQS_TIMING_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_RD_DEL_SEL;
> + reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS;
> + reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON;
> + reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD;
> + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL,
> + SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL);
> + 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,
> + SDHCI_CDNS6_PHY_DEFAULT_PHASE_DETECT_SEL);
> + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM,
> + SDHCI_CDNS6_PHY_DEFAULT_DLL_LOCK_NUM);
> + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT,
> + SDHCI_CDNS6_PHY_DEFAULT_DLL_START);
> + 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_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,
> + SDHCI_CDNS6_PHY_DEFAULT_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;
> +
> + reg |= SDHCI_CDNS_HRS09_RDDATA_EN;
> + 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 5 to 5.5 ms 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_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_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 = false;
> + phy->sdhc_extended_rd_mode = false;
> + } else {
> + phy->sdhc_extended_wr_mode = true;
> + phy->sdhc_extended_rd_mode = true;
> + }
> +
> + 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;
> +
> + return 0;
> +}
> +
> +void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing)
> +{
> + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
> + struct sdhci_cdns6_phy *phy = priv->phy;
> + int ret;
> +
> + /* 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;
> +
> + ret = sdhci_cdns6_phy_update_timings(host);
> + if (ret) {
> + dev_warn(mmc_dev(host->mmc), "%s: update timings failed: %d\n", __func__, ret);
> + return;
> + }
> +
> + ret = sdhci_cdns6_phy_init(priv);
> + if (ret)
> + dev_warn(mmc_dev(host->mmc), "%s: phy init failed: %d\n", __func__, ret);
> +}
> +
> +void sdhci_cdns6_hw_reset(struct sdhci_host *host)
> +{
> + struct sdhci_cdns_priv *priv = sdhci_cdns_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);
> + /* For eMMC, minimum is 200us but give it 300us for good measure */
> + usleep_range(300, 1000);
> +}
> diff --git a/drivers/mmc/host/sdhci-cadence.h b/drivers/mmc/host/sdhci-cadence.h
> new file mode 100644
> index 000000000000..82780c86e98d
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-cadence.h
> @@ -0,0 +1,114 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * 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-cadence-core.c.
> + */
> +
> +#ifndef _MMC_HOST_SDHCI_CADENCE_H
> +#define _MMC_HOST_SDHCI_CADENCE_H
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/iopoll.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +#include <linux/mmc/host.h>
It is better if a file includes only what it uses:
#include <linux/types.h>
#include <linux/compiler.h>
#include <linux/spinlock.h>
#include "sdhci-pltfm.h"
struct reset_control;
> +
> +#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 eMMC card RST_n pin (SD4HC only).
> + * @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);
Unnecessary line wrap
> + struct reset_control *rst_hw;
> + void *phy;
> +};
> +
> +/**
> + * sdhci_cdns_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_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 */
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 9/9] mmc: sdhci-cadence: add Altera Agilex5 SD6HC support
2026-06-27 20:14 ` [PATCH v2 9/9] mmc: sdhci-cadence: add Altera Agilex5 " Tanmay Kathpalia
2026-06-29 7:08 ` Krzysztof Kozlowski
@ 2026-07-04 11:18 ` Adrian Hunter
1 sibling, 0 replies; 24+ messages in thread
From: Adrian Hunter @ 2026-07-04 11:18 UTC (permalink / raw)
To: Tanmay Kathpalia, linux-mmc
Cc: ulf.hansson, Ulf Hansson, Philipp Zabel, linux-kernel
On 27/06/2026 23:14, Tanmay Kathpalia wrote:
> The Altera Agilex5 SoC integrates a Cadence SD6HC controller that needs
> platform-specific configuration to operate correctly.
>
> The SoC requires three named resets: "sdhc-reset" and "sdmmc-ocp" are
> exclusive and must be asserted together before being released, so both
> clock domains cross the reset boundary simultaneously. "combophy" is
> shared with NAND and must only be deasserted, not toggled.
>
> The IOMMU maps DMA addresses within a 40-bit physical address space, so
> the DMA mask is capped at 40 bits to prevent allocation beyond the
> controller's reach.
>
> The silicon requires the MULTIBLOCK_READ_ACMD12, CAP_CLOCK_BASE_BROKEN,
> PRESET_VALUE_BROKEN, and ACMD23_BROKEN quirks. Since
> CAP_CLOCK_BASE_BROKEN prevents reading the base clock from the
> capabilities register, the maximum clock is supplied from the platform
> clock instead.
>
> Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
> ---
> drivers/mmc/host/sdhci-cadence-core.c | 112 ++++++++++++++++++++++++++
> 1 file changed, 112 insertions(+)
>
> diff --git a/drivers/mmc/host/sdhci-cadence-core.c b/drivers/mmc/host/sdhci-cadence-core.c
> index 5b8a83c9a0aa..0f9b9dd2d2c4 100644
> --- a/drivers/mmc/host/sdhci-cadence-core.c
> +++ b/drivers/mmc/host/sdhci-cadence-core.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,29 @@ 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
> + *
> + * Limit DMA addresses to the physical range the controller can reach.
> + */
> +static int sdhci_cdns_set_dma_mask(struct sdhci_host *host)
> +{
> + const struct sdhci_cdns_drv_data *data;
> + struct device *dev = mmc_dev(host->mmc);
> + int ret;
> +
> + data = of_device_get_match_data(dev);
> + if (!data || !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;
> @@ -460,6 +485,65 @@ static int elba_drv_init(struct platform_device *pdev)
> return 0;
> }
>
> +static int sdhci_cdns6_agilex5_init(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct reset_control *rst_sdhc;
> + struct reset_control *rst_ocp;
> + struct reset_control *rst_combophy;
> + int ret;
> +
> + /*
> + * The combo PHY reset is shared with other peripheral (NAND).
> + * Use the _deasserted variant so devres calls assert + put on driver
> + * detach, keeping the shared deassert reference count balanced across
> + * probe/unbind cycles.
> + */
> + rst_combophy = devm_reset_control_get_shared_deasserted(dev, "combophy");
> + if (IS_ERR(rst_combophy))
> + return dev_err_probe(dev, PTR_ERR(rst_combophy),
> + "failed to get combophy reset\n");
Please don't wrap lines that fit in 100 columns. Same for two below.
> +
> + /*
> + * Assert SDHCI core and SDMMC OCP/AXI bus resets together so their
> + * active periods overlap before both clock domains are released.
> + */
> + rst_sdhc = devm_reset_control_get_exclusive(dev, "sdhc-reset");
> + if (IS_ERR(rst_sdhc))
> + return dev_err_probe(dev, PTR_ERR(rst_sdhc),
> + "failed to get sdhc-reset\n");
> +
> + rst_ocp = devm_reset_control_get_exclusive(dev, "sdmmc-ocp");
> + if (IS_ERR(rst_ocp))
> + return dev_err_probe(dev, PTR_ERR(rst_ocp),
> + "failed to get sdmmc-ocp reset\n");
> +
> + ret = reset_control_assert(rst_sdhc);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to assert sdhc-reset\n");
> +
> + ret = reset_control_assert(rst_ocp);
> + if (ret) {
> + reset_control_deassert(rst_sdhc);
> + return dev_err_probe(dev, ret, "failed to assert sdmmc-ocp reset\n");
> + }
> +
> + /* Hold resets asserted long enough for all clock domains to capture. */
> + usleep_range(10, 20);
> +
> + ret = reset_control_deassert(rst_sdhc);
> + if (ret) {
> + reset_control_deassert(rst_ocp);
> + return dev_err_probe(dev, ret, "failed to deassert sdhc-reset\n");
> + }
> +
> + ret = reset_control_deassert(rst_ocp);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to deassert sdmmc-ocp reset\n");
> +
> + return 0;
> +}
> +
> static const struct sdhci_ops sdhci_cdns4_ops = {
> .set_clock = sdhci_set_clock,
> .get_timeout_clock = sdhci_cdns_get_timeout_clock,
> @@ -479,6 +563,18 @@ 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_max_clock = sdhci_pltfm_clk_get_max_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 +602,18 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
> },
> };
>
> +static const struct sdhci_cdns_drv_data sdhci_cdns6_agilex5_drv_data = {
> + .init = sdhci_cdns6_agilex5_init,
> + .pltfm_data = {
> + .ops = &sdhci_cdns6_agilex5_ops,
> + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
> + 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,
> @@ -704,6 +812,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,
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-07-04 11:18 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-27 20:14 [PATCH v2 0/9] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement Tanmay Kathpalia
2026-06-27 20:14 ` [PATCH v2 1/9] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Tanmay Kathpalia
2026-07-02 15:53 ` (subset) " Philipp Zabel
2026-06-27 20:14 ` [PATCH v2 2/9] dt-bindings: mmc: cdns,sdhci: add SD6HC support and PHY properties Tanmay Kathpalia
2026-06-29 7:04 ` Krzysztof Kozlowski
2026-07-02 8:58 ` Kathpalia, Tanmay
2026-06-27 20:14 ` [PATCH v2 3/9] arm64: dts: agilex5: add Cadence SD6HC controller and SOCDK enablement Tanmay Kathpalia
2026-06-29 7:06 ` Krzysztof Kozlowski
2026-07-02 9:01 ` Kathpalia, Tanmay
2026-06-27 20:14 ` [PATCH v2 4/9] dt-bindings: arm: intel: add Agilex5 SOCDK eMMC board variant Tanmay Kathpalia
2026-06-29 7:06 ` Krzysztof Kozlowski
2026-07-02 9:07 ` Kathpalia, Tanmay
2026-06-27 20:14 ` [PATCH v2 5/9] arm64: dts: agilex5: add SOCDK eMMC daughter board support Tanmay Kathpalia
2026-06-29 7:07 ` Krzysztof Kozlowski
2026-07-02 9:07 ` Kathpalia, Tanmay
2026-06-27 20:14 ` [PATCH v2 6/9] mmc: sdhci-cadence: rename V4 functions for V6 controller groundwork Tanmay Kathpalia
2026-07-04 11:17 ` Adrian Hunter
2026-06-27 20:14 ` [PATCH v2 7/9] mmc: sdhci-cadence: refactor driver structure for V6 controller support Tanmay Kathpalia
2026-06-27 20:14 ` [PATCH v2 8/9] mmc: sdhci-cadence: add Cadence SD6HC support Tanmay Kathpalia
2026-07-04 11:18 ` Adrian Hunter
2026-06-27 20:14 ` [PATCH v2 9/9] mmc: sdhci-cadence: add Altera Agilex5 " Tanmay Kathpalia
2026-06-29 7:08 ` Krzysztof Kozlowski
2026-07-02 9:04 ` Kathpalia, Tanmay
2026-07-04 11:18 ` Adrian Hunter
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox