On Monday 31 August 2020 05:25:37 Andre Heider wrote: > From: Wojciech Macek <w...@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 <w...@semihalf.com> > Reviewed-on: http://vgitil04.il.marvell.com:8080/59230 > Tested-by: iSoC Platform CI <ykj...@marvell.com> > Reviewed-by: Grzegorz Jaszczyk <j...@semihalf.com> > Reviewed-by: Kostya Porotchkin <kos...@marvell.com> > Reviewed-by: Igal Liberman <ig...@marvell.com> > [a.heider: adapt to mainline] > Signed-off-by: Andre Heider <a.hei...@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 >