[PATCH 7/9] mmc: exynos_dw_mmc: add support for MMC HS200 and HS400 modes
    Kaustabh Chakraborty 
    kauschluss at disroot.org
       
    Fri Oct 17 17:24:12 CEST 2025
    
    
  
MMC HS200 and HS400 modes are already supported by the Exynos DW-MMC
driver in mainline Linux. Using that as reference, add support in the
U-Boot driver.
The maximum frequency was capped to 50000000, increase it to 200000000,
which is the required frequency for HS200/HS400. Moreover, add
MMC_MODE_HS200 and MMC_MODE_HS400 to host capailities. These changes
allow both host and card to recognize support for HS200/HS400.
This change also includes a new ops function, namely execute_tuning.
Implementing it would mean that we can no longer rely on the default ops
provided by dw_mmc.c, thus a new ops instance is created with proper
fields. The execute_tuning function is modeled after the one available
in the Linux driver.
Signed-off-by: Kaustabh Chakraborty <kauschluss at disroot.org>
---
 drivers/mmc/exynos_dw_mmc.c | 88 ++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 83 insertions(+), 5 deletions(-)
diff --git a/drivers/mmc/exynos_dw_mmc.c b/drivers/mmc/exynos_dw_mmc.c
index 12e37cb4b7884c5cdac0303245c93988d034459a..8311036109837d710ec141137684cd4f45ae449f 100644
--- a/drivers/mmc/exynos_dw_mmc.c
+++ b/drivers/mmc/exynos_dw_mmc.c
@@ -18,13 +18,18 @@
 #include <linux/printk.h>
 
 #define	DWMMC_MAX_CH_NUM		4
-#define	DWMMC_MAX_FREQ			52000000
+#define	DWMMC_MAX_FREQ			200000000
 #define	DWMMC_MIN_FREQ			400000
 #define	DWMMC_MMC0_SDR_TIMING_VAL	0x03030001
 #define	DWMMC_MMC2_SDR_TIMING_VAL	0x03020001
 
 #define EXYNOS4412_FIXED_CIU_CLK_DIV	4
 
+/* CLKSEL register defines */
+#define CLKSEL_CCLK_SAMPLE(x)	(((x) & 7) << 0)
+#define CLKSEL_UP_SAMPLE(x, y)	(((x) & ~CLKSEL_CCLK_SAMPLE(7)) |\
+					 CLKSEL_CCLK_SAMPLE(y))
+
 /* Quirks */
 #define DWMCI_QUIRK_DISABLE_SMU		BIT(0)
 
@@ -166,8 +171,9 @@ static unsigned int exynos_dwmci_get_clk(struct dwmci_host *host, uint freq)
 	u8 clk_div;
 	int err;
 
-	/* Should be double rate for DDR mode */
-	if (host->mmc->selected_mode == MMC_DDR_52 && host->mmc->bus_width == 8)
+	/* Should be double rate for DDR or HS mode */
+	if ((host->mmc->selected_mode == MMC_DDR_52 && host->mmc->bus_width == 8) ||
+	    host->mmc->selected_mode == MMC_HS_400)
 		freq *= 2;
 
 	clk_div = exynos_dwmmc_get_ciu_div(host);
@@ -282,6 +288,72 @@ static int exynos_dwmmc_of_to_plat(struct udevice *dev)
 	return 0;
 }
 
+static int exynos_dwmmc_get_best_clksmpl(u8 candidates)
+{
+	u8 i;
+
+	for (i = 0; i < 8; i++) {
+		candidates = (candidates >> 1) | (candidates << 7); /* ror */
+		if ((candidates & 0xc7) == 0xc7)
+			return i;
+	}
+
+	for (i = 0; i < 8; i++) {
+		candidates = (candidates >> 1) | (candidates << 7); /* ror */
+		if ((candidates & 0x83) == 0x83)
+			return i;
+	}
+
+	/*
+	 * If no valid clock sample values are found, use the first
+	 * canditate bit for clock sample value.
+	 */
+	for (i = 0; i < 8; i++) {
+		candidates = (candidates >> 1) | (candidates << 7); /* ror */
+		if ((candidates & 0x1) == 0x1)
+			return i;
+	}
+
+	return -EIO;
+}
+
+static int exynos_dwmmc_execute_tuning(struct udevice *dev, u32 opcode)
+{
+	struct dwmci_exynos_priv_data *priv = dev_get_priv(dev);
+	struct dwmci_host *host = &priv->host;
+	struct mmc *mmc = mmc_get_mmc_dev(dev);
+	u8 start_smpl, smpl, candidates = 0;
+	u32 clksel;
+	int ret;
+
+	clksel = dwmci_readl(host, priv->chip->clksel);
+	start_smpl = CLKSEL_CCLK_SAMPLE(clksel);
+
+	do {
+		dwmci_writel(host, DWMCI_TMOUT, ~0);
+
+		/* move to the next clksmpl */
+		smpl = (clksel + 1) & 0x7;
+		clksel = CLKSEL_UP_SAMPLE(clksel, smpl);
+		dwmci_writel(host, priv->chip->clksel, clksel);
+
+		if (!mmc_send_tuning(mmc, opcode))
+			candidates |= (1 << smpl);
+
+	} while (start_smpl != smpl);
+
+	ret = exynos_dwmmc_get_best_clksmpl(candidates);
+	if (ret < 0) {
+		printf("DWMMC%d: No candidates for clksmpl\n", host->dev_index);
+		return ret;
+	}
+
+	dwmci_writel(host, priv->chip->clksel,
+		     CLKSEL_UP_SAMPLE(clksel, ret));
+
+	return 0;
+}
+
 static int exynos_dwmmc_probe(struct udevice *dev)
 {
 	struct exynos_mmc_plat *plat = dev_get_plat(dev);
@@ -321,7 +393,7 @@ static int exynos_dwmmc_probe(struct udevice *dev)
 
 	host->name = dev->name;
 	host->board_init = exynos_dwmci_board_init;
-	host->caps = MMC_MODE_DDR_52MHz;
+	host->caps = MMC_MODE_DDR_52MHz | MMC_MODE_HS200 | MMC_MODE_HS400;
 	host->clksel = exynos_dwmci_clksel;
 	host->get_mmc_clk = exynos_dwmci_get_clk;
 
@@ -388,6 +460,12 @@ static const struct udevice_id exynos_dwmmc_ids[] = {
 	{ }
 };
 
+struct dm_mmc_ops exynos_dwmmc_ops = {
+	.send_cmd	= dwmci_send_cmd,
+	.set_ios	= dwmci_set_ios,
+	.execute_tuning	= exynos_dwmmc_execute_tuning,
+};
+
 U_BOOT_DRIVER(exynos_dwmmc_drv) = {
 	.name		= "exynos_dwmmc",
 	.id		= UCLASS_MMC,
@@ -395,7 +473,7 @@ U_BOOT_DRIVER(exynos_dwmmc_drv) = {
 	.of_to_plat	= exynos_dwmmc_of_to_plat,
 	.bind		= exynos_dwmmc_bind,
 	.probe		= exynos_dwmmc_probe,
-	.ops		= &dm_dwmci_ops,
+	.ops		= &exynos_dwmmc_ops,
 	.priv_auto	= sizeof(struct dwmci_exynos_priv_data),
 	.plat_auto	= sizeof(struct exynos_mmc_plat),
 };
-- 
2.51.0
    
    
More information about the U-Boot
mailing list