diff options
Diffstat (limited to 'drivers/mmc/host/sdhci-cadence.c')
| -rw-r--r-- | drivers/mmc/host/sdhci-cadence.c | 288 |
1 files changed, 243 insertions, 45 deletions
diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c index 6f2de54a5987..435603c8c00b 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence.c @@ -11,7 +11,8 @@ #include <linux/mmc/host.h> #include <linux/mmc/mmc.h> #include <linux/of.h> -#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reset.h> #include "sdhci-pltfm.h" @@ -35,6 +36,24 @@ #define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5 #define SDHCI_CDNS_HRS06_MODE_MMC_HS400ES 0x6 +/* Read block gap */ +#define SDHCI_CDNS_HRS37 0x94 /* interface mode select */ +#define SDHCI_CDNS_HRS37_MODE_DS 0x0 +#define SDHCI_CDNS_HRS37_MODE_HS 0x1 +#define SDHCI_CDNS_HRS37_MODE_UDS_SDR12 0x8 +#define SDHCI_CDNS_HRS37_MODE_UDS_SDR25 0x9 +#define SDHCI_CDNS_HRS37_MODE_UDS_SDR50 0xa +#define SDHCI_CDNS_HRS37_MODE_UDS_SDR104 0xb +#define SDHCI_CDNS_HRS37_MODE_UDS_DDR50 0xc +#define SDHCI_CDNS_HRS37_MODE_MMC_LEGACY 0x20 +#define SDHCI_CDNS_HRS37_MODE_MMC_SDR 0x21 +#define SDHCI_CDNS_HRS37_MODE_MMC_DDR 0x22 +#define SDHCI_CDNS_HRS37_MODE_MMC_HS200 0x23 +#define SDHCI_CDNS_HRS37_MODE_MMC_HS400 0x24 +#define SDHCI_CDNS_HRS37_MODE_MMC_HS400ES 0x25 +#define SDHCI_CDNS_HRS38 0x98 /* Read block gap coefficient */ +#define SDHCI_CDNS_HRS38_BLKGAP_MAX 0xf + /* SRS - Slot Register Set (SDHCI-compatible) */ #define SDHCI_CDNS_SRS_BASE 0x200 @@ -66,7 +85,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 +99,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 +118,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 +138,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); @@ -128,7 +162,7 @@ static unsigned int sdhci_cdns_phy_param_count(struct device_node *np) int i; for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) - if (of_property_read_bool(np, sdhci_cdns_phy_cfgs[i].property)) + if (of_property_present(np, sdhci_cdns_phy_cfgs[i].property)) count++; return count; @@ -191,7 +225,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 +257,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), @@ -235,6 +269,43 @@ static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val) return 0; } +/** + * sdhci_cdns_tune_blkgap() - tune multi-block read gap + * @mmc: MMC host + * + * Tune delay used in multi block read. To do so, + * try sending multi-block read command with incremented gap, unless + * it succeeds. + * + * Return: error code + */ +static int sdhci_cdns_tune_blkgap(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_cdns_priv *priv = sdhci_pltfm_priv(pltfm_host); + void __iomem *hrs37_reg = priv->hrs_addr + SDHCI_CDNS_HRS37; + void __iomem *hrs38_reg = priv->hrs_addr + SDHCI_CDNS_HRS38; + int ret; + u32 gap; + + /* Currently only needed in HS200 mode */ + if (host->timing != MMC_TIMING_MMC_HS200) + return 0; + + writel(SDHCI_CDNS_HRS37_MODE_MMC_HS200, hrs37_reg); + + for (gap = 0; gap <= SDHCI_CDNS_HRS38_BLKGAP_MAX; gap++) { + writel(gap, hrs38_reg); + ret = mmc_read_tuning(mmc, 512, 32); + if (!ret) + break; + } + + dev_dbg(mmc_dev(mmc), "read block gap tune %s, gap %d\n", ret ? "failed" : "OK", gap); + return ret; +} + /* * In SD mode, software must not use the hardware tuning and instead perform * an almost identical procedure to eMMC. @@ -245,6 +316,7 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) int max_streak = 0; int end_of_streak = 0; int i; + int ret; /* * Do not execute tuning for UHS_SDR50 or UHS_DDR50. @@ -272,7 +344,11 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) return -EIO; } - return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2); + ret = sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2); + if (ret) + return ret; + + return sdhci_cdns_tune_blkgap(host->mmc); } static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, @@ -309,6 +385,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 +479,31 @@ 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_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_eyeq_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_cdns_drv_data = { + .pltfm_data = { + .ops = &sdhci_cdns_ops, + }, }; static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc, @@ -347,10 +526,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; @@ -359,25 +554,19 @@ static int sdhci_cdns_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT; - clk = devm_clk_get(dev, NULL); + clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(clk)) return PTR_ERR(clk); - ret = clk_prepare_enable(clk); - if (ret) - return ret; - 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); - goto disable_clk; - } + if (IS_ERR(host)) + return PTR_ERR(host); pltfm_host = sdhci_priv(host); pltfm_host->clk = clk; @@ -386,9 +575,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) + return ret; + } sdhci_enable_v4_mode(host); __sdhci_read_caps(host, &version, NULL, NULL); @@ -396,28 +591,26 @@ static int sdhci_cdns_probe(struct platform_device *pdev) ret = mmc_of_parse(host->mmc); if (ret) - goto free; + return ret; sdhci_cdns_phy_param_parse(dev->of_node, priv); ret = sdhci_cdns_phy_init(priv); if (ret) - goto free; - - ret = sdhci_add_host(host); - if (ret) - goto free; + return ret; - return 0; -free: - sdhci_pltfm_free(pdev); -disable_clk: - clk_disable_unprepare(clk); + 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)) + return dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw), + "reset controller error\n"); + if (priv->rst_hw) + host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset; + } - return ret; + return sdhci_add_host(host); } -#ifdef CONFIG_PM_SLEEP static int sdhci_cdns_resume(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); @@ -444,16 +637,21 @@ disable_clk: return ret; } -#endif -static const struct dev_pm_ops sdhci_cdns_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend, sdhci_cdns_resume) -}; +static DEFINE_SIMPLE_DEV_PM_OPS(sdhci_cdns_pm_ops, sdhci_pltfm_suspend, sdhci_cdns_resume); 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 = "mobileye,eyeq-sd4hc", + .data = &sdhci_eyeq_drv_data, }, { .compatible = "cdns,sd4hc" }, { /* sentinel */ } @@ -464,11 +662,11 @@ static struct platform_driver sdhci_cdns_driver = { .driver = { .name = "sdhci-cdns", .probe_type = PROBE_PREFER_ASYNCHRONOUS, - .pm = &sdhci_cdns_pm_ops, + .pm = pm_sleep_ptr(&sdhci_cdns_pm_ops), .of_match_table = sdhci_cdns_match, }, .probe = sdhci_cdns_probe, - .remove = sdhci_pltfm_unregister, + .remove = sdhci_pltfm_remove, }; module_platform_driver(sdhci_cdns_driver); |
