diff mbox series

[1/2] xenon_sdhci: support for HS200 mode

Message ID 20200831032538.467336-1-a.heider@gmail.com
State Rejected
Delegated to: Stefan Roese
Headers show
Series [1/2] xenon_sdhci: support for HS200 mode | expand

Commit Message

Andre Heider Aug. 31, 2020, 3:25 a.m. UTC
From: Wojciech Macek <wma@semihalf.com>

Add support for Marvell Xenon SDHCI HS200 mode.

Changes focue mostly on correct PHY initialization.
All procedure is similar to the one done by Linux
driver, but simplified.

Change-Id: I5e2396eeb23784f495abc18ea5a2eb7a92390730
Signed-off-by: Wojciech Macek <wma@semihalf.com>
Reviewed-on: http://vgitil04.il.marvell.com:8080/59230
Tested-by: iSoC Platform CI <ykjenk@marvell.com>
Reviewed-by: Grzegorz Jaszczyk <jaz@semihalf.com>
Reviewed-by: Kostya Porotchkin <kostap@marvell.com>
Reviewed-by: Igal Liberman <igall@marvell.com>
[a.heider: adapt to mainline]
Signed-off-by: Andre Heider <a.heider@gmail.com>
---
Missing downstream patch, noticed while diffing branches:
https://github.com/MarvellEmbeddedProcessors/u-boot-marvell/commit/387232507a0d9dda3990284221eaf87d7541dd02

 drivers/mmc/xenon_sdhci.c | 335 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 323 insertions(+), 12 deletions(-)

Comments

Pali Rohár Aug. 31, 2020, 7:50 a.m. UTC | #1
On Monday 31 August 2020 05:25:37 Andre Heider wrote:
> From: Wojciech Macek <wma@semihalf.com>
> 
> Add support for Marvell Xenon SDHCI HS200 mode.
> 
> Changes focue mostly on correct PHY initialization.
> All procedure is similar to the one done by Linux
> driver, but simplified.
> 
> Change-Id: I5e2396eeb23784f495abc18ea5a2eb7a92390730
> Signed-off-by: Wojciech Macek <wma@semihalf.com>
> Reviewed-on: http://vgitil04.il.marvell.com:8080/59230
> Tested-by: iSoC Platform CI <ykjenk@marvell.com>
> Reviewed-by: Grzegorz Jaszczyk <jaz@semihalf.com>
> Reviewed-by: Kostya Porotchkin <kostap@marvell.com>
> Reviewed-by: Igal Liberman <igall@marvell.com>
> [a.heider: adapt to mainline]
> Signed-off-by: Andre Heider <a.heider@gmail.com>
> ---
> Missing downstream patch, noticed while diffing branches:
> https://github.com/MarvellEmbeddedProcessors/u-boot-marvell/commit/387232507a0d9dda3990284221eaf87d7541dd02

Hello Andre! Why is this patch needed? Or what it is fixing?
I tested upstream U-Boot with all previous patches on Turris MOX and
Espressobin and SD cards worked fine.

