OpenWrt – Rev 4

Subversion Repositories:
Rev:
--- a/Documentation/devicetree/bindings/mmc/sunxi-mmc.txt
+++ b/Documentation/devicetree/bindings/mmc/sunxi-mmc.txt
@@ -13,6 +13,7 @@ Required properties:
    * "allwinner,sun5i-a13-mmc"
    * "allwinner,sun7i-a20-mmc"
    * "allwinner,sun9i-a80-mmc"
+   * "allwinner,sun50i-a64-emmc"
    * "allwinner,sun50i-a64-mmc"
  - reg : mmc controller base registers
  - clocks : a list with 4 phandle + clock specifier pairs
--- a/drivers/mmc/host/sunxi-mmc.c
+++ b/drivers/mmc/host/sunxi-mmc.c
@@ -5,6 +5,7 @@
  * (C) Copyright 2013-2014 O2S GmbH <www.o2s.ch>
  * (C) Copyright 2013-2014 David Lanzend�rfer <david.lanzendoerfer@o2s.ch>
  * (C) Copyright 2013-2014 Hans de Goede <hdegoede@redhat.com>
+ * (C) Copyright 2017 Sootech SA
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -101,6 +102,7 @@
        (SDXC_SOFT_RESET | SDXC_FIFO_RESET | SDXC_DMA_RESET)
 
 /* clock control bits */
+#define SDXC_MASK_DATA0                        BIT(31)
 #define SDXC_CARD_CLOCK_ON             BIT(16)
 #define SDXC_LOW_POWER_ON              BIT(17)
 
@@ -253,6 +255,11 @@ struct sunxi_mmc_cfg {
 
        /* does the IP block support autocalibration? */
        bool can_calibrate;
+
+       /* Does DATA0 needs to be masked while the clock is updated */
+       bool mask_data0;
+
+       bool needs_new_timings;
 };
 
 struct sunxi_mmc_host {
@@ -482,7 +489,7 @@ static void sunxi_mmc_dump_errinfo(struc
                                      cmd->opcode == SD_IO_RW_DIRECT))
                return;
 
