mmc: exynos_dw_mmc: add support for MMC HS200 and HS400 modes

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@disroot.org>
Reviewed-by: Peng Fan <peng.fan@nxp.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
This commit is contained in:
Kaustabh Chakraborty
2025-10-30 10:11:17 +08:00
committed by Peng Fan
parent 0f425edd10
commit bbe3b9fa09
+83 -5
View File
@@ -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;
@@ -391,6 +463,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,
@@ -398,7 +476,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),
};