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>
This commit is contained in:
		
							
								
								
									
										288
									
								
								target/linux/sunxi/patches-4.9/090-sunxi-mmc-from-4-13.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								target/linux/sunxi/patches-4.9/090-sunxi-mmc-from-4-13.patch
									
									
									
									
									
										Normal file
									
								
							| @@ -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<6E>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); | ||||
		Reference in New Issue
	
	Block a user
	 Hauke Mehrtens
					Hauke Mehrtens