Hi Kaustabh,

On Sun, May 03, 2026 at 05:51:26PM +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 <[email protected]>

Reviewed-by: Henrik Grimler <[email protected]>

This works fine on exynos5422-odroid-xu4 without hs400. I was not able
to get hs400 working on the device, seems more changes than just dts
update are needed.

Best regards,
Henrik Grimler

> ---
>  arch/arm/mach-exynos/include/mach/dwmmc.h |  5 ++
>  drivers/mmc/exynos_dw_mmc.c               | 81 
> +++++++++++++++++++++++++++++++
>  2 files changed, 86 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..6558cdc803d 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,9 @@ static int exynos_dwmci_clksel(struct dwmci_host *host)
>  
>       dwmci_writel(host, priv->chip->clksel, timing);
>  
> +     if (CONFIG_IS_ENABLED(MMC_HS400_SUPPORT))
> +             exynos_config_hs400(host, host->mmc->selected_mode);
> +
>       return 0;
>  }
>  
> @@ -223,6 +264,16 @@ static void exynos_dwmci_board_init(struct dwmci_host 
> *host)
>  {
>       struct dwmci_exynos_priv_data *priv = exynos_dwmmc_get_priv(host);
>  
> +     if (CONFIG_IS_ENABLED(MMC_HS400_SUPPORT)) {
> +             priv->saved_strobe_ctrl = dwmci_readl(host, 
> DWMCI_HS400_DLINE_CTRL);
> +             priv->saved_dqs_en = dwmci_readl(host, DWMCI_HS400_DQS_EN);
> +             priv->saved_dqs_en |= AXI_NON_BLOCKING_WR;
> +             dwmci_writel(host, DWMCI_HS400_DQS_EN, priv->saved_dqs_en);
> +             if (!priv->dqs_delay)
> +                     priv->dqs_delay =
> +                             DQS_CTRL_GET_RD_DELAY(priv->saved_strobe_ctrl);
> +     }
> +
>       if (priv->chip->quirks & DWMCI_QUIRK_DISABLE_SMU) {
>               dwmci_writel(host, EMMCP_MPSBEGIN0, 0);
>               dwmci_writel(host, EMMCP_SEND0, 0);
> @@ -319,6 +370,22 @@ static int exynos_dwmmc_of_to_plat(struct udevice *dev)
>                                  DWMCI_SET_DIV_RATIO(div);
>       }
>  
> +     err = dev_read_u32_array(dev, "samsung,dw-mshc-hs400-timing", timing, 
> 2);
> +     if (err) {
> +             debug("DWMMC%d: Can't get hs400-timings, using ddr-timings\n",
> +                   host->dev_index);
> +             priv->hs400_timing = priv->ddr_timing;
> +     } else {
> +             priv->hs400_timing = DWMCI_SET_SAMPLE_CLK(timing[0]) |
> +                                  DWMCI_SET_DRV_CLK(timing[1]) |
> +                                  DWMCI_SET_DIV_RATIO(1);
> +             if (dev_read_u32(dev, "samsung,read-strobe-delay", 
> &priv->dqs_delay)) {
> +                     priv->dqs_delay = 0;
> +                     debug("DWMMC%d: read-strobe-delay is not found, 
> assuming usage of default value\n",
> +                           host->dev_index);
> +             }
> +     }
> +
>       host->buswidth = dev_read_u32_default(dev, "bus-width", 4);
>       host->fifo_depth = dev_read_u32_default(dev, "fifo-depth", 0);
>       host->bus_hz = dev_read_u32_default(dev, "clock-frequency", 0);
> @@ -356,6 +423,16 @@ static int exynos_dwmmc_get_best_clksmpl(u8 candidates)
>       return -EIO;
>  }
>  
> +static int dw_mci_exynos_prepare_hs400_tuning(struct dwmci_host *host)
> +{
> +     struct dwmci_exynos_priv_data *priv = exynos_dwmmc_get_priv(host);
> +
> +     dwmci_writel(host, priv->chip->clksel, priv->hs400_timing);
> +     host->bus_hz = exynos_dwmci_get_clk(host, host->clock);
> +
> +     return 0;
> +}
> +
>  static int exynos_dwmmc_execute_tuning(struct udevice *dev, u32 opcode)
>  {
>       struct dwmci_exynos_priv_data *priv = dev_get_priv(dev);
> @@ -365,6 +442,9 @@ static int exynos_dwmmc_execute_tuning(struct udevice 
> *dev, u32 opcode)
>       u32 clksel;
>       int ret;
>  
> +     if (mmc->hs400_tuning)
> +             dw_mci_exynos_prepare_hs400_tuning(host);
> +
>       clksel = dwmci_readl(host, priv->chip->clksel);
>       start_smpl = CLKSEL_CCLK_SAMPLE(clksel);
>  
> @@ -387,6 +467,7 @@ static int exynos_dwmmc_execute_tuning(struct udevice 
> *dev, u32 opcode)
>               return ret;
>       }
>  
> +     priv->tuned_sample = ret;
>       dwmci_writel(host, priv->chip->clksel, CLKSEL_UP_SAMPLE(clksel, ret));
>  
>       return 0;
> 
> -- 
> 2.53.0
> 

Reply via email to