[PATCH 1/4] mmc: exynos_dw_mmc: add proper init sequence for HS400 support
Henrik Grimler
henrik at grimler.se
Thu Apr 30 19:26:38 CEST 2026
Hi Kaustabh,
On Mon, Apr 27, 2026 at 11:22:49AM +0530, Kaustabh Chakraborty wrote:
> HS400 support was added, but configuration necessary for HS400 support
> was left out. Add necessary changes, which includes:
> - Device tree properties, such as "samsung,dw-mshc-hs400-timing" and
> "samsung,read-strobe-delay", which function as per dt-bindings.
> - Registers related to HS400, which are necessary to enable HS400+ support.
> - Appropriate timing tunings for the HS400 mode.
>
> Note that these changes are loosely based off of its Linux kernel
> counterpart.
>
> Fixes: bbe3b9fa0922 ("mmc: exynos_dw_mmc: add support for MMC HS200 and HS400 modes")
> Signed-off-by: Kaustabh Chakraborty <kauschluss at disroot.org>
> ---
> arch/arm/mach-exynos/include/mach/dwmmc.h | 5 ++
> drivers/mmc/exynos_dw_mmc.c | 80 +++++++++++++++++++++++++++++++
> 2 files changed, 85 insertions(+)
>
> diff --git a/arch/arm/mach-exynos/include/mach/dwmmc.h b/arch/arm/mach-exynos/include/mach/dwmmc.h
> index 4432deedef7..50081326c25 100644
> --- a/arch/arm/mach-exynos/include/mach/dwmmc.h
> +++ b/arch/arm/mach-exynos/include/mach/dwmmc.h
> @@ -15,6 +15,11 @@
> #define DWMCI_SET_DRV_CLK(x) ((x) << 16)
> #define DWMCI_SET_DIV_RATIO(x) ((x) << 24)
>
> +/* HS400 Related Registers */
> +#define DWMCI_HS400_DQS_EN 0x180
> +#define DWMCI_HS400_ASYNC_FIFO_CTRL 0x184
> +#define DWMCI_HS400_DLINE_CTRL 0x188
> +
> /* Protector Register */
> #define DWMCI_EMMCP_BASE 0x1000
> #define EMMCP_MPSECURITY (DWMCI_EMMCP_BASE + 0x0010)
> diff --git a/drivers/mmc/exynos_dw_mmc.c b/drivers/mmc/exynos_dw_mmc.c
> index 7ccd113bd79..d5e90a9bd5c 100644
> --- a/drivers/mmc/exynos_dw_mmc.c
> +++ b/drivers/mmc/exynos_dw_mmc.c
> @@ -8,6 +8,7 @@
> #include <dwmmc.h>
> #include <asm/global_data.h>
> #include <malloc.h>
> +#include <mmc.h>
> #include <errno.h>
> #include <asm/arch/dwmmc.h>
> #include <asm/arch/clk.h>
> @@ -30,6 +31,14 @@
> #define CLKSEL_UP_SAMPLE(x, y) (((x) & ~CLKSEL_CCLK_SAMPLE(7)) | \
> CLKSEL_CCLK_SAMPLE(y))
>
> +/* RCLK_EN register defines */
> +#define DATA_STROBE_EN BIT(0)
> +#define AXI_NON_BLOCKING_WR BIT(7)
> +
> +/* DLINE_CTRL register defines */
> +#define DQS_CTRL_RD_DELAY(x, y) (((x) & ~0x3FF) | ((y) & 0x3FF))
> +#define DQS_CTRL_GET_RD_DELAY(x) ((x) & 0x3FF)
> +
> /**
> * DOC: Quirk flags for different Exynos DW MMC blocks
> *
> @@ -71,6 +80,11 @@ struct dwmci_exynos_priv_data {
> struct clk clk;
> u32 sdr_timing;
> u32 ddr_timing;
> + u32 hs400_timing;
> + u32 tuned_sample;
> + u32 dqs_delay;
> + u32 saved_dqs_en;
> + u32 saved_strobe_ctrl;
> const struct exynos_dwmmc_variant *chip;
> };
>
> @@ -162,6 +176,27 @@ static u8 exynos_dwmmc_get_ciu_div(struct dwmci_host *host)
> & DWMCI_DIVRATIO_MASK) + 1;
> }
>
> +static void exynos_config_hs400(struct dwmci_host *host, enum bus_mode mode)
> +{
> + struct dwmci_exynos_priv_data *priv = exynos_dwmmc_get_priv(host);
> + u32 dqs, strobe;
> +
> + dqs = priv->saved_dqs_en;
> + strobe = priv->saved_strobe_ctrl;
> +
> + switch (mode) {
> + case MMC_HS_400:
> + dqs |= DATA_STROBE_EN;
> + strobe = DQS_CTRL_RD_DELAY(strobe, priv->dqs_delay);
> + break;
> + default:
> + dqs &= ~DATA_STROBE_EN;
> + }
> +
> + dwmci_writel(host, DWMCI_HS400_DQS_EN, dqs);
> + dwmci_writel(host, DWMCI_HS400_DLINE_CTRL, strobe);
> +}
> +
> /* Configure CLKSEL register with chosen timing values */
> static int exynos_dwmci_clksel(struct dwmci_host *host)
> {
> @@ -170,6 +205,9 @@ static int exynos_dwmci_clksel(struct dwmci_host *host)
> u32 timing;
>
> switch (host->mmc->selected_mode) {
> + case MMC_HS_400:
> + timing = CLKSEL_UP_SAMPLE(priv->hs400_timing, priv->tuned_sample);
> + break;
> case MMC_DDR_52:
> timing = priv->ddr_timing;
> break;
> @@ -186,6 +224,8 @@ static int exynos_dwmci_clksel(struct dwmci_host *host)
>
> dwmci_writel(host, priv->chip->clksel, timing);
>
> + exynos_config_hs400(host, host->mmc->selected_mode);
> +
This breaks emmc in Linux for existing devices without hs400 support:
exynos_config_hs400 is run no matter if the dt has the hs400
parameters, meaning 0x0 is written to DWMCI_HS400_DQS_EN and
DWMCI_HS400_DLINE_CTRL. U-boot does not seem to care, it loads the
kernel and initramfs without issues, but Linux later gives errors:
```
[ 20.741025] mmc_host mmc0: Bus speed (slot 0) = 50000000Hz (slot req 400000Hz, actual 396825HZ div = 63)
[ 20.886686] mmc_host mmc0: Bus speed (slot 0) = 50000000Hz (slot req 52000000Hz, actual 50000000HZ div = 0)
[ 20.895138] mmc_host mmc0: Bus speed (slot 0) = 200000000Hz (slot req 200000000Hz, actual 200000000HZ div = 0)
[ 20.905990] mmc_host mmc0: Bus speed (slot 0) = 50000000Hz (slot req 52000000Hz, actual 50000000HZ div = 0)
[ 20.914988] mmc_host mmc0: Bus speed (slot 0) = 400000000Hz (slot req 200000000Hz, actual 200000000HZ div = 1)
[ 21.099546] I/O error, dev mmcblk0, sector 1000001 op 0x1:(WRITE) flags 0x800 phys_seg 16 prio class 2
[ 21.099853] I/O error, dev mmcblk0, sector 1000825 op 0x1:(WRITE) flags 0x800 phys_seg 8 prio class 2
[ 21.107443] Buffer I/O error on dev mmcblk0p2, logical block 0, lost async page write
[ 21.107473] Buffer I/O error on dev mmcblk0p2, logical block 1, lost async page write
[ 21.116657] Buffer I/O error on dev mmcblk0p2, logical block 824, lost async page write
[ 21.124408] Buffer I/O error on dev mmcblk0p2, logical block 2, lost async page write
[ 21.132206] Buffer I/O error on dev mmcblk0p2, logical block 825, lost async page write
[ 21.140178] Buffer I/O error on dev mmcblk0p2, logical block 3, lost async page write
[ 21.147973] Buffer I/O error on dev mmcblk0p2, logical block 826, lost async page write
[ 21.155960] Buffer I/O error on dev mmcblk0p2, logical block 4, lost async page write
[ 21.163749] Buffer I/O error on dev mmcblk0p2, logical block 827, lost async page write
[ 21.171720] Buffer I/O error on dev mmcblk0p2, logical block 5, lost async page write
[ 21.200708] I/O error, dev mmcblk0, sector 1000873 op 0x1:(WRITE) flags 0x800 phys_seg 8 prio class 2
[ 21.201043] I/O error, dev mmcblk0, sector 1000929 op 0x1:(WRITE) flags 0x800 phys_seg 8 prio class 2
[ 21.218287] I/O error, dev mmcblk0, sector 1000945 op 0x1:(WRITE) flags 0x800 phys_seg 80 prio class 2
[ 21.218623] I/O error, dev mmcblk0, sector 1001033 op 0x1:(WRITE) flags 0x800 phys_seg 24 prio class 2
[ 21.236813] I/O error, dev mmcblk0, sector 1001097 op 0x1:(WRITE) flags 0x800 phys_seg 8 prio class 2
[ 21.237146] I/O error, dev mmcblk0, sector 1001121 op 0x1:(WRITE) flags 0x800 phys_seg 24 prio class 2
[ 21.255370] I/O error, dev mmcblk0, sector 1001161 op 0x1:(WRITE) flags 0x800 phys_seg 56 prio class 2
[ 21.256253] I/O error, dev mmcblk0, sector 1001249 op 0x1:(WRITE) flags 0x800 phys_seg 8 prio class 2
```
Specifically it seems to be writing 0x0 to DWMCI_HS400_DQS_EN that is
the issue, if I comment out that line it works also for my odroid-xu4
in non-hs400 mode.
Tested on odroid-xu4 with odroid-xu3_defconfig after applying the
entire series, without any other changes.
Best regards,
Henrik Grimler
More information about the U-Boot
mailing list