summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSahitya Tummala <stummala@codeaurora.org>2013-01-11 11:30:45 +0530
committerSubhash Jadavani <subhashj@codeaurora.org>2016-05-27 10:28:27 -0700
commit15685a80dbe71dc8f3fc3f2448e5b340e1d9e50c (patch)
tree6d3061fa3b16f462fcad7ed82ba432f12b19ab6f
parenta507b5a6c4f7b46189b04bf81bf1d66e2fd371f0 (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.c75
-rw-r--r--drivers/mmc/host/sdhci.c33
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)