>  drivers/mmc/xenon_sdhci.c | 335 ++++++++++++++++++++++++++++++++++++--
>  1 file changed, 323 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/mmc/xenon_sdhci.c b/drivers/mmc/xenon_sdhci.c
> index 7f9a579c83..ec255d30b0 100644
> --- a/drivers/mmc/xenon_sdhci.c
> +++ b/drivers/mmc/xenon_sdhci.c
> @@ -16,6 +16,7 @@
>  
>  #include <common.h>
>  #include <dm.h>
> +#include <dm/device_compat.h>
>  #include <fdtdec.h>
>  #include <linux/bitops.h>
>  #include <linux/delay.h>
> @@ -45,6 +46,7 @@ DECLARE_GLOBAL_DATA_PTR;
>  
>  #define SDHC_SLOT_EMMC_CTRL			0x0130
>  #define ENABLE_DATA_STROBE_SHIFT		24
> +#define ENABLE_DATA_STROBE			BIT(ENABLE_DATA_STROBE_SHIFT)
>  #define SET_EMMC_RSTN_SHIFT			16
>  #define EMMC_VCCQ_MASK				0x3
>  #define EMMC_VCCQ_1_8V				0x1
> @@ -64,6 +66,7 @@ DECLARE_GLOBAL_DATA_PTR;
>  #define OUTPUT_QSN_PHASE_SELECT			BIT(17)
>  #define SAMPL_INV_QSP_PHASE_SELECT		BIT(18)
>  #define SAMPL_INV_QSP_PHASE_SELECT_SHIFT	18
> +#define EMMC_PHY_SDIO_MODE			BIT(28)
>  #define EMMC_PHY_SLOW_MODE			BIT(29)
>  #define PHY_INITIALIZAION			BIT(31)
>  #define WAIT_CYCLE_BEFORE_USING_MASK		0xf
> @@ -90,6 +93,7 @@ DECLARE_GLOBAL_DATA_PTR;
>  #define FC_QSN_RECEN				BIT(27)
>  #define OEN_QSN					BIT(28)
>  #define AUTO_RECEN_CTRL				BIT(30)
> +#define FC_ALL_CMOS_RECEIVER			(REC_EN_MASK << REC_EN_SHIFT)
>  
>  #define EMMC_PHY_PAD_CONTROL1			(EMMC_PHY_REG_BASE + 0xc)
>  #define EMMC5_1_FC_QSP_PD			BIT(9)
> @@ -99,10 +103,71 @@ DECLARE_GLOBAL_DATA_PTR;
>  #define EMMC5_1_FC_DQ_PD			0xff
>  #define EMMC5_1_FC_DQ_PU			(0xff << 16)
>  
> +#define EMMC_PHY_PAD_CONTROL2			(EMMC_PHY_REG_BASE + 0x10)
> +#define ZNR_MASK				0x1F
> +#define ZNR_SHIFT				8
> +#define ZPR_MASK				0x1F
> +
>  #define SDHCI_RETUNE_EVT_INTSIG			0x00001000
>  
> +#define SDHCI_HOST_CONTROL2		0x3E
> +#define  SDHCI_CTRL_UHS_MASK		0x0007
> +#define   SDHCI_CTRL_UHS_SDR12		0x0000
> +#define   SDHCI_CTRL_UHS_SDR25		0x0001
> +#define   SDHCI_CTRL_UHS_SDR50		0x0002
> +#define   SDHCI_CTRL_UHS_SDR104		0x0003
> +#define   SDHCI_CTRL_UHS_DDR50		0x0004
> +#define   SDHCI_CTRL_HS400		0x0005 /* Non-standard */
> +#define   SDHCI_CTRL_HS200_ONLY		0x0005 /* Non-standard */
> +#define   SDHCI_CTRL_HS400_ONLY		0x0006 /* Non-standard */
> +#define  SDHCI_CTRL_VDD_180		0x0008
> +#define  SDHCI_CTRL_DRV_TYPE_MASK	0x0030
> +#define   SDHCI_CTRL_DRV_TYPE_B		0x0000
> +#define   SDHCI_CTRL_DRV_TYPE_A		0x0010
> +#define   SDHCI_CTRL_DRV_TYPE_C		0x0020
> +#define   SDHCI_CTRL_DRV_TYPE_D		0x0030
> +#define  SDHCI_CTRL_EXEC_TUNING		0x0040
> +#define  SDHCI_CTRL_TUNED_CLK		0x0080
> +#define  SDHCI_CTRL_PRESET_VAL_ENABLE	0x8000
> +
> +/*
> + * Config to eMMC PHY to prepare for tuning.
> + * Enable HW DLL and set the TUNING_STEP
> + */
> +#define XENON_SLOT_DLL_CUR_DLY_VAL		0x0150
> +
> +#define XENON_SLOT_OP_STATUS_CTRL		0x0128
> +#define XENON_TUN_CONSECUTIVE_TIMES_SHIFT	16
> +#define XENON_TUN_CONSECUTIVE_TIMES_MASK	0x7
> +#define XENON_TUN_CONSECUTIVE_TIMES		0x4
> +#define XENON_TUNING_STEP_SHIFT			12
> +#define XENON_TUNING_STEP_MASK			0xF
> +#define XENON_TUNING_STEP_DIVIDER		BIT(6)
> +
> +#define XENON_EMMC_PHY_DLL_CONTROL		(EMMC_PHY_REG_BASE + 0x14)
> +#define XENON_EMMC_5_0_PHY_DLL_CONTROL		\
> +	(XENON_EMMC_5_0_PHY_REG_BASE + 0x10)
> +#define XENON_DLL_ENABLE			BIT(31)
> +#define XENON_DLL_UPDATE_STROBE_5_0		BIT(30)
> +#define XENON_DLL_REFCLK_SEL			BIT(30)
> +#define XENON_DLL_UPDATE			BIT(23)
> +#define XENON_DLL_PHSEL1_SHIFT			24
> +#define XENON_DLL_PHSEL0_SHIFT			16
> +#define XENON_DLL_PHASE_MASK			0x3F
> +#define XENON_DLL_PHASE_90_DEGREE		0x1F
> +#define XENON_DLL_FAST_LOCK			BIT(5)
> +#define XENON_DLL_GAIN2X			BIT(3)
> +#define XENON_DLL_BYPASS_EN			BIT(0)
> +
> +#define XENON_SLOT_EXT_PRESENT_STATE		0x014C
> +#define XENON_DLL_LOCK_STATE			0x1
> +
>  /* Hyperion only have one slot 0 */
>  #define XENON_MMC_SLOT_ID_HYPERION		0
> +#define SLOT_MASK(slot)				BIT(slot)
> +
> +#define XENON_EMMC_PHY_LOGIC_TIMING_ADJUST	(EMMC_PHY_REG_BASE + 0x18)
> +#define XENON_LOGIC_TIMING_VALUE		0x00AA8977
>  
>  #define MMC_TIMING_LEGACY	0
>  #define MMC_TIMING_MMC_HS	1
> @@ -266,6 +331,176 @@ static int xenon_mmc_start_signal_voltage_switch(struct sdhci_host *host)
>  	return ret;
>  }
>  
> +/*
> + * Xenon defines different values for HS200 and HS400
> + * in Host_Control_2
> + */
> +static void xenon_set_uhs_signaling(struct sdhci_host *host,
> +				    unsigned int timing)
> +{
> +	u16 ctrl_2;
> +
> +	ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +	/* Select Bus Speed Mode for host */
> +	ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
> +	if (timing == MMC_TIMING_MMC_HS200)
> +		ctrl_2 |= SDHCI_CTRL_HS200_ONLY;
> +	else if (timing == MMC_TIMING_UHS_SDR104)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
> +	else if (timing == MMC_TIMING_UHS_SDR12)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
> +	else if (timing == MMC_TIMING_UHS_SDR25)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
> +	else if (timing == MMC_TIMING_UHS_SDR50)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
> +	else if ((timing == MMC_TIMING_UHS_DDR50) ||
> +		 (timing == MMC_TIMING_MMC_DDR52))
> +		ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
> +	else if (timing == MMC_TIMING_MMC_HS400)
> +		ctrl_2 |= SDHCI_CTRL_HS400_ONLY;
> +	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> +}
> +
> +/*
> + * If eMMC PHY Slow Mode is required in lower speed mode (SDCLK < 55MHz)
> + * in SDR mode, enable Slow Mode to bypass eMMC PHY.
> + * SDIO slower SDR mode also requires Slow Mode.
> + *
> + * If Slow Mode is enabled, return 0.
> + * Otherwise, return -EINVAL.
> + */
> +static int xenon_emmc_phy_slow_mode(struct sdhci_host *host,
> +				    unsigned char timing)
> +{
> +	u32 reg;
> +	int ret = -EINVAL;
> +
> +	if (host->mmc->tran_speed > 52000000)
> +		return -EINVAL;
> +
> +	reg = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST);
> +	/* When in slower SDR mode, enable Slow Mode for SDIO */
> +	switch (timing) {
> +	case MMC_TIMING_LEGACY:
> +		/*
> +		 * If Slow Mode is required, enable Slow Mode by default
> +		 * in early init phase to avoid any potential issue.
> +		 */
> +		reg |= EMMC_PHY_SLOW_MODE;
> +		ret = 0;
> +		break;
> +	case MMC_TIMING_UHS_SDR25:
> +	case MMC_TIMING_UHS_SDR12:
> +	case MMC_TIMING_SD_HS:
> +	case MMC_TIMING_MMC_HS:
> +		if (IS_SD(host->mmc)) {
> +			reg |= EMMC_PHY_SLOW_MODE;
> +			ret = 0;
> +			break;
> +		}
> +	default:
> +		reg &= ~EMMC_PHY_SLOW_MODE;
> +		ret = -EINVAL;
> +	}
> +
> +	sdhci_writel(host, reg, EMMC_PHY_TIMING_ADJUST);
> +	return ret;
> +}
> +
> +static void xenon_emmc_phy_disable_data_strobe(struct sdhci_host *host)
> +{
> +	u32 reg;
> +
> +	/* Disable SDHC Data Strobe */
> +	reg = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
> +	reg &= ~ENABLE_DATA_STROBE;
> +	sdhci_writel(host, reg, SDHC_SLOT_EMMC_CTRL);
> +}
> +
> +/*
> + * Enable eMMC PHY HW DLL
> + * DLL should be enabled and stable before HS200/SDR104 tuning,
> + * and before HS400 data strobe setting.
> + */
> +static int xenon_emmc_phy_enable_dll(struct sdhci_host *host)
> +{
> +	u32 reg;
> +	u32 timeout;
> +
> +	if (host->mmc->tran_speed <= 52000000)
> +		return -EINVAL;
> +
> +	reg = sdhci_readl(host, XENON_EMMC_PHY_DLL_CONTROL);
> +	if (reg & XENON_DLL_ENABLE)
> +		return 0;
> +
> +	/* Enable DLL */
> +	reg = sdhci_readl(host, XENON_EMMC_PHY_DLL_CONTROL);
> +	reg |= (XENON_DLL_ENABLE | XENON_DLL_FAST_LOCK);
> +
> +	/*
> +	 * Set Phase as 90 degree, which is most common value.
> +	 * Might set another value if necessary.
> +	 * The granularity is 1 degree.
> +	 */
> +	reg &= ~((XENON_DLL_PHASE_MASK << XENON_DLL_PHSEL0_SHIFT) |
> +		 (XENON_DLL_PHASE_MASK << XENON_DLL_PHSEL1_SHIFT));
> +	reg |= ((XENON_DLL_PHASE_90_DEGREE << XENON_DLL_PHSEL0_SHIFT) |
> +		(XENON_DLL_PHASE_90_DEGREE << XENON_DLL_PHSEL1_SHIFT));
> +
> +	reg &= ~(XENON_DLL_BYPASS_EN | XENON_DLL_REFCLK_SEL);
> +	reg |= XENON_DLL_UPDATE;
> +	sdhci_writel(host, reg, XENON_EMMC_PHY_DLL_CONTROL);
> +
> +	/* Wait max 32 ms */
> +	timeout = 32;
> +	while (!(sdhci_readw(host, XENON_SLOT_EXT_PRESENT_STATE) &
> +		XENON_DLL_LOCK_STATE)) {
> +		if (timeout > 32) {
> +			printf("Wait for DLL Lock time-out\n");
> +			return -ETIMEDOUT;
> +		}
> +		udelay(1000);
> +		timeout++;
> +	}
> +	return 0;
> +}
> +
> +static int xenon_emmc_phy_config_tuning(struct sdhci_host *host)
> +{
> +	u32 reg, tuning_step;
> +	int ret;
> +
> +	if (host->mmc->tran_speed <= 52000000)
> +		return -EINVAL;
> +
> +	ret = xenon_emmc_phy_enable_dll(host);
> +	if (ret)
> +		return ret;
> +
> +	/* Achieve TUNING_STEP with HW DLL help */
> +	reg = sdhci_readl(host, XENON_SLOT_DLL_CUR_DLY_VAL);
> +	tuning_step = reg / XENON_TUNING_STEP_DIVIDER;
> +	if (unlikely(tuning_step > XENON_TUNING_STEP_MASK)) {
> +		dev_warn(mmc_dev(host->mmc),
> +			 "HS200 TUNING_STEP %d is larger than MAX value\n",
> +			 tuning_step);
> +		tuning_step = XENON_TUNING_STEP_MASK;
> +	}
> +
> +	/* Set TUNING_STEP for later tuning */
> +	reg = sdhci_readl(host, XENON_SLOT_OP_STATUS_CTRL);
> +	reg &= ~(XENON_TUN_CONSECUTIVE_TIMES_MASK <<
> +		 XENON_TUN_CONSECUTIVE_TIMES_SHIFT);
> +	reg |= (XENON_TUN_CONSECUTIVE_TIMES <<
> +		XENON_TUN_CONSECUTIVE_TIMES_SHIFT);
> +	reg &= ~(XENON_TUNING_STEP_MASK << XENON_TUNING_STEP_SHIFT);
> +	reg |= (tuning_step << XENON_TUNING_STEP_SHIFT);
> +	sdhci_writel(host, reg, XENON_SLOT_OP_STATUS_CTRL);
> +
> +	return 0;
> +}
> +
>  static void xenon_mmc_phy_set(struct sdhci_host *host)
>  {
>  	struct xenon_sdhci_priv *priv = host->mmc->priv;
> @@ -273,8 +508,8 @@ static void xenon_mmc_phy_set(struct sdhci_host *host)
>  
>  	/* Setup pad, set bit[30], bit[28] and bits[26:24] */
>  	var = sdhci_readl(host, EMMC_PHY_PAD_CONTROL);
> -	var |= AUTO_RECEN_CTRL | OEN_QSN | FC_QSP_RECEN |
> -		FC_CMD_RECEN | FC_DQ_RECEN;
> +	var |= OEN_QSN | FC_QSP_RECEN | FC_CMD_RECEN | FC_DQ_RECEN |
> +		FC_ALL_CMOS_RECEIVER;
>  	sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL);
>  
>  	/* Set CMD and DQ Pull Up */
> @@ -284,20 +519,45 @@ static void xenon_mmc_phy_set(struct sdhci_host *host)
>  	sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL1);
>  
>  	/*
> -	 * If timing belongs to high speed, set bit[17] of
> +	 * If Timing belongs to high speed, clear bit[17] of
>  	 * EMMC_PHY_TIMING_ADJUST register
>  	 */
> +	var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST);
>  	if ((priv->timing == MMC_TIMING_MMC_HS400) ||
>  	    (priv->timing == MMC_TIMING_MMC_HS200) ||
> +	    (priv->timing == MMC_TIMING_MMC_DDR52) ||
>  	    (priv->timing == MMC_TIMING_UHS_SDR50) ||
>  	    (priv->timing == MMC_TIMING_UHS_SDR104) ||
>  	    (priv->timing == MMC_TIMING_UHS_DDR50) ||
> -	    (priv->timing == MMC_TIMING_UHS_SDR25) ||
> -	    (priv->timing == MMC_TIMING_MMC_DDR52)) {
> -		var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST);
> -		var |= OUTPUT_QSN_PHASE_SELECT;
> +	    (priv->timing == MMC_TIMING_UHS_SDR25)) {
> +		var &= ~OUTPUT_QSN_PHASE_SELECT;
>  		sdhci_writel(host, var, EMMC_PHY_TIMING_ADJUST);
>  	}
> +	if (priv->timing == MMC_TIMING_LEGACY) {
> +		xenon_emmc_phy_slow_mode(host, priv->timing);
> +		goto phy_init;
> +	}
> +
> +	/*
> +	 * If SDIO card, set SDIO Mode
> +	 * Otherwise, clear SDIO Mode
> +	 */
> +	var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST);
> +	if (IS_SD(host->mmc))
> +		var |= EMMC_PHY_SDIO_MODE;
> +	else
> +		var &= ~EMMC_PHY_SDIO_MODE;
> +	sdhci_writel(host, var, EMMC_PHY_TIMING_ADJUST);
> +
> +	/*
> +	 * Set preferred ZNR and ZPR value
> +	 * The ZNR and ZPR value vary between different boards.
> +	 * Define them both in sdhci-xenon-emmc-phy.h.
> +	 */
> +	var = sdhci_readl(host, EMMC_PHY_PAD_CONTROL2);
> +	var &= ~((ZNR_MASK << ZNR_SHIFT) | ZPR_MASK);
> +	var |= ((0xf << ZNR_SHIFT) | 0xf);
> +	sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL2);
>  
>  	/*
>  	 * When setting EMMC_PHY_FUNC_CONTROL register,
> @@ -308,11 +568,21 @@ static void xenon_mmc_phy_set(struct sdhci_host *host)
>  	sdhci_writew(host, var, SDHCI_CLOCK_CONTROL);
>  
>  	var = sdhci_readl(host, EMMC_PHY_FUNC_CONTROL);
> -	if (host->mmc->ddr_mode) {
> -		var |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE;
> -	} else {
> +	switch (priv->timing) {
> +	case MMC_TIMING_MMC_HS400:
> +		var |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) |
> +		       CMD_DDR_MODE;
> +		var &= ~DQ_ASYNC_MODE;
> +		break;
> +	case MMC_TIMING_UHS_DDR50:
> +	case MMC_TIMING_MMC_DDR52:
> +		var |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) |
> +		       CMD_DDR_MODE | DQ_ASYNC_MODE;
> +		break;
> +	default:
>  		var &= ~((DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) |
>  			 CMD_DDR_MODE);
> +		var |= DQ_ASYNC_MODE;
>  	}
>  	sdhci_writel(host, var, EMMC_PHY_FUNC_CONTROL);
>  
> @@ -321,7 +591,26 @@ static void xenon_mmc_phy_set(struct sdhci_host *host)
>  	var |= SDHCI_CLOCK_CARD_EN;
>  	sdhci_writew(host, var, SDHCI_CLOCK_CONTROL);
>  
> +	udelay(1000);
> +
> +	/* Quirk, value suggested by hardware team */
> +	if (priv->timing == MMC_TIMING_MMC_HS400)
> +		/* Hardware team recommend a value for HS400 */
> +		sdhci_writel(host, XENON_EMMC_PHY_LOGIC_TIMING_ADJUST,
> +			     XENON_LOGIC_TIMING_VALUE);
> +	else
> +		xenon_emmc_phy_disable_data_strobe(host);
> +
> +phy_init:
> +
> +	xenon_set_uhs_signaling(host, priv->timing);
>  	xenon_mmc_phy_init(host);
> +
> +	if ((priv->timing == MMC_TIMING_MMC_HS400) ||
> +	    (priv->timing == MMC_TIMING_MMC_HS200)) {
> +		if (xenon_emmc_phy_config_tuning(host) != 0)
> +			printf("Error, failed to tune MMC PHY\n");
> +	}
>  }
>  
>  /* Enable/Disable the Auto Clock Gating function of this slot */
> @@ -338,8 +627,6 @@ static void xenon_mmc_set_acg(struct sdhci_host *host, bool enable)
>  	sdhci_writel(host, var, SDHC_SYS_OP_CTRL);
>  }
>  
> -#define SLOT_MASK(slot)		BIT(slot)
> -
>  /* Enable specific slot */
>  static void xenon_mmc_enable_slot(struct sdhci_host *host, u8 slot)
>  {
> @@ -391,6 +678,7 @@ static int xenon_sdhci_set_ios_post(struct sdhci_host *host)
>  	struct xenon_sdhci_priv *priv = host->mmc->priv;
>  	uint speed = host->mmc->tran_speed;
>  	int pwr_18v = 0;
> +	u32 reg;
>  
>  	/*
>  	 * Signal Voltage Switching is only applicable for Host Controllers
> @@ -423,12 +711,22 @@ static int xenon_sdhci_set_ios_post(struct sdhci_host *host)
>  		/* eMMC */
>  		if (host->mmc->ddr_mode)
>  			priv->timing = MMC_TIMING_MMC_DDR52;
> +		else if (speed == 200000000)
> +			priv->timing = MMC_TIMING_MMC_HS200;
>  		else if (speed <= 26000000)
>  			priv->timing = MMC_TIMING_LEGACY;
>  		else
>  			priv->timing = MMC_TIMING_MMC_HS;
>  	}
>  
> +	if ((priv->timing == MMC_TIMING_MMC_HS400) ||
> +	    (priv->timing == MMC_TIMING_MMC_HS200) ||
> +	    (priv->timing == MMC_TIMING_MMC_HS)) {
> +		reg = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +		reg &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
> +		sdhci_writew(host, reg, SDHCI_HOST_CONTROL2);
> +	}
> +
>  	/* Re-init the PHY */
>  	xenon_mmc_phy_set(host);
>  
> @@ -447,6 +745,7 @@ static int xenon_sdhci_probe(struct udevice *dev)
>  	struct xenon_sdhci_priv *priv = dev_get_priv(dev);
>  	struct sdhci_host *host = dev_get_priv(dev);
>  	int ret;
> +	int len;
>  
>  	host->mmc = &plat->mmc;
>  	host->mmc->priv = host;
> @@ -500,6 +799,18 @@ static int xenon_sdhci_probe(struct udevice *dev)
>  		return -EINVAL;
>  	}
>  
> +	/* Support for High Speed modes */
> +	if (fdt_getprop(gd->fdt_blob,
> +			dev_of_offset(dev), "mmc-hs400-1_8v", &len) != NULL) {
> +		host->host_caps |= (MMC_MODE_HS400 | MMC_MODE_HS200);
> +		sdhci_writeb(host,  SDHCI_POWER_180 |
> +			     SDHCI_POWER_ON, SDHCI_POWER_CONTROL);
> +	}
> +	if (fdt_getprop(gd->fdt_blob,
> +			dev_of_offset(dev), "mmc-hs200-1_8v", &len) != NULL) {
> +		host->host_caps |= MMC_MODE_HS200;
> +	}
> +
>  	host->ops = &xenon_sdhci_ops;
>  
>  	host->max_clk = XENON_MMC_MAX_CLK;
> -- 
> 2.28.0
>
Andre Heider Aug. 31, 2020, 8:10 a.m. UTC | #2
On 31/08/2020 09:50, Pali Rohár wrote:
> On Monday 31 August 2020 05:25:37 Andre Heider wrote:
>> From: Wojciech Macek <wma@semihalf.com>
>>
>> Add support for Marvell Xenon SDHCI HS200 mode.
>>
>> Changes focue mostly on correct PHY initialization.
>> All procedure is similar to the one done by Linux
>> driver, but simplified.
>>
>> Change-Id: I5e2396eeb23784f495abc18ea5a2eb7a92390730
>> Signed-off-by: Wojciech Macek <wma@semihalf.com>
>> Reviewed-on: http://vgitil04.il.marvell.com:8080/59230
>> Tested-by: iSoC Platform CI <ykjenk@marvell.com>
>> Reviewed-by: Grzegorz Jaszczyk <jaz@semihalf.com>
>> Reviewed-by: Kostya Porotchkin <kostap@marvell.com>
>> Reviewed-by: Igal Liberman <igall@marvell.com>
>> [a.heider: adapt to mainline]
>> Signed-off-by: Andre Heider <a.heider@gmail.com>
>> ---
>> Missing downstream patch, noticed while diffing branches:
>> https://github.com/MarvellEmbeddedProcessors/u-boot-marvell/commit/387232507a0d9dda3990284221eaf87d7541dd02
> 
> Hello Andre! Why is this patch needed? Or what it is fixing?
> I tested upstream U-Boot with all previous patches on Turris MOX and
> Espressobin and SD cards worked fine.

