[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