Message ID | b646d28daf3d8cb8e57774ce040f39350364041e.1487091464.git-series.gregory.clement@free-electrons.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
On 14 February 2017 at 18:01, Gregory CLEMENT <gregory.clement@free-electrons.com> wrote: > From: Hu Ziji <huziji@marvell.com> > > Marvell Xenon eMMC/SD/SDIO Host Controller contains PHY. > Multiple types of PHYs are supported. > > Add support to multiple types of PHYs init and configuration. > Add register definitions of PHYs. > > Xenon PHY cannot fit in kernel common PHY framework. > Xenon SDHC PHY register is a part of Xenon SDHC register set. > Besides, MMC initialization has to call several PHY functions to > complete timing setting. > Those PHY setting functions have to access SDHC registers and know > current MMC setting, such as bus width, clock frequency and > speed mode. > As a result, implement Xenon PHY in MMC host directory. > > Signed-off-by: Hu Ziji <huziji@marvell.com> > Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> > --- > drivers/mmc/host/Makefile | 2 +- > drivers/mmc/host/sdhci-xenon-phy.c | 751 ++++++++++++++++++++++++++++++- > drivers/mmc/host/sdhci-xenon.c | 3 +- > drivers/mmc/host/sdhci-xenon.h | 37 +- > 4 files changed, 791 insertions(+), 2 deletions(-) > create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c I see, you put the phy implementation in a separate file. I guess that makes sense. However, I leave the decision for Adrian. Anyway, in such case I guess the extended Makefile options and the separate header files makes sense. > > diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile > index b0a2ab4b256e..893b48db5513 100644 > --- a/drivers/mmc/host/Makefile > +++ b/drivers/mmc/host/Makefile > @@ -84,4 +84,4 @@ ifeq ($(CONFIG_CB710_DEBUG),y) > endif > > obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o > -sdhci-xenon-driver-y += sdhci-xenon.o > +sdhci-xenon-driver-y += sdhci-xenon.o sdhci-xenon-phy.o > diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c > new file mode 100644 > index 000000000000..c26ba3a180a0 > --- /dev/null > +++ b/drivers/mmc/host/sdhci-xenon-phy.c [...] > +/* > + * eMMC PHY configuration and operations > + */ > +struct emmc_phy_params { Perhaps prefix the struct name with "xenon_". > + bool slow_mode; > + > + u8 znr; > + u8 zpr; > + > + /* Nr of consecutive Sampling Points of a Valid Sampling Window */ > + u8 nr_tun_times; > + /* Divider for calculating Tuning Step */ > + u8 tun_step_divider; > +}; > + > +static int alloc_emmc_phy(struct sdhci_xenon_priv *priv) > +{ > + struct emmc_phy_params *params; > + > + params = kzalloc(sizeof(*params), GFP_KERNEL); Why not using devm_kzalloc()? [...] > + > +/* > + * 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 true. > + * Otherwise, return false. > + */ > +static bool emmc_phy_slow_mode(struct sdhci_host *host, > + unsigned char timing) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + struct emmc_phy_params *params = priv->phy_params; > + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; > + u32 reg; > + > + if (host->clock > MMC_HIGH_52_MAX_DTR) > + return false; > + > + reg = sdhci_readl(host, phy_regs->timing_adj); > + /* Enable Slow Mode for SDIO in slower SDR mode */ > + if ((priv->init_card_type == MMC_TYPE_SDIO) && > + ((timing == MMC_TIMING_UHS_SDR25) || > + (timing == MMC_TIMING_UHS_SDR12) || > + (timing == MMC_TIMING_SD_HS))) { > + reg |= XENON_TIMING_ADJUST_SLOW_MODE; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + return true; > + } > + > + /* Check if Slow Mode is required in lower speed mode in SDR mode */ > + if (((timing == MMC_TIMING_UHS_SDR25) || > + (timing == MMC_TIMING_UHS_SDR12) || > + (timing == MMC_TIMING_SD_HS) || > + (timing == MMC_TIMING_MMC_HS)) && params->slow_mode) { > + reg |= XENON_TIMING_ADJUST_SLOW_MODE; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + return true; > + } > + Two quite similar if statement. Can you perhaps combine them to avoid code duplication. > + reg &= ~XENON_TIMING_ADJUST_SLOW_MODE; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + return false; > +} > + > +/* > + * Set-up eMMC 5.0/5.1 PHY. > + * Specific configuration depends on the current speed mode in use. > + */ > +static void emmc_phy_set(struct sdhci_host *host, > + unsigned char timing) > +{ > + u32 reg; > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + struct emmc_phy_params *params = priv->phy_params; > + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; > + unsigned long flags; > + > + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting starts\n"); > + > + spin_lock_irqsave(&host->lock, flags); > + > + /* Setup pad, set bit[28] and bits[26:24] */ > + reg = sdhci_readl(host, phy_regs->pad_ctrl); > + reg |= (XENON_FC_DQ_RECEN | XENON_FC_CMD_RECEN | > + XENON_FC_QSP_RECEN | XENON_OEN_QSN); > + /* All FC_XX_RECEIVCE should be set as CMOS Type */ > + reg |= XENON_FC_ALL_CMOS_RECEIVER; > + sdhci_writel(host, reg, phy_regs->pad_ctrl); > + > + /* Set CMD and DQ Pull Up */ > + if (priv->phy_type == EMMC_5_0_PHY) { > + reg = sdhci_readl(host, XENON_EMMC_5_0_PHY_PAD_CONTROL); > + reg |= (XENON_EMMC5_FC_CMD_PU | XENON_EMMC5_FC_DQ_PU); > + reg &= ~(XENON_EMMC5_FC_CMD_PD | XENON_EMMC5_FC_DQ_PD); > + sdhci_writel(host, reg, XENON_EMMC_5_0_PHY_PAD_CONTROL); > + } else { > + reg = sdhci_readl(host, XENON_EMMC_PHY_PAD_CONTROL1); > + reg |= (XENON_EMMC5_1_FC_CMD_PU | XENON_EMMC5_1_FC_DQ_PU); > + reg &= ~(XENON_EMMC5_1_FC_CMD_PD | XENON_EMMC5_1_FC_DQ_PD); > + sdhci_writel(host, reg, XENON_EMMC_PHY_PAD_CONTROL1); > + } > + > + if (timing == MMC_TIMING_LEGACY) { > + /* > + * If Slow Mode is required, enable Slow Mode by default > + * in early init phase to avoid any potential issue. > + */ > + if (params->slow_mode) { > + reg = sdhci_readl(host, phy_regs->timing_adj); > + reg |= XENON_TIMING_ADJUST_SLOW_MODE; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + } > + goto phy_init; > + } > + > + /* > + * FIXME: should depends on the specific board timing. > + */ > + if ((timing == MMC_TIMING_MMC_HS400) || > + (timing == MMC_TIMING_MMC_HS200) || > + (timing == MMC_TIMING_UHS_SDR50) || > + (timing == MMC_TIMING_UHS_SDR104) || > + (timing == MMC_TIMING_UHS_DDR50) || > + (timing == MMC_TIMING_UHS_SDR25) || > + (timing == MMC_TIMING_MMC_DDR52)) { > + reg = sdhci_readl(host, phy_regs->timing_adj); > + reg &= ~XENON_OUTPUT_QSN_PHASE_SELECT; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + } > + > + /* > + * If SDIO card, set SDIO Mode > + * Otherwise, clear SDIO Mode > + */ > + reg = sdhci_readl(host, phy_regs->timing_adj); > + if (priv->init_card_type == MMC_TYPE_SDIO) > + reg |= XENON_TIMING_ADJUST_SDIO_MODE; > + else > + reg &= ~XENON_TIMING_ADJUST_SDIO_MODE; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + > + if (emmc_phy_slow_mode(host, timing)) > + goto phy_init; > + > + /* > + * Set preferred ZNR and ZPR value > + * The ZNR and ZPR value vary between different boards. > + * Define them both in sdhci-xenon-emmc-phy.h. > + */ > + reg = sdhci_readl(host, phy_regs->pad_ctrl2); > + reg &= ~((XENON_ZNR_MASK << XENON_ZNR_SHIFT) | XENON_ZPR_MASK); > + reg |= ((params->znr << XENON_ZNR_SHIFT) | params->zpr); > + sdhci_writel(host, reg, phy_regs->pad_ctrl2); > + > + /* > + * When setting EMMC_PHY_FUNC_CONTROL register, > + * SD clock should be disabled > + */ > + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); > + reg &= ~SDHCI_CLOCK_CARD_EN; > + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL); > + > + reg = sdhci_readl(host, phy_regs->func_ctrl); > + if ((timing == MMC_TIMING_UHS_DDR50) || > + (timing == MMC_TIMING_MMC_HS400) || > + (timing == MMC_TIMING_MMC_DDR52)) > + reg |= (XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | > + XENON_CMD_DDR_MODE; > + else > + reg &= ~((XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | > + XENON_CMD_DDR_MODE); > + > + if (timing == MMC_TIMING_MMC_HS400) > + reg &= ~XENON_DQ_ASYNC_MODE; > + else > + reg |= XENON_DQ_ASYNC_MODE; > + sdhci_writel(host, reg, phy_regs->func_ctrl); > + > + /* Enable bus clock */ > + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); > + reg |= SDHCI_CLOCK_CARD_EN; > + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL); > + > + if (timing == MMC_TIMING_MMC_HS400) > + /* Hardware team recommend a value for HS400 */ > + sdhci_writel(host, XENON_LOGIC_TIMING_VALUE, > + phy_regs->logic_timing_adj); > + else > + __emmc_phy_disable_data_strobe(host); > + > +phy_init: > + emmc_phy_init(host); > + > + spin_unlock_irqrestore(&host->lock, flags); > + > + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n"); Again you are checking the MMC_TIMING_* in several if statements. Perhaps you can use switch statement instead and build a couple of different register variables, that you use when reading/writing afterwards. > +} > + > +static int emmc_phy_parse_param_dt(struct sdhci_host *host, > + struct device_node *np, > + struct emmc_phy_params *params) > +{ > + u32 value; > + > + if (of_property_read_bool(np, "marvell,xenon-phy-slow-mode")) > + params->slow_mode = true; > + else > + params->slow_mode = false; > + > + if (!of_property_read_u32(np, "marvell,xenon-phy-znr", &value)) > + params->znr = value & XENON_ZNR_MASK; > + else > + params->znr = XENON_ZNR_DEF_VALUE; > + > + if (!of_property_read_u32(np, "marvell,xenon-phy-zpr", &value)) > + params->zpr = value & XENON_ZPR_MASK; > + else > + params->zpr = XENON_ZPR_DEF_VALUE; > + > + if (!of_property_read_u32(np, "marvell,xenon-phy-nr-success-tun", > + &value)) > + params->nr_tun_times = value & XENON_TUN_CONSECUTIVE_TIMES_MASK; > + else > + params->nr_tun_times = XENON_TUN_CONSECUTIVE_TIMES; > + > + if (!of_property_read_u32(np, "marvell,xenon-phy-tun-step-divider", > + &value)) > + params->tun_step_divider = value & 0xFF; > + else > + params->tun_step_divider = XENON_TUNING_STEP_DIVIDER; > + > + return 0; Instead of all these if-else, let's start by assigning default values instead. This makes the code more readable. > +} > + > +/* > + * Setting PHY when card is working in High Speed Mode. > + * HS400 set data strobe line. > + * HS200/SDR104 set tuning config to prepare for tuning. > + */ > +static int xenon_hs_delay_adj(struct sdhci_host *host) > +{ > + int ret = 0; > + > + if (WARN_ON(host->clock <= XENON_DEFAULT_SDCLK_FREQ)) > + return -EINVAL; > + > + if (host->timing == MMC_TIMING_MMC_HS400) { > + emmc_phy_strobe_delay_adj(host); > + return 0; > + } > + > + if ((host->timing == MMC_TIMING_MMC_HS200) || > + (host->timing == MMC_TIMING_UHS_SDR104)) > + return emmc_phy_config_tuning(host); > + > + /* > + * DDR Mode requires driver to scan Sampling Fixed Delay Line, > + * to find out a perfect operation sampling point. > + * It is hard to implement such a scan in host driver since initiating > + * commands by host driver is not safe. > + * Thus so far just keep PHY Sampling Fixed Delay in default value > + * in DDR mode. > + * > + * If any timing issue occurs in DDR mode on Marvell products, > + * please contact maintainer to ask for internal support in Marvell. > + */ > + if ((host->timing == MMC_TIMING_MMC_DDR52) || > + (host->timing == MMC_TIMING_UHS_DDR50)) > + dev_warn_once(mmc_dev(host->mmc), "Timing issue might occur in DDR mode\n"); Please consider a to convert to use a switch statment instead of the several if statements. > + return ret; > +} > + [...] > + > +static int add_xenon_phy(struct device_node *np, struct sdhci_host *host, > + const char *phy_name) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + int i, ret; > + > + for (i = 0; i < NR_PHY_TYPES; i++) { > + if (!strcmp(phy_name, phy_types[i])) { > + priv->phy_type = i; > + break; > + } > + } > + if (i == NR_PHY_TYPES) { > + dev_err(mmc_dev(host->mmc), > + "Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n", > + phy_name); > + priv->phy_type = EMMC_5_1_PHY; > + } > + > + ret = alloc_emmc_phy(priv); > + if (ret) > + return ret; > + > + ret = emmc_phy_parse_param_dt(host, np, priv->phy_params); > + if (ret) > + clean_emmc_phy(priv); > + > + return ret; > +} > + > +int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host) > +{ > + const char *phy_type = NULL; > + > + if (!of_property_read_string(np, "marvell,xenon-phy-type", &phy_type)) > + return add_xenon_phy(np, host, phy_type); > + > + dev_info(mmc_dev(host->mmc), "Fail to get Xenon PHY type. Use default eMMC 5.1 PHY\n"); Isn't there already a dev_err() being printed for this case. You probably only need one. > + return add_xenon_phy(np, host, "emmc 5.1 phy"); > +} [...] Kind regards Uffe -- To unsubscribe from this list: send the line "unsubscribe linux-clk" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Ulf, On 2017/3/15 21:39, Ulf Hansson wrote: > On 14 February 2017 at 18:01, Gregory CLEMENT > <gregory.clement@free-electrons.com> wrote: >> From: Hu Ziji <huziji@marvell.com> >> <snip> >> + >> + /* >> + * FIXME: should depends on the specific board timing. >> + */ >> + if ((timing == MMC_TIMING_MMC_HS400) || >> + (timing == MMC_TIMING_MMC_HS200) || >> + (timing == MMC_TIMING_UHS_SDR50) || >> + (timing == MMC_TIMING_UHS_SDR104) || >> + (timing == MMC_TIMING_UHS_DDR50) || >> + (timing == MMC_TIMING_UHS_SDR25) || >> + (timing == MMC_TIMING_MMC_DDR52)) { >> + reg = sdhci_readl(host, phy_regs->timing_adj); >> + reg &= ~XENON_OUTPUT_QSN_PHASE_SELECT; >> + sdhci_writel(host, reg, phy_regs->timing_adj); >> + } >> + <snip> >> + >> + reg = sdhci_readl(host, phy_regs->func_ctrl); >> + if ((timing == MMC_TIMING_UHS_DDR50) || >> + (timing == MMC_TIMING_MMC_HS400) || >> + (timing == MMC_TIMING_MMC_DDR52)) >> + reg |= (XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | >> + XENON_CMD_DDR_MODE; >> + else >> + reg &= ~((XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | >> + XENON_CMD_DDR_MODE); <snip> > > Again you are checking the MMC_TIMING_* in several if statements. > Perhaps you can use switch statement instead and build a couple of > different register variables, that you use when reading/writing > afterwards. > Sorry. I don't quite understand your comment. Do you mean combine all the related register settings under a specific MMC_TIMING_*? swhich (timing) { case MMC_TIMING_HS400: config reg_1; config reg_2; ... case MMC_TIMING_HS200: config reg_2; config reg_3 .. case ... }; I have a little concern that there might be a lot of duplicate register settings. Some PHY operations are irrelevant to the speed mode. They have to be executed in a particular sequence in all speed modes. >> +} >> + >> +static int emmc_phy_parse_param_dt(struct sdhci_host *host, >> + struct device_node *np, >> + struct emmc_phy_params *params) >> +{ >> + u32 value; >> + >> + if (of_property_read_bool(np, "marvell,xenon-phy-slow-mode")) >> + params->slow_mode = true; >> + else >> + params->slow_mode = false; >> + >> + if (!of_property_read_u32(np, "marvell,xenon-phy-znr", &value)) >> + params->znr = value & XENON_ZNR_MASK; >> + else >> + params->znr = XENON_ZNR_DEF_VALUE; >> + >> + if (!of_property_read_u32(np, "marvell,xenon-phy-zpr", &value)) >> + params->zpr = value & XENON_ZPR_MASK; >> + else >> + params->zpr = XENON_ZPR_DEF_VALUE; >> + >> + if (!of_property_read_u32(np, "marvell,xenon-phy-nr-success-tun", >> + &value)) >> + params->nr_tun_times = value & XENON_TUN_CONSECUTIVE_TIMES_MASK; >> + else >> + params->nr_tun_times = XENON_TUN_CONSECUTIVE_TIMES; >> + >> + if (!of_property_read_u32(np, "marvell,xenon-phy-tun-step-divider", >> + &value)) >> + params->tun_step_divider = value & 0xFF; >> + else >> + params->tun_step_divider = XENON_TUNING_STEP_DIVIDER; >> + >> + return 0; > > Instead of all these if-else, let's start by assigning default values > instead. This makes the code more readable. > Sure. I'll change it. >> +} >> + >> +/* >> + * Setting PHY when card is working in High Speed Mode. >> + * HS400 set data strobe line. >> + * HS200/SDR104 set tuning config to prepare for tuning. >> + */ >> +static int xenon_hs_delay_adj(struct sdhci_host *host) >> +{ >> + int ret = 0; >> + >> + if (WARN_ON(host->clock <= XENON_DEFAULT_SDCLK_FREQ)) >> + return -EINVAL; >> + >> + if (host->timing == MMC_TIMING_MMC_HS400) { >> + emmc_phy_strobe_delay_adj(host); >> + return 0; >> + } >> + >> + if ((host->timing == MMC_TIMING_MMC_HS200) || >> + (host->timing == MMC_TIMING_UHS_SDR104)) >> + return emmc_phy_config_tuning(host); >> + >> + /* >> + * DDR Mode requires driver to scan Sampling Fixed Delay Line, >> + * to find out a perfect operation sampling point. >> + * It is hard to implement such a scan in host driver since initiating >> + * commands by host driver is not safe. >> + * Thus so far just keep PHY Sampling Fixed Delay in default value >> + * in DDR mode. >> + * >> + * If any timing issue occurs in DDR mode on Marvell products, >> + * please contact maintainer to ask for internal support in Marvell. >> + */ >> + if ((host->timing == MMC_TIMING_MMC_DDR52) || >> + (host->timing == MMC_TIMING_UHS_DDR50)) >> + dev_warn_once(mmc_dev(host->mmc), "Timing issue might occur in DDR mode\n"); > > Please consider a to convert to use a switch statment instead of the > several if statements. > It is a good idea to use switch here. >> + return ret; >> +} >> + > <snip> >> +int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host) >> +{ >> + const char *phy_type = NULL; >> + >> + if (!of_property_read_string(np, "marvell,xenon-phy-type", &phy_type)) >> + return add_xenon_phy(np, host, phy_type); >> + >> + dev_info(mmc_dev(host->mmc), "Fail to get Xenon PHY type. Use default eMMC 5.1 PHY\n"); > > Isn't there already a dev_err() being printed for this case. You > probably only need one. By default, "emmc 5.1 phy" is used on most of our products. Thus this dt property is an optional one. I can remove this dev_info. > >> + return add_xenon_phy(np, host, "emmc 5.1 phy"); >> +} > > [...] > > Kind regards > Uffe > -- To unsubscribe from this list: send the line "unsubscribe linux-clk" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On 14/02/17 19:01, Gregory CLEMENT wrote: > From: Hu Ziji <huziji@marvell.com> > > Marvell Xenon eMMC/SD/SDIO Host Controller contains PHY. > Multiple types of PHYs are supported. > > Add support to multiple types of PHYs init and configuration. > Add register definitions of PHYs. > > Xenon PHY cannot fit in kernel common PHY framework. > Xenon SDHC PHY register is a part of Xenon SDHC register set. > Besides, MMC initialization has to call several PHY functions to > complete timing setting. > Those PHY setting functions have to access SDHC registers and know > current MMC setting, such as bus width, clock frequency and > speed mode. > As a result, implement Xenon PHY in MMC host directory. > > Signed-off-by: Hu Ziji <huziji@marvell.com> > Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> A couple of minor comments below. > --- > drivers/mmc/host/Makefile | 2 +- > drivers/mmc/host/sdhci-xenon-phy.c | 751 ++++++++++++++++++++++++++++++- > drivers/mmc/host/sdhci-xenon.c | 3 +- > drivers/mmc/host/sdhci-xenon.h | 37 +- > 4 files changed, 791 insertions(+), 2 deletions(-) > create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c > > diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile > index b0a2ab4b256e..893b48db5513 100644 > --- a/drivers/mmc/host/Makefile > +++ b/drivers/mmc/host/Makefile > @@ -84,4 +84,4 @@ ifeq ($(CONFIG_CB710_DEBUG),y) > endif > > obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o > -sdhci-xenon-driver-y += sdhci-xenon.o > +sdhci-xenon-driver-y += sdhci-xenon.o sdhci-xenon-phy.o > diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c > new file mode 100644 > index 000000000000..c26ba3a180a0 > --- /dev/null > +++ b/drivers/mmc/host/sdhci-xenon-phy.c > @@ -0,0 +1,751 @@ > +/* > + * PHY support for Xenon SDHC > + * > + * Copyright (C) 2016 Marvell, All Rights Reserved. > + * > + * Author: Hu Ziji <huziji@marvell.com> > + * Date: 2016-8-24 > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License as > + * published by the Free Software Foundation version 2. > + */ > + > +#include <linux/slab.h> > +#include <linux/delay.h> > +#include <linux/of_address.h> > + > +#include "sdhci-pltfm.h" > +#include "sdhci-xenon.h" > + > +/* Register base for eMMC PHY 5.0 Version */ > +#define XENON_EMMC_5_0_PHY_REG_BASE 0x0160 > +/* Register base for eMMC PHY 5.1 Version */ > +#define XENON_EMMC_PHY_REG_BASE 0x0170 > + > +#define XENON_EMMC_PHY_TIMING_ADJUST XENON_EMMC_PHY_REG_BASE > +#define XENON_EMMC_5_0_PHY_TIMING_ADJUST XENON_EMMC_5_0_PHY_REG_BASE > +#define XENON_TIMING_ADJUST_SLOW_MODE BIT(29) > +#define XENON_TIMING_ADJUST_SDIO_MODE BIT(28) > +#define XENON_OUTPUT_QSN_PHASE_SELECT BIT(17) > +#define XENON_SAMPL_INV_QSP_PHASE_SELECT BIT(18) > +#define XENON_SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18 > +#define XENON_PHY_INITIALIZAION BIT(31) > +#define XENON_WAIT_CYCLE_BEFORE_USING_MASK 0xF > +#define XENON_WAIT_CYCLE_BEFORE_USING_SHIFT 12 > +#define XENON_FC_SYNC_EN_DURATION_MASK 0xF > +#define XENON_FC_SYNC_EN_DURATION_SHIFT 8 > +#define XENON_FC_SYNC_RST_EN_DURATION_MASK 0xF > +#define XENON_FC_SYNC_RST_EN_DURATION_SHIFT 4 > +#define XENON_FC_SYNC_RST_DURATION_MASK 0xF > +#define XENON_FC_SYNC_RST_DURATION_SHIFT 0 > + > +#define XENON_EMMC_PHY_FUNC_CONTROL (XENON_EMMC_PHY_REG_BASE + 0x4) > +#define XENON_EMMC_5_0_PHY_FUNC_CONTROL \ > + (XENON_EMMC_5_0_PHY_REG_BASE + 0x4) > +#define XENON_ASYNC_DDRMODE_MASK BIT(23) > +#define XENON_ASYNC_DDRMODE_SHIFT 23 > +#define XENON_CMD_DDR_MODE BIT(16) > +#define XENON_DQ_DDR_MODE_SHIFT 8 > +#define XENON_DQ_DDR_MODE_MASK 0xFF > +#define XENON_DQ_ASYNC_MODE BIT(4) > + > +#define XENON_EMMC_PHY_PAD_CONTROL (XENON_EMMC_PHY_REG_BASE + 0x8) > +#define XENON_EMMC_5_0_PHY_PAD_CONTROL \ > + (XENON_EMMC_5_0_PHY_REG_BASE + 0x8) > +#define XENON_REC_EN_SHIFT 24 > +#define XENON_REC_EN_MASK 0xF > +#define XENON_FC_DQ_RECEN BIT(24) > +#define XENON_FC_CMD_RECEN BIT(25) > +#define XENON_FC_QSP_RECEN BIT(26) > +#define XENON_FC_QSN_RECEN BIT(27) > +#define XENON_OEN_QSN BIT(28) > +#define XENON_AUTO_RECEN_CTRL BIT(30) > +#define XENON_FC_ALL_CMOS_RECEIVER 0xF000 > + > +#define XENON_EMMC5_FC_QSP_PD BIT(18) > +#define XENON_EMMC5_FC_QSP_PU BIT(22) > +#define XENON_EMMC5_FC_CMD_PD BIT(17) > +#define XENON_EMMC5_FC_CMD_PU BIT(21) > +#define XENON_EMMC5_FC_DQ_PD BIT(16) > +#define XENON_EMMC5_FC_DQ_PU BIT(20) > + > +#define XENON_EMMC_PHY_PAD_CONTROL1 (XENON_EMMC_PHY_REG_BASE + 0xC) > +#define XENON_EMMC5_1_FC_QSP_PD BIT(9) > +#define XENON_EMMC5_1_FC_QSP_PU BIT(25) > +#define XENON_EMMC5_1_FC_CMD_PD BIT(8) > +#define XENON_EMMC5_1_FC_CMD_PU BIT(24) > +#define XENON_EMMC5_1_FC_DQ_PD 0xFF > +#define XENON_EMMC5_1_FC_DQ_PU (0xFF << 16) > + > +#define XENON_EMMC_PHY_PAD_CONTROL2 (XENON_EMMC_PHY_REG_BASE + 0x10) > +#define XENON_EMMC_5_0_PHY_PAD_CONTROL2 \ > + (XENON_EMMC_5_0_PHY_REG_BASE + 0xC) > +#define XENON_ZNR_MASK 0x1F > +#define XENON_ZNR_SHIFT 8 > +#define XENON_ZPR_MASK 0x1F > +/* Preferred ZNR and ZPR value vary between different boards. > + * The specific ZNR and ZPR value should be defined here > + * according to board actual timing. > + */ > +#define XENON_ZNR_DEF_VALUE 0xF > +#define XENON_ZPR_DEF_VALUE 0xF > + > +#define XENON_EMMC_PHY_DLL_CONTROL (XENON_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_EMMC_5_0_PHY_LOGIC_TIMING_ADJUST \ > + (XENON_EMMC_5_0_PHY_REG_BASE + 0x14) > +#define XENON_EMMC_PHY_LOGIC_TIMING_ADJUST (XENON_EMMC_PHY_REG_BASE + 0x18) > +#define XENON_LOGIC_TIMING_VALUE 0x00AA8977 > + > +/* > + * List offset of PHY registers and some special register values > + * in eMMC PHY 5.0 or eMMC PHY 5.1 > + */ > +struct xenon_emmc_phy_regs { > + /* Offset of Timing Adjust register */ > + u16 timing_adj; > + /* Offset of Func Control register */ > + u16 func_ctrl; > + /* Offset of Pad Control register */ > + u16 pad_ctrl; > + /* Offset of Pad Control register 2 */ > + u16 pad_ctrl2; > + /* Offset of DLL Control register */ > + u16 dll_ctrl; > + /* Offset of Logic Timing Adjust register */ > + u16 logic_timing_adj; > + /* DLL Update Enable bit */ > + u32 dll_update; > +}; > + > +static const char * const phy_types[] = { > + "emmc 5.0 phy", > + "emmc 5.1 phy" > +}; > + > +enum phy_type_enum { > + EMMC_5_0_PHY, > + EMMC_5_1_PHY, > + NR_PHY_TYPES > +}; > + > +static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = { > + .timing_adj = XENON_EMMC_5_0_PHY_TIMING_ADJUST, > + .func_ctrl = XENON_EMMC_5_0_PHY_FUNC_CONTROL, > + .pad_ctrl = XENON_EMMC_5_0_PHY_PAD_CONTROL, > + .pad_ctrl2 = XENON_EMMC_5_0_PHY_PAD_CONTROL2, > + .dll_ctrl = XENON_EMMC_5_0_PHY_DLL_CONTROL, > + .logic_timing_adj = XENON_EMMC_5_0_PHY_LOGIC_TIMING_ADJUST, > + .dll_update = XENON_DLL_UPDATE_STROBE_5_0, > +}; > + > +static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = { > + .timing_adj = XENON_EMMC_PHY_TIMING_ADJUST, > + .func_ctrl = XENON_EMMC_PHY_FUNC_CONTROL, > + .pad_ctrl = XENON_EMMC_PHY_PAD_CONTROL, > + .pad_ctrl2 = XENON_EMMC_PHY_PAD_CONTROL2, > + .dll_ctrl = XENON_EMMC_PHY_DLL_CONTROL, > + .logic_timing_adj = XENON_EMMC_PHY_LOGIC_TIMING_ADJUST, > + .dll_update = XENON_DLL_UPDATE, > +}; > + > +/* > + * eMMC PHY configuration and operations > + */ > +struct emmc_phy_params { > + bool slow_mode; > + > + u8 znr; > + u8 zpr; > + > + /* Nr of consecutive Sampling Points of a Valid Sampling Window */ > + u8 nr_tun_times; > + /* Divider for calculating Tuning Step */ > + u8 tun_step_divider; > +}; > + > +static int alloc_emmc_phy(struct sdhci_xenon_priv *priv) > +{ > + struct emmc_phy_params *params; > + > + params = kzalloc(sizeof(*params), GFP_KERNEL); > + if (!params) > + return -ENOMEM; > + > + priv->phy_params = params; Doesn't phy_params need to be freed e.g. when sdhci_xenon_remove is called()? > + if (priv->phy_type == EMMC_5_0_PHY) > + priv->emmc_phy_regs = &xenon_emmc_5_0_phy_regs; > + else > + priv->emmc_phy_regs = &xenon_emmc_5_1_phy_regs; > + > + return 0; > +} > + > +/* > + * eMMC 5.0/5.1 PHY init/re-init. > + * eMMC PHY init should be executed after: > + * 1. SDCLK frequency changes. > + * 2. SDCLK is stopped and re-enabled. > + * 3. config in emmc_phy_regs->timing_adj and emmc_phy_regs->func_ctrl > + * are changed > + */ > +static int emmc_phy_init(struct sdhci_host *host) > +{ > + u32 reg; > + u32 wait, clock; > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; > + > + reg = sdhci_readl(host, phy_regs->timing_adj); > + reg |= XENON_PHY_INITIALIZAION; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + > + /* Add duration of FC_SYNC_RST */ > + wait = ((reg >> XENON_FC_SYNC_RST_DURATION_SHIFT) & > + XENON_FC_SYNC_RST_DURATION_MASK); > + /* Add interval between FC_SYNC_EN and FC_SYNC_RST */ > + wait += ((reg >> XENON_FC_SYNC_RST_EN_DURATION_SHIFT) & > + XENON_FC_SYNC_RST_EN_DURATION_MASK); > + /* Add duration of asserting FC_SYNC_EN */ > + wait += ((reg >> XENON_FC_SYNC_EN_DURATION_SHIFT) & > + XENON_FC_SYNC_EN_DURATION_MASK); > + /* Add duration of waiting for PHY */ > + wait += ((reg >> XENON_WAIT_CYCLE_BEFORE_USING_SHIFT) & > + XENON_WAIT_CYCLE_BEFORE_USING_MASK); > + /* 4 additional bus clock and 4 AXI bus clock are required */ > + wait += 8; > + wait <<= 20; > + > + clock = host->clock; > + if (!clock) > + /* Use the possibly slowest bus frequency value */ > + clock = XENON_LOWEST_SDCLK_FREQ; > + /* get the wait time */ > + wait /= clock; > + wait++; > + /* wait for host eMMC PHY init completes */ > + udelay(wait); > + > + reg = sdhci_readl(host, phy_regs->timing_adj); > + reg &= XENON_PHY_INITIALIZAION; > + if (reg) { > + dev_err(mmc_dev(host->mmc), "eMMC PHY init cannot complete after %d us\n", > + wait); > + return -ETIMEDOUT; > + } > + > + return 0; > +} > + > +/* > + * Enable eMMC PHY HW DLL > + * DLL should be enabled and stable before HS200/SDR104 tuning, > + * and before HS400 data strobe setting. > + */ > +static int emmc_phy_enable_dll(struct sdhci_host *host) > +{ > + u32 reg; > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; > + u8 timeout; > + > + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR)) > + return -EINVAL; > + > + reg = sdhci_readl(host, phy_regs->dll_ctrl); > + if (reg & XENON_DLL_ENABLE) > + return 0; > + > + /* Enable DLL */ > + reg = sdhci_readl(host, phy_regs->dll_ctrl); > + 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; > + reg |= phy_regs->dll_update; > + if (priv->phy_type == EMMC_5_1_PHY) > + reg &= ~XENON_DLL_REFCLK_SEL; > + sdhci_writel(host, reg, phy_regs->dll_ctrl); > + > + /* Wait max 32 ms */ > + timeout = 32; > + while (!(sdhci_readw(host, XENON_SLOT_EXT_PRESENT_STATE) & > + XENON_DLL_LOCK_STATE)) { > + if (!timeout) { > + dev_err(mmc_dev(host->mmc), "Wait for DLL Lock time-out\n"); > + return -ETIMEDOUT; > + } > + timeout--; > + mdelay(1); > + } > + return 0; > +} > + > +/* > + * Config to eMMC PHY to prepare for tuning. > + * Enable HW DLL and set the TUNING_STEP > + */ > +static int emmc_phy_config_tuning(struct sdhci_host *host) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + struct emmc_phy_params *params = priv->phy_params; > + u32 reg, tuning_step; > + int ret; > + unsigned long flags; > + > + if (host->clock <= MMC_HIGH_52_MAX_DTR) > + return -EINVAL; > + > + spin_lock_irqsave(&host->lock, flags); I have eliminated the spin lock from set_ios and reduced its usage in sdhci_execute_tuning. You might want to review whether you really need the spin lock here and elsewhere in this file. > + > + ret = emmc_phy_enable_dll(host); > + if (ret) { > + spin_unlock_irqrestore(&host->lock, flags); > + return ret; > + } > + > + /* Achieve TUNING_STEP with HW DLL help */ > + reg = sdhci_readl(host, XENON_SLOT_DLL_CUR_DLY_VAL); > + tuning_step = reg / params->tun_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 |= (params->nr_tun_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); > + > + spin_unlock_irqrestore(&host->lock, flags); > + return 0; > +} > + > +static void __emmc_phy_disable_data_strobe(struct sdhci_host *host) > +{ > + u32 reg; > + > + /* Disable SDHC Data Strobe */ > + reg = sdhci_readl(host, XENON_SLOT_EMMC_CTRL); > + reg &= ~XENON_ENABLE_DATA_STROBE; > + sdhci_writel(host, reg, XENON_SLOT_EMMC_CTRL); > +} > + > +/* Set HS400 Data Strobe */ > +static void emmc_phy_strobe_delay_adj(struct sdhci_host *host) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + unsigned long flags; > + u32 reg; > + > + if (WARN_ON(host->timing != MMC_TIMING_MMC_HS400)) > + return; > + > + if (host->clock <= MMC_HIGH_52_MAX_DTR) > + return; > + > + dev_dbg(mmc_dev(host->mmc), "starts HS400 strobe delay adjustment\n"); > + > + spin_lock_irqsave(&host->lock, flags); > + > + emmc_phy_enable_dll(host); > + > + /* Enable SDHC Data Strobe */ > + reg = sdhci_readl(host, XENON_SLOT_EMMC_CTRL); > + reg |= XENON_ENABLE_DATA_STROBE; > + sdhci_writel(host, reg, XENON_SLOT_EMMC_CTRL); > + > + /* Set Data Strobe Pull down */ > + if (priv->phy_type == EMMC_5_0_PHY) { > + reg = sdhci_readl(host, XENON_EMMC_5_0_PHY_PAD_CONTROL); > + reg |= XENON_EMMC5_FC_QSP_PD; > + reg &= ~XENON_EMMC5_FC_QSP_PU; > + sdhci_writel(host, reg, XENON_EMMC_5_0_PHY_PAD_CONTROL); > + } else { > + reg = sdhci_readl(host, XENON_EMMC_PHY_PAD_CONTROL1); > + reg |= XENON_EMMC5_1_FC_QSP_PD; > + reg &= ~XENON_EMMC5_1_FC_QSP_PU; > + sdhci_writel(host, reg, XENON_EMMC_PHY_PAD_CONTROL1); > + } > + spin_unlock_irqrestore(&host->lock, flags); > +} > + > +/* > + * 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 true. > + * Otherwise, return false. > + */ > +static bool emmc_phy_slow_mode(struct sdhci_host *host, > + unsigned char timing) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + struct emmc_phy_params *params = priv->phy_params; > + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; > + u32 reg; > + > + if (host->clock > MMC_HIGH_52_MAX_DTR) > + return false; > + > + reg = sdhci_readl(host, phy_regs->timing_adj); > + /* Enable Slow Mode for SDIO in slower SDR mode */ > + if ((priv->init_card_type == MMC_TYPE_SDIO) && > + ((timing == MMC_TIMING_UHS_SDR25) || > + (timing == MMC_TIMING_UHS_SDR12) || > + (timing == MMC_TIMING_SD_HS))) { > + reg |= XENON_TIMING_ADJUST_SLOW_MODE; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + return true; > + } > + > + /* Check if Slow Mode is required in lower speed mode in SDR mode */ > + if (((timing == MMC_TIMING_UHS_SDR25) || > + (timing == MMC_TIMING_UHS_SDR12) || > + (timing == MMC_TIMING_SD_HS) || > + (timing == MMC_TIMING_MMC_HS)) && params->slow_mode) { > + reg |= XENON_TIMING_ADJUST_SLOW_MODE; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + return true; > + } > + > + reg &= ~XENON_TIMING_ADJUST_SLOW_MODE; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + return false; > +} > + > +/* > + * Set-up eMMC 5.0/5.1 PHY. > + * Specific configuration depends on the current speed mode in use. > + */ > +static void emmc_phy_set(struct sdhci_host *host, > + unsigned char timing) > +{ > + u32 reg; > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + struct emmc_phy_params *params = priv->phy_params; > + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; > + unsigned long flags; > + > + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting starts\n"); > + > + spin_lock_irqsave(&host->lock, flags); > + > + /* Setup pad, set bit[28] and bits[26:24] */ > + reg = sdhci_readl(host, phy_regs->pad_ctrl); > + reg |= (XENON_FC_DQ_RECEN | XENON_FC_CMD_RECEN | > + XENON_FC_QSP_RECEN | XENON_OEN_QSN); > + /* All FC_XX_RECEIVCE should be set as CMOS Type */ > + reg |= XENON_FC_ALL_CMOS_RECEIVER; > + sdhci_writel(host, reg, phy_regs->pad_ctrl); > + > + /* Set CMD and DQ Pull Up */ > + if (priv->phy_type == EMMC_5_0_PHY) { > + reg = sdhci_readl(host, XENON_EMMC_5_0_PHY_PAD_CONTROL); > + reg |= (XENON_EMMC5_FC_CMD_PU | XENON_EMMC5_FC_DQ_PU); > + reg &= ~(XENON_EMMC5_FC_CMD_PD | XENON_EMMC5_FC_DQ_PD); > + sdhci_writel(host, reg, XENON_EMMC_5_0_PHY_PAD_CONTROL); > + } else { > + reg = sdhci_readl(host, XENON_EMMC_PHY_PAD_CONTROL1); > + reg |= (XENON_EMMC5_1_FC_CMD_PU | XENON_EMMC5_1_FC_DQ_PU); > + reg &= ~(XENON_EMMC5_1_FC_CMD_PD | XENON_EMMC5_1_FC_DQ_PD); > + sdhci_writel(host, reg, XENON_EMMC_PHY_PAD_CONTROL1); > + } > + > + if (timing == MMC_TIMING_LEGACY) { > + /* > + * If Slow Mode is required, enable Slow Mode by default > + * in early init phase to avoid any potential issue. > + */ > + if (params->slow_mode) { > + reg = sdhci_readl(host, phy_regs->timing_adj); > + reg |= XENON_TIMING_ADJUST_SLOW_MODE; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + } > + goto phy_init; > + } > + > + /* > + * FIXME: should depends on the specific board timing. > + */ > + if ((timing == MMC_TIMING_MMC_HS400) || > + (timing == MMC_TIMING_MMC_HS200) || > + (timing == MMC_TIMING_UHS_SDR50) || > + (timing == MMC_TIMING_UHS_SDR104) || > + (timing == MMC_TIMING_UHS_DDR50) || > + (timing == MMC_TIMING_UHS_SDR25) || > + (timing == MMC_TIMING_MMC_DDR52)) { > + reg = sdhci_readl(host, phy_regs->timing_adj); > + reg &= ~XENON_OUTPUT_QSN_PHASE_SELECT; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + } > + > + /* > + * If SDIO card, set SDIO Mode > + * Otherwise, clear SDIO Mode > + */ > + reg = sdhci_readl(host, phy_regs->timing_adj); > + if (priv->init_card_type == MMC_TYPE_SDIO) > + reg |= XENON_TIMING_ADJUST_SDIO_MODE; > + else > + reg &= ~XENON_TIMING_ADJUST_SDIO_MODE; > + sdhci_writel(host, reg, phy_regs->timing_adj); > + > + if (emmc_phy_slow_mode(host, timing)) > + goto phy_init; > + > + /* > + * Set preferred ZNR and ZPR value > + * The ZNR and ZPR value vary between different boards. > + * Define them both in sdhci-xenon-emmc-phy.h. > + */ > + reg = sdhci_readl(host, phy_regs->pad_ctrl2); > + reg &= ~((XENON_ZNR_MASK << XENON_ZNR_SHIFT) | XENON_ZPR_MASK); > + reg |= ((params->znr << XENON_ZNR_SHIFT) | params->zpr); > + sdhci_writel(host, reg, phy_regs->pad_ctrl2); > + > + /* > + * When setting EMMC_PHY_FUNC_CONTROL register, > + * SD clock should be disabled > + */ > + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); > + reg &= ~SDHCI_CLOCK_CARD_EN; > + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL); > + > + reg = sdhci_readl(host, phy_regs->func_ctrl); > + if ((timing == MMC_TIMING_UHS_DDR50) || > + (timing == MMC_TIMING_MMC_HS400) || > + (timing == MMC_TIMING_MMC_DDR52)) > + reg |= (XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | > + XENON_CMD_DDR_MODE; > + else > + reg &= ~((XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | > + XENON_CMD_DDR_MODE); > + > + if (timing == MMC_TIMING_MMC_HS400) > + reg &= ~XENON_DQ_ASYNC_MODE; > + else > + reg |= XENON_DQ_ASYNC_MODE; > + sdhci_writel(host, reg, phy_regs->func_ctrl); > + > + /* Enable bus clock */ > + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); > + reg |= SDHCI_CLOCK_CARD_EN; > + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL); > + > + if (timing == MMC_TIMING_MMC_HS400) > + /* Hardware team recommend a value for HS400 */ > + sdhci_writel(host, XENON_LOGIC_TIMING_VALUE, > + phy_regs->logic_timing_adj); > + else > + __emmc_phy_disable_data_strobe(host); > + > +phy_init: > + emmc_phy_init(host); > + > + spin_unlock_irqrestore(&host->lock, flags); > + > + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n"); > +} > + > +static int emmc_phy_parse_param_dt(struct sdhci_host *host, > + struct device_node *np, > + struct emmc_phy_params *params) > +{ > + u32 value; > + > + if (of_property_read_bool(np, "marvell,xenon-phy-slow-mode")) > + params->slow_mode = true; > + else > + params->slow_mode = false; > + > + if (!of_property_read_u32(np, "marvell,xenon-phy-znr", &value)) > + params->znr = value & XENON_ZNR_MASK; > + else > + params->znr = XENON_ZNR_DEF_VALUE; > + > + if (!of_property_read_u32(np, "marvell,xenon-phy-zpr", &value)) > + params->zpr = value & XENON_ZPR_MASK; > + else > + params->zpr = XENON_ZPR_DEF_VALUE; > + > + if (!of_property_read_u32(np, "marvell,xenon-phy-nr-success-tun", > + &value)) > + params->nr_tun_times = value & XENON_TUN_CONSECUTIVE_TIMES_MASK; > + else > + params->nr_tun_times = XENON_TUN_CONSECUTIVE_TIMES; > + > + if (!of_property_read_u32(np, "marvell,xenon-phy-tun-step-divider", > + &value)) > + params->tun_step_divider = value & 0xFF; > + else > + params->tun_step_divider = XENON_TUNING_STEP_DIVIDER; > + > + return 0; > +} > + > +/* > + * Setting PHY when card is working in High Speed Mode. > + * HS400 set data strobe line. > + * HS200/SDR104 set tuning config to prepare for tuning. > + */ > +static int xenon_hs_delay_adj(struct sdhci_host *host) > +{ > + int ret = 0; > + > + if (WARN_ON(host->clock <= XENON_DEFAULT_SDCLK_FREQ)) > + return -EINVAL; > + > + if (host->timing == MMC_TIMING_MMC_HS400) { > + emmc_phy_strobe_delay_adj(host); > + return 0; > + } > + > + if ((host->timing == MMC_TIMING_MMC_HS200) || > + (host->timing == MMC_TIMING_UHS_SDR104)) > + return emmc_phy_config_tuning(host); > + > + /* > + * DDR Mode requires driver to scan Sampling Fixed Delay Line, > + * to find out a perfect operation sampling point. > + * It is hard to implement such a scan in host driver since initiating > + * commands by host driver is not safe. > + * Thus so far just keep PHY Sampling Fixed Delay in default value > + * in DDR mode. > + * > + * If any timing issue occurs in DDR mode on Marvell products, > + * please contact maintainer to ask for internal support in Marvell. > + */ > + if ((host->timing == MMC_TIMING_MMC_DDR52) || > + (host->timing == MMC_TIMING_UHS_DDR50)) > + dev_warn_once(mmc_dev(host->mmc), "Timing issue might occur in DDR mode\n"); > + return ret; > +} > + > +/* > + * Adjust PHY setting. > + * PHY setting should be adjusted when SDCLK frequency, Bus Width > + * or Speed Mode is changed. > + * Additional config are required when card is working in High Speed mode, > + * after leaving Legacy Mode. > + */ > +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + int ret = 0; > + > + if (!host->clock) { > + priv->clock = 0; > + return 0; > + } > + > + /* > + * The timing, frequency or bus width is changed, > + * better to set eMMC PHY based on current setting > + * and adjust Xenon SDHC delay. > + */ > + if ((host->clock == priv->clock) && > + (ios->bus_width == priv->bus_width) && > + (ios->timing == priv->timing)) > + return 0; > + > + emmc_phy_set(host, ios->timing); > + > + /* Update the record */ > + priv->bus_width = ios->bus_width; > + > + priv->timing = ios->timing; > + priv->clock = host->clock; > + > + /* Legacy mode is a special case */ > + if (ios->timing == MMC_TIMING_LEGACY) > + return 0; > + > + if (host->clock > XENON_DEFAULT_SDCLK_FREQ) > + ret = xenon_hs_delay_adj(host); > + return ret; > +} > + > +static void clean_emmc_phy(struct sdhci_xenon_priv *priv) > +{ > + kfree(priv->phy_params); > +} > + > +static int add_xenon_phy(struct device_node *np, struct sdhci_host *host, > + const char *phy_name) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + int i, ret; > + > + for (i = 0; i < NR_PHY_TYPES; i++) { > + if (!strcmp(phy_name, phy_types[i])) { > + priv->phy_type = i; > + break; > + } > + } > + if (i == NR_PHY_TYPES) { > + dev_err(mmc_dev(host->mmc), > + "Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n", > + phy_name); > + priv->phy_type = EMMC_5_1_PHY; > + } > + > + ret = alloc_emmc_phy(priv); > + if (ret) > + return ret; > + > + ret = emmc_phy_parse_param_dt(host, np, priv->phy_params); > + if (ret) > + clean_emmc_phy(priv); > + > + return ret; > +} > + > +int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host) > +{ > + const char *phy_type = NULL; > + > + if (!of_property_read_string(np, "marvell,xenon-phy-type", &phy_type)) > + return add_xenon_phy(np, host, phy_type); > + > + dev_info(mmc_dev(host->mmc), "Fail to get Xenon PHY type. Use default eMMC 5.1 PHY\n"); > + return add_xenon_phy(np, host, "emmc 5.1 phy"); > +} > diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c > index e633f803907a..99c18cad6460 100644 > --- a/drivers/mmc/host/sdhci-xenon.c > +++ b/drivers/mmc/host/sdhci-xenon.c > @@ -260,6 +260,7 @@ static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) > spin_unlock_irqrestore(&host->lock, flags); > > sdhci_set_ios(mmc, ios); > + xenon_phy_adj(host, ios); > > if (host->clock > XENON_DEFAULT_SDCLK_FREQ) { > spin_lock_irqsave(&host->lock, flags); > @@ -465,7 +466,7 @@ static int xenon_probe_dt(struct platform_device *pdev) > } > priv->tuning_count = tuning_count; > > - return 0; > + return xenon_phy_parse_dt(np, host); > } > > static int xenon_sdhc_probe(struct sdhci_host *host) > diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h > index 69de711db9eb..e87639774bca 100644 > --- a/drivers/mmc/host/sdhci-xenon.h > +++ b/drivers/mmc/host/sdhci-xenon.h > @@ -24,7 +24,17 @@ > #define XENON_SYS_EXT_OP_CTRL 0x010C > #define XENON_MASK_CMD_CONFLICT_ERR BIT(8) > > +#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_SLOT_EMMC_CTRL 0x0130 > +#define XENON_ENABLE_DATA_STROBE BIT(24) > #define XENON_EMMC_VCCQ_MASK 0x3 > #define XENON_EMMC_VCCQ_1_8V 0x1 > #define XENON_EMMC_VCCQ_3_3V 0x3 > @@ -33,11 +43,17 @@ > /* retuning compatible */ > #define XENON_RETUNING_COMPATIBLE 0x1 > > +#define XENON_SLOT_EXT_PRESENT_STATE 0x014C > +#define XENON_DLL_LOCK_STATE 0x1 > + > +#define XENON_SLOT_DLL_CUR_DLY_VAL 0x0150 > + > /* Tuning Parameter */ > #define XENON_TMR_RETUN_NO_PRESENT 0xF > #define XENON_DEF_TUNING_COUNT 0x9 > > #define XENON_DEFAULT_SDCLK_FREQ 400000 > +#define XENON_LOWEST_SDCLK_FREQ 100000 > > /* Xenon specific Mode Select value */ > #define XENON_CTRL_HS200 0x5 > @@ -65,6 +81,27 @@ struct sdhci_xenon_priv { > * initialization completes. > */ > unsigned int init_card_type; > + > + /* > + * The bus_width, timing, and clock fields in below > + * record the current ios setting of Xenon SDHC. > + * Driver will adjust PHY setting if any change to > + * ios affects PHY timing. > + */ > + unsigned char bus_width; > + unsigned char timing; > + unsigned int clock; > + > + int phy_type; > + /* > + * Contains board-specific PHY parameters > + * passed from device tree. > + */ > + void *phy_params; > + struct xenon_emmc_phy_regs *emmc_phy_regs; > }; > > +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios); > +int xenon_phy_parse_dt(struct device_node *np, > + struct sdhci_host *host); > #endif > -- To unsubscribe from this list: send the line "unsubscribe linux-clk" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index b0a2ab4b256e..893b48db5513 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -84,4 +84,4 @@ ifeq ($(CONFIG_CB710_DEBUG),y) endif obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o -sdhci-xenon-driver-y += sdhci-xenon.o +sdhci-xenon-driver-y += sdhci-xenon.o sdhci-xenon-phy.o diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c new file mode 100644 index 000000000000..c26ba3a180a0 --- /dev/null +++ b/drivers/mmc/host/sdhci-xenon-phy.c @@ -0,0 +1,751 @@ +/* + * PHY support for Xenon SDHC + * + * Copyright (C) 2016 Marvell, All Rights Reserved. + * + * Author: Hu Ziji <huziji@marvell.com> + * Date: 2016-8-24 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + */ + +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/of_address.h> + +#include "sdhci-pltfm.h" +#include "sdhci-xenon.h" + +/* Register base for eMMC PHY 5.0 Version */ +#define XENON_EMMC_5_0_PHY_REG_BASE 0x0160 +/* Register base for eMMC PHY 5.1 Version */ +#define XENON_EMMC_PHY_REG_BASE 0x0170 + +#define XENON_EMMC_PHY_TIMING_ADJUST XENON_EMMC_PHY_REG_BASE +#define XENON_EMMC_5_0_PHY_TIMING_ADJUST XENON_EMMC_5_0_PHY_REG_BASE +#define XENON_TIMING_ADJUST_SLOW_MODE BIT(29) +#define XENON_TIMING_ADJUST_SDIO_MODE BIT(28) +#define XENON_OUTPUT_QSN_PHASE_SELECT BIT(17) +#define XENON_SAMPL_INV_QSP_PHASE_SELECT BIT(18) +#define XENON_SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18 +#define XENON_PHY_INITIALIZAION BIT(31) +#define XENON_WAIT_CYCLE_BEFORE_USING_MASK 0xF +#define XENON_WAIT_CYCLE_BEFORE_USING_SHIFT 12 +#define XENON_FC_SYNC_EN_DURATION_MASK 0xF +#define XENON_FC_SYNC_EN_DURATION_SHIFT 8 +#define XENON_FC_SYNC_RST_EN_DURATION_MASK 0xF +#define XENON_FC_SYNC_RST_EN_DURATION_SHIFT 4 +#define XENON_FC_SYNC_RST_DURATION_MASK 0xF +#define XENON_FC_SYNC_RST_DURATION_SHIFT 0 + +#define XENON_EMMC_PHY_FUNC_CONTROL (XENON_EMMC_PHY_REG_BASE + 0x4) +#define XENON_EMMC_5_0_PHY_FUNC_CONTROL \ + (XENON_EMMC_5_0_PHY_REG_BASE + 0x4) +#define XENON_ASYNC_DDRMODE_MASK BIT(23) +#define XENON_ASYNC_DDRMODE_SHIFT 23 +#define XENON_CMD_DDR_MODE BIT(16) +#define XENON_DQ_DDR_MODE_SHIFT 8 +#define XENON_DQ_DDR_MODE_MASK 0xFF +#define XENON_DQ_ASYNC_MODE BIT(4) + +#define XENON_EMMC_PHY_PAD_CONTROL (XENON_EMMC_PHY_REG_BASE + 0x8) +#define XENON_EMMC_5_0_PHY_PAD_CONTROL \ + (XENON_EMMC_5_0_PHY_REG_BASE + 0x8) +#define XENON_REC_EN_SHIFT 24 +#define XENON_REC_EN_MASK 0xF +#define XENON_FC_DQ_RECEN BIT(24) +#define XENON_FC_CMD_RECEN BIT(25) +#define XENON_FC_QSP_RECEN BIT(26) +#define XENON_FC_QSN_RECEN BIT(27) +#define XENON_OEN_QSN BIT(28) +#define XENON_AUTO_RECEN_CTRL BIT(30) +#define XENON_FC_ALL_CMOS_RECEIVER 0xF000 + +#define XENON_EMMC5_FC_QSP_PD BIT(18) +#define XENON_EMMC5_FC_QSP_PU BIT(22) +#define XENON_EMMC5_FC_CMD_PD BIT(17) +#define XENON_EMMC5_FC_CMD_PU BIT(21) +#define XENON_EMMC5_FC_DQ_PD BIT(16) +#define XENON_EMMC5_FC_DQ_PU BIT(20) + +#define XENON_EMMC_PHY_PAD_CONTROL1 (XENON_EMMC_PHY_REG_BASE + 0xC) +#define XENON_EMMC5_1_FC_QSP_PD BIT(9) +#define XENON_EMMC5_1_FC_QSP_PU BIT(25) +#define XENON_EMMC5_1_FC_CMD_PD BIT(8) +#define XENON_EMMC5_1_FC_CMD_PU BIT(24) +#define XENON_EMMC5_1_FC_DQ_PD 0xFF +#define XENON_EMMC5_1_FC_DQ_PU (0xFF << 16) + +#define XENON_EMMC_PHY_PAD_CONTROL2 (XENON_EMMC_PHY_REG_BASE + 0x10) +#define XENON_EMMC_5_0_PHY_PAD_CONTROL2 \ + (XENON_EMMC_5_0_PHY_REG_BASE + 0xC) +#define XENON_ZNR_MASK 0x1F +#define XENON_ZNR_SHIFT 8 +#define XENON_ZPR_MASK 0x1F +/* Preferred ZNR and ZPR value vary between different boards. + * The specific ZNR and ZPR value should be defined here + * according to board actual timing. + */ +#define XENON_ZNR_DEF_VALUE 0xF +#define XENON_ZPR_DEF_VALUE 0xF + +#define XENON_EMMC_PHY_DLL_CONTROL (XENON_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_EMMC_5_0_PHY_LOGIC_TIMING_ADJUST \ + (XENON_EMMC_5_0_PHY_REG_BASE + 0x14) +#define XENON_EMMC_PHY_LOGIC_TIMING_ADJUST (XENON_EMMC_PHY_REG_BASE + 0x18) +#define XENON_LOGIC_TIMING_VALUE 0x00AA8977 + +/* + * List offset of PHY registers and some special register values + * in eMMC PHY 5.0 or eMMC PHY 5.1 + */ +struct xenon_emmc_phy_regs { + /* Offset of Timing Adjust register */ + u16 timing_adj; + /* Offset of Func Control register */ + u16 func_ctrl; + /* Offset of Pad Control register */ + u16 pad_ctrl; + /* Offset of Pad Control register 2 */ + u16 pad_ctrl2; + /* Offset of DLL Control register */ + u16 dll_ctrl; + /* Offset of Logic Timing Adjust register */ + u16 logic_timing_adj; + /* DLL Update Enable bit */ + u32 dll_update; +}; + +static const char * const phy_types[] = { + "emmc 5.0 phy", + "emmc 5.1 phy" +}; + +enum phy_type_enum { + EMMC_5_0_PHY, + EMMC_5_1_PHY, + NR_PHY_TYPES +}; + +static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = { + .timing_adj = XENON_EMMC_5_0_PHY_TIMING_ADJUST, + .func_ctrl = XENON_EMMC_5_0_PHY_FUNC_CONTROL, + .pad_ctrl = XENON_EMMC_5_0_PHY_PAD_CONTROL, + .pad_ctrl2 = XENON_EMMC_5_0_PHY_PAD_CONTROL2, + .dll_ctrl = XENON_EMMC_5_0_PHY_DLL_CONTROL, + .logic_timing_adj = XENON_EMMC_5_0_PHY_LOGIC_TIMING_ADJUST, + .dll_update = XENON_DLL_UPDATE_STROBE_5_0, +}; + +static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = { + .timing_adj = XENON_EMMC_PHY_TIMING_ADJUST, + .func_ctrl = XENON_EMMC_PHY_FUNC_CONTROL, + .pad_ctrl = XENON_EMMC_PHY_PAD_CONTROL, + .pad_ctrl2 = XENON_EMMC_PHY_PAD_CONTROL2, + .dll_ctrl = XENON_EMMC_PHY_DLL_CONTROL, + .logic_timing_adj = XENON_EMMC_PHY_LOGIC_TIMING_ADJUST, + .dll_update = XENON_DLL_UPDATE, +}; + +/* + * eMMC PHY configuration and operations + */ +struct emmc_phy_params { + bool slow_mode; + + u8 znr; + u8 zpr; + + /* Nr of consecutive Sampling Points of a Valid Sampling Window */ + u8 nr_tun_times; + /* Divider for calculating Tuning Step */ + u8 tun_step_divider; +}; + +static int alloc_emmc_phy(struct sdhci_xenon_priv *priv) +{ + struct emmc_phy_params *params; + + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + priv->phy_params = params; + if (priv->phy_type == EMMC_5_0_PHY) + priv->emmc_phy_regs = &xenon_emmc_5_0_phy_regs; + else + priv->emmc_phy_regs = &xenon_emmc_5_1_phy_regs; + + return 0; +} + +/* + * eMMC 5.0/5.1 PHY init/re-init. + * eMMC PHY init should be executed after: + * 1. SDCLK frequency changes. + * 2. SDCLK is stopped and re-enabled. + * 3. config in emmc_phy_regs->timing_adj and emmc_phy_regs->func_ctrl + * are changed + */ +static int emmc_phy_init(struct sdhci_host *host) +{ + u32 reg; + u32 wait, clock; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; + + reg = sdhci_readl(host, phy_regs->timing_adj); + reg |= XENON_PHY_INITIALIZAION; + sdhci_writel(host, reg, phy_regs->timing_adj); + + /* Add duration of FC_SYNC_RST */ + wait = ((reg >> XENON_FC_SYNC_RST_DURATION_SHIFT) & + XENON_FC_SYNC_RST_DURATION_MASK); + /* Add interval between FC_SYNC_EN and FC_SYNC_RST */ + wait += ((reg >> XENON_FC_SYNC_RST_EN_DURATION_SHIFT) & + XENON_FC_SYNC_RST_EN_DURATION_MASK); + /* Add duration of asserting FC_SYNC_EN */ + wait += ((reg >> XENON_FC_SYNC_EN_DURATION_SHIFT) & + XENON_FC_SYNC_EN_DURATION_MASK); + /* Add duration of waiting for PHY */ + wait += ((reg >> XENON_WAIT_CYCLE_BEFORE_USING_SHIFT) & + XENON_WAIT_CYCLE_BEFORE_USING_MASK); + /* 4 additional bus clock and 4 AXI bus clock are required */ + wait += 8; + wait <<= 20; + + clock = host->clock; + if (!clock) + /* Use the possibly slowest bus frequency value */ + clock = XENON_LOWEST_SDCLK_FREQ; + /* get the wait time */ + wait /= clock; + wait++; + /* wait for host eMMC PHY init completes */ + udelay(wait); + + reg = sdhci_readl(host, phy_regs->timing_adj); + reg &= XENON_PHY_INITIALIZAION; + if (reg) { + dev_err(mmc_dev(host->mmc), "eMMC PHY init cannot complete after %d us\n", + wait); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * Enable eMMC PHY HW DLL + * DLL should be enabled and stable before HS200/SDR104 tuning, + * and before HS400 data strobe setting. + */ +static int emmc_phy_enable_dll(struct sdhci_host *host) +{ + u32 reg; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; + u8 timeout; + + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR)) + return -EINVAL; + + reg = sdhci_readl(host, phy_regs->dll_ctrl); + if (reg & XENON_DLL_ENABLE) + return 0; + + /* Enable DLL */ + reg = sdhci_readl(host, phy_regs->dll_ctrl); + 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; + reg |= phy_regs->dll_update; + if (priv->phy_type == EMMC_5_1_PHY) + reg &= ~XENON_DLL_REFCLK_SEL; + sdhci_writel(host, reg, phy_regs->dll_ctrl); + + /* Wait max 32 ms */ + timeout = 32; + while (!(sdhci_readw(host, XENON_SLOT_EXT_PRESENT_STATE) & + XENON_DLL_LOCK_STATE)) { + if (!timeout) { + dev_err(mmc_dev(host->mmc), "Wait for DLL Lock time-out\n"); + return -ETIMEDOUT; + } + timeout--; + mdelay(1); + } + return 0; +} + +/* + * Config to eMMC PHY to prepare for tuning. + * Enable HW DLL and set the TUNING_STEP + */ +static int emmc_phy_config_tuning(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct emmc_phy_params *params = priv->phy_params; + u32 reg, tuning_step; + int ret; + unsigned long flags; + + if (host->clock <= MMC_HIGH_52_MAX_DTR) + return -EINVAL; + + spin_lock_irqsave(&host->lock, flags); + + ret = emmc_phy_enable_dll(host); + if (ret) { + spin_unlock_irqrestore(&host->lock, flags); + return ret; + } + + /* Achieve TUNING_STEP with HW DLL help */ + reg = sdhci_readl(host, XENON_SLOT_DLL_CUR_DLY_VAL); + tuning_step = reg / params->tun_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 |= (params->nr_tun_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); + + spin_unlock_irqrestore(&host->lock, flags); + return 0; +} + +static void __emmc_phy_disable_data_strobe(struct sdhci_host *host) +{ + u32 reg; + + /* Disable SDHC Data Strobe */ + reg = sdhci_readl(host, XENON_SLOT_EMMC_CTRL); + reg &= ~XENON_ENABLE_DATA_STROBE; + sdhci_writel(host, reg, XENON_SLOT_EMMC_CTRL); +} + +/* Set HS400 Data Strobe */ +static void emmc_phy_strobe_delay_adj(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + unsigned long flags; + u32 reg; + + if (WARN_ON(host->timing != MMC_TIMING_MMC_HS400)) + return; + + if (host->clock <= MMC_HIGH_52_MAX_DTR) + return; + + dev_dbg(mmc_dev(host->mmc), "starts HS400 strobe delay adjustment\n"); + + spin_lock_irqsave(&host->lock, flags); + + emmc_phy_enable_dll(host); + + /* Enable SDHC Data Strobe */ + reg = sdhci_readl(host, XENON_SLOT_EMMC_CTRL); + reg |= XENON_ENABLE_DATA_STROBE; + sdhci_writel(host, reg, XENON_SLOT_EMMC_CTRL); + + /* Set Data Strobe Pull down */ + if (priv->phy_type == EMMC_5_0_PHY) { + reg = sdhci_readl(host, XENON_EMMC_5_0_PHY_PAD_CONTROL); + reg |= XENON_EMMC5_FC_QSP_PD; + reg &= ~XENON_EMMC5_FC_QSP_PU; + sdhci_writel(host, reg, XENON_EMMC_5_0_PHY_PAD_CONTROL); + } else { + reg = sdhci_readl(host, XENON_EMMC_PHY_PAD_CONTROL1); + reg |= XENON_EMMC5_1_FC_QSP_PD; + reg &= ~XENON_EMMC5_1_FC_QSP_PU; + sdhci_writel(host, reg, XENON_EMMC_PHY_PAD_CONTROL1); + } + spin_unlock_irqrestore(&host->lock, flags); +} + +/* + * 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 true. + * Otherwise, return false. + */ +static bool emmc_phy_slow_mode(struct sdhci_host *host, + unsigned char timing) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct emmc_phy_params *params = priv->phy_params; + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; + u32 reg; + + if (host->clock > MMC_HIGH_52_MAX_DTR) + return false; + + reg = sdhci_readl(host, phy_regs->timing_adj); + /* Enable Slow Mode for SDIO in slower SDR mode */ + if ((priv->init_card_type == MMC_TYPE_SDIO) && + ((timing == MMC_TIMING_UHS_SDR25) || + (timing == MMC_TIMING_UHS_SDR12) || + (timing == MMC_TIMING_SD_HS))) { + reg |= XENON_TIMING_ADJUST_SLOW_MODE; + sdhci_writel(host, reg, phy_regs->timing_adj); + return true; + } + + /* Check if Slow Mode is required in lower speed mode in SDR mode */ + if (((timing == MMC_TIMING_UHS_SDR25) || + (timing == MMC_TIMING_UHS_SDR12) || + (timing == MMC_TIMING_SD_HS) || + (timing == MMC_TIMING_MMC_HS)) && params->slow_mode) { + reg |= XENON_TIMING_ADJUST_SLOW_MODE; + sdhci_writel(host, reg, phy_regs->timing_adj); + return true; + } + + reg &= ~XENON_TIMING_ADJUST_SLOW_MODE; + sdhci_writel(host, reg, phy_regs->timing_adj); + return false; +} + +/* + * Set-up eMMC 5.0/5.1 PHY. + * Specific configuration depends on the current speed mode in use. + */ +static void emmc_phy_set(struct sdhci_host *host, + unsigned char timing) +{ + u32 reg; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct emmc_phy_params *params = priv->phy_params; + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; + unsigned long flags; + + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting starts\n"); + + spin_lock_irqsave(&host->lock, flags); + + /* Setup pad, set bit[28] and bits[26:24] */ + reg = sdhci_readl(host, phy_regs->pad_ctrl); + reg |= (XENON_FC_DQ_RECEN | XENON_FC_CMD_RECEN | + XENON_FC_QSP_RECEN | XENON_OEN_QSN); + /* All FC_XX_RECEIVCE should be set as CMOS Type */ + reg |= XENON_FC_ALL_CMOS_RECEIVER; + sdhci_writel(host, reg, phy_regs->pad_ctrl); + + /* Set CMD and DQ Pull Up */ + if (priv->phy_type == EMMC_5_0_PHY) { + reg = sdhci_readl(host, XENON_EMMC_5_0_PHY_PAD_CONTROL); + reg |= (XENON_EMMC5_FC_CMD_PU | XENON_EMMC5_FC_DQ_PU); + reg &= ~(XENON_EMMC5_FC_CMD_PD | XENON_EMMC5_FC_DQ_PD); + sdhci_writel(host, reg, XENON_EMMC_5_0_PHY_PAD_CONTROL); + } else { + reg = sdhci_readl(host, XENON_EMMC_PHY_PAD_CONTROL1); + reg |= (XENON_EMMC5_1_FC_CMD_PU | XENON_EMMC5_1_FC_DQ_PU); + reg &= ~(XENON_EMMC5_1_FC_CMD_PD | XENON_EMMC5_1_FC_DQ_PD); + sdhci_writel(host, reg, XENON_EMMC_PHY_PAD_CONTROL1); + } + + if (timing == MMC_TIMING_LEGACY) { + /* + * If Slow Mode is required, enable Slow Mode by default + * in early init phase to avoid any potential issue. + */ + if (params->slow_mode) { + reg = sdhci_readl(host, phy_regs->timing_adj); + reg |= XENON_TIMING_ADJUST_SLOW_MODE; + sdhci_writel(host, reg, phy_regs->timing_adj); + } + goto phy_init; + } + + /* + * FIXME: should depends on the specific board timing. + */ + if ((timing == MMC_TIMING_MMC_HS400) || + (timing == MMC_TIMING_MMC_HS200) || + (timing == MMC_TIMING_UHS_SDR50) || + (timing == MMC_TIMING_UHS_SDR104) || + (timing == MMC_TIMING_UHS_DDR50) || + (timing == MMC_TIMING_UHS_SDR25) || + (timing == MMC_TIMING_MMC_DDR52)) { + reg = sdhci_readl(host, phy_regs->timing_adj); + reg &= ~XENON_OUTPUT_QSN_PHASE_SELECT; + sdhci_writel(host, reg, phy_regs->timing_adj); + } + + /* + * If SDIO card, set SDIO Mode + * Otherwise, clear SDIO Mode + */ + reg = sdhci_readl(host, phy_regs->timing_adj); + if (priv->init_card_type == MMC_TYPE_SDIO) + reg |= XENON_TIMING_ADJUST_SDIO_MODE; + else + reg &= ~XENON_TIMING_ADJUST_SDIO_MODE; + sdhci_writel(host, reg, phy_regs->timing_adj); + + if (emmc_phy_slow_mode(host, timing)) + goto phy_init; + + /* + * Set preferred ZNR and ZPR value + * The ZNR and ZPR value vary between different boards. + * Define them both in sdhci-xenon-emmc-phy.h. + */ + reg = sdhci_readl(host, phy_regs->pad_ctrl2); + reg &= ~((XENON_ZNR_MASK << XENON_ZNR_SHIFT) | XENON_ZPR_MASK); + reg |= ((params->znr << XENON_ZNR_SHIFT) | params->zpr); + sdhci_writel(host, reg, phy_regs->pad_ctrl2); + + /* + * When setting EMMC_PHY_FUNC_CONTROL register, + * SD clock should be disabled + */ + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); + reg &= ~SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL); + + reg = sdhci_readl(host, phy_regs->func_ctrl); + if ((timing == MMC_TIMING_UHS_DDR50) || + (timing == MMC_TIMING_MMC_HS400) || + (timing == MMC_TIMING_MMC_DDR52)) + reg |= (XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | + XENON_CMD_DDR_MODE; + else + reg &= ~((XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | + XENON_CMD_DDR_MODE); + + if (timing == MMC_TIMING_MMC_HS400) + reg &= ~XENON_DQ_ASYNC_MODE; + else + reg |= XENON_DQ_ASYNC_MODE; + sdhci_writel(host, reg, phy_regs->func_ctrl); + + /* Enable bus clock */ + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); + reg |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL); + + if (timing == MMC_TIMING_MMC_HS400) + /* Hardware team recommend a value for HS400 */ + sdhci_writel(host, XENON_LOGIC_TIMING_VALUE, + phy_regs->logic_timing_adj); + else + __emmc_phy_disable_data_strobe(host); + +phy_init: + emmc_phy_init(host); + + spin_unlock_irqrestore(&host->lock, flags); + + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n"); +} + +static int emmc_phy_parse_param_dt(struct sdhci_host *host, + struct device_node *np, + struct emmc_phy_params *params) +{ + u32 value; + + if (of_property_read_bool(np, "marvell,xenon-phy-slow-mode")) + params->slow_mode = true; + else + params->slow_mode = false; + + if (!of_property_read_u32(np, "marvell,xenon-phy-znr", &value)) + params->znr = value & XENON_ZNR_MASK; + else + params->znr = XENON_ZNR_DEF_VALUE; + + if (!of_property_read_u32(np, "marvell,xenon-phy-zpr", &value)) + params->zpr = value & XENON_ZPR_MASK; + else + params->zpr = XENON_ZPR_DEF_VALUE; + + if (!of_property_read_u32(np, "marvell,xenon-phy-nr-success-tun", + &value)) + params->nr_tun_times = value & XENON_TUN_CONSECUTIVE_TIMES_MASK; + else + params->nr_tun_times = XENON_TUN_CONSECUTIVE_TIMES; + + if (!of_property_read_u32(np, "marvell,xenon-phy-tun-step-divider", + &value)) + params->tun_step_divider = value & 0xFF; + else + params->tun_step_divider = XENON_TUNING_STEP_DIVIDER; + + return 0; +} + +/* + * Setting PHY when card is working in High Speed Mode. + * HS400 set data strobe line. + * HS200/SDR104 set tuning config to prepare for tuning. + */ +static int xenon_hs_delay_adj(struct sdhci_host *host) +{ + int ret = 0; + + if (WARN_ON(host->clock <= XENON_DEFAULT_SDCLK_FREQ)) + return -EINVAL; + + if (host->timing == MMC_TIMING_MMC_HS400) { + emmc_phy_strobe_delay_adj(host); + return 0; + } + + if ((host->timing == MMC_TIMING_MMC_HS200) || + (host->timing == MMC_TIMING_UHS_SDR104)) + return emmc_phy_config_tuning(host); + + /* + * DDR Mode requires driver to scan Sampling Fixed Delay Line, + * to find out a perfect operation sampling point. + * It is hard to implement such a scan in host driver since initiating + * commands by host driver is not safe. + * Thus so far just keep PHY Sampling Fixed Delay in default value + * in DDR mode. + * + * If any timing issue occurs in DDR mode on Marvell products, + * please contact maintainer to ask for internal support in Marvell. + */ + if ((host->timing == MMC_TIMING_MMC_DDR52) || + (host->timing == MMC_TIMING_UHS_DDR50)) + dev_warn_once(mmc_dev(host->mmc), "Timing issue might occur in DDR mode\n"); + return ret; +} + +/* + * Adjust PHY setting. + * PHY setting should be adjusted when SDCLK frequency, Bus Width + * or Speed Mode is changed. + * Additional config are required when card is working in High Speed mode, + * after leaving Legacy Mode. + */ +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + int ret = 0; + + if (!host->clock) { + priv->clock = 0; + return 0; + } + + /* + * The timing, frequency or bus width is changed, + * better to set eMMC PHY based on current setting + * and adjust Xenon SDHC delay. + */ + if ((host->clock == priv->clock) && + (ios->bus_width == priv->bus_width) && + (ios->timing == priv->timing)) + return 0; + + emmc_phy_set(host, ios->timing); + + /* Update the record */ + priv->bus_width = ios->bus_width; + + priv->timing = ios->timing; + priv->clock = host->clock; + + /* Legacy mode is a special case */ + if (ios->timing == MMC_TIMING_LEGACY) + return 0; + + if (host->clock > XENON_DEFAULT_SDCLK_FREQ) + ret = xenon_hs_delay_adj(host); + return ret; +} + +static void clean_emmc_phy(struct sdhci_xenon_priv *priv) +{ + kfree(priv->phy_params); +} + +static int add_xenon_phy(struct device_node *np, struct sdhci_host *host, + const char *phy_name) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + int i, ret; + + for (i = 0; i < NR_PHY_TYPES; i++) { + if (!strcmp(phy_name, phy_types[i])) { + priv->phy_type = i; + break; + } + } + if (i == NR_PHY_TYPES) { + dev_err(mmc_dev(host->mmc), + "Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n", + phy_name); + priv->phy_type = EMMC_5_1_PHY; + } + + ret = alloc_emmc_phy(priv); + if (ret) + return ret; + + ret = emmc_phy_parse_param_dt(host, np, priv->phy_params); + if (ret) + clean_emmc_phy(priv); + + return ret; +} + +int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host) +{ + const char *phy_type = NULL; + + if (!of_property_read_string(np, "marvell,xenon-phy-type", &phy_type)) + return add_xenon_phy(np, host, phy_type); + + dev_info(mmc_dev(host->mmc), "Fail to get Xenon PHY type. Use default eMMC 5.1 PHY\n"); + return add_xenon_phy(np, host, "emmc 5.1 phy"); +} diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c index e633f803907a..99c18cad6460 100644 --- a/drivers/mmc/host/sdhci-xenon.c +++ b/drivers/mmc/host/sdhci-xenon.c @@ -260,6 +260,7 @@ static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) spin_unlock_irqrestore(&host->lock, flags); sdhci_set_ios(mmc, ios); + xenon_phy_adj(host, ios); if (host->clock > XENON_DEFAULT_SDCLK_FREQ) { spin_lock_irqsave(&host->lock, flags); @@ -465,7 +466,7 @@ static int xenon_probe_dt(struct platform_device *pdev) } priv->tuning_count = tuning_count; - return 0; + return xenon_phy_parse_dt(np, host); } static int xenon_sdhc_probe(struct sdhci_host *host) diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h index 69de711db9eb..e87639774bca 100644 --- a/drivers/mmc/host/sdhci-xenon.h +++ b/drivers/mmc/host/sdhci-xenon.h @@ -24,7 +24,17 @@ #define XENON_SYS_EXT_OP_CTRL 0x010C #define XENON_MASK_CMD_CONFLICT_ERR BIT(8) +#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_SLOT_EMMC_CTRL 0x0130 +#define XENON_ENABLE_DATA_STROBE BIT(24) #define XENON_EMMC_VCCQ_MASK 0x3 #define XENON_EMMC_VCCQ_1_8V 0x1 #define XENON_EMMC_VCCQ_3_3V 0x3 @@ -33,11 +43,17 @@ /* retuning compatible */ #define XENON_RETUNING_COMPATIBLE 0x1 +#define XENON_SLOT_EXT_PRESENT_STATE 0x014C +#define XENON_DLL_LOCK_STATE 0x1 + +#define XENON_SLOT_DLL_CUR_DLY_VAL 0x0150 + /* Tuning Parameter */ #define XENON_TMR_RETUN_NO_PRESENT 0xF #define XENON_DEF_TUNING_COUNT 0x9 #define XENON_DEFAULT_SDCLK_FREQ 400000 +#define XENON_LOWEST_SDCLK_FREQ 100000 /* Xenon specific Mode Select value */ #define XENON_CTRL_HS200 0x5 @@ -65,6 +81,27 @@ struct sdhci_xenon_priv { * initialization completes. */ unsigned int init_card_type; + + /* + * The bus_width, timing, and clock fields in below + * record the current ios setting of Xenon SDHC. + * Driver will adjust PHY setting if any change to + * ios affects PHY timing. + */ + unsigned char bus_width; + unsigned char timing; + unsigned int clock; + + int phy_type; + /* + * Contains board-specific PHY parameters + * passed from device tree. + */ + void *phy_params; + struct xenon_emmc_phy_regs *emmc_phy_regs; }; +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios); +int xenon_phy_parse_dt(struct device_node *np, + struct sdhci_host *host); #endif