common: Drop linux/delay.h from common header
[oweals/u-boot.git] / drivers / mmc / sunxi_mmc.c
index 588574fab6a9a02ff2dae4b49d1a8f1729e209f6..0e03b07ce555fec568cbfb56b4efdea1579a5599 100644 (file)
@@ -1,24 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0+
 /*
  * (C) Copyright 2007-2011
  * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
  * Aaron <leafy.myeh@allwinnertech.com>
  *
  * MMC driver for allwinner sunxi platform.
- *
- * SPDX-License-Identifier:    GPL-2.0+
  */
 
 #include <common.h>
 #include <dm.h>
 #include <errno.h>
+#include <log.h>
 #include <malloc.h>
 #include <mmc.h>
+#include <clk.h>
+#include <reset.h>
 #include <asm/io.h>
 #include <asm/arch/clock.h>
 #include <asm/arch/cpu.h>
 #include <asm/arch/gpio.h>
 #include <asm/arch/mmc.h>
 #include <asm-generic/gpio.h>
+#include <linux/delay.h>
+
+#ifdef CONFIG_DM_MMC
+struct sunxi_mmc_variant {
+       u16 mclk_offset;
+};
+#endif
 
 struct sunxi_mmc_plat {
        struct mmc_config cfg;
@@ -30,8 +39,12 @@ struct sunxi_mmc_priv {
        uint32_t *mclkreg;
        unsigned fatal_err;
        struct gpio_desc cd_gpio;       /* Change Detect GPIO */
+       int cd_inverted;                /* Inverted Card Detect */
        struct sunxi_mmc *reg;
        struct mmc_config cfg;
+#ifdef CONFIG_DM_MMC
+       const struct sunxi_mmc_variant *variant;
+#endif
 };
 
 #if !CONFIG_IS_ENABLED(DM_MMC)
@@ -70,10 +83,12 @@ static int mmc_resource_init(int sdc_no)
                priv->reg = (struct sunxi_mmc *)SUNXI_MMC2_BASE;
                priv->mclkreg = &ccm->sd2_clk_cfg;
                break;
+#ifdef SUNXI_MMC3_BASE
        case 3:
                priv->reg = (struct sunxi_mmc *)SUNXI_MMC3_BASE;
                priv->mclkreg = &ccm->sd3_clk_cfg;
                break;
+#endif
        default:
                printf("Wrong mmc number %d\n", sdc_no);
                return -1;
@@ -96,6 +111,20 @@ static int mmc_resource_init(int sdc_no)
 static int mmc_set_mod_clk(struct sunxi_mmc_priv *priv, unsigned int hz)
 {
        unsigned int pll, pll_hz, div, n, oclk_dly, sclk_dly;
+       bool new_mode = true;
+       bool calibrate = false;
+       u32 val = 0;
+
+       if (!IS_ENABLED(CONFIG_MMC_SUNXI_HAS_NEW_MODE))
+               new_mode = false;
+
+       /* A83T support new mode only on eMMC */
+       if (IS_ENABLED(CONFIG_MACH_SUN8I_A83T) && priv->mmc_no != 2)
+               new_mode = false;
+
+#if defined(CONFIG_MACH_SUN50I) || defined(CONFIG_MACH_SUN50I_H6)
+       calibrate = true;
+#endif
 
        if (hz <= 24000000) {
                pll = CCM_MMC_CTRL_OSCM24;
@@ -104,6 +133,9 @@ static int mmc_set_mod_clk(struct sunxi_mmc_priv *priv, unsigned int hz)
 #ifdef CONFIG_MACH_SUN9I
                pll = CCM_MMC_CTRL_PLL_PERIPH0;
                pll_hz = clock_get_pll4_periph0();
+#elif defined(CONFIG_MACH_SUN50I_H6)
+               pll = CCM_MMC_CTRL_PLL6X2;
+               pll_hz = clock_get_pll6() * 2;
 #else
                pll = CCM_MMC_CTRL_PLL6;
                pll_hz = clock_get_pll6();
@@ -134,27 +166,42 @@ static int mmc_set_mod_clk(struct sunxi_mmc_priv *priv, unsigned int hz)
                oclk_dly = 0;
                sclk_dly = 5;
 #ifdef CONFIG_MACH_SUN9I
-       } else if (hz <= 50000000) {
+       } else if (hz <= 52000000) {
                oclk_dly = 5;
                sclk_dly = 4;
        } else {
-               /* hz > 50000000 */
+               /* hz > 52000000 */
                oclk_dly = 2;
                sclk_dly = 4;
 #else
-       } else if (hz <= 50000000) {
+       } else if (hz <= 52000000) {
                oclk_dly = 3;
                sclk_dly = 4;
        } else {
-               /* hz > 50000000 */
+               /* hz > 52000000 */
                oclk_dly = 1;
                sclk_dly = 4;
 #endif
        }
 
-       writel(CCM_MMC_CTRL_ENABLE | pll | CCM_MMC_CTRL_SCLK_DLY(sclk_dly) |
-              CCM_MMC_CTRL_N(n) | CCM_MMC_CTRL_OCLK_DLY(oclk_dly) |
-              CCM_MMC_CTRL_M(div), priv->mclkreg);
+       if (new_mode) {
+#ifdef CONFIG_MMC_SUNXI_HAS_NEW_MODE
+#ifdef CONFIG_MMC_SUNXI_HAS_MODE_SWITCH
+               val = CCM_MMC_CTRL_MODE_SEL_NEW;
+#endif
+               setbits_le32(&priv->reg->ntsr, SUNXI_MMC_NTSR_MODE_SEL_NEW);
+#endif
+       } else if (!calibrate) {
+               /*
+                * Use hardcoded delay values if controller doesn't support
+                * calibration
+                */
+               val = CCM_MMC_CTRL_OCLK_DLY(oclk_dly) |
+                       CCM_MMC_CTRL_SCLK_DLY(sclk_dly);
+       }
+
+       writel(CCM_MMC_CTRL_ENABLE| pll | CCM_MMC_CTRL_N(n) |
+              CCM_MMC_CTRL_M(div) | val, priv->mclkreg);
 
        debug("mmc %u set mod-clk req %u parent %u n %u m %u rate %u\n",
              priv->mmc_no, hz, pll_hz, 1u << n, div, pll_hz / (1u << n) / div);
@@ -166,15 +213,16 @@ static int mmc_update_clk(struct sunxi_mmc_priv *priv)
 {
        unsigned int cmd;
        unsigned timeout_msecs = 2000;
+       unsigned long start = get_timer(0);
 
        cmd = SUNXI_MMC_CMD_START |
              SUNXI_MMC_CMD_UPCLK_ONLY |
              SUNXI_MMC_CMD_WAIT_PRE_OVER;
+
        writel(cmd, &priv->reg->cmd);
        while (readl(&priv->reg->cmd) & SUNXI_MMC_CMD_START) {
-               if (!timeout_msecs--)
+               if (get_timer(start) > timeout_msecs)
                        return -1;
-               udelay(1000);
        }
 
        /* clock update sets various irq status bits, clear these */
@@ -201,6 +249,16 @@ static int mmc_config_clock(struct sunxi_mmc_priv *priv, struct mmc *mmc)
        rval &= ~SUNXI_MMC_CLK_DIVIDER_MASK;
        writel(rval, &priv->reg->clkcr);
 
+#if defined(CONFIG_MACH_SUN50I) || defined(CONFIG_MACH_SUN50I_H6)
+       /* A64 supports calibration of delays on MMC controller and we
+        * have to set delay of zero before starting calibration.
+        * Allwinner BSP driver sets a delay only in the case of
+        * using HS400 which is not supported by mainline U-Boot or
+        * Linux at the moment
+        */
+       writel(SUNXI_MMC_CAL_DL_SW_EN, &priv->reg->samp_dl);
+#endif
+
        /* Re-enable Clock */
        rval |= SUNXI_MMC_CLK_ENABLE;
        writel(rval, &priv->reg->clkcr);
@@ -255,18 +313,21 @@ static int mmc_trans_data_by_cpu(struct sunxi_mmc_priv *priv, struct mmc *mmc,
        unsigned i;
        unsigned *buff = (unsigned int *)(reading ? data->dest : data->src);
        unsigned byte_cnt = data->blocksize * data->blocks;
-       unsigned timeout_usecs = (byte_cnt >> 8) * 1000;
-       if (timeout_usecs < 2000000)
-               timeout_usecs = 2000000;
+       unsigned timeout_msecs = byte_cnt >> 8;
+       unsigned long  start;
+
+       if (timeout_msecs < 2000)
+               timeout_msecs = 2000;
 
        /* Always read / write data through the CPU */
        setbits_le32(&priv->reg->gctrl, SUNXI_MMC_GCTRL_ACCESS_BY_AHB);
 
+       start = get_timer(0);
+
        for (i = 0; i < (byte_cnt >> 2); i++) {
                while (readl(&priv->reg->status) & status_bit) {
-                       if (!timeout_usecs--)
+                       if (get_timer(start) > timeout_msecs)
                                return -1;
-                       udelay(1);
                }
 
                if (reading)
@@ -282,16 +343,16 @@ static int mmc_rint_wait(struct sunxi_mmc_priv *priv, struct mmc *mmc,
                         uint timeout_msecs, uint done_bit, const char *what)
 {
        unsigned int status;
+       unsigned long start = get_timer(0);
 
        do {
                status = readl(&priv->reg->rint);
-               if (!timeout_msecs-- ||
+               if ((get_timer(start) > timeout_msecs) ||
                    (status & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT)) {
                        debug("%s timeout %x\n", what,
                              status & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT);
                        return -ETIMEDOUT;
                }
-               udelay(1000);
        } while (!(status & done_bit));
 
        return 0;
@@ -383,15 +444,16 @@ static int sunxi_mmc_send_cmd_common(struct sunxi_mmc_priv *priv,
        }
 
        if (cmd->resp_type & MMC_RSP_BUSY) {
+               unsigned long start = get_timer(0);
                timeout_msecs = 2000;
+
                do {
                        status = readl(&priv->reg->status);
-                       if (!timeout_msecs--) {
+                       if (get_timer(start) > timeout_msecs) {
                                debug("busy timeout\n");
                                error = -ETIMEDOUT;
                                goto out;
                        }
-                       udelay(1000);
                } while (status & SUNXI_MMC_STATUS_CARD_DATA_BUSY);
        }
 
@@ -468,7 +530,7 @@ struct mmc *sunxi_mmc_init(int sdc_no)
 
        cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
        cfg->host_caps = MMC_MODE_4BIT;
-#if defined(CONFIG_MACH_SUN50I) || defined(CONFIG_MACH_SUN8I)
+#if defined(CONFIG_MACH_SUN50I) || defined(CONFIG_MACH_SUN8I) || defined(CONFIG_MACH_SUN50I_H6)
        if (sdc_no == 2)
                cfg->host_caps = MMC_MODE_8BIT;
 #endif
@@ -483,6 +545,7 @@ struct mmc *sunxi_mmc_init(int sdc_no)
 
        /* config ahb clock */
        debug("init mmc %d clock and io\n", sdc_no);
+#if !defined(CONFIG_MACH_SUN50I_H6)
        setbits_le32(&ccm->ahb_gate0, 1 << AHB_GATE_OFFSET_MMC(sdc_no));
 
 #ifdef CONFIG_SUNXI_GEN_SUN6I
@@ -493,12 +556,17 @@ struct mmc *sunxi_mmc_init(int sdc_no)
        /* sun9i has a mmc-common module, also set the gate and reset there */
        writel(SUNXI_MMC_COMMON_CLK_GATE | SUNXI_MMC_COMMON_RESET,
               SUNXI_MMC_COMMON_BASE + 4 * sdc_no);
+#endif
+#else /* CONFIG_MACH_SUN50I_H6 */
+       setbits_le32(&ccm->sd_gate_reset, 1 << sdc_no);
+       /* unassert reset */
+       setbits_le32(&ccm->sd_gate_reset, 1 << (RESET_SHIFT + sdc_no));
 #endif
        ret = mmc_set_mod_clk(priv, 24000000);
        if (ret)
                return NULL;
 
-       return mmc_create(cfg, mmc_host);
+       return mmc_create(cfg, priv);
 }
 #else
 
@@ -523,9 +591,11 @@ static int sunxi_mmc_getcd(struct udevice *dev)
 {
        struct sunxi_mmc_priv *priv = dev_get_priv(dev);
 
-       if (dm_gpio_is_valid(&priv->cd_gpio))
-               return dm_gpio_get_value(&priv->cd_gpio);
+       if (dm_gpio_is_valid(&priv->cd_gpio)) {
+               int cd_state = dm_gpio_get_value(&priv->cd_gpio);
 
+               return cd_state ^ priv->cd_inverted;
+       }
        return 1;
 }
 
@@ -540,9 +610,11 @@ static int sunxi_mmc_probe(struct udevice *dev)
        struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
        struct sunxi_mmc_plat *plat = dev_get_platdata(dev);
        struct sunxi_mmc_priv *priv = dev_get_priv(dev);
+       struct reset_ctl_bulk reset_bulk;
+       struct clk gate_clk;
        struct mmc_config *cfg = &plat->cfg;
        struct ofnode_phandle_args args;
-       u32 *gate_reg;
+       u32 *ccu_reg;
        int bus_width, ret;
 
        cfg->name = dev->name;
@@ -561,34 +633,44 @@ static int sunxi_mmc_probe(struct udevice *dev)
        cfg->f_max = 52000000;
 
        priv->reg = (void *)dev_read_addr(dev);
+       priv->variant =
+               (const struct sunxi_mmc_variant *)dev_get_driver_data(dev);
 
        /* We don't have a sunxi clock driver so find the clock address here */
        ret = dev_read_phandle_with_args(dev, "clocks", "#clock-cells", 0,
                                          1, &args);
        if (ret)
                return ret;
-       priv->mclkreg = (u32 *)ofnode_get_addr(args.node);
+       ccu_reg = (u32 *)ofnode_get_addr(args.node);
 
-       ret = dev_read_phandle_with_args(dev, "clocks", "#clock-cells", 0,
-                                         0, &args);
-       if (ret)
-               return ret;
-       gate_reg = (u32 *)ofnode_get_addr(args.node);
-       setbits_le32(gate_reg, 1 << args.args[0]);
-       priv->mmc_no = args.args[0] - 8;
+       priv->mmc_no = ((uintptr_t)priv->reg - SUNXI_MMC0_BASE) / 0x1000;
+       priv->mclkreg = (void *)ccu_reg +
+                       (priv->variant->mclk_offset + (priv->mmc_no * 4));
+
+       ret = clk_get_by_name(dev, "ahb", &gate_clk);
+       if (!ret)
+               clk_enable(&gate_clk);
+
+       ret = reset_get_bulk(dev, &reset_bulk);
+       if (!ret)
+               reset_deassert_bulk(&reset_bulk);
 
        ret = mmc_set_mod_clk(priv, 24000000);
        if (ret)
                return ret;
 
        /* This GPIO is optional */
-       if (!gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio,
+       if (!dev_read_bool(dev, "non-removable") &&
+           !gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio,
                                  GPIOD_IS_IN)) {
                int cd_pin = gpio_get_number(&priv->cd_gpio);
 
                sunxi_gpio_set_pull(cd_pin, SUNXI_GPIO_PULL_UP);
        }
 
+       /* Check if card detect is inverted */
+       priv->cd_inverted = dev_read_bool(dev, "cd-inverted");
+
        upriv->mmc = &plat->mmc;
 
        /* Reset controller */
@@ -605,9 +687,56 @@ static int sunxi_mmc_bind(struct udevice *dev)
        return mmc_bind(dev, &plat->mmc, &plat->cfg);
 }
 
+static const struct sunxi_mmc_variant sun4i_a10_variant = {
+       .mclk_offset = 0x88,
+};
+
+static const struct sunxi_mmc_variant sun9i_a80_variant = {
+       .mclk_offset = 0x410,
+};
+
+static const struct sunxi_mmc_variant sun50i_h6_variant = {
+       .mclk_offset = 0x830,
+};
+
 static const struct udevice_id sunxi_mmc_ids[] = {
-       { .compatible = "allwinner,sun5i-a13-mmc" },
-       { }
+       {
+         .compatible = "allwinner,sun4i-a10-mmc",
+         .data = (ulong)&sun4i_a10_variant,
+       },
+       {
+         .compatible = "allwinner,sun5i-a13-mmc",
+         .data = (ulong)&sun4i_a10_variant,
+       },
+       {
+         .compatible = "allwinner,sun7i-a20-mmc",
+         .data = (ulong)&sun4i_a10_variant,
+       },
+       {
+         .compatible = "allwinner,sun8i-a83t-emmc",
+         .data = (ulong)&sun4i_a10_variant,
+       },
+       {
+         .compatible = "allwinner,sun9i-a80-mmc",
+         .data = (ulong)&sun9i_a80_variant,
+       },
+       {
+         .compatible = "allwinner,sun50i-a64-mmc",
+         .data = (ulong)&sun4i_a10_variant,
+       },
+       {
+         .compatible = "allwinner,sun50i-a64-emmc",
+         .data = (ulong)&sun4i_a10_variant,
+       },
+       {
+         .compatible = "allwinner,sun50i-h6-mmc",
+         .data = (ulong)&sun50i_h6_variant,
+       },
+       {
+         .compatible = "allwinner,sun50i-h6-emmc",
+         .data = (ulong)&sun50i_h6_variant,
+       },
+       { /* sentinel */ }
 };
 
 U_BOOT_DRIVER(sunxi_mmc_drv) = {