diff options
author | Sahitya Tummala <stummala@codeaurora.org> | 2013-01-11 11:30:45 +0530 |
---|---|---|
committer | Subhash Jadavani <subhashj@codeaurora.org> | 2016-05-27 10:28:27 -0700 |
commit | 15685a80dbe71dc8f3fc3f2448e5b340e1d9e50c (patch) | |
tree | 6d3061fa3b16f462fcad7ed82ba432f12b19ab6f | |
parent | a507b5a6c4f7b46189b04bf81bf1d66e2fd371f0 (diff) |
mmc: sdhci: Add host driver support to enable clock gating
Enable config MMC_CLKGATE to enable aggressive clock gating framework that
will disable clocks when the host is not in use for 200ms.
Change-Id: I6bef5dc18b561871689b3d730fd3486323b12520
Signed-off-by: Sahitya Tummala <stummala@codeaurora.org>
[venkatg@codeaurora.org: sdhci_set_clock is now a library function thats
called from platform clock handler, make changes to address that]
Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>
[subhashj@codeaurora.org: fixed minor merge conflict]
Signed-off-by: Subhash Jadavani <subhashj@codeaurora.org>
-rw-r--r-- | drivers/mmc/host/sdhci-msm.c | 75 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 33 |
2 files changed, 90 insertions, 18 deletions
diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 5b5cbdfa706b..c92b61b15779 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -78,6 +78,7 @@ /* 8KB descriptors */ #define SDHCI_MSM_MAX_SEGMENTS (1 << 13) +#define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */ static const u32 tuning_block_64[] = { 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, @@ -198,6 +199,7 @@ struct sdhci_msm_host { struct clk *clk; /* main SD/MMC bus clock */ struct clk *pclk; /* SDHC peripheral bus clock */ struct clk *bus_clk; /* SDHC bus voter clock */ + atomic_t clks_on; /* Set if clocks are enabled */ struct sdhci_msm_pltfm_data *pdata; struct mmc_host *mmc; struct sdhci_pltfm_data sdhci_msm_pdata; @@ -1506,11 +1508,73 @@ static unsigned int sdhci_msm_max_segs(void) return SDHCI_MSM_MAX_SEGMENTS; } +void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) +{ + int rc; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + unsigned long flags; + + if (clock && !atomic_read(&msm_host->clks_on)) { + pr_debug("%s: request to enable clock at rate %u\n", + mmc_hostname(host->mmc), clock); + if (!IS_ERR_OR_NULL(msm_host->bus_clk)) { + rc = clk_prepare_enable(msm_host->bus_clk); + if (rc) { + pr_err("%s: %s: failed to enable the bus-clock with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto out; + } + } + if (!IS_ERR(msm_host->pclk)) { + rc = clk_prepare_enable(msm_host->pclk); + if (rc) { + pr_err("%s: %s: failed to enable the pclk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto disable_bus_clk; + } + } + rc = clk_prepare_enable(msm_host->clk); + if (rc) { + pr_err("%s: %s: failed to enable the host-clk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto disable_pclk; + } + mb(); + atomic_set(&msm_host->clks_on, 1); + + } else if (!clock && atomic_read(&msm_host->clks_on)) { + pr_debug("%s: request to disable clocks\n", + mmc_hostname(host->mmc)); + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + mb(); + clk_disable_unprepare(msm_host->clk); + if (!IS_ERR(msm_host->pclk)) + clk_disable_unprepare(msm_host->pclk); + if (!IS_ERR_OR_NULL(msm_host->bus_clk)) + clk_disable_unprepare(msm_host->bus_clk); + atomic_set(&msm_host->clks_on, 0); + } + spin_lock_irqsave(&host->lock, flags); + host->clock = clock; + spin_unlock_irqrestore(&host->lock, flags); + goto out; +disable_pclk: + if (!IS_ERR_OR_NULL(msm_host->pclk)) + clk_disable_unprepare(msm_host->pclk); +disable_bus_clk: + if (!IS_ERR_OR_NULL(msm_host->bus_clk)) + clk_disable_unprepare(msm_host->bus_clk); +out: + return; +} + static struct sdhci_ops sdhci_msm_ops = { .check_power_status = sdhci_msm_check_power_status, .platform_execute_tuning = sdhci_msm_execute_tuning, .toggle_cdr = sdhci_msm_toggle_cdr, .get_max_segments = sdhci_msm_max_segs, + .set_clock = sdhci_msm_set_clock, }; static int sdhci_msm_probe(struct platform_device *pdev) @@ -1587,6 +1651,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (ret) goto pclk_disable; + atomic_set(&msm_host->clks_on, 1); /* Setup regulators */ ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true); if (ret) { @@ -1657,6 +1722,9 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Enable pwr irq interrupts */ writel_relaxed(INT_MASK, (msm_host->core_mem + CORE_PWRCTL_MASK)); + /* Set clock gating delay to be used when CONFIG_MMC_CLKGATE is set */ + msm_host->mmc->clkgate_delay = SDHCI_MSM_MMC_CLK_GATE_DELAY; + /* Set host capabilities */ msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; msm_host->mmc->caps |= msm_host->pdata->caps; @@ -1725,12 +1793,7 @@ static int sdhci_msm_remove(struct platform_device *pdev) sdhci_remove_host(host, dead); sdhci_pltfm_free(pdev); sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); - if (!IS_ERR(msm_host->clk)) - clk_disable_unprepare(msm_host->clk); - if (!IS_ERR(msm_host->pclk)) - clk_disable_unprepare(msm_host->pclk); - if (!IS_ERR_OR_NULL(msm_host->bus_clk)) - clk_disable_unprepare(msm_host->bus_clk); + if (pdata->pin_data) sdhci_msm_setup_pins(pdata, false); return 0; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 0237da8bf1f9..2ed69b8ac9b5 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1168,7 +1168,8 @@ void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) host->mmc->actual_clock = 0; - sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + if (host->clock) + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); if (host->quirks2 & SDHCI_QUIRK2_NEED_DELAY_AFTER_INT_CLK_RST) mdelay(1); @@ -1472,22 +1473,16 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) return; } - /* - * Reset the chip on each power off. - * Should clear out any weird states. - */ - if (ios->power_mode == MMC_POWER_OFF) { - sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE); - sdhci_reinit(host); - } - if (host->version >= SDHCI_SPEC_300 && (ios->power_mode == MMC_POWER_UP) && !(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN)) sdhci_enable_preset_value(host, false); + spin_lock_irqsave(&host->lock, flags); if (!ios->clock || ios->clock != host->clock) { + spin_unlock_irqrestore(&host->lock, flags); host->ops->set_clock(host, ios->clock); + spin_lock_irqsave(&host->lock, flags); host->clock = ios->clock; if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK && @@ -1502,8 +1497,10 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) host->mmc->max_busy_timeout /= host->timeout_clk; } } + spin_unlock_irqrestore(&host->lock, flags); - sdhci_set_power(host, ios->power_mode, ios->vdd); + if (ios->power_mode & (MMC_POWER_UP | MMC_POWER_ON)) + sdhci_set_power(host, ios->power_mode, ios->vdd); if (host->ops->platform_send_init_74_clocks) host->ops->platform_send_init_74_clocks(host, ios->power_mode); @@ -1602,6 +1599,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) } else sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + spin_unlock_irqrestore(&host->lock, flags); /* * Some (ENE) controllers go apeshit on some ios operation, * signalling timeout and CRC errors even on CMD0. Resetting @@ -1610,8 +1608,19 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) if (host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS) sdhci_do_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + /* + * Reset the chip on each power off. + * Should clear out any weird states. + */ + if (ios->power_mode == MMC_POWER_OFF) { + sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE); + sdhci_reinit(host); + sdhci_set_power(host, ios->power_mode, ios->vdd); + } + if (!ios->clock) + sdhci_set_clock(host, ios->clock); + mmiowb(); - spin_unlock_irqrestore(&host->lock, flags); } static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) |