My primary aim was to reduces the diff against the downstream fork, to 
make upstream work just as well.

But the HS200/400 mode is not used by those two boards, but by:
arch/arm/dts/armada-3720-uDPU.dts
arch/arm/dts/armada-3720-db.dts

And my other patch for espressobin-emmc is using it too. They're 
independent, so it works without it, just not as fast.

Having said that, I don't have either of those boards, so I haven't 
tested it myself. But the patches have been downstream for over two 
years, so I'd say they're well tested since everybody runs a downstream 
firmware (which will hopefully change now ;)

Regards,
Andre
Pali Rohár Aug. 31, 2020, 11:39 a.m. UTC | #3
On Monday 31 August 2020 10:10:50 Andre Heider wrote:
> On 31/08/2020 09:50, Pali Rohár wrote:
> > On Monday 31 August 2020 05:25:37 Andre Heider wrote:
> > > From: Wojciech Macek <wma@semihalf.com>
> > > 
> > > Add support for Marvell Xenon SDHCI HS200 mode.
> > > 
> > > Changes focue mostly on correct PHY initialization.
> > > All procedure is similar to the one done by Linux
> > > driver, but simplified.
> > > 
> > > Change-Id: I5e2396eeb23784f495abc18ea5a2eb7a92390730
> > > Signed-off-by: Wojciech Macek <wma@semihalf.com>
> > > Reviewed-on: http://vgitil04.il.marvell.com:8080/59230
> > > Tested-by: iSoC Platform CI <ykjenk@marvell.com>
> > > Reviewed-by: Grzegorz Jaszczyk <jaz@semihalf.com>
> > > Reviewed-by: Kostya Porotchkin <kostap@marvell.com>
> > > Reviewed-by: Igal Liberman <igall@marvell.com>
> > > [a.heider: adapt to mainline]
> > > Signed-off-by: Andre Heider <a.heider@gmail.com>
> > > ---
> > > Missing downstream patch, noticed while diffing branches:
> > > https://github.com/MarvellEmbeddedProcessors/u-boot-marvell/commit/387232507a0d9dda3990284221eaf87d7541dd02
> > 
> > Hello Andre! Why is this patch needed? Or what it is fixing?
> > I tested upstream U-Boot with all previous patches on Turris MOX and
> > Espressobin and SD cards worked fine.
> 
> My primary aim was to reduces the diff against the downstream fork, to make
> upstream work just as well.

