mmc: msm_sdcc: adapt sdcc driver to new clock apis
Turning on/off a clock involves a lot of steps. Depending on the clock and
its state, the steps to turn it on/off might involve sending the request
to RPM, changing voltages, enabling the source PLLs, register writes, etc.
Some of these steps take a lot of time and the some of them are really
fast.
The current APIs (clk_enable/disable()) have the requirement that they be
callable from atomic context to allow clocks to be turned on in any
situation (IRQ handlers, inside spinlocks, etc).
While this allows for a very simple API for users of clocks, it has the
negative impact of forcing the clock driver to do the slow steps of
turning on/off a clock with spinlocks held and busy waiting for
them to finish.
By splitting, these steps into the 4 new APIs
(clk_prepare/clk_enable & clk_disable/clk_unprepare)
instead of the 2 current APIs, it allows the clock driver to perform
the slow steps with a mutex lock held and removes the need to busy
wait for the slow steps to finish.
clk_prepare - might sleep
clk_enable - can be called from atomic context
clk_unprepare - might sleep
clk_disable - can be called from atomic context
clk_set_rate - might sleep
This change ensures:
1. The older clock APIs are replaced by the new
clock APIs.
clk_enable replaced by
clk_prepare_enable
clk_disable replaced by
clk_disable_unprepare
2. No clock APIs are invoked in atomic context.
CRs-Fixed: 349546
Change-Id: Ibfa63e46809d64bfa8ea470aa1103d784619b1c7
Signed-off-by: Asutosh Das <asutoshd@codeaurora.org>
diff --git a/drivers/mmc/host/msm_sdcc.c b/drivers/mmc/host/msm_sdcc.c
index 3622ff3..122c891 100644
--- a/drivers/mmc/host/msm_sdcc.c
+++ b/drivers/mmc/host/msm_sdcc.c
@@ -2213,24 +2213,28 @@
return 0;
}
+/*
+ * Any function calling msmsdcc_setup_clocks must
+ * acquire clk_mutex. May sleep.
+ */
static inline void msmsdcc_setup_clocks(struct msmsdcc_host *host, bool enable)
{
if (enable) {
if (!IS_ERR_OR_NULL(host->dfab_pclk))
- clk_enable(host->dfab_pclk);
+ clk_prepare_enable(host->dfab_pclk);
if (!IS_ERR(host->pclk))
- clk_enable(host->pclk);
- clk_enable(host->clk);
+ clk_prepare_enable(host->pclk);
+ clk_prepare_enable(host->clk);
mb();
msmsdcc_delay(host);
} else {
mb();
msmsdcc_delay(host);
- clk_disable(host->clk);
+ clk_disable_unprepare(host->clk);
if (!IS_ERR(host->pclk))
- clk_disable(host->pclk);
+ clk_disable_unprepare(host->pclk);
if (!IS_ERR_OR_NULL(host->dfab_pclk))
- clk_disable(host->dfab_pclk);
+ clk_disable_unprepare(host->dfab_pclk);
}
}
@@ -2541,7 +2545,6 @@
unsigned long flags;
unsigned int clock;
- DBG(host, "ios->clock = %u\n", ios->clock);
/*
* Disable SDCC core interrupt until set_ios is completed.
@@ -2549,8 +2552,12 @@
* when turning on/off the clocks. One possible
* scenario is SDIO operational interrupt while the clock
* is turned off.
+ * host->lock is being released intermittently below.
+ * Thus, prevent concurrent access to host.
*/
+ mutex_lock(&host->clk_mutex);
+ DBG(host, "ios->clock = %u\n", ios->clock);
spin_lock_irqsave(&host->lock, flags);
if (!host->sdcc_irq_disabled) {
spin_unlock_irqrestore(&host->lock, flags);
@@ -2565,7 +2572,9 @@
spin_lock_irqsave(&host->lock, flags);
if (ios->clock) {
if (!host->clks_on) {
+ spin_unlock_irqrestore(&host->lock, flags);
msmsdcc_setup_clocks(host, true);
+ spin_lock_irqsave(&host->lock, flags);
host->clks_on = 1;
writel_relaxed(host->mci_irqenable,
host->base + MMCIMASK0);
@@ -2596,7 +2605,9 @@
}
if (clock != host->clk_rate) {
+ spin_unlock_irqrestore(&host->lock, flags);
rc = clk_set_rate(host->clk, clock);
+ spin_lock_irqsave(&host->lock, flags);
if (rc < 0)
pr_err("%s: failed to set clk rate %u\n",
mmc_hostname(mmc), clock);
@@ -2667,7 +2678,13 @@
if (!(clk & MCI_CLK_ENABLE) && host->clks_on) {
msmsdcc_cfg_sdio_wakeup(host, true);
+ spin_unlock_irqrestore(&host->lock, flags);
+ /*
+ * May get a wake-up interrupt the instant we disable the
+ * clocks. This would disable the wake-up interrupt.
+ */
msmsdcc_setup_clocks(host, false);
+ spin_lock_irqsave(&host->lock, flags);
host->clks_on = 0;
}
@@ -2682,6 +2699,7 @@
}
spin_unlock_irqrestore(&host->lock, flags);
+ mutex_unlock(&host->clk_mutex);
}
int msmsdcc_set_pwrsave(struct mmc_host *mmc, int pwrsave)
@@ -2832,12 +2850,16 @@
msmsdcc_pm_qos_update_latency(host, 1);
+ mutex_lock(&host->clk_mutex);
spin_lock_irqsave(&host->lock, flags);
if (!host->clks_on) {
+ spin_unlock_irqrestore(&host->lock, flags);
msmsdcc_setup_clocks(host, true);
+ spin_lock_irqsave(&host->lock, flags);
host->clks_on = 1;
}
spin_unlock_irqrestore(&host->lock, flags);
+ mutex_unlock(&host->clk_mutex);
return 0;
}
@@ -2852,12 +2874,16 @@
if (mmc->card && mmc_card_sdio(mmc->card))
return 0;
+ mutex_lock(&host->clk_mutex);
spin_lock_irqsave(&host->lock, flags);
if (host->clks_on) {
+ spin_unlock_irqrestore(&host->lock, flags);
msmsdcc_setup_clocks(host, false);
+ spin_lock_irqsave(&host->lock, flags);
host->clks_on = 0;
}
spin_unlock_irqrestore(&host->lock, flags);
+ mutex_unlock(&host->clk_mutex);
return 0;
}
@@ -3510,18 +3536,8 @@
host->sdio_wakeupirq_disabled = 1;
}
if (host->plat->is_sdio_al_client) {
- if (!host->clks_on) {
- msmsdcc_setup_clocks(host, true);
- host->clks_on = 1;
- }
- if (host->sdcc_irq_disabled) {
- writel_relaxed(host->mci_irqenable,
- host->base + MMCIMASK0);
- mb();
- enable_irq(host->core_irqres->start);
- host->sdcc_irq_disabled = 0;
- }
wake_lock(&host->sdio_wlock);
+ mmc_signal_sdio_irq(host->mmc);
}
spin_unlock(&host->lock);
@@ -4380,6 +4396,7 @@
host->dmares = dmares;
host->dma_crci_res = dma_crci_res;
spin_lock_init(&host->lock);
+ mutex_init(&host->clk_mutex);
#ifdef CONFIG_MMC_EMBEDDED_SDIO
if (plat->embedded_sdio)
@@ -4416,7 +4433,7 @@
ret = clk_set_rate(host->dfab_pclk, 64000000);
if (ret)
goto dfab_pclk_put;
- ret = clk_enable(host->dfab_pclk);
+ ret = clk_prepare_enable(host->dfab_pclk);
if (ret)
goto dfab_pclk_put;
} else
@@ -4428,7 +4445,7 @@
*/
host->pclk = clk_get(&pdev->dev, "iface_clk");
if (!IS_ERR(host->pclk)) {
- ret = clk_enable(host->pclk);
+ ret = clk_prepare_enable(host->pclk);
if (ret)
goto pclk_put;
@@ -4450,7 +4467,7 @@
goto clk_put;
}
- ret = clk_enable(host->clk);
+ ret = clk_prepare_enable(host->clk);
if (ret)
goto clk_put;
@@ -4771,12 +4788,12 @@
clk_put(host->clk);
pclk_disable:
if (!IS_ERR(host->pclk))
- clk_disable(host->pclk);
+ clk_disable_unprepare(host->pclk);
pclk_put:
if (!IS_ERR(host->pclk))
clk_put(host->pclk);
if (!IS_ERR_OR_NULL(host->dfab_pclk))
- clk_disable(host->dfab_pclk);
+ clk_disable_unprepare(host->dfab_pclk);
dfab_pclk_put:
if (!IS_ERR_OR_NULL(host->dfab_pclk))
clk_put(host->dfab_pclk);
@@ -4874,6 +4891,7 @@
struct msmsdcc_host *host = mmc_priv(mmc);
unsigned long flags;
+ mutex_lock(&host->clk_mutex);
spin_lock_irqsave(&host->lock, flags);
pr_debug("%s: %sabling LPM\n", mmc_hostname(mmc),
enable ? "En" : "Dis");
@@ -4886,7 +4904,9 @@
}
if (host->clks_on) {
+ spin_unlock_irqrestore(&host->lock, flags);
msmsdcc_setup_clocks(host, false);
+ spin_lock_irqsave(&host->lock, flags);
host->clks_on = 0;
}
@@ -4919,7 +4939,9 @@
}
if (!host->clks_on) {
+ spin_unlock_irqrestore(&host->lock, flags);
msmsdcc_setup_clocks(host, true);
+ spin_lock_irqsave(&host->lock, flags);
host->clks_on = 1;
}
@@ -4932,6 +4954,7 @@
}
}
spin_unlock_irqrestore(&host->lock, flags);
+ mutex_unlock(&host->clk_mutex);
return 0;
}
#else
@@ -5121,7 +5144,7 @@
* during suspend and not allowing TCXO.
*/
- if (host->clks_on) {
+ if (host->clks_on && !host->plat->is_sdio_al_client) {
pr_warn("%s: clocks are on after suspend, aborting system "
"suspend\n", mmc_hostname(mmc));
rc = -EAGAIN;
diff --git a/drivers/mmc/host/msm_sdcc.h b/drivers/mmc/host/msm_sdcc.h
index 832e437..406af4f 100644
--- a/drivers/mmc/host/msm_sdcc.h
+++ b/drivers/mmc/host/msm_sdcc.h
@@ -399,6 +399,7 @@
bool sdcc_irq_disabled;
bool sdcc_suspended;
bool sdio_wakeupirq_disabled;
+ struct mutex clk_mutex;
};
int msmsdcc_set_pwrsave(struct mmc_host *mmc, int pwrsave);