-       dev_err(mmc_dev(host->mmc),
+       dev_dbg(mmc_dev(host->mmc),
                "smc %d err, cmd %d,%s%s%s%s%s%s%s%s%s%s !!\n",
                host->mmc->index, cmd->opcode,
                data ? (data->flags & MMC_DATA_WRITE ? " WR" : " RD") : "",
@@ -654,11 +661,16 @@ static int sunxi_mmc_oclk_onoff(struct s
        unsigned long expire = jiffies + msecs_to_jiffies(750);
        u32 rval;
 
+       dev_dbg(mmc_dev(host->mmc), "%sabling the clock\n",
+               oclk_en ? "en" : "dis");
+
        rval = mmc_readl(host, REG_CLKCR);
-       rval &= ~(SDXC_CARD_CLOCK_ON | SDXC_LOW_POWER_ON);
+       rval &= ~(SDXC_CARD_CLOCK_ON | SDXC_LOW_POWER_ON | SDXC_MASK_DATA0);
 
        if (oclk_en)
                rval |= SDXC_CARD_CLOCK_ON;
+       if (host->cfg->mask_data0)
+               rval |= SDXC_MASK_DATA0;
 
        mmc_writel(host, REG_CLKCR, rval);
 
@@ -678,46 +690,29 @@ static int sunxi_mmc_oclk_onoff(struct s
                return -EIO;
        }
 
+       if (host->cfg->mask_data0) {
+               rval = mmc_readl(host, REG_CLKCR);
+               mmc_writel(host, REG_CLKCR, rval & ~SDXC_MASK_DATA0);
+       }
+
        return 0;
 }
 
 static int sunxi_mmc_calibrate(struct sunxi_mmc_host *host, int reg_off)
 {
-       u32 reg = readl(host->reg_base + reg_off);
-       u32 delay;
-       unsigned long timeout;
-
        if (!host->cfg->can_calibrate)
                return 0;
 
-       reg &= ~(SDXC_CAL_DL_MASK << SDXC_CAL_DL_SW_SHIFT);
-       reg &= ~SDXC_CAL_DL_SW_EN;
-
-       writel(reg | SDXC_CAL_START, host->reg_base + reg_off);
-
-       dev_dbg(mmc_dev(host->mmc), "calibration started\n");
-
-       timeout = jiffies + HZ * SDXC_CAL_TIMEOUT;
-
-       while (!((reg = readl(host->reg_base + reg_off)) & SDXC_CAL_DONE)) {
-               if (time_before(jiffies, timeout))
-                       cpu_relax();
-               else {
-                       reg &= ~SDXC_CAL_START;
-                       writel(reg, host->reg_base + reg_off);
-
-                       return -ETIMEDOUT;
-               }
-       }
-
-       delay = (reg >> SDXC_CAL_DL_SHIFT) & SDXC_CAL_DL_MASK;
-
-       reg &= ~SDXC_CAL_START;
-       reg |= (delay << SDXC_CAL_DL_SW_SHIFT) | SDXC_CAL_DL_SW_EN;
-
-       writel(reg, host->reg_base + reg_off);
-
-       dev_dbg(mmc_dev(host->mmc), "calibration ended, reg is 0x%x\n", reg);
+       /*
+        * FIXME:
+        * This is not clear how the calibration is supposed to work
+        * yet. The best rate have been obtained by simply setting the
+        * delay to 0, as Allwinner does in its BSP.
+        *
+        * The only mode that doesn't have such a delay is HS400, that
+        * is in itself a TODO.
+        */
+       writel(SDXC_CAL_DL_SW_EN, host->reg_base + reg_off);
 
        return 0;
 }
@@ -745,6 +740,7 @@ static int sunxi_mmc_clk_set_phase(struc
                        index = SDXC_CLK_50M_DDR;
                }
        } else {
+               dev_dbg(mmc_dev(host->mmc), "Invalid clock... returning\n");
                return -EINVAL;
        }
 
@@ -757,10 +753,21 @@ static int sunxi_mmc_clk_set_phase(struc
 static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host,
                                  struct mmc_ios *ios)
 {
+       struct mmc_host *mmc = host->mmc;
        long rate;
        u32 rval, clock = ios->clock;
        int ret;
 
+       ret = sunxi_mmc_oclk_onoff(host, 0);
+       if (ret)
+               return ret;
+
+       /* Our clock is gated now */
+       mmc->actual_clock = 0;
+
+       if (!ios->clock)
+               return 0;
+
        /* 8 bit DDR requires a higher module clock */
        if (ios->timing == MMC_TIMING_MMC_DDR52 &&
            ios->bus_width == MMC_BUS_WIDTH_8)
@@ -768,25 +775,21 @@ static int sunxi_mmc_clk_set_rate(struct
 
        rate = clk_round_rate(host->clk_mmc, clock);
        if (rate < 0) {
-               dev_err(mmc_dev(host->mmc), "error rounding clk to %d: %ld\n",
+               dev_err(mmc_dev(mmc), "error rounding clk to %d: %ld\n",
                        clock, rate);
                return rate;
        }
-       dev_dbg(mmc_dev(host->mmc), "setting clk to %d, rounded %ld\n",
+       dev_dbg(mmc_dev(mmc), "setting clk to %d, rounded %ld\n",
                clock, rate);
 
        /* setting clock rate */
        ret = clk_set_rate(host->clk_mmc, rate);
        if (ret) {
-               dev_err(mmc_dev(host->mmc), "error setting clk to %ld: %d\n",
+               dev_err(mmc_dev(mmc), "error setting clk to %ld: %d\n",
                        rate, ret);
                return ret;
        }
 
-       ret = sunxi_mmc_oclk_onoff(host, 0);
-       if (ret)
-               return ret;
-
        /* clear internal divider */
        rval = mmc_readl(host, REG_CLKCR);
        rval &= ~0xff;
@@ -798,6 +801,13 @@ static int sunxi_mmc_clk_set_rate(struct
        }
        mmc_writel(host, REG_CLKCR, rval);
 
+       if (host->cfg->needs_new_timings) {
+               /* Don't touch the delay bits */
+               rval = mmc_readl(host, REG_SD_NTSR);
+               rval |= SDXC_2X_TIMING_MODE;
+               mmc_writel(host, REG_SD_NTSR, rval);
+       }
+
        ret = sunxi_mmc_clk_set_phase(host, ios, rate);
        if (ret)
                return ret;
@@ -806,9 +816,22 @@ static int sunxi_mmc_clk_set_rate(struct
        if (ret)
                return ret;
 
-       /* TODO: enable calibrate on sdc2 SDXC_REG_DS_DL_REG of A64 */
+       /*
+        * FIXME:
+        *
+        * In HS400 we'll also need to calibrate the data strobe
+        * signal. This should only happen on the MMC2 controller (at
+        * least on the A64).
+        */
+
+       ret = sunxi_mmc_oclk_onoff(host, 1);
+       if (ret)
+               return ret;
 
-       return sunxi_mmc_oclk_onoff(host, 1);
+       /* And we just enabled our clock back */
+       mmc->actual_clock = rate;
+
+       return 0;
 }
 
 static void sunxi_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
@@ -822,10 +845,13 @@ static void sunxi_mmc_set_ios(struct mmc
                break;
 
        case MMC_POWER_UP:
-               host->ferror = mmc_regulator_set_ocr(mmc, mmc->supply.vmmc,
-                                                    ios->vdd);
-               if (host->ferror)
-                       return;
+               if (!IS_ERR(mmc->supply.vmmc)) {
+                       host->ferror = mmc_regulator_set_ocr(mmc,
+                                                            mmc->supply.vmmc,
+                                                            ios->vdd);
+                       if (host->ferror)
+                               return;
+               }
 
                if (!IS_ERR(mmc->supply.vqmmc)) {
                        host->ferror = regulator_enable(mmc->supply.vqmmc);
@@ -847,7 +873,9 @@ static void sunxi_mmc_set_ios(struct mmc
        case MMC_POWER_OFF:
                dev_dbg(mmc_dev(mmc), "power off!\n");
                sunxi_mmc_reset_host(host);
-               mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
+               if (!IS_ERR(mmc->supply.vmmc))
+                       mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
+
                if (!IS_ERR(mmc->supply.vqmmc) && host->vqmmc_enabled)
                        regulator_disable(mmc->supply.vqmmc);
                host->vqmmc_enabled = false;
@@ -877,7 +905,7 @@ static void sunxi_mmc_set_ios(struct mmc
        mmc_writel(host, REG_GCTRL, rval);
 
        /* set up clock */
-       if (ios->clock && ios->power_mode) {
+       if (ios->power_mode) {
                host->ferror = sunxi_mmc_clk_set_rate(host, ios);
                /* Android code had a usleep_range(50000, 55000); here */
        }
@@ -1084,6 +1112,14 @@ static const struct sunxi_mmc_cfg sun50i
        .idma_des_size_bits = 16,
        .clk_delays = NULL,
        .can_calibrate = true,
+       .mask_data0 = true,
+       .needs_new_timings = true,
+};
+
+static const struct sunxi_mmc_cfg sun50i_a64_emmc_cfg = {
+       .idma_des_size_bits = 13,
+       .clk_delays = NULL,
+       .can_calibrate = true,
 };
 
 static const struct of_device_id sunxi_mmc_of_match[] = {
@@ -1092,6 +1128,7 @@ static const struct of_device_id sunxi_m
        { .compatible = "allwinner,sun7i-a20-mmc", .data = &sun7i_a20_cfg },
        { .compatible = "allwinner,sun9i-a80-mmc", .data = &sun9i_a80_cfg },
        { .compatible = "allwinner,sun50i-a64-mmc", .data = &sun50i_a64_cfg },
+       { .compatible = "allwinner,sun50i-a64-emmc", .data = &sun50i_a64_emmc_cfg },
        { /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, sunxi_mmc_of_match);