On 2026-05-13 18:58 +08:00, Peng Fan wrote:
> On Tue, May 05, 2026 at 09:07:29PM +0200, Henrik Grimler wrote:
>>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.
>
> Is this patchset good for you all or is there any plan for a new version for
> 2026.07?

It's fine by me. It also works with odroid-xu4 mainline u-boot (which
does not enable HS400/ES in its config), so it's fine to be pulled in.

>
> Thanks
> Peng
>
>>
>>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