common: Drop linux/delay.h from common header
[oweals/u-boot.git] / drivers / mmc / tegra_mmc.c
index 22990fa98b78d1314fa6c654dde5dd8643774481..78838682c7c242354dddb1cc7d0466d528d1e752 100644 (file)
@@ -3,17 +3,23 @@
  * (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;
@@ -29,6 +35,7 @@ struct tegra_mmc_priv {
        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,
@@ -371,6 +378,25 @@ static void tegra_mmc_change_clock(struct tegra_mmc_priv *priv, uint clock)
 
        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);
@@ -445,16 +471,19 @@ static int tegra_mmc_set_ios(struct udevice *dev)
 
 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;
        }
 
@@ -463,11 +492,65 @@ static void tegra_mmc_pad_init(struct tegra_mmc_priv *priv)
        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)
@@ -513,6 +596,13 @@ static int tegra_mmc_init(struct udevice *dev)
        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)