diff options
Diffstat (limited to 'drivers/mmc/host/sdhci-brcmstb.c')
| -rw-r--r-- | drivers/mmc/host/sdhci-brcmstb.c | 440 |
1 files changed, 400 insertions, 40 deletions
diff --git a/drivers/mmc/host/sdhci-brcmstb.c b/drivers/mmc/host/sdhci-brcmstb.c index f24623aac2db..c9442499876c 100644 --- a/drivers/mmc/host/sdhci-brcmstb.c +++ b/drivers/mmc/host/sdhci-brcmstb.c @@ -6,34 +6,206 @@ */ #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/mmc/host.h> #include <linux/module.h> #include <linux/of.h> #include <linux/bitops.h> #include <linux/delay.h> +#include "sdhci-cqhci.h" #include "sdhci-pltfm.h" #include "cqhci.h" #define SDHCI_VENDOR 0x78 #define SDHCI_VENDOR_ENHANCED_STRB 0x1 +#define SDHCI_VENDOR_GATE_SDCLK_EN 0x2 -#define BRCMSTB_PRIV_FLAGS_NO_64BIT BIT(0) -#define BRCMSTB_PRIV_FLAGS_BROKEN_TIMEOUT BIT(1) +#define BRCMSTB_MATCH_FLAGS_NO_64BIT BIT(0) +#define BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT BIT(1) +#define BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE BIT(2) +#define BRCMSTB_MATCH_FLAGS_USE_CARD_BUSY BIT(4) + +#define BRCMSTB_PRIV_FLAGS_HAS_CQE BIT(0) +#define BRCMSTB_PRIV_FLAGS_GATE_CLOCK BIT(1) #define SDHCI_ARASAN_CQE_BASE_ADDR 0x200 -struct sdhci_brcmstb_priv { - void __iomem *cfg_regs; - bool has_cqe; +#define SDIO_CFG_CTRL 0x0 +#define SDIO_CFG_CTRL_SDCD_N_TEST_EN BIT(31) +#define SDIO_CFG_CTRL_SDCD_N_TEST_LEV BIT(30) +#define SDIO_CFG_OP_DLY 0x34 +#define SDIO_CFG_OP_DLY_DEFAULT 0x80000003 +#define SDIO_CFG_CQ_CAPABILITY 0x4c +#define SDIO_CFG_CQ_CAPABILITY_FMUL GENMASK(13, 12) +#define SDIO_CFG_SD_PIN_SEL 0x44 +#define SDIO_CFG_V1_SD_PIN_SEL 0x54 +#define SDIO_CFG_PHY_SW_MODE_0_RX_CTRL 0x7C +#define SDIO_CFG_MAX_50MHZ_MODE 0x1ac +#define SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE BIT(31) +#define SDIO_CFG_MAX_50MHZ_MODE_ENABLE BIT(0) + +#define SDIO_BOOT_MAIN_CTL 0x0 + +#define MMC_CAP_HSE_MASK (MMC_CAP2_HSX00_1_2V | MMC_CAP2_HSX00_1_8V) +/* Select all SD UHS type I SDR speed above 50MB/s */ +#define MMC_CAP_UHS_I_SDR_MASK (MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104) + +enum cfg_core_ver { + SDIO_CFG_CORE_V1 = 1, + SDIO_CFG_CORE_V2, +}; + +struct sdhci_brcmstb_saved_regs { + u32 sd_pin_sel; + u32 phy_sw_mode0_rxctrl; + u32 max_50mhz_mode; + u32 boot_main_ctl; }; struct brcmstb_match_priv { + void (*cfginit)(struct sdhci_host *host); void (*hs400es)(struct mmc_host *mmc, struct mmc_ios *ios); + void (*save_restore_regs)(struct mmc_host *mmc, int save); struct sdhci_ops *ops; + const unsigned int flags; +}; + +struct sdhci_brcmstb_priv { + void __iomem *cfg_regs; + void __iomem *boot_regs; + struct sdhci_brcmstb_saved_regs saved_regs; unsigned int flags; + struct clk *base_clk; + u32 base_freq_hz; + const struct brcmstb_match_priv *match_priv; }; +static void sdhci_brcmstb_save_regs(struct mmc_host *mmc, enum cfg_core_ver ver) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct sdhci_brcmstb_saved_regs *sr = &priv->saved_regs; + void __iomem *cr = priv->cfg_regs; + bool is_emmc = mmc->caps & MMC_CAP_NONREMOVABLE; + + if (is_emmc && priv->boot_regs) + sr->boot_main_ctl = readl(priv->boot_regs + SDIO_BOOT_MAIN_CTL); + + if (ver == SDIO_CFG_CORE_V1) { + sr->sd_pin_sel = readl(cr + SDIO_CFG_V1_SD_PIN_SEL); + return; + } + + sr->sd_pin_sel = readl(cr + SDIO_CFG_SD_PIN_SEL); + sr->phy_sw_mode0_rxctrl = readl(cr + SDIO_CFG_PHY_SW_MODE_0_RX_CTRL); + sr->max_50mhz_mode = readl(cr + SDIO_CFG_MAX_50MHZ_MODE); +} + +static void sdhci_brcmstb_restore_regs(struct mmc_host *mmc, enum cfg_core_ver ver) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct sdhci_brcmstb_saved_regs *sr = &priv->saved_regs; + void __iomem *cr = priv->cfg_regs; + bool is_emmc = mmc->caps & MMC_CAP_NONREMOVABLE; + + if (is_emmc && priv->boot_regs) + writel(sr->boot_main_ctl, priv->boot_regs + SDIO_BOOT_MAIN_CTL); + + if (ver == SDIO_CFG_CORE_V1) { + writel(sr->sd_pin_sel, cr + SDIO_CFG_SD_PIN_SEL); + return; + } + + writel(sr->sd_pin_sel, cr + SDIO_CFG_SD_PIN_SEL); + writel(sr->phy_sw_mode0_rxctrl, cr + SDIO_CFG_PHY_SW_MODE_0_RX_CTRL); + writel(sr->max_50mhz_mode, cr + SDIO_CFG_MAX_50MHZ_MODE); +} + +static void sdhci_brcmstb_save_restore_regs_v1(struct mmc_host *mmc, int save) +{ + if (save) + sdhci_brcmstb_save_regs(mmc, SDIO_CFG_CORE_V1); + else + sdhci_brcmstb_restore_regs(mmc, SDIO_CFG_CORE_V1); +} + +static void sdhci_brcmstb_save_restore_regs_v2(struct mmc_host *mmc, int save) +{ + if (save) + sdhci_brcmstb_save_regs(mmc, SDIO_CFG_CORE_V2); + else + sdhci_brcmstb_restore_regs(mmc, SDIO_CFG_CORE_V2); +} + +static inline void enable_clock_gating(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); + u32 reg; + + if (!(priv->flags & BRCMSTB_PRIV_FLAGS_GATE_CLOCK)) + return; + + reg = sdhci_readl(host, SDHCI_VENDOR); + reg |= SDHCI_VENDOR_GATE_SDCLK_EN; + sdhci_writel(host, reg, SDHCI_VENDOR); +} + +static void brcmstb_reset(struct sdhci_host *host, u8 mask) +{ + sdhci_and_cqhci_reset(host, mask); + + /* Reset will clear this, so re-enable it */ + enable_clock_gating(host); +} + +static void brcmstb_sdhci_reset_cmd_data(struct sdhci_host *host, u8 mask) +{ + u32 new_mask = (mask & (SDHCI_RESET_CMD | SDHCI_RESET_DATA)) << 24; + int ret; + u32 reg; + + /* + * SDHCI_CLOCK_CONTROL register CARD_EN and CLOCK_INT_EN bits shall + * be set along with SOFTWARE_RESET register RESET_CMD or RESET_DATA + * bits, hence access SDHCI_CLOCK_CONTROL register as 32-bit register + */ + new_mask |= SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN; + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); + sdhci_writel(host, reg | new_mask, SDHCI_CLOCK_CONTROL); + + reg = sdhci_readb(host, SDHCI_SOFTWARE_RESET); + + ret = read_poll_timeout_atomic(sdhci_readb, reg, !(reg & mask), + 10, 10000, false, + host, SDHCI_SOFTWARE_RESET); + + if (ret) { + pr_err("%s: Reset 0x%x never completed.\n", + mmc_hostname(host->mmc), (int)mask); + sdhci_err_stats_inc(host, CTRL_TIMEOUT); + sdhci_dumpregs(host); + } +} + +static void brcmstb_reset_74165b0(struct sdhci_host *host, u8 mask) +{ + /* take care of RESET_ALL as usual */ + if (mask & SDHCI_RESET_ALL) + sdhci_and_cqhci_reset(host, SDHCI_RESET_ALL); + + /* cmd and/or data treated differently on this core */ + if (mask & (SDHCI_RESET_CMD | SDHCI_RESET_DATA)) + brcmstb_sdhci_reset_cmd_data(host, mask); + + /* Reset will clear this, so re-enable it */ + enable_clock_gating(host); +} + static void sdhci_brcmstb_hs400es(struct mmc_host *mmc, struct mmc_ios *ios) { struct sdhci_host *host = mmc_priv(mmc); @@ -94,6 +266,48 @@ static void sdhci_brcmstb_set_uhs_signaling(struct sdhci_host *host, sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); } +static void sdhci_brcmstb_cfginit_2712(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host); + u32 reg; + + /* + * If we support a speed that requires tuning, + * then select the delay line PHY as the clock source. + */ + if ((host->mmc->caps & MMC_CAP_UHS_I_SDR_MASK) || (host->mmc->caps2 & MMC_CAP_HSE_MASK)) { + reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_MAX_50MHZ_MODE); + reg &= ~SDIO_CFG_MAX_50MHZ_MODE_ENABLE; + reg |= SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE; + writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_MAX_50MHZ_MODE); + } + + if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) || + (host->mmc->caps & MMC_CAP_NEEDS_POLL)) { + /* Force presence */ + reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_CTRL); + reg &= ~SDIO_CFG_CTRL_SDCD_N_TEST_LEV; + reg |= SDIO_CFG_CTRL_SDCD_N_TEST_EN; + writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_CTRL); + } +} + +static void sdhci_brcmstb_set_72116_uhs_signaling(struct sdhci_host *host, unsigned int timing) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); + u32 reg; + + /* no change to SDIO_CFG_OP_DLY_DEFAULT when using preset clk rate */ + if (!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN)) + return; + + reg = (timing == MMC_TIMING_MMC_HS200) ? 0 : SDIO_CFG_OP_DLY_DEFAULT; + writel(reg, priv->cfg_regs + SDIO_CFG_OP_DLY); + sdhci_set_uhs_signaling(host, timing); +} + static void sdhci_brcmstb_dumpregs(struct mmc_host *mmc) { sdhci_dumpregs(mmc_priv(mmc)); @@ -126,33 +340,85 @@ static struct sdhci_ops sdhci_brcmstb_ops = { .set_uhs_signaling = sdhci_set_uhs_signaling, }; +static struct sdhci_ops sdhci_brcmstb_ops_2712 = { + .set_clock = sdhci_set_clock, + .set_power = sdhci_set_power_and_bus_voltage, + .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_reset, + .set_uhs_signaling = sdhci_set_uhs_signaling, +}; + +static struct sdhci_ops sdhci_brcmstb_ops_72116 = { + .set_clock = sdhci_set_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_reset, + .set_uhs_signaling = sdhci_brcmstb_set_72116_uhs_signaling, +}; + static struct sdhci_ops sdhci_brcmstb_ops_7216 = { .set_clock = sdhci_brcmstb_set_clock, .set_bus_width = sdhci_set_bus_width, - .reset = sdhci_reset, + .reset = brcmstb_reset, + .set_uhs_signaling = sdhci_brcmstb_set_uhs_signaling, +}; + +static struct sdhci_ops sdhci_brcmstb_ops_74165b0 = { + .set_clock = sdhci_brcmstb_set_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = brcmstb_reset_74165b0, .set_uhs_signaling = sdhci_brcmstb_set_uhs_signaling, }; +static const struct brcmstb_match_priv match_priv_2712 = { + .cfginit = sdhci_brcmstb_cfginit_2712, + .ops = &sdhci_brcmstb_ops_2712, +}; + static struct brcmstb_match_priv match_priv_7425 = { - .flags = BRCMSTB_PRIV_FLAGS_NO_64BIT | - BRCMSTB_PRIV_FLAGS_BROKEN_TIMEOUT, + .flags = BRCMSTB_MATCH_FLAGS_NO_64BIT | + BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT, + .ops = &sdhci_brcmstb_ops, +}; + +static struct brcmstb_match_priv match_priv_74371 = { + .flags = BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT, .ops = &sdhci_brcmstb_ops, }; static struct brcmstb_match_priv match_priv_7445 = { - .flags = BRCMSTB_PRIV_FLAGS_BROKEN_TIMEOUT, + .flags = BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT, + .save_restore_regs = sdhci_brcmstb_save_restore_regs_v1, .ops = &sdhci_brcmstb_ops, }; +static struct brcmstb_match_priv match_priv_72116 = { + .flags = BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT, + .save_restore_regs = sdhci_brcmstb_save_restore_regs_v1, + .ops = &sdhci_brcmstb_ops_72116, +}; + static const struct brcmstb_match_priv match_priv_7216 = { + .flags = BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE, + .save_restore_regs = sdhci_brcmstb_save_restore_regs_v2, .hs400es = sdhci_brcmstb_hs400es, .ops = &sdhci_brcmstb_ops_7216, }; -static const struct of_device_id sdhci_brcm_of_match[] = { +static struct brcmstb_match_priv match_priv_74165b0 = { + .flags = BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE, + .save_restore_regs = sdhci_brcmstb_save_restore_regs_v2, + .hs400es = sdhci_brcmstb_hs400es, + .ops = &sdhci_brcmstb_ops_74165b0, +}; + +static const struct of_device_id __maybe_unused sdhci_brcm_of_match[] = { + { .compatible = "brcm,bcm2712-sdhci", .data = &match_priv_2712 }, { .compatible = "brcm,bcm7425-sdhci", .data = &match_priv_7425 }, + { .compatible = "brcm,bcm74371-sdhci", .data = &match_priv_74371 }, { .compatible = "brcm,bcm7445-sdhci", .data = &match_priv_7445 }, + { .compatible = "brcm,bcm72116-sdhci", .data = &match_priv_72116 }, { .compatible = "brcm,bcm7216-sdhci", .data = &match_priv_7216 }, + { .compatible = "brcm,bcm74165b0-sdhci", .data = &match_priv_74165b0 }, {}, }; @@ -176,7 +442,7 @@ static int sdhci_brcmstb_add_host(struct sdhci_host *host, bool dma64; int ret; - if (!priv->has_cqe) + if ((priv->flags & BRCMSTB_PRIV_FLAGS_HAS_CQE) == 0) return sdhci_add_host(host); dev_dbg(mmc_dev(host->mmc), "CQE is enabled\n"); @@ -223,10 +489,10 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host; const struct of_device_id *match; struct sdhci_brcmstb_priv *priv; + u32 actual_clock_mhz; struct sdhci_host *host; - struct resource *iomem; - bool has_cqe = false; struct clk *clk; + struct clk *base_clk = NULL; int res; match = of_match_node(sdhci_brcm_of_match, pdev->dev.of_node); @@ -234,35 +500,28 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "Probe found match for %s\n", match->compatible); - clk = devm_clk_get_optional(&pdev->dev, NULL); + clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); if (IS_ERR(clk)) return dev_err_probe(&pdev->dev, PTR_ERR(clk), - "Failed to get clock from Device Tree\n"); - - res = clk_prepare_enable(clk); - if (res) - return res; + "Failed to get and enable clock from Device Tree\n"); memset(&brcmstb_pdata, 0, sizeof(brcmstb_pdata)); - if (device_property_read_bool(&pdev->dev, "supports-cqe")) { - has_cqe = true; - match_priv->ops->irq = sdhci_brcmstb_cqhci_irq; - } brcmstb_pdata.ops = match_priv->ops; host = sdhci_pltfm_init(pdev, &brcmstb_pdata, sizeof(struct sdhci_brcmstb_priv)); - if (IS_ERR(host)) { - res = PTR_ERR(host); - goto err_clk; - } + if (IS_ERR(host)) + return PTR_ERR(host); pltfm_host = sdhci_priv(host); priv = sdhci_pltfm_priv(pltfm_host); - priv->has_cqe = has_cqe; + priv->match_priv = match->data; + if (device_property_read_bool(&pdev->dev, "supports-cqe")) { + priv->flags |= BRCMSTB_PRIV_FLAGS_HAS_CQE; + match_priv->ops->irq = sdhci_brcmstb_cqhci_irq; + } /* Map in the non-standard CFG registers */ - iomem = platform_get_resource(pdev, IORESOURCE_MEM, 1); - priv->cfg_regs = devm_ioremap_resource(&pdev->dev, iomem); + priv->cfg_regs = devm_platform_get_and_ioremap_resource(pdev, 1, NULL); if (IS_ERR(priv->cfg_regs)) { res = PTR_ERR(priv->cfg_regs); goto err; @@ -273,6 +532,21 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) if (res) goto err; + /* map non-standard BOOT registers if present */ + if (host->mmc->caps & MMC_CAP_NONREMOVABLE) { + priv->boot_regs = devm_platform_get_and_ioremap_resource(pdev, 2, NULL); + if (IS_ERR(priv->boot_regs)) + priv->boot_regs = NULL; + } + + /* + * Automatic clock gating does not work for SD cards that may + * voltage switch so only enable it for non-removable devices. + */ + if ((match_priv->flags & BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE) && + (host->mmc->caps & MMC_CAP_NONREMOVABLE)) + priv->flags |= BRCMSTB_PRIV_FLAGS_GATE_CLOCK; + /* * If the chip has enhanced strobe and it's enabled, add * callback @@ -281,22 +555,55 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) (host->mmc->caps2 & MMC_CAP2_HS400_ES)) host->mmc_host_ops.hs400_enhanced_strobe = match_priv->hs400es; + if (match_priv->cfginit) + match_priv->cfginit(host); + /* * Supply the existing CAPS, but clear the UHS modes. This * will allow these modes to be specified by device tree * properties through mmc_of_parse(). */ - host->caps = sdhci_readl(host, SDHCI_CAPABILITIES); - if (match_priv->flags & BRCMSTB_PRIV_FLAGS_NO_64BIT) + sdhci_read_caps(host); + if (match_priv->flags & BRCMSTB_MATCH_FLAGS_NO_64BIT) host->caps &= ~SDHCI_CAN_64BIT; - host->caps1 = sdhci_readl(host, SDHCI_CAPABILITIES_1); host->caps1 &= ~(SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_DDR50); - host->quirks |= SDHCI_QUIRK_MISSING_CAPS; - if (match_priv->flags & BRCMSTB_PRIV_FLAGS_BROKEN_TIMEOUT) + if (match_priv->flags & BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT) host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; + if (!(match_priv->flags & BRCMSTB_MATCH_FLAGS_USE_CARD_BUSY)) + host->mmc_host_ops.card_busy = NULL; + + /* Change the base clock frequency if the DT property exists */ + if (device_property_read_u32(&pdev->dev, "clock-frequency", + &priv->base_freq_hz) != 0) + goto add_host; + + base_clk = devm_clk_get_optional(&pdev->dev, "sdio_freq"); + if (IS_ERR(base_clk)) { + dev_warn(&pdev->dev, "Clock for \"sdio_freq\" not found\n"); + goto add_host; + } + + res = clk_prepare_enable(base_clk); + if (res) + goto err; + + /* set improved clock rate */ + clk_set_rate(base_clk, priv->base_freq_hz); + actual_clock_mhz = clk_get_rate(base_clk) / 1000000; + + host->caps &= ~SDHCI_CLOCK_V3_BASE_MASK; + host->caps |= (actual_clock_mhz << SDHCI_CLOCK_BASE_SHIFT); + /* Disable presets because they are now incorrect */ + host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN; + + dev_dbg(&pdev->dev, "Base Clock Frequency changed to %dMHz\n", + actual_clock_mhz); + priv->base_clk = base_clk; + +add_host: res = sdhci_brcmstb_add_host(host, priv); if (res) goto err; @@ -305,9 +612,7 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) return res; err: - sdhci_pltfm_free(pdev); -err_clk: - clk_disable_unprepare(clk); + clk_disable_unprepare(base_clk); return res; } @@ -318,15 +623,70 @@ static void sdhci_brcmstb_shutdown(struct platform_device *pdev) MODULE_DEVICE_TABLE(of, sdhci_brcm_of_match); +static int sdhci_brcmstb_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); + const struct brcmstb_match_priv *match_priv = priv->match_priv; + + int ret; + + if (match_priv->save_restore_regs) + match_priv->save_restore_regs(host->mmc, 1); + + clk_disable_unprepare(priv->base_clk); + if (host->mmc->caps2 & MMC_CAP2_CQE) { + ret = cqhci_suspend(host->mmc); + if (ret) + return ret; + } + + return sdhci_pltfm_suspend(dev); +} + +static int sdhci_brcmstb_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); + const struct brcmstb_match_priv *match_priv = priv->match_priv; + int ret; + + ret = sdhci_pltfm_resume(dev); + if (!ret && priv->base_freq_hz) { + ret = clk_prepare_enable(priv->base_clk); + /* + * Note: using clk_get_rate() below as clk_get_rate() + * honors CLK_GET_RATE_NOCACHE attribute, but clk_set_rate() + * may do implicit get_rate() calls that do not honor + * CLK_GET_RATE_NOCACHE. + */ + if (!ret && + (clk_get_rate(priv->base_clk) != priv->base_freq_hz)) + ret = clk_set_rate(priv->base_clk, priv->base_freq_hz); + } + + if (match_priv->save_restore_regs) + match_priv->save_restore_regs(host->mmc, 0); + + if (host->mmc->caps2 & MMC_CAP2_CQE) + ret = cqhci_resume(host->mmc); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(sdhci_brcmstb_pmops, sdhci_brcmstb_suspend, sdhci_brcmstb_resume); + static struct platform_driver sdhci_brcmstb_driver = { .driver = { .name = "sdhci-brcmstb", .probe_type = PROBE_PREFER_ASYNCHRONOUS, - .pm = &sdhci_pltfm_pmops, + .pm = pm_sleep_ptr(&sdhci_brcmstb_pmops), .of_match_table = of_match_ptr(sdhci_brcm_of_match), }, .probe = sdhci_brcmstb_probe, - .remove = sdhci_pltfm_unregister, + .remove = sdhci_pltfm_remove, .shutdown = sdhci_brcmstb_shutdown, }; |