I think the point is not to copy+paste code from Marvell's fork to
upstream U-Boot as is, but rather to provide missing functionality or
fixing bugs in upstream U-Boot.

I guess that U-Boot maintainers do not want new code which nobody knows
what is doing or do not know why is needed or do not know if it even
works for specified HW (like 3720 DB or uDPU board).

> But the HS200/400 mode is not used by those two boards, but by:
> arch/arm/dts/armada-3720-uDPU.dts
> arch/arm/dts/armada-3720-db.dts
> 
> And my other patch for espressobin-emmc is using it too. They're
> independent, so it works without it, just not as fast.
> 
> Having said that, I don't have either of those boards, so I haven't tested
> it myself. But the patches have been downstream for over two years, so I'd
> say they're well tested since everybody runs a downstream firmware (which
> will hopefully change now ;)

Yea, they are probably tested, but with historic U-Boot version -- which
is not enough.

The best thing would be to find somebody who wants to test changes on
real HW.
diff mbox series

Patch

diff --git a/drivers/mmc/xenon_sdhci.c b/drivers/mmc/xenon_sdhci.c
index 7f9a579c83..ec255d30b0 100644
--- a/drivers/mmc/xenon_sdhci.c
+++ b/drivers/mmc/xenon_sdhci.c
@@ -16,6 +16,7 @@ 
 
 #include <common.h>
 #include <dm.h>
