diff options
author | Hauke Mehrtens <hauke@hauke-m.de> | 2017-10-01 14:01:28 +0200 |
---|---|---|
committer | Hauke Mehrtens <hauke@hauke-m.de> | 2017-11-23 19:16:42 +0100 |
commit | c048ca0ce03608e0906137aaf5d3a3abc8d16991 (patch) | |
tree | 09b38e917973df27d2d27a3cd262a0e407896e6f /target/linux/sunxi/patches-4.9/090-sunxi-mmc-from-4-13.patch | |
parent | 4e93ec1ad6798695f32d67e9b24f9d7637f85008 (diff) | |
download | upstream-c048ca0ce03608e0906137aaf5d3a3abc8d16991.tar.gz upstream-c048ca0ce03608e0906137aaf5d3a3abc8d16991.tar.bz2 upstream-c048ca0ce03608e0906137aaf5d3a3abc8d16991.zip |
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@hauke-m.de>
Diffstat (limited to 'target/linux/sunxi/patches-4.9/090-sunxi-mmc-from-4-13.patch')
-rw-r--r-- | target/linux/sunxi/patches-4.9/090-sunxi-mmc-from-4-13.patch | 288 |
1 files changed, 288 insertions, 0 deletions
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 0000000000..e64581ff83 --- /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@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); |