|
|
--- 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<EFBFBD>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);
|
|
|
|