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>
This commit is contained in:
parent
a507b5a6c4
commit
15685a80db
2 changed files with 90 additions and 18 deletions
|
@ -78,6 +78,7 @@
|
||||||
|
|
||||||
/* 8KB descriptors */
|
/* 8KB descriptors */
|
||||||
#define SDHCI_MSM_MAX_SEGMENTS (1 << 13)
|
#define SDHCI_MSM_MAX_SEGMENTS (1 << 13)
|
||||||
|
#define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */
|
||||||
|
|
||||||
static const u32 tuning_block_64[] = {
|
static const u32 tuning_block_64[] = {
|
||||||
0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE,
|
0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE,
|
||||||
|
@ -198,6 +199,7 @@ struct sdhci_msm_host {
|
||||||
struct clk *clk; /* main SD/MMC bus clock */
|
struct clk *clk; /* main SD/MMC bus clock */
|
||||||
struct clk *pclk; /* SDHC peripheral bus clock */
|
struct clk *pclk; /* SDHC peripheral bus clock */
|
||||||
struct clk *bus_clk; /* SDHC bus voter 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 sdhci_msm_pltfm_data *pdata;
|
||||||
struct mmc_host *mmc;
|
struct mmc_host *mmc;
|
||||||
struct sdhci_pltfm_data sdhci_msm_pdata;
|
struct sdhci_pltfm_data sdhci_msm_pdata;
|
||||||
|
@ -1506,11 +1508,73 @@ static unsigned int sdhci_msm_max_segs(void)
|
||||||
return SDHCI_MSM_MAX_SEGMENTS;
|
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 = {
|
static struct sdhci_ops sdhci_msm_ops = {
|
||||||
.check_power_status = sdhci_msm_check_power_status,
|
.check_power_status = sdhci_msm_check_power_status,
|
||||||
.platform_execute_tuning = sdhci_msm_execute_tuning,
|
.platform_execute_tuning = sdhci_msm_execute_tuning,
|
||||||
.toggle_cdr = sdhci_msm_toggle_cdr,
|
.toggle_cdr = sdhci_msm_toggle_cdr,
|
||||||
.get_max_segments = sdhci_msm_max_segs,
|
.get_max_segments = sdhci_msm_max_segs,
|
||||||
|
.set_clock = sdhci_msm_set_clock,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int sdhci_msm_probe(struct platform_device *pdev)
|
static int sdhci_msm_probe(struct platform_device *pdev)
|
||||||
|
@ -1587,6 +1651,7 @@ static int sdhci_msm_probe(struct platform_device *pdev)
|
||||||
if (ret)
|
if (ret)
|
||||||
goto pclk_disable;
|
goto pclk_disable;
|
||||||
|
|
||||||
|
atomic_set(&msm_host->clks_on, 1);
|
||||||
/* Setup regulators */
|
/* Setup regulators */
|
||||||
ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true);
|
ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
@ -1657,6 +1722,9 @@ static int sdhci_msm_probe(struct platform_device *pdev)
|
||||||
/* Enable pwr irq interrupts */
|
/* Enable pwr irq interrupts */
|
||||||
writel_relaxed(INT_MASK, (msm_host->core_mem + CORE_PWRCTL_MASK));
|
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 */
|
/* Set host capabilities */
|
||||||
msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width;
|
msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width;
|
||||||
msm_host->mmc->caps |= msm_host->pdata->caps;
|
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_remove_host(host, dead);
|
||||||
sdhci_pltfm_free(pdev);
|
sdhci_pltfm_free(pdev);
|
||||||
sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false);
|
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)
|
if (pdata->pin_data)
|
||||||
sdhci_msm_setup_pins(pdata, false);
|
sdhci_msm_setup_pins(pdata, false);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -1168,6 +1168,7 @@ void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
|
||||||
|
|
||||||
host->mmc->actual_clock = 0;
|
host->mmc->actual_clock = 0;
|
||||||
|
|
||||||
|
if (host->clock)
|
||||||
sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
|
sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
|
||||||
if (host->quirks2 & SDHCI_QUIRK2_NEED_DELAY_AFTER_INT_CLK_RST)
|
if (host->quirks2 & SDHCI_QUIRK2_NEED_DELAY_AFTER_INT_CLK_RST)
|
||||||
mdelay(1);
|
mdelay(1);
|
||||||
|
@ -1472,22 +1473,16 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
|
||||||
return;
|
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 &&
|
if (host->version >= SDHCI_SPEC_300 &&
|
||||||
(ios->power_mode == MMC_POWER_UP) &&
|
(ios->power_mode == MMC_POWER_UP) &&
|
||||||
!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN))
|
!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN))
|
||||||
sdhci_enable_preset_value(host, false);
|
sdhci_enable_preset_value(host, false);
|
||||||
|
|
||||||
|
spin_lock_irqsave(&host->lock, flags);
|
||||||
if (!ios->clock || ios->clock != host->clock) {
|
if (!ios->clock || ios->clock != host->clock) {
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
host->ops->set_clock(host, ios->clock);
|
host->ops->set_clock(host, ios->clock);
|
||||||
|
spin_lock_irqsave(&host->lock, flags);
|
||||||
host->clock = ios->clock;
|
host->clock = ios->clock;
|
||||||
|
|
||||||
if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK &&
|
if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK &&
|
||||||
|
@ -1502,7 +1497,9 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
|
||||||
host->mmc->max_busy_timeout /= host->timeout_clk;
|
host->mmc->max_busy_timeout /= host->timeout_clk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
|
||||||
|
if (ios->power_mode & (MMC_POWER_UP | MMC_POWER_ON))
|
||||||
sdhci_set_power(host, ios->power_mode, ios->vdd);
|
sdhci_set_power(host, ios->power_mode, ios->vdd);
|
||||||
|
|
||||||
if (host->ops->platform_send_init_74_clocks)
|
if (host->ops->platform_send_init_74_clocks)
|
||||||
|
@ -1602,6 +1599,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
|
||||||
} else
|
} else
|
||||||
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
|
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
/*
|
/*
|
||||||
* Some (ENE) controllers go apeshit on some ios operation,
|
* Some (ENE) controllers go apeshit on some ios operation,
|
||||||
* signalling timeout and CRC errors even on CMD0. Resetting
|
* 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)
|
if (host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS)
|
||||||
sdhci_do_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
|
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();
|
mmiowb();
|
||||||
spin_unlock_irqrestore(&host->lock, flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||||
|
|
Loading…
Add table
Reference in a new issue