+#include <dm/device_compat.h>
 #include <fdtdec.h>
 #include <linux/bitops.h>
 #include <linux/delay.h>
@@ -45,6 +46,7 @@  DECLARE_GLOBAL_DATA_PTR;
 
 #define SDHC_SLOT_EMMC_CTRL			0x0130
 #define ENABLE_DATA_STROBE_SHIFT		24
+#define ENABLE_DATA_STROBE			BIT(ENABLE_DATA_STROBE_SHIFT)
 #define SET_EMMC_RSTN_SHIFT			16
 #define EMMC_VCCQ_MASK				0x3
 #define EMMC_VCCQ_1_8V				0x1
@@ -64,6 +66,7 @@  DECLARE_GLOBAL_DATA_PTR;
 #define OUTPUT_QSN_PHASE_SELECT			BIT(17)
 #define SAMPL_INV_QSP_PHASE_SELECT		BIT(18)
 #define SAMPL_INV_QSP_PHASE_SELECT_SHIFT	18
+#define EMMC_PHY_SDIO_MODE			BIT(28)
 #define EMMC_PHY_SLOW_MODE			BIT(29)
 #define PHY_INITIALIZAION			BIT(31)
 #define WAIT_CYCLE_BEFORE_USING_MASK		0xf
@@ -90,6 +93,7 @@  DECLARE_GLOBAL_DATA_PTR;
 #define FC_QSN_RECEN				BIT(27)
 #define OEN_QSN					BIT(28)
 #define AUTO_RECEN_CTRL				BIT(30)
+#define FC_ALL_CMOS_RECEIVER			(REC_EN_MASK << REC_EN_SHIFT)
 
 #define EMMC_PHY_PAD_CONTROL1			(EMMC_PHY_REG_BASE + 0xc)
 #define EMMC5_1_FC_QSP_PD			BIT(9)
