diff options
| author | Dmitry Torokhov <[email protected]> | 2023-08-30 16:06:38 -0700 | 
|---|---|---|
| committer | Dmitry Torokhov <[email protected]> | 2023-08-30 16:06:38 -0700 | 
| commit | 1ac731c529cd4d6adbce134754b51ff7d822b145 (patch) | |
| tree | 143ab3f35ca5f3b69f583c84e6964b17139c2ec1 /drivers/mmc/host/sdhci-cadence.c | |
| parent | 07b4c950f27bef0362dc6ad7ee713aab61d58149 (diff) | |
| parent | 54116d442e001e1b6bd482122043b1870998a1f3 (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 6.6 merge window.
Diffstat (limited to 'drivers/mmc/host/sdhci-cadence.c')
| -rw-r--r-- | drivers/mmc/host/sdhci-cadence.c | 177 | 
1 files changed, 163 insertions, 14 deletions
diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c index 6f2de54a5987..d2f625054689 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence.c @@ -12,6 +12,7 @@  #include <linux/mmc/mmc.h>  #include <linux/of.h>  #include <linux/of_device.h> +#include <linux/reset.h>  #include "sdhci-pltfm.h" @@ -66,7 +67,11 @@ struct sdhci_cdns_phy_param {  struct sdhci_cdns_priv {  	void __iomem *hrs_addr; +	void __iomem *ctl_addr;	/* write control */ +	spinlock_t wrlock;	/* write lock */  	bool enhanced_strobe; +	void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg); +	struct reset_control *rst_hw;  	unsigned int nr_phy_params;  	struct sdhci_cdns_phy_param phy_params[];  }; @@ -76,6 +81,11 @@ struct sdhci_cdns_phy_cfg {  	u8 addr;  }; +struct sdhci_cdns_drv_data { +	int (*init)(struct platform_device *pdev); +	const struct sdhci_pltfm_data pltfm_data; +}; +  static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = {  	{ "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, },  	{ "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, @@ -90,6 +100,12 @@ static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = {  	{ "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, },  }; +static inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val, +			       void __iomem *reg) +{ +	writel(val, reg); +} +  static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,  				    u8 addr, u8 data)  { @@ -104,17 +120,17 @@ static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,  	tmp = FIELD_PREP(SDHCI_CDNS_HRS04_WDATA, data) |  	      FIELD_PREP(SDHCI_CDNS_HRS04_ADDR, addr); -	writel(tmp, reg); +	priv->priv_writel(priv, tmp, reg);  	tmp |= SDHCI_CDNS_HRS04_WR; -	writel(tmp, reg); +	priv->priv_writel(priv, tmp, reg);  	ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 0, 10);  	if (ret)  		return ret;  	tmp &= ~SDHCI_CDNS_HRS04_WR; -	writel(tmp, reg); +	priv->priv_writel(priv, tmp, reg);  	ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS04_ACK),  				 0, 10); @@ -191,7 +207,7 @@ static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode)  	tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06);  	tmp &= ~SDHCI_CDNS_HRS06_MODE;  	tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode); -	writel(tmp, priv->hrs_addr + SDHCI_CDNS_HRS06); +	priv->priv_writel(priv, tmp, priv->hrs_addr + SDHCI_CDNS_HRS06);  }  static u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv) @@ -223,7 +239,7 @@ static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)  	 */  	for (i = 0; i < 2; i++) {  		tmp |= SDHCI_CDNS_HRS06_TUNE_UP; -		writel(tmp, reg); +		priv->priv_writel(priv, tmp, reg);  		ret = readl_poll_timeout(reg, tmp,  					 !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), @@ -309,6 +325,91 @@ static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,  		sdhci_set_uhs_signaling(host, timing);  } +/* Elba control register bits [6:3] are byte-lane enables */ +#define ELBA_BYTE_ENABLE_MASK(x)	((x) << 3) + +/* + * The Pensando Elba SoC explicitly controls byte-lane enabling on writes + * which includes writes to the HRS registers.  The write lock (wrlock) + * is used to ensure byte-lane enable, using write control (ctl_addr), + * occurs before the data write. + */ +static void elba_priv_writel(struct sdhci_cdns_priv *priv, u32 val, +			     void __iomem *reg) +{ +	unsigned long flags; + +	spin_lock_irqsave(&priv->wrlock, flags); +	writel(GENMASK(7, 3), priv->ctl_addr); +	writel(val, reg); +	spin_unlock_irqrestore(&priv->wrlock, flags); +} + +static void elba_write_l(struct sdhci_host *host, u32 val, int reg) +{ +	elba_priv_writel(sdhci_cdns_priv(host), val, host->ioaddr + reg); +} + +static void elba_write_w(struct sdhci_host *host, u16 val, int reg) +{ +	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); +	u32 shift = reg & GENMASK(1, 0); +	unsigned long flags; +	u32 byte_enables; + +	byte_enables = GENMASK(1, 0) << shift; +	spin_lock_irqsave(&priv->wrlock, flags); +	writel(ELBA_BYTE_ENABLE_MASK(byte_enables), priv->ctl_addr); +	writew(val, host->ioaddr + reg); +	spin_unlock_irqrestore(&priv->wrlock, flags); +} + +static void elba_write_b(struct sdhci_host *host, u8 val, int reg) +{ +	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); +	u32 shift = reg & GENMASK(1, 0); +	unsigned long flags; +	u32 byte_enables; + +	byte_enables = BIT(0) << shift; +	spin_lock_irqsave(&priv->wrlock, flags); +	writel(ELBA_BYTE_ENABLE_MASK(byte_enables), priv->ctl_addr); +	writeb(val, host->ioaddr + reg); +	spin_unlock_irqrestore(&priv->wrlock, flags); +} + +static const struct sdhci_ops sdhci_elba_ops = { +	.write_l = elba_write_l, +	.write_w = elba_write_w, +	.write_b = elba_write_b, +	.set_clock = sdhci_set_clock, +	.get_timeout_clock = sdhci_cdns_get_timeout_clock, +	.set_bus_width = sdhci_set_bus_width, +	.reset = sdhci_reset, +	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling, +}; + +static int elba_drv_init(struct platform_device *pdev) +{ +	struct sdhci_host *host = platform_get_drvdata(pdev); +	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); +	void __iomem *ioaddr; + +	host->mmc->caps |= MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA; +	spin_lock_init(&priv->wrlock); + +	/* Byte-lane control register */ +	ioaddr = devm_platform_ioremap_resource(pdev, 1); +	if (IS_ERR(ioaddr)) +		return PTR_ERR(ioaddr); + +	priv->ctl_addr = ioaddr; +	priv->priv_writel = elba_priv_writel; +	writel(ELBA_BYTE_ENABLE_MASK(0xf), priv->ctl_addr); + +	return 0; +} +  static const struct sdhci_ops sdhci_cdns_ops = {  	.set_clock = sdhci_set_clock,  	.get_timeout_clock = sdhci_cdns_get_timeout_clock, @@ -318,13 +419,24 @@ static const struct sdhci_ops sdhci_cdns_ops = {  	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,  }; -static const struct sdhci_pltfm_data sdhci_cdns_uniphier_pltfm_data = { -	.ops = &sdhci_cdns_ops, -	.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, +static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = { +	.pltfm_data = { +		.ops = &sdhci_cdns_ops, +		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, +	},  }; -static const struct sdhci_pltfm_data sdhci_cdns_pltfm_data = { -	.ops = &sdhci_cdns_ops, +static const struct sdhci_cdns_drv_data sdhci_elba_drv_data = { +	.init = elba_drv_init, +	.pltfm_data = { +		.ops = &sdhci_elba_ops, +	}, +}; + +static const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = { +	.pltfm_data = { +		.ops = &sdhci_cdns_ops, +	},  };  static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc, @@ -347,10 +459,26 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,  					 SDHCI_CDNS_HRS06_MODE_MMC_HS400);  } +static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc) +{ +	struct sdhci_host *host = mmc_priv(mmc); +	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + +	dev_dbg(mmc_dev(host->mmc), "emmc hardware reset\n"); + +	reset_control_assert(priv->rst_hw); +	/* For eMMC, minimum is 1us but give it 3us for good measure */ +	udelay(3); + +	reset_control_deassert(priv->rst_hw); +	/* For eMMC, minimum is 200us but give it 300us for good measure */ +	usleep_range(300, 1000); +} +  static int sdhci_cdns_probe(struct platform_device *pdev)  {  	struct sdhci_host *host; -	const struct sdhci_pltfm_data *data; +	const struct sdhci_cdns_drv_data *data;  	struct sdhci_pltfm_host *pltfm_host;  	struct sdhci_cdns_priv *priv;  	struct clk *clk; @@ -369,10 +497,10 @@ static int sdhci_cdns_probe(struct platform_device *pdev)  	data = of_device_get_match_data(dev);  	if (!data) -		data = &sdhci_cdns_pltfm_data; +		data = &sdhci_cdns_drv_data;  	nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node); -	host = sdhci_pltfm_init(pdev, data, +	host = sdhci_pltfm_init(pdev, &data->pltfm_data,  				struct_size(priv, phy_params, nr_phy_params));  	if (IS_ERR(host)) {  		ret = PTR_ERR(host); @@ -386,9 +514,15 @@ static int sdhci_cdns_probe(struct platform_device *pdev)  	priv->nr_phy_params = nr_phy_params;  	priv->hrs_addr = host->ioaddr;  	priv->enhanced_strobe = false; +	priv->priv_writel = cdns_writel;  	host->ioaddr += SDHCI_CDNS_SRS_BASE;  	host->mmc_host_ops.hs400_enhanced_strobe =  				sdhci_cdns_hs400_enhanced_strobe; +	if (data->init) { +		ret = data->init(pdev); +		if (ret) +			goto free; +	}  	sdhci_enable_v4_mode(host);  	__sdhci_read_caps(host, &version, NULL, NULL); @@ -404,6 +538,17 @@ static int sdhci_cdns_probe(struct platform_device *pdev)  	if (ret)  		goto free; +	if (host->mmc->caps & MMC_CAP_HW_RESET) { +		priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL); +		if (IS_ERR(priv->rst_hw)) { +			ret = dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw), +					    "reset controller error\n"); +			goto free; +		} +		if (priv->rst_hw) +			host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset; +	} +  	ret = sdhci_add_host(host);  	if (ret)  		goto free; @@ -453,7 +598,11 @@ static const struct dev_pm_ops sdhci_cdns_pm_ops = {  static const struct of_device_id sdhci_cdns_match[] = {  	{  		.compatible = "socionext,uniphier-sd4hc", -		.data = &sdhci_cdns_uniphier_pltfm_data, +		.data = &sdhci_cdns_uniphier_drv_data, +	}, +	{ +		.compatible = "amd,pensando-elba-sd4hc", +		.data = &sdhci_elba_drv_data,  	},  	{ .compatible = "cdns,sd4hc" },  	{ /* sentinel */ }  |