* (C) Copyright 2009 SAMSUNG Electronics
* Minkyu Kang <mk7.kang@samsung.com>
* Jaehoon Chung <jh80.chung@samsung.com>
- * Portions Copyright 2011-2016 NVIDIA Corporation
+ * Portions Copyright 2011-2019 NVIDIA Corporation
*/
#include <bouncebuf.h>
#include <common.h>
#include <dm.h>
#include <errno.h>
+#include <log.h>
#include <mmc.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch-tegra/tegra_mmc.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#if defined(CONFIG_TEGRA30) || defined(CONFIG_TEGRA210)
+#include <asm/arch/clock.h>
+#endif
struct tegra_mmc_plat {
struct mmc_config cfg;
struct gpio_desc wp_gpio; /* Write Protect GPIO */
unsigned int version; /* SDHCI spec. version */
unsigned int clock; /* Current clock (MHz) */
+ int mmc_id; /* peripheral id */
};
static void tegra_mmc_set_power(struct tegra_mmc_priv *priv,
rate = clk_set_rate(&priv->clk, clock);
div = (rate + clock - 1) / clock;
+
+#if defined(CONFIG_TEGRA210)
+ if (priv->mmc_id == PERIPH_ID_SDMMC1 && clock <= 400000) {
+ /* clock_adjust_periph_pll_div() chooses a 'bad' clock
+ * on SDMMC1 T210, so skip it here and force a clock
+ * that's been spec'd in the table in the TRM for
+ * card-detect (400KHz).
+ */
+ uint effective_rate = clock_adjust_periph_pll_div(priv->mmc_id,
+ CLOCK_ID_PERIPH, 24727273, NULL);
+ div = 62;
+
+ debug("%s: WAR: Using SDMMC1 clock of %u, div %d to achieve %dHz card clock ...\n",
+ __func__, effective_rate, div, clock);
+ } else {
+ clock_adjust_periph_pll_div(priv->mmc_id, CLOCK_ID_PERIPH,
+ clock, &div);
+ }
+#endif
debug("div = %d\n", div);
writew(0, &priv->reg->clkcon);
static void tegra_mmc_pad_init(struct tegra_mmc_priv *priv)
{
-#if defined(CONFIG_TEGRA30)
+#if defined(CONFIG_TEGRA30) || defined(CONFIG_TEGRA210)
u32 val;
+ u16 clk_con;
+ int timeout;
+ int id = priv->mmc_id;
- debug("%s: sdmmc address = %08x\n", __func__, (unsigned int)priv->reg);
+ debug("%s: sdmmc address = %p, id = %d\n", __func__,
+ priv->reg, id);
/* Set the pad drive strength for SDMMC1 or 3 only */
- if (priv->reg != (void *)0x78000000 &&
- priv->reg != (void *)0x78000400) {
+ if (id != PERIPH_ID_SDMMC1 && id != PERIPH_ID_SDMMC3) {
debug("%s: settings are only valid for SDMMC1/SDMMC3!\n",
- __func__);
+ __func__);
return;
}
val |= MEMCOMP_PADCTRL_VREF;
writel(val, &priv->reg->sdmemcmppadctl);
+ /* Disable SD Clock Enable before running auto-cal as per TRM */
+ clk_con = readw(&priv->reg->clkcon);
+ debug("%s: CLOCK_CONTROL = 0x%04X\n", __func__, clk_con);
+ clk_con &= ~TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE;
+ writew(clk_con, &priv->reg->clkcon);
+
val = readl(&priv->reg->autocalcfg);
val &= 0xFFFF0000;
- val |= AUTO_CAL_PU_OFFSET | AUTO_CAL_PD_OFFSET | AUTO_CAL_ENABLED;
+ val |= AUTO_CAL_PU_OFFSET | AUTO_CAL_PD_OFFSET;
writel(val, &priv->reg->autocalcfg);
-#endif
+ val |= AUTO_CAL_START | AUTO_CAL_ENABLE;
+ writel(val, &priv->reg->autocalcfg);
+ debug("%s: AUTO_CAL_CFG = 0x%08X\n", __func__, val);
+ udelay(1);
+ timeout = 100; /* 10 mSec max (100*100uS) */
+ do {
+ val = readl(&priv->reg->autocalsts);
+ udelay(100);
+ } while ((val & AUTO_CAL_ACTIVE) && --timeout);
+ val = readl(&priv->reg->autocalsts);
+ debug("%s: Final AUTO_CAL_STATUS = 0x%08X, timeout = %d\n",
+ __func__, val, timeout);
+
+ /* Re-enable SD Clock Enable when auto-cal is done */
+ clk_con |= TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE;
+ writew(clk_con, &priv->reg->clkcon);
+ clk_con = readw(&priv->reg->clkcon);
+ debug("%s: final CLOCK_CONTROL = 0x%04X\n", __func__, clk_con);
+
+ if (timeout == 0) {
+ printf("%s: Warning: Autocal timed out!\n", __func__);
+ /* TBD: Set CFG2TMC_SDMMC1_PAD_CAL_DRV* regs here */
+ }
+
+#if defined(CONFIG_TEGRA210)
+ u32 tap_value, trim_value;
+
+ /* Set tap/trim values for SDMMC1/3 @ <48MHz here */
+ val = readl(&priv->reg->venspictl); /* aka VENDOR_SYS_SW_CNTL */
+ val &= IO_TRIM_BYPASS_MASK;
+ if (id == PERIPH_ID_SDMMC1) {
+ tap_value = 4; /* default */
+ if (val)
+ tap_value = 3;
+ trim_value = 2;
+ } else { /* SDMMC3 */
+ tap_value = 3;
+ trim_value = 3;
+ }
+
+ val = readl(&priv->reg->venclkctl);
+ val &= ~TRIM_VAL_MASK;
+ val |= (trim_value << TRIM_VAL_SHIFT);
+ val &= ~TAP_VAL_MASK;
+ val |= (tap_value << TAP_VAL_SHIFT);
+ writel(val, &priv->reg->venclkctl);
+ debug("%s: VENDOR_CLOCK_CNTRL = 0x%08X\n", __func__, val);
+#endif /* T210 */
+#endif /* T30/T210 */
}
static void tegra_mmc_reset(struct tegra_mmc_priv *priv, struct mmc *mmc)
unsigned int mask;
debug(" tegra_mmc_init called\n");
+#if defined(CONFIG_TEGRA210)
+ priv->mmc_id = clock_decode_periph_id(dev);
+ if (priv->mmc_id == PERIPH_ID_NONE) {
+ printf("%s: Missing/invalid peripheral ID\n", __func__);
+ return -EINVAL;
+ }
+#endif
tegra_mmc_reset(priv, mmc);
#if defined(CONFIG_TEGRA124_MMC_DISABLE_EXT_LOOPBACK)