@@ -99,10 +103,71 @@  DECLARE_GLOBAL_DATA_PTR;
 #define EMMC5_1_FC_DQ_PD			0xff
 #define EMMC5_1_FC_DQ_PU			(0xff << 16)
 
+#define EMMC_PHY_PAD_CONTROL2			(EMMC_PHY_REG_BASE + 0x10)
+#define ZNR_MASK				0x1F
+#define ZNR_SHIFT				8
+#define ZPR_MASK				0x1F
+
 #define SDHCI_RETUNE_EVT_INTSIG			0x00001000
 
+#define SDHCI_HOST_CONTROL2		0x3E
+#define  SDHCI_CTRL_UHS_MASK		0x0007
+#define   SDHCI_CTRL_UHS_SDR12		0x0000
+#define   SDHCI_CTRL_UHS_SDR25		0x0001
+#define   SDHCI_CTRL_UHS_SDR50		0x0002
+#define   SDHCI_CTRL_UHS_SDR104		0x0003
+#define   SDHCI_CTRL_UHS_DDR50		0x0004
+#define   SDHCI_CTRL_HS400		0x0005 /* Non-standard */
+#define   SDHCI_CTRL_HS200_ONLY		0x0005 /* Non-standard */
+#define   SDHCI_CTRL_HS400_ONLY		0x0006 /* Non-standard */
+#define  SDHCI_CTRL_VDD_180		0x0008
+#define  SDHCI_CTRL_DRV_TYPE_MASK	0x0030
+#define   SDHCI_CTRL_DRV_TYPE_B		0x0000
+#define   SDHCI_CTRL_DRV_TYPE_A		0x0010
+#define   SDHCI_CTRL_DRV_TYPE_C		0x0020
+#define   SDHCI_CTRL_DRV_TYPE_D		0x0030
+#define  SDHCI_CTRL_EXEC_TUNING		0x0040
+#define  SDHCI_CTRL_TUNED_CLK		0x0080
+#define  SDHCI_CTRL_PRESET_VAL_ENABLE	0x8000
+
+/*
+ * Config to eMMC PHY to prepare for tuning.
+ * Enable HW DLL and set the TUNING_STEP
+ */
+#define XENON_SLOT_DLL_CUR_DLY_VAL		0x0150
+
+#define XENON_SLOT_OP_STATUS_CTRL		0x0128
+#define XENON_TUN_CONSECUTIVE_TIMES_SHIFT	16
+#define XENON_TUN_CONSECUTIVE_TIMES_MASK	0x7
+#define XENON_TUN_CONSECUTIVE_TIMES		0x4
+#define XENON_TUNING_STEP_SHIFT			12
+#define XENON_TUNING_STEP_MASK			0xF
+#define XENON_TUNING_STEP_DIVIDER		BIT(6)
+
+#define XENON_EMMC_PHY_DLL_CONTROL		(EMMC_PHY_REG_BASE + 0x14)
+#define XENON_EMMC_5_0_PHY_DLL_CONTROL		\
+	(XENON_EMMC_5_0_PHY_REG_BASE + 0x10)
+#define XENON_DLL_ENABLE			BIT(31)
+#define XENON_DLL_UPDATE_STROBE_5_0		BIT(30)
+#define XENON_DLL_REFCLK_SEL			BIT(30)
+#define XENON_DLL_UPDATE			BIT(23)
+#define XENON_DLL_PHSEL1_SHIFT			24
+#define XENON_DLL_PHSEL0_SHIFT			16
+#define XENON_DLL_PHASE_MASK			0x3F
+#define XENON_DLL_PHASE_90_DEGREE		0x1F
+#define XENON_DLL_FAST_LOCK			BIT(5)
+#define XENON_DLL_GAIN2X			BIT(3)
+#define XENON_DLL_BYPASS_EN			BIT(0)
+
+#define XENON_SLOT_EXT_PRESENT_STATE		0x014C
+#define XENON_DLL_LOCK_STATE			0x1
+
 /* Hyperion only have one slot 0 */
 #define XENON_MMC_SLOT_ID_HYPERION		0
+#define SLOT_MASK(slot)				BIT(slot)
+
+#define XENON_EMMC_PHY_LOGIC_TIMING_ADJUST	(EMMC_PHY_REG_BASE + 0x18)
+#define XENON_LOGIC_TIMING_VALUE		0x00AA8977
 
 #define MMC_TIMING_LEGACY	0
 #define MMC_TIMING_MMC_HS	1
@@ -266,6 +331,176 @@  static int xenon_mmc_start_signal_voltage_switch(struct sdhci_host *host)
 	return ret;
 }
 
