summaryrefslogtreecommitdiff
path: root/drivers/mmc/exynos_dw_mmc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc/exynos_dw_mmc.c')
-rw-r--r--drivers/mmc/exynos_dw_mmc.c182
1 files changed, 158 insertions, 24 deletions
diff --git a/drivers/mmc/exynos_dw_mmc.c b/drivers/mmc/exynos_dw_mmc.c
index 12e37cb4b78..7ccd113bd79 100644
--- a/drivers/mmc/exynos_dw_mmc.c
+++ b/drivers/mmc/exynos_dw_mmc.c
@@ -18,15 +18,33 @@
#include <linux/printk.h>
#define DWMMC_MAX_CH_NUM 4
-#define DWMMC_MAX_FREQ 52000000
+#define DWMMC_MAX_FREQ 208000000
#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
-/* Quirks */
+/* 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))
+
+/**
+ * DOC: Quirk flags for different Exynos DW MMC blocks
+ *
+ * %DWMCI_QUIRK_DISABLE_SMU: DW MMC block has Security Management Unit (SMU)
+ * which has to be configured in non-encryption mode during driver's init.
+ *
+ * %DWMCI_QUIRK_DISABLE_FMP: DW MMC block has Flash Memory Protector (FMP) which
+ * has to be disabled during driver's init. This flag disables FMP encryption
+ * and lets external non-secure main CPUs access the SFR (peripheral memory
+ * region, i.e. registers) in MMC core. Although it's usually done by early
+ * bootloaders (before U-Boot), in some cases like during USB boot the FMP might
+ * be left unconfigured.
+ */
#define DWMCI_QUIRK_DISABLE_SMU BIT(0)
+#define DWMCI_QUIRK_DISABLE_FMP BIT(1)
#ifdef CONFIG_DM_MMC
#include <dm.h>
@@ -121,22 +139,6 @@ static int exynos_dwmmc_set_sclk(struct dwmci_host *host, unsigned long rate)
return 0;
}
-/* Configure CLKSEL register with chosen timing values */
-static int exynos_dwmci_clksel(struct dwmci_host *host)
-{
- struct dwmci_exynos_priv_data *priv = exynos_dwmmc_get_priv(host);
- u32 timing;
-
- if (host->mmc->selected_mode == MMC_DDR_52)
- timing = priv->ddr_timing;
- else
- timing = priv->sdr_timing;
-
- dwmci_writel(host, priv->chip->clksel, timing);
-
- return 0;
-}
-
/**
* exynos_dwmmc_get_ciu_div - Get internal clock divider value
* @host: MMC controller object
@@ -160,15 +162,45 @@ static u8 exynos_dwmmc_get_ciu_div(struct dwmci_host *host)
& DWMCI_DIVRATIO_MASK) + 1;
}
+/* Configure CLKSEL register with chosen timing values */
+static int exynos_dwmci_clksel(struct dwmci_host *host)
+{
+ struct dwmci_exynos_priv_data *priv = exynos_dwmmc_get_priv(host);
+ u8 clk_div = exynos_dwmmc_get_ciu_div(host) - 1;
+ u32 timing;
+
+ switch (host->mmc->selected_mode) {
+ case MMC_DDR_52:
+ timing = priv->ddr_timing;
+ break;
+ case UHS_SDR104:
+ case UHS_SDR50:
+ timing = (priv->sdr_timing & 0xfff8ffff) | (clk_div << 16);
+ break;
+ case UHS_DDR50:
+ timing = (priv->ddr_timing & 0xfff8ffff) | (clk_div << 16);
+ break;
+ default:
+ timing = priv->sdr_timing;
+ }
+
+ dwmci_writel(host, priv->chip->clksel, timing);
+
+ return 0;
+}
+
static unsigned int exynos_dwmci_get_clk(struct dwmci_host *host, uint freq)
{
unsigned long sclk;
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);
err = exynos_dwmmc_set_sclk(host, freq * clk_div);
@@ -201,6 +233,18 @@ static void exynos_dwmci_board_init(struct dwmci_host *host)
MPSCTRL_NON_SECURE_WRITE_BIT | MPSCTRL_VALID);
}
+ if (priv->chip->quirks & DWMCI_QUIRK_DISABLE_FMP) {
+ u32 reg;
+
+ reg = dwmci_readl(host, EMMCP_MPSECURITY);
+ if (reg & MPSECURITY_FMP_ON ||
+ reg & MPSECURITY_MMC_SFR_PROT_ON) {
+ reg &= ~MPSECURITY_FMP_ON;
+ reg &= ~MPSECURITY_MMC_SFR_PROT_ON;
+ dwmci_writel(host, EMMCP_MPSECURITY, reg);
+ }
+ }
+
if (priv->sdr_timing)
exynos_dwmci_clksel(host);
}
@@ -282,6 +326,75 @@ static int exynos_dwmmc_of_to_plat(struct udevice *dev)
return 0;
}
+#if CONFIG_IS_ENABLED(MMC_SUPPORTS_TUNING)
+static int exynos_dwmmc_get_best_clksmpl(u8 candidates)
+{
+ int 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 candidate
+ * 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;
+}
+#endif /* CONFIG_MMC_SUPPORTS_TUNING */
+
+struct dm_mmc_ops exynos_dwmmc_ops;
+
static int exynos_dwmmc_probe(struct udevice *dev)
{
struct exynos_mmc_plat *plat = dev_get_plat(dev);
@@ -291,6 +404,12 @@ static int exynos_dwmmc_probe(struct udevice *dev)
unsigned long freq;
int err;
+ /* Extend generic 'dm_dwmci_ops' with .execute_tuning implementation */
+ memcpy(&exynos_dwmmc_ops, &dm_dwmci_ops, sizeof(struct dm_mmc_ops));
+#if CONFIG_IS_ENABLED(MMC_SUPPORTS_TUNING)
+ exynos_dwmmc_ops.execute_tuning = exynos_dwmmc_execute_tuning;
+#endif
+
#ifndef CONFIG_CPU_V7A
err = clk_get_by_index(dev, 1, &priv->clk); /* ciu */
if (err)
@@ -303,7 +422,7 @@ static int exynos_dwmmc_probe(struct udevice *dev)
flag = host->buswidth == 8 ? PINMUX_FLAG_8BIT_MODE : PINMUX_FLAG_NONE;
err = exynos_pinmux_config(host->dev_id, flag);
if (err) {
- printf("DWMMC%d not configure\n", host->dev_index);
+ printf("DWMMC%d not configured\n", host->dev_index);
return err;
}
#endif
@@ -321,7 +440,8 @@ 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 |
+ UHS_CAPS;
host->clksel = exynos_dwmci_clksel;
host->get_mmc_clk = exynos_dwmci_get_clk;
@@ -368,6 +488,11 @@ static const struct exynos_dwmmc_variant exynos7_smu_drv_data = {
.quirks = DWMCI_QUIRK_DISABLE_SMU,
};
+static const struct exynos_dwmmc_variant exynos850_drv_data = {
+ .clksel = DWMCI_CLKSEL64,
+ .quirks = DWMCI_QUIRK_DISABLE_SMU | DWMCI_QUIRK_DISABLE_FMP,
+};
+
static const struct udevice_id exynos_dwmmc_ids[] = {
{
.compatible = "samsung,exynos4412-dw-mshc",
@@ -379,11 +504,20 @@ static const struct udevice_id exynos_dwmmc_ids[] = {
.compatible = "samsung,exynos5420-dw-mshc",
.data = (ulong)&exynos5_drv_data,
}, {
+ .compatible = "samsung,exynos5250-dw-mshc",
+ .data = (ulong)&exynos5_drv_data,
+ }, {
.compatible = "samsung,exynos-dwmmc",
.data = (ulong)&exynos5_drv_data,
}, {
.compatible = "samsung,exynos7-dw-mshc-smu",
.data = (ulong)&exynos7_smu_drv_data,
+ }, {
+ .compatible = "samsung,exynos7870-dw-mshc-smu",
+ .data = (ulong)&exynos7_smu_drv_data,
+ }, {
+ .compatible = "samsung,exynos850-dw-mshc-smu",
+ .data = (ulong)&exynos850_drv_data,
},
{ }
};
@@ -395,8 +529,8 @@ 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),
};
-#endif
+#endif /* CONFIG_DM_MMC */