[PATCH 1/4] mmc: exynos_dw_mmc: add proper init sequence for HS400 support

Kaustabh Chakraborty kauschluss at disroot.org
Fri May 1 11:02:50 CEST 2026


On 2026-05-01 08:58 +02:00, Henrik Grimler wrote:
> Hi Kaustabh,
>
> On Fri, May 01, 2026 at 01:11:29AM +0530, Kaustabh Chakraborty wrote:
>> On 2026-04-30 19:26 +02:00, Henrik Grimler wrote:
>> > 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:
>> 
>> Is it that the same defconfig is used for two devices, one with and one
>> without HS400 support? If that's the case, guarding the call with
>> HS400_SUPPORT config options won't be a real fix.
>
> A family of devices are using the same defconfig. I assume the
> hardware in all of them supports hs400, but at the moment only one
> device seems to be configured to use hs400 _in Linux_, and none in
> u-boot.

It won't. If one of the devices supports HS400 and others don't (not
specifically in your case, but a general scenario), then the config
would have HS400 support enabled. As a result non-HS400 hardware would
also try to assume HS400.

>
> Adding a config guard around exynos_config_hs400() would probably
> work. I see Linux uses a check like
> if (priv->ctrl_type >= DW_MCI_TYPE_EXYNOS5420)
> instead.
>
>> mmc_of_parse() in mmc-uclass.h seems promising, this needs to be
>> integrated with dwmci_setup_cfg() in dw_mmc.c, and this change _may_ be
>> regressive in nature, so I may also need to add some safety guards for
>> this.
>> 
>> I will thus add this patch in the next rev. Would you be up to test the patch
>> for the concerned devices too?
>
> Yeah I can test on these older devices.
>
> Best regards,
> Henrik Grimler
>
>> >
>> > ```
>> > [   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