+/*
+ * Xenon defines different values for HS200 and HS400
+ * in Host_Control_2
+ */
+static void xenon_set_uhs_signaling(struct sdhci_host *host,
+				    unsigned int timing)
+{
+	u16 ctrl_2;
+
+	ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+	/* Select Bus Speed Mode for host */
+	ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+	if (timing == MMC_TIMING_MMC_HS200)
+		ctrl_2 |= SDHCI_CTRL_HS200_ONLY;
+	else if (timing == MMC_TIMING_UHS_SDR104)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
+	else if (timing == MMC_TIMING_UHS_SDR12)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
+	else if (timing == MMC_TIMING_UHS_SDR25)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
+	else if (timing == MMC_TIMING_UHS_SDR50)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
+	else if ((timing == MMC_TIMING_UHS_DDR50) ||
+		 (timing == MMC_TIMING_MMC_DDR52))
+		ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
+	else if (timing == MMC_TIMING_MMC_HS400)
+		ctrl_2 |= SDHCI_CTRL_HS400_ONLY;
+	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+}
+
+/*
+ * If eMMC PHY Slow Mode is required in lower speed mode (SDCLK < 55MHz)
+ * in SDR mode, enable Slow Mode to bypass eMMC PHY.
+ * SDIO slower SDR mode also requires Slow Mode.
+ *
+ * If Slow Mode is enabled, return 0.
+ * Otherwise, return -EINVAL.
+ */
+static int xenon_emmc_phy_slow_mode(struct sdhci_host *host,
+				    unsigned char timing)
+{
+	u32 reg;
+	int ret = -EINVAL;
+
+	if (host->mmc->tran_speed > 52000000)
+		return -EINVAL;
+
+	reg = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST);
+	/* When in slower SDR mode, enable Slow Mode for SDIO */
+	switch (timing) {
+	case MMC_TIMING_LEGACY:
+		/*
+		 * If Slow Mode is required, enable Slow Mode by default
+		 * in early init phase to avoid any potential issue.
+		 */
+		reg |= EMMC_PHY_SLOW_MODE;
+		ret = 0;
+		break;
+	case MMC_TIMING_UHS_SDR25:
+	case MMC_TIMING_UHS_SDR12:
+	case MMC_TIMING_SD_HS:
+	case MMC_TIMING_MMC_HS:
+		if (IS_SD(host->mmc)) {
+			reg |= EMMC_PHY_SLOW_MODE;
+			ret = 0;
+			break;
+		}
+	default:
+		reg &= ~EMMC_PHY_SLOW_MODE;
+		ret = -EINVAL;
+	}
+
+	sdhci_writel(host, reg, EMMC_PHY_TIMING_ADJUST);
+	return ret;
+}
+
+static void xenon_emmc_phy_disable_data_strobe(struct sdhci_host *host)
+{
+	u32 reg;
+
+	/* Disable SDHC Data Strobe */
+	reg = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+	reg &= ~ENABLE_DATA_STROBE;
+	sdhci_writel(host, reg, SDHC_SLOT_EMMC_CTRL);
+}
+
+/*
+ * Enable eMMC PHY HW DLL
+ * DLL should be enabled and stable before HS200/SDR104 tuning,
+ * and before HS400 data strobe setting.
+ */
+static int xenon_emmc_phy_enable_dll(struct sdhci_host *host)
+{
+	u32 reg;
+	u32 timeout;
+
+	if (host->mmc->tran_speed <= 52000000)
+		return -EINVAL;
+
+	reg = sdhci_readl(host, XENON_EMMC_PHY_DLL_CONTROL);
+	if (reg & XENON_DLL_ENABLE)
+		return 0;
+
+	/* Enable DLL */
+	reg = sdhci_readl(host, XENON_EMMC_PHY_DLL_CONTROL);
+	reg |= (XENON_DLL_ENABLE | XENON_DLL_FAST_LOCK);
+
+	/*
+	 * Set Phase as 90 degree, which is most common value.
+	 * Might set another value if necessary.
+	 * The granularity is 1 degree.
+	 */
+	reg &= ~((XENON_DLL_PHASE_MASK << XENON_DLL_PHSEL0_SHIFT) |
+		 (XENON_DLL_PHASE_MASK << XENON_DLL_PHSEL1_SHIFT));
+	reg |= ((XENON_DLL_PHASE_90_DEGREE << XENON_DLL_PHSEL0_SHIFT) |
+		(XENON_DLL_PHASE_90_DEGREE << XENON_DLL_PHSEL1_SHIFT));
+
+	reg &= ~(XENON_DLL_BYPASS_EN | XENON_DLL_REFCLK_SEL);
+	reg |= XENON_DLL_UPDATE;
+	sdhci_writel(host, reg, XENON_EMMC_PHY_DLL_CONTROL);
+
+	/* Wait max 32 ms */
+	timeout = 32;
+	while (!(sdhci_readw(host, XENON_SLOT_EXT_PRESENT_STATE) &
+		XENON_DLL_LOCK_STATE)) {
+		if (timeout > 32) {
+			printf("Wait for DLL Lock time-out\n");
+			return -ETIMEDOUT;
+		}
+		udelay(1000);
+		timeout++;
+	}
+	return 0;
+}
+
+static int xenon_emmc_phy_config_tuning(struct sdhci_host *host)
+{
+	u32 reg, tuning_step;
+	int ret;
+
+	if (host->mmc->tran_speed <= 52000000)
+		return -EINVAL;
+
+	ret = xenon_emmc_phy_enable_dll(host);
+	if (ret)
+		return ret;
+
+	/* Achieve TUNING_STEP with HW DLL help */
+	reg = sdhci_readl(host, XENON_SLOT_DLL_CUR_DLY_VAL);
+	tuning_step = reg / XENON_TUNING_STEP_DIVIDER;
+	if (unlikely(tuning_step > XENON_TUNING_STEP_MASK)) {
+		dev_warn(mmc_dev(host->mmc),
+			 "HS200 TUNING_STEP %d is larger than MAX value\n",
+			 tuning_step);
+		tuning_step = XENON_TUNING_STEP_MASK;
+	}
+
+	/* Set TUNING_STEP for later tuning */
+	reg = sdhci_readl(host, XENON_SLOT_OP_STATUS_CTRL);
+	reg &= ~(XENON_TUN_CONSECUTIVE_TIMES_MASK <<
+		 XENON_TUN_CONSECUTIVE_TIMES_SHIFT);
+	reg |= (XENON_TUN_CONSECUTIVE_TIMES <<
+		XENON_TUN_CONSECUTIVE_TIMES_SHIFT);
+	reg &= ~(XENON_TUNING_STEP_MASK << XENON_TUNING_STEP_SHIFT);
+	reg |= (tuning_step << XENON_TUNING_STEP_SHIFT);
+	sdhci_writel(host, reg, XENON_SLOT_OP_STATUS_CTRL);
+
+	return 0;
+}
+
 static void xenon_mmc_phy_set(struct sdhci_host *host)
 {
 	struct xenon_sdhci_priv *priv = host->mmc->priv;
@@ -273,8 +508,8 @@  static void xenon_mmc_phy_set(struct sdhci_host *host)
 
 	/* Setup pad, set bit[30], bit[28] and bits[26:24] */
 	var = sdhci_readl(host, EMMC_PHY_PAD_CONTROL);
-	var |= AUTO_RECEN_CTRL | OEN_QSN | FC_QSP_RECEN |
-		FC_CMD_RECEN | FC_DQ_RECEN;
+	var |= OEN_QSN | FC_QSP_RECEN | FC_CMD_RECEN | FC_DQ_RECEN |
+		FC_ALL_CMOS_RECEIVER;
 	sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL);
 
 	/* Set CMD and DQ Pull Up */
@@ -284,20 +519,45 @@  static void xenon_mmc_phy_set(struct sdhci_host *host)
 	sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL1);
 
 	/*
-	 * If timing belongs to high speed, set bit[17] of
+	 * If Timing belongs to high speed, clear bit[17] of
 	 * EMMC_PHY_TIMING_ADJUST register
 	 */
+	var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST);
 	if ((priv->timing == MMC_TIMING_MMC_HS400) ||
 	    (priv->timing == MMC_TIMING_MMC_HS200) ||
+	    (priv->timing == MMC_TIMING_MMC_DDR52) ||
 	    (priv->timing == MMC_TIMING_UHS_SDR50) ||
 	    (priv->timing == MMC_TIMING_UHS_SDR104) ||
 	    (priv->timing == MMC_TIMING_UHS_DDR50) ||
-	    (priv->timing == MMC_TIMING_UHS_SDR25) ||
-	    (priv->timing == MMC_TIMING_MMC_DDR52)) {
-		var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST);
-		var |= OUTPUT_QSN_PHASE_SELECT;
+	    (priv->timing == MMC_TIMING_UHS_SDR25)) {
+		var &= ~OUTPUT_QSN_PHASE_SELECT;
 		sdhci_writel(host, var, EMMC_PHY_TIMING_ADJUST);
 	}
