* [PATCH 1/4] mmc: sdhci-cadence6: add PHY settle delay after tuning DLL re-lock
2026-09-22 11:12 [PATCH 0/4] mmc: sdhci-cadence: SD6HC DDR50 read-path tuning and fixes tze.yee.ng
@ 2026-09-22 11:12 ` tze.yee.ng
2026-09-24 6:41 ` Adrian Hunter
2026-09-24 8:49 ` Kathpalia, Tanmay
2026-09-22 11:12 ` [PATCH 2/4] mmc: sdhci-cadence6: program PHONY_DQS_TIMING for extended-read DDR tze.yee.ng
` (2 subsequent siblings)
3 siblings, 2 replies; 9+ messages in thread
From: tze.yee.ng @ 2026-09-22 11:12 UTC (permalink / raw)
To: Adrian Hunter, Ulf Hansson, Tanmay Kathpalia, linux-mmc,
linux-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree
From: Tze Yee Ng <tze.yee.ng@altera.com>
After re-locking the DLL, allow the same 5 to 5.5 ms for the clock and
PHY signals to stabilize as sdhci_cdns6_phy_init() does. Without this
settle time the command issued immediately after tuning (e.g. the R1b
CMD6 that switches eMMC from HS200 down to HS during HS400 selection)
can time out.
Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com>
---
drivers/mmc/host/sdhci-cadence-phy-v6.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/mmc/host/sdhci-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c
index 22d56bb46d75..35f35ef9c710 100644
--- a/drivers/mmc/host/sdhci-cadence-phy-v6.c
+++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c
@@ -811,6 +811,9 @@ int sdhci_cdns6_set_tune_val(struct sdhci_host *host, unsigned int val)
if (ret)
dev_warn(mmc_dev(host->mmc), "%s: DLL reset release failed: %d\n", __func__, ret);
+ /* Allow 5 to 5.5 ms for clock and PHY signals to stabilize after configuration */
+ usleep_range(5000, 5500);
+
return ret;
}
--
2.43.7
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH 1/4] mmc: sdhci-cadence6: add PHY settle delay after tuning DLL re-lock
2026-09-22 11:12 ` [PATCH 1/4] mmc: sdhci-cadence6: add PHY settle delay after tuning DLL re-lock tze.yee.ng
@ 2026-09-24 6:41 ` Adrian Hunter
2026-09-24 8:49 ` Kathpalia, Tanmay
1 sibling, 0 replies; 9+ messages in thread
From: Adrian Hunter @ 2026-09-24 6:41 UTC (permalink / raw)
To: tze.yee.ng, Ulf Hansson, Tanmay Kathpalia, linux-mmc,
linux-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree
On 22/09/2026 14:12, tze.yee.ng@altera.com wrote:
> From: Tze Yee Ng <tze.yee.ng@altera.com>
>
> After re-locking the DLL, allow the same 5 to 5.5 ms for the clock and
> PHY signals to stabilize as sdhci_cdns6_phy_init() does. Without this
> settle time the command issued immediately after tuning (e.g. the R1b
> CMD6 that switches eMMC from HS200 down to HS during HS400 selection)
> can time out.
>
> Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com>
Presume you realize the delay is inside the tuning loop, so 40x 5ms
is 200ms total.
Nevertheless:
Acked-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
> drivers/mmc/host/sdhci-cadence-phy-v6.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/drivers/mmc/host/sdhci-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> index 22d56bb46d75..35f35ef9c710 100644
> --- a/drivers/mmc/host/sdhci-cadence-phy-v6.c
> +++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> @@ -811,6 +811,9 @@ int sdhci_cdns6_set_tune_val(struct sdhci_host *host, unsigned int val)
> if (ret)
> dev_warn(mmc_dev(host->mmc), "%s: DLL reset release failed: %d\n", __func__, ret);
>
> + /* Allow 5 to 5.5 ms for clock and PHY signals to stabilize after configuration */
> + usleep_range(5000, 5500);
> +
> return ret;
> }
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 1/4] mmc: sdhci-cadence6: add PHY settle delay after tuning DLL re-lock
2026-09-22 11:12 ` [PATCH 1/4] mmc: sdhci-cadence6: add PHY settle delay after tuning DLL re-lock tze.yee.ng
2026-09-24 6:41 ` Adrian Hunter
@ 2026-09-24 8:49 ` Kathpalia, Tanmay
1 sibling, 0 replies; 9+ messages in thread
From: Kathpalia, Tanmay @ 2026-09-24 8:49 UTC (permalink / raw)
To: tze.yee.ng, Adrian Hunter, Ulf Hansson, linux-mmc, linux-kernel,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree
Hi Tze,
On 9/22/2026 4:42 PM, tze.yee.ng@altera.com wrote:
> From: Tze Yee Ng <tze.yee.ng@altera.com>
>
> After re-locking the DLL, allow the same 5 to 5.5 ms for the clock and
> PHY signals to stabilize as sdhci_cdns6_phy_init() does. Without this
I do not think the comparison with sdhci_cdns6_phy_init() holds. That
5 ms comes after a full PHY and host reprogram, including HRS writes
that happen after PHY_INIT_COMPLETE, and that path is only used after
SDCLK or the speed mode changes. set_tune_val() only updates two
phy_dll_slave_ctrl_reg fields and re-locks the DLL; it does not touch
HRS, clock, or mode.
> settle time the command issued immediately after tuning (e.g. the R1b
> CMD6 that switches eMMC from HS200 down to HS during HS400 selection)
> can time out.
Do you have a log for this? Which mode, which card, and how often it
reproduces. I ran a long regression on eMMC and on SD cards from
several vendors and sizes, and never hit a post-tuning CMD6 timeout
without this delay. I would prefer to see the failure before we add an
unconditional 5 ms.
> Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com>
> ---
> drivers/mmc/host/sdhci-cadence-phy-v6.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/drivers/mmc/host/sdhci-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> index 22d56bb46d75..35f35ef9c710 100644
> --- a/drivers/mmc/host/sdhci-cadence-phy-v6.c
> +++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> @@ -811,6 +811,9 @@ int sdhci_cdns6_set_tune_val(struct sdhci_host *host, unsigned int val)
> if (ret)
> dev_warn(mmc_dev(host->mmc), "%s: DLL reset release failed: %d\n", __func__, ret);
>
> + /* Allow 5 to 5.5 ms for clock and PHY signals to stabilize after configuration */
The comment is copied from phy_init() and says "after configuration",
but here we only reprogrammed the slave delay taps and re-locked the
DLL. Please reword it for this call site.
> + usleep_range(5000, 5500);
> +
I think the placement also contradicts the rationale. After
sdhci_cdns6_dll_reset(priv, false), PHY_INIT_COMPLETE is already polled,
and per the Cadence DLL PHY user guide section 1.2 that means the master
DLLs have locked and the PHY is ready to accept commands. If a command
still cannot be issued for 5 ms after that, then all 40 scan commands
were sent on an unsettled PHY before this patch, and the real bug is a
wrongly chosen tap rather than a slow CMD6. If the scan was reliable, the
delay is only needed once, after the final tap is programmed.
Also, the sleep is placed after the "DLL reset release failed" warning,
so we also wait 5 ms when the DLL did not re-lock and we are about to
return an error. Skip it when ret is non-zero.
> return ret;
> }
>
Thanks Adrian - I'd appreciate your view on the comments I posted, given
your experience with this subsystem. I'd like to hold the patch until the
mechanism and the cost are clarified, and happy to go with whatever you
think is right once those points are answered.
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 2/4] mmc: sdhci-cadence6: program PHONY_DQS_TIMING for extended-read DDR
2026-09-22 11:12 [PATCH 0/4] mmc: sdhci-cadence: SD6HC DDR50 read-path tuning and fixes tze.yee.ng
2026-09-22 11:12 ` [PATCH 1/4] mmc: sdhci-cadence6: add PHY settle delay after tuning DLL re-lock tze.yee.ng
@ 2026-09-22 11:12 ` tze.yee.ng
2026-09-24 6:41 ` Adrian Hunter
2026-09-22 11:12 ` [PATCH 3/4] dt-bindings: mmc: cdns,sdhci: add SD6HC DDR50 read-path tuning tze.yee.ng
2026-09-22 11:12 ` [PATCH 4/4] mmc: sdhci-cadence: read SD6HC DDR50 tuning from device tree tze.yee.ng
3 siblings, 1 reply; 9+ messages in thread
From: tze.yee.ng @ 2026-09-22 11:12 UTC (permalink / raw)
To: Adrian Hunter, Ulf Hansson, Tanmay Kathpalia, linux-mmc,
linux-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree
From: Tze Yee Ng <tze.yee.ng@altera.com>
The SD6HC PHY left PHONY_DQS_TIMING (phy_ctrl_reg[9:4]) at 0 in all
modes. Per the Cadence DLL PHY documentation it must be the rebar (RE#)
pulse width in clk_phy cycles minus 1 in extended read mode, and 0
otherwise. Leaving it 0 in extended-read DDR duplicates one DDR edge
(the silent odd/even edge-capture defect).
This controller's rebar pulse is a fixed 2 clk_phy cycles, so extended-
read DDR needs 1; confirmed on DDR50 hardware (1 captures both beats, 2
corrupts reads). Derive it from the extended-read-mode state and apply
it only in DDR modes, since SDR extended-read samples a single edge and
is unaffected. This is generic to any SD6HC-PHY SoC, so it is kept
separate from the per-SoC read-path tuning.
Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com>
---
drivers/mmc/host/sdhci-cadence-phy-v6.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/mmc/host/sdhci-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c
index 35f35ef9c710..84592ae42762 100644
--- a/drivers/mmc/host/sdhci-cadence-phy-v6.c
+++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c
@@ -90,6 +90,9 @@
#define SDHCI_CDNS6_PHY_CTRL_REG 0x2080
#define SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING GENMASK(9, 4)
+/* Width of this controller's rebar (RE#) pulse in clk_phy cycles. */
+#define SDHCI_CDNS6_PHY_REBAR_PULSE_CYCLES 2
+
/* Default PHY settings */
#define SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY 2500
#define SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT 24
@@ -143,6 +146,9 @@ struct sdhci_cdns6_phy {
bool cp_use_phony_dqs; /* bit [20] */
bool cp_use_phony_dqs_cmd; /* bit [19] */
+ /* PHY_CTRL register fields */
+ u32 cp_phony_dqs_timing;
+
/* HRS07 register - IO delay Information */
u8 sdhc_rw_compensate; /* bits [20:16] */
u8 sdhc_idelay_val; /* bits [4:0] */
@@ -517,6 +523,13 @@ static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy)
if (phy->mode == MMC_TIMING_MMC_HS200)
phy->cp_read_dqs_delay = phy->hs200_tune_val;
+ if (phy->sdhc_extended_rd_mode &&
+ (phy->mode == MMC_TIMING_UHS_DDR50 ||
+ phy->mode == MMC_TIMING_MMC_DDR52))
+ phy->cp_phony_dqs_timing = SDHCI_CDNS6_PHY_REBAR_PULSE_CYCLES - 1;
+ else
+ phy->cp_phony_dqs_timing = 0;
+
if (strobe_dat) {
/* dqs loopback input via IO cell */
hcsdclkadj += phy->iocell_input_delay;
@@ -715,6 +728,8 @@ int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv)
reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG);
reg &= ~SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING;
+ reg |= FIELD_PREP(SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING,
+ phy->cp_phony_dqs_timing);
sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG, reg);
/*
--
2.43.7
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH 2/4] mmc: sdhci-cadence6: program PHONY_DQS_TIMING for extended-read DDR
2026-09-22 11:12 ` [PATCH 2/4] mmc: sdhci-cadence6: program PHONY_DQS_TIMING for extended-read DDR tze.yee.ng
@ 2026-09-24 6:41 ` Adrian Hunter
0 siblings, 0 replies; 9+ messages in thread
From: Adrian Hunter @ 2026-09-24 6:41 UTC (permalink / raw)
To: tze.yee.ng, Ulf Hansson, Tanmay Kathpalia, linux-mmc,
linux-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree
On 22/09/2026 14:12, tze.yee.ng@altera.com wrote:
> From: Tze Yee Ng <tze.yee.ng@altera.com>
>
> The SD6HC PHY left PHONY_DQS_TIMING (phy_ctrl_reg[9:4]) at 0 in all
> modes. Per the Cadence DLL PHY documentation it must be the rebar (RE#)
> pulse width in clk_phy cycles minus 1 in extended read mode, and 0
> otherwise. Leaving it 0 in extended-read DDR duplicates one DDR edge
> (the silent odd/even edge-capture defect).
>
> This controller's rebar pulse is a fixed 2 clk_phy cycles, so extended-
> read DDR needs 1; confirmed on DDR50 hardware (1 captures both beats, 2
> corrupts reads). Derive it from the extended-read-mode state and apply
> it only in DDR modes, since SDR extended-read samples a single edge and
> is unaffected. This is generic to any SD6HC-PHY SoC, so it is kept
> separate from the per-SoC read-path tuning.
>
> Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com>
Acked-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
> drivers/mmc/host/sdhci-cadence-phy-v6.c | 15 +++++++++++++++
> 1 file changed, 15 insertions(+)
>
> diff --git a/drivers/mmc/host/sdhci-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> index 35f35ef9c710..84592ae42762 100644
> --- a/drivers/mmc/host/sdhci-cadence-phy-v6.c
> +++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> @@ -90,6 +90,9 @@
> #define SDHCI_CDNS6_PHY_CTRL_REG 0x2080
> #define SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING GENMASK(9, 4)
>
> +/* Width of this controller's rebar (RE#) pulse in clk_phy cycles. */
> +#define SDHCI_CDNS6_PHY_REBAR_PULSE_CYCLES 2
> +
> /* Default PHY settings */
> #define SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY 2500
> #define SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT 24
> @@ -143,6 +146,9 @@ struct sdhci_cdns6_phy {
> bool cp_use_phony_dqs; /* bit [20] */
> bool cp_use_phony_dqs_cmd; /* bit [19] */
>
> + /* PHY_CTRL register fields */
> + u32 cp_phony_dqs_timing;
> +
> /* HRS07 register - IO delay Information */
> u8 sdhc_rw_compensate; /* bits [20:16] */
> u8 sdhc_idelay_val; /* bits [4:0] */
> @@ -517,6 +523,13 @@ static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy)
> if (phy->mode == MMC_TIMING_MMC_HS200)
> phy->cp_read_dqs_delay = phy->hs200_tune_val;
>
> + if (phy->sdhc_extended_rd_mode &&
> + (phy->mode == MMC_TIMING_UHS_DDR50 ||
> + phy->mode == MMC_TIMING_MMC_DDR52))
> + phy->cp_phony_dqs_timing = SDHCI_CDNS6_PHY_REBAR_PULSE_CYCLES - 1;
> + else
> + phy->cp_phony_dqs_timing = 0;
> +
> if (strobe_dat) {
> /* dqs loopback input via IO cell */
> hcsdclkadj += phy->iocell_input_delay;
> @@ -715,6 +728,8 @@ int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv)
>
> reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG);
> reg &= ~SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING;
> + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING,
> + phy->cp_phony_dqs_timing);
> sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG, reg);
>
> /*
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 3/4] dt-bindings: mmc: cdns,sdhci: add SD6HC DDR50 read-path tuning
2026-09-22 11:12 [PATCH 0/4] mmc: sdhci-cadence: SD6HC DDR50 read-path tuning and fixes tze.yee.ng
2026-09-22 11:12 ` [PATCH 1/4] mmc: sdhci-cadence6: add PHY settle delay after tuning DLL re-lock tze.yee.ng
2026-09-22 11:12 ` [PATCH 2/4] mmc: sdhci-cadence6: program PHONY_DQS_TIMING for extended-read DDR tze.yee.ng
@ 2026-09-22 11:12 ` tze.yee.ng
2026-09-22 11:12 ` [PATCH 4/4] mmc: sdhci-cadence: read SD6HC DDR50 tuning from device tree tze.yee.ng
3 siblings, 0 replies; 9+ messages in thread
From: tze.yee.ng @ 2026-09-22 11:12 UTC (permalink / raw)
To: Adrian Hunter, Ulf Hansson, Tanmay Kathpalia, linux-mmc,
linux-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree
From: Tze Yee Ng <tze.yee.ng@altera.com>
DDR50 has no CMD19 tuning, so the SD6HC read path must be centred by
static, board/SoC-characterised PHY settings. Add three optional SD6HC
properties:
- cdns,ddr50-read-dqs-delay: DLL_SLAVE[7:0] read-DQS delay that centres
the read eye (0-255).
- cdns,ddr50-use-lpbk-dqs: DQS_TIMING[21] read-DQS source
(0 = phony, 1 = loopback).
- cdns,ddr50-phony-dqs-timing: PHY_CTRL[9:4] phony DQS assertion timing
(0-63) that positions the fabricated strobe relative to the returning
DDR data; not produced by the Cadence timing calculation.
All three are disallowed for the SD4HC variant.
Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com>
---
.../devicetree/bindings/mmc/cdns,sd6hc.yaml | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml b/Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml
index d5ea2717904b..df86872603d0 100644
--- a/Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml
+++ b/Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml
@@ -74,6 +74,33 @@ properties:
maximum: 1000
default: 24
+ cdns,ddr50-read-dqs-delay:
+ description: |
+ SD6HC DDR50 read-DQS delay (DLL_SLAVE[7:0]) used to centre the read
+ eye. DDR50 has no CMD19 tuning, so this is a board/SoC-characterised
+ value. If absent, the driver default is used.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 0
+ maximum: 0xff
+
+ cdns,ddr50-use-lpbk-dqs:
+ description: |
+ SD6HC DDR50 read-DQS source (DQS_TIMING[21]): 0 = phony DQS,
+ 1 = loopback DQS. If absent, the driver default is used.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [0, 1]
+
+ cdns,ddr50-phony-dqs-timing:
+ description: |
+ SD6HC DDR50 phony DQS assertion timing (PHY_CTRL[9:4]). Positions the
+ fabricated read strobe relative to the returning DDR data; the correct
+ value depends on the board's SD flight time and is not produced by the
+ Cadence timing calculation. If absent, the driver default
+ (REBAR_PULSE_CYCLES-1) is used.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 0
+ maximum: 0x3f
+
required:
- compatible
- reg
--
2.43.7
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 4/4] mmc: sdhci-cadence: read SD6HC DDR50 tuning from device tree
2026-09-22 11:12 [PATCH 0/4] mmc: sdhci-cadence: SD6HC DDR50 read-path tuning and fixes tze.yee.ng
` (2 preceding siblings ...)
2026-09-22 11:12 ` [PATCH 3/4] dt-bindings: mmc: cdns,sdhci: add SD6HC DDR50 read-path tuning tze.yee.ng
@ 2026-09-22 11:12 ` tze.yee.ng
2026-09-24 6:40 ` Adrian Hunter
3 siblings, 1 reply; 9+ messages in thread
From: tze.yee.ng @ 2026-09-22 11:12 UTC (permalink / raw)
To: Adrian Hunter, Ulf Hansson, Tanmay Kathpalia, linux-mmc,
linux-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree
From: Tze Yee Ng <tze.yee.ng@altera.com>
DDR50 has no CMD19 tuning, so the SD6HC read path relies on static PHY
settings that need board/SoC characterisation. Read the read-DQS delay,
read-DQS source and phony DQS assertion timing from the new
cdns,ddr50-read-dqs-delay, cdns,ddr50-use-lpbk-dqs and
cdns,ddr50-phony-dqs-timing DT properties at PHY probe, range-check them,
and apply them only in DDR50. When a property is absent the existing
driver default is kept - for the phony DQS timing, the derived
REBAR_PULSE_CYCLES-1.
Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com>
---
drivers/mmc/host/sdhci-cadence-phy-v6.c | 72 ++++++++++++++++++++++++-
1 file changed, 71 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/sdhci-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c
index 84592ae42762..0f47fa62d894 100644
--- a/drivers/mmc/host/sdhci-cadence-phy-v6.c
+++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c
@@ -129,6 +129,11 @@ struct sdhci_cdns6_phy {
/* Active delay element (ps); doubled when one SDMCLK requires > 256 steps */
u32 delay_element;
+ /* DDR read-path overrides (SoC-specific) */
+ s32 ddr_read_dqs_delay;
+ s32 ddr_use_lpbk_dqs;
+ s32 ddr_phony_dqs_timing;
+
/* PHY_DLL_SLAVE_CTRL register fields */
u8 cp_read_dqs_cmd_delay; /* bits [31:24] */
u8 cp_clk_wrdqs_delay; /* bits [23:16] */
@@ -145,6 +150,7 @@ struct sdhci_cdns6_phy {
/* PHY_DQS_TIMING register fields */
bool cp_use_phony_dqs; /* bit [20] */
bool cp_use_phony_dqs_cmd; /* bit [19] */
+ bool cp_use_lpbk_dqs; /* bit [21] */
/* PHY_CTRL register fields */
u32 cp_phony_dqs_timing;
@@ -523,6 +529,12 @@ static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy)
if (phy->mode == MMC_TIMING_MMC_HS200)
phy->cp_read_dqs_delay = phy->hs200_tune_val;
+ if (phy->mode == MMC_TIMING_UHS_DDR50 && phy->ddr_read_dqs_delay >= 0)
+ phy->cp_read_dqs_delay = phy->ddr_read_dqs_delay &
+ SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY;
+
+ phy->cp_use_lpbk_dqs = 1;
+
if (phy->sdhc_extended_rd_mode &&
(phy->mode == MMC_TIMING_UHS_DDR50 ||
phy->mode == MMC_TIMING_MMC_DDR52))
@@ -530,6 +542,18 @@ static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy)
else
phy->cp_phony_dqs_timing = 0;
+ if (phy->mode == MMC_TIMING_UHS_DDR50 && phy->ddr_use_lpbk_dqs >= 0)
+ phy->cp_use_lpbk_dqs = phy->ddr_use_lpbk_dqs &
+ FIELD_MAX(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS);
+
+ /*
+ * The phony DQS timing derived above depends on the board's SD flight
+ * time, so allow a DT override to re-position the fabricated strobe.
+ */
+ if (phy->mode == MMC_TIMING_UHS_DDR50 && phy->ddr_phony_dqs_timing >= 0)
+ phy->cp_phony_dqs_timing = phy->ddr_phony_dqs_timing &
+ FIELD_MAX(SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING);
+
if (strobe_dat) {
/* dqs loopback input via IO cell */
hcsdclkadj += phy->iocell_input_delay;
@@ -693,10 +717,11 @@ int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv)
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_LPBK_DQS;
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_LPBK_DQS, phy->cp_use_lpbk_dqs);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS, phy->cp_use_phony_dqs);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD, phy->cp_use_phony_dqs_cmd);
sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG, reg);
@@ -881,6 +906,7 @@ int sdhci_cdns6_phy_probe(struct platform_device *pdev, struct sdhci_cdns_priv *
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_cdns6_phy *phy;
unsigned long val;
+ u32 prop;
int ret;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
@@ -919,6 +945,50 @@ int sdhci_cdns6_phy_probe(struct platform_device *pdev, struct sdhci_cdns_priv *
phy->delay_element_org = phy->delay_element;
+ /*
+ * Optional DDR50 read-path tuning. These are board/card-characterised
+ * values with no CMD19 tuning in DDR50; absence keeps the driver
+ * default (-1 => not overridden).
+ */
+ phy->ddr_read_dqs_delay = -1;
+ if (!of_property_read_u32(dev->of_node, "cdns,ddr50-read-dqs-delay",
+ &prop)) {
+ if (prop > SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY) {
+ dev_warn(dev,
+ "cdns,ddr50-read-dqs-delay %u out of range, clamping to %lu\n",
+ prop,
+ (unsigned long)SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY);
+ prop = SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY;
+ }
+ phy->ddr_read_dqs_delay = prop;
+ }
+
+ phy->ddr_use_lpbk_dqs = -1;
+ if (!of_property_read_u32(dev->of_node, "cdns,ddr50-use-lpbk-dqs",
+ &prop)) {
+ if (prop > FIELD_MAX(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS)) {
+ dev_warn(dev,
+ "cdns,ddr50-use-lpbk-dqs %u out of range, clamping to %lu\n",
+ prop,
+ (unsigned long)FIELD_MAX(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS));
+ prop = FIELD_MAX(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS);
+ }
+ phy->ddr_use_lpbk_dqs = prop;
+ }
+
+ phy->ddr_phony_dqs_timing = -1;
+ if (!of_property_read_u32(dev->of_node, "cdns,ddr50-phony-dqs-timing",
+ &prop)) {
+ if (prop > FIELD_MAX(SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING)) {
+ dev_warn(dev,
+ "cdns,ddr50-phony-dqs-timing %u out of range, clamping to %lu\n",
+ prop,
+ (unsigned long)FIELD_MAX(SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING));
+ prop = FIELD_MAX(SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING);
+ }
+ phy->ddr_phony_dqs_timing = prop;
+ }
+
priv->phy = phy;
return 0;
--
2.43.7
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH 4/4] mmc: sdhci-cadence: read SD6HC DDR50 tuning from device tree
2026-09-22 11:12 ` [PATCH 4/4] mmc: sdhci-cadence: read SD6HC DDR50 tuning from device tree tze.yee.ng
@ 2026-09-24 6:40 ` Adrian Hunter
0 siblings, 0 replies; 9+ messages in thread
From: Adrian Hunter @ 2026-09-24 6:40 UTC (permalink / raw)
To: tze.yee.ng, Ulf Hansson, Tanmay Kathpalia, linux-mmc,
linux-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree
On 22/09/2026 14:12, tze.yee.ng@altera.com wrote:
> From: Tze Yee Ng <tze.yee.ng@altera.com>
>
> DDR50 has no CMD19 tuning, so the SD6HC read path relies on static PHY
> settings that need board/SoC characterisation. Read the read-DQS delay,
> read-DQS source and phony DQS assertion timing from the new
> cdns,ddr50-read-dqs-delay, cdns,ddr50-use-lpbk-dqs and
> cdns,ddr50-phony-dqs-timing DT properties at PHY probe, range-check them,
Firmware values are expected to be correct, and so are not validated.
> and apply them only in DDR50. When a property is absent the existing
> driver default is kept - for the phony DQS timing, the derived
> REBAR_PULSE_CYCLES-1.
>
> Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com>
> ---
> drivers/mmc/host/sdhci-cadence-phy-v6.c | 72 ++++++++++++++++++++++++-
> 1 file changed, 71 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/mmc/host/sdhci-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> index 84592ae42762..0f47fa62d894 100644
> --- a/drivers/mmc/host/sdhci-cadence-phy-v6.c
> +++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> @@ -129,6 +129,11 @@ struct sdhci_cdns6_phy {
> /* Active delay element (ps); doubled when one SDMCLK requires > 256 steps */
> u32 delay_element;
>
> + /* DDR read-path overrides (SoC-specific) */
> + s32 ddr_read_dqs_delay;
> + s32 ddr_use_lpbk_dqs;
> + s32 ddr_phony_dqs_timing;
> +
> /* PHY_DLL_SLAVE_CTRL register fields */
> u8 cp_read_dqs_cmd_delay; /* bits [31:24] */
> u8 cp_clk_wrdqs_delay; /* bits [23:16] */
> @@ -145,6 +150,7 @@ struct sdhci_cdns6_phy {
> /* PHY_DQS_TIMING register fields */
> bool cp_use_phony_dqs; /* bit [20] */
> bool cp_use_phony_dqs_cmd; /* bit [19] */
> + bool cp_use_lpbk_dqs; /* bit [21] */
>
> /* PHY_CTRL register fields */
> u32 cp_phony_dqs_timing;
> @@ -523,6 +529,12 @@ static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy)
> if (phy->mode == MMC_TIMING_MMC_HS200)
> phy->cp_read_dqs_delay = phy->hs200_tune_val;
>
> + if (phy->mode == MMC_TIMING_UHS_DDR50 && phy->ddr_read_dqs_delay >= 0)
> + phy->cp_read_dqs_delay = phy->ddr_read_dqs_delay &
> + SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY;
So here is unnecessarily assuming DT is providing a bad value. It could just be:
if (phy->mode == MMC_TIMING_UHS_DDR50 && phy->ddr_read_dqs_delay >= 0)
phy->cp_read_dqs_delay = phy->ddr_read_dqs_delay;
> +
> + phy->cp_use_lpbk_dqs = 1;
> +
> if (phy->sdhc_extended_rd_mode &&
> (phy->mode == MMC_TIMING_UHS_DDR50 ||
> phy->mode == MMC_TIMING_MMC_DDR52))
> @@ -530,6 +542,18 @@ static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy)
> else
> phy->cp_phony_dqs_timing = 0;
>
> + if (phy->mode == MMC_TIMING_UHS_DDR50 && phy->ddr_use_lpbk_dqs >= 0)
> + phy->cp_use_lpbk_dqs = phy->ddr_use_lpbk_dqs &
> + FIELD_MAX(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS);
Ditto
> +
> + /*
> + * The phony DQS timing derived above depends on the board's SD flight
> + * time, so allow a DT override to re-position the fabricated strobe.
> + */
> + if (phy->mode == MMC_TIMING_UHS_DDR50 && phy->ddr_phony_dqs_timing >= 0)
> + phy->cp_phony_dqs_timing = phy->ddr_phony_dqs_timing &
> + FIELD_MAX(SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING);
Ditto
> +
> if (strobe_dat) {
> /* dqs loopback input via IO cell */
> hcsdclkadj += phy->iocell_input_delay;
> @@ -693,10 +717,11 @@ int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv)
> 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_LPBK_DQS;
> 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_LPBK_DQS, phy->cp_use_lpbk_dqs);
> reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS, phy->cp_use_phony_dqs);
> reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD, phy->cp_use_phony_dqs_cmd);
> sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG, reg);
> @@ -881,6 +906,7 @@ int sdhci_cdns6_phy_probe(struct platform_device *pdev, struct sdhci_cdns_priv *
> struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> struct sdhci_cdns6_phy *phy;
> unsigned long val;
> + u32 prop;
> int ret;
>
> phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
> @@ -919,6 +945,50 @@ int sdhci_cdns6_phy_probe(struct platform_device *pdev, struct sdhci_cdns_priv *
>
> phy->delay_element_org = phy->delay_element;
>
> + /*
> + * Optional DDR50 read-path tuning. These are board/card-characterised
> + * values with no CMD19 tuning in DDR50; absence keeps the driver
> + * default (-1 => not overridden).
> + */
> + phy->ddr_read_dqs_delay = -1;
> + if (!of_property_read_u32(dev->of_node, "cdns,ddr50-read-dqs-delay",
> + &prop)) {
> + if (prop > SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY) {
> + dev_warn(dev,
> + "cdns,ddr50-read-dqs-delay %u out of range, clamping to %lu\n",
> + prop,
> + (unsigned long)SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY);
> + prop = SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY;
> + }
> + phy->ddr_read_dqs_delay = prop;
> + }
If the unnecessary validation is dropped:
phy->ddr_read_dqs_delay = -1;
of_property_read_u32(dev->of_node, "cdns,ddr50-read-dqs-delay", &phy->ddr_read_dqs_delay);
etc
> +
> + phy->ddr_use_lpbk_dqs = -1;
> + if (!of_property_read_u32(dev->of_node, "cdns,ddr50-use-lpbk-dqs",
> + &prop)) {
> + if (prop > FIELD_MAX(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS)) {
> + dev_warn(dev,
> + "cdns,ddr50-use-lpbk-dqs %u out of range, clamping to %lu\n",
> + prop,
> + (unsigned long)FIELD_MAX(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS));
> + prop = FIELD_MAX(SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS);
> + }
> + phy->ddr_use_lpbk_dqs = prop;
> + }
> +
> + phy->ddr_phony_dqs_timing = -1;
> + if (!of_property_read_u32(dev->of_node, "cdns,ddr50-phony-dqs-timing",
> + &prop)) {
> + if (prop > FIELD_MAX(SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING)) {
> + dev_warn(dev,
> + "cdns,ddr50-phony-dqs-timing %u out of range, clamping to %lu\n",
> + prop,
> + (unsigned long)FIELD_MAX(SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING));
> + prop = FIELD_MAX(SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING);
> + }
> + phy->ddr_phony_dqs_timing = prop;
> + }
> +
> priv->phy = phy;
>
> return 0;
^ permalink raw reply [flat|nested] 9+ messages in thread