[source] sunxi: backport sunxi-mmc controller driver from 4.13

LEDE Commits lede-commits at lists.infradead.org
Thu Nov 23 12:48:23 PST 2017


hauke pushed a commit to source.git, branch master:
https://git.lede-project.org/c048ca0ce03608e0906137aaf5d3a3abc8d16991

commit c048ca0ce03608e0906137aaf5d3a3abc8d16991
Author: Hauke Mehrtens <hauke at hauke-m.de>
AuthorDate: Sun Oct 1 14:01:28 2017 +0200

    sunxi: backport sunxi-mmc controller driver from 4.13
    
    There are multiple problems on the A64 SoC with the older drivers which
    are fixed in the upstream kernel.
    
    Signed-off-by: Hauke Mehrtens <hauke at hauke-m.de>
---
 .../patches-4.9/090-sunxi-mmc-from-4-13.patch      | 288 +++++++++++++++++++++
 1 file changed, 288 insertions(+)

diff --git a/target/linux/sunxi/patches-4.9/090-sunxi-mmc-from-4-13.patch b/target/linux/sunxi/patches-4.9/090-sunxi-mmc-from-4-13.patch
new file mode 100644
index 0000000..e64581f
--- /dev/null
+++ b/target/linux/sunxi/patches-4.9/090-sunxi-mmc-from-4-13.patch
@@ -0,0 +1,288 @@
+--- 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 at o2s.ch>
+  * (C) Copyright 2013-2014 Hans de Goede <hdegoede at 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);



More information about the lede-commits mailing list