+	if (priv->timing == MMC_TIMING_LEGACY) {
+		xenon_emmc_phy_slow_mode(host, priv->timing);
+		goto phy_init;
+	}
+
+	/*
+	 * If SDIO card, set SDIO Mode
+	 * Otherwise, clear SDIO Mode
+	 */
+	var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST);
+	if (IS_SD(host->mmc))
+		var |= EMMC_PHY_SDIO_MODE;
+	else
+		var &= ~EMMC_PHY_SDIO_MODE;
+	sdhci_writel(host, var, EMMC_PHY_TIMING_ADJUST);
+
+	/*
+	 * Set preferred ZNR and ZPR value
+	 * The ZNR and ZPR value vary between different boards.
+	 * Define them both in sdhci-xenon-emmc-phy.h.
+	 */
+	var = sdhci_readl(host, EMMC_PHY_PAD_CONTROL2);
+	var &= ~((ZNR_MASK << ZNR_SHIFT) | ZPR_MASK);
+	var |= ((0xf << ZNR_SHIFT) | 0xf);
+	sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL2);
 
 	/*
 	 * When setting EMMC_PHY_FUNC_CONTROL register,
@@ -308,11 +568,21 @@  static void xenon_mmc_phy_set(struct sdhci_host *host)
 	sdhci_writew(host, var, SDHCI_CLOCK_CONTROL);
 
 	var = sdhci_readl(host, EMMC_PHY_FUNC_CONTROL);
-	if (host->mmc->ddr_mode) {
-		var |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE;
-	} else {
+	switch (priv->timing) {
+	case MMC_TIMING_MMC_HS400:
+		var |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) |
+		       CMD_DDR_MODE;
+		var &= ~DQ_ASYNC_MODE;
+		break;
+	case MMC_TIMING_UHS_DDR50:
+	case MMC_TIMING_MMC_DDR52:
+		var |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) |
+		       CMD_DDR_MODE | DQ_ASYNC_MODE;
+		break;
+	default:
 		var &= ~((DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) |
 			 CMD_DDR_MODE);
+		var |= DQ_ASYNC_MODE;
 	}
 	sdhci_writel(host, var, EMMC_PHY_FUNC_CONTROL);
 
@@ -321,7 +591,26 @@  static void xenon_mmc_phy_set(struct sdhci_host *host)
 	var |= SDHCI_CLOCK_CARD_EN;
 	sdhci_writew(host, var, SDHCI_CLOCK_CONTROL);
 
+	udelay(1000);
+
+	/* Quirk, value suggested by hardware team */
+	if (priv->timing == MMC_TIMING_MMC_HS400)
+		/* Hardware team recommend a value for HS400 */
+		sdhci_writel(host, XENON_EMMC_PHY_LOGIC_TIMING_ADJUST,
+			     XENON_LOGIC_TIMING_VALUE);
+	else
+		xenon_emmc_phy_disable_data_strobe(host);
+
+phy_init:
+
+	xenon_set_uhs_signaling(host, priv->timing);
 	xenon_mmc_phy_init(host);
+
+	if ((priv->timing == MMC_TIMING_MMC_HS400) ||
+	    (priv->timing == MMC_TIMING_MMC_HS200)) {
+		if (xenon_emmc_phy_config_tuning(host) != 0)
+			printf("Error, failed to tune MMC PHY\n");
+	}
 }
 
 /* Enable/Disable the Auto Clock Gating function of this slot */
@@ -338,8 +627,6 @@  static void xenon_mmc_set_acg(struct sdhci_host *host, bool enable)
 	sdhci_writel(host, var, SDHC_SYS_OP_CTRL);
 }
 
-#define SLOT_MASK(slot)		BIT(slot)
-
 /* Enable specific slot */
 static void xenon_mmc_enable_slot(struct sdhci_host *host, u8 slot)
 {
@@ -391,6 +678,7 @@  static int xenon_sdhci_set_ios_post(struct sdhci_host *host)
 	struct xenon_sdhci_priv *priv = host->mmc->priv;
 	uint speed = host->mmc->tran_speed;
 	int pwr_18v = 0;
+	u32 reg;
 
 	/*
 	 * Signal Voltage Switching is only applicable for Host Controllers
@@ -423,12 +711,22 @@  static int xenon_sdhci_set_ios_post(struct sdhci_host *host)
 		/* eMMC */
 		if (host->mmc->ddr_mode)
 			priv->timing = MMC_TIMING_MMC_DDR52;
+		else if (speed == 200000000)
+			priv->timing = MMC_TIMING_MMC_HS200;
 		else if (speed <= 26000000)
 			priv->timing = MMC_TIMING_LEGACY;
 		else
 			priv->timing = MMC_TIMING_MMC_HS;
 	}
 
+	if ((priv->timing == MMC_TIMING_MMC_HS400) ||
+	    (priv->timing == MMC_TIMING_MMC_HS200) ||
+	    (priv->timing == MMC_TIMING_MMC_HS)) {
+		reg = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+		reg &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
+		sdhci_writew(host, reg, SDHCI_HOST_CONTROL2);
+	}
+
 	/* Re-init the PHY */
 	xenon_mmc_phy_set(host);
 
@@ -447,6 +745,7 @@  static int xenon_sdhci_probe(struct udevice *dev)
 	struct xenon_sdhci_priv *priv = dev_get_priv(dev);
 	struct sdhci_host *host = dev_get_priv(dev);
 	int ret;
+	int len;
 
 	host->mmc = &plat->mmc;
 	host->mmc->priv = host;
@@ -500,6 +799,18 @@  static int xenon_sdhci_probe(struct udevice *dev)
 		return -EINVAL;
 	}
 
+	/* Support for High Speed modes */
+	if (fdt_getprop(gd->fdt_blob,
+			dev_of_offset(dev), "mmc-hs400-1_8v", &len) != NULL) {
+		host->host_caps |= (MMC_MODE_HS400 | MMC_MODE_HS200);
+		sdhci_writeb(host,  SDHCI_POWER_180 |
+			     SDHCI_POWER_ON, SDHCI_POWER_CONTROL);
+	}
+	if (fdt_getprop(gd->fdt_blob,
+			dev_of_offset(dev), "mmc-hs200-1_8v", &len) != NULL) {
+		host->host_caps |= MMC_MODE_HS200;
+	}
+
 	host->ops = &xenon_sdhci_ops;
 
 	host->max_clk = XENON_MMC_MAX_CLK;