This patch modified the MMC core code to optionally call the
set_ios() operation on the driver with the clock frequency set
to 0 to gate the hardware block clock (and thus the MCI clock)
for an MMC host controller after a grace period of at least 8
MCLK cycles. It is inspired by existing clock gating code found
in the OMAP and Atmel drivers and brings this up to the host
abstraction. Gating is performed before and after any MMC
request.
It exemplifies by implementing this for the MMCI/PL180 MMC/SD
host controller, but it should be simple to switch OMAP and
Atmel over to using this instead.
Signed-off-by: Linus Walleij
ChangeLog v2->v3:
* Swapped alloc*/remove* pair for init*/exit* in the clocking
functions.
* Remove mmc_clk_enable/disable pair in the mmc_rescan(). It's
not needed here.
* Rename struct member .clk_users to .clk_requests so as to
reflect what this is actually counting. It counts the number
of current requests that need to have the MCI clk enabled.
* Save and restore flags on all spinlocks, the code can be
called in different contexts according to lockdep.
* Spelling mistakes fixed.
---
drivers/mmc/core/Kconfig | 11 +++
drivers/mmc/core/core.c| 35 +
drivers/mmc/core/core.h|2 +
drivers/mmc/core/debugfs.c | 10 ++-
drivers/mmc/core/host.c| 165 +++-
drivers/mmc/core/host.h|3 +
include/linux/mmc/host.h |9 +++
7 files changed, 232 insertions(+), 3 deletions(-)
diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig
index ab37a6d..6ae2156 100644
--- a/drivers/mmc/core/Kconfig
+++ b/drivers/mmc/core/Kconfig
@@ -14,3 +14,14 @@ config MMC_UNSAFE_RESUME
This option is usually just for embedded systems which use
a MMC/SD card for rootfs. Most people should say N here.
+config MMC_CLKGATE
+ bool "MMC host clock gaing (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ help
+ This will attempt to agressively gate the clock to the MMC host,
+ which typically also will gate the MCI clock to the card. This
+ is done to save power due to gating off the logic and bus noise
+ when MMC is not in use. Your host driver has to support this in
+ order for it to be of any use.
+
+ Of unsure, say N.
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 2649117..e9093b6 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -113,6 +113,8 @@ void mmc_request_done(struct mmc_host *host, struct
mmc_request *mrq)
if (mrq->done)
mrq->done(mrq);
+
+ mmc_clk_disable(host);
}
}
@@ -173,6 +175,7 @@ mmc_start_request(struct mmc_host *host, struct mmc_request
*mrq)
mrq->stop->mrq = mrq;
}
}
+ mmc_clk_enable(host);
host->ops->request(host, mrq);
}
@@ -447,6 +450,38 @@ void mmc_set_clock(struct mmc_host *host, unsigned int hz)
mmc_set_ios(host);
}
+#ifdef CONFIG_MMC_CLKGATE
+/*
+ * This gates the clock by setting it to 0 Hz.
+ */
+void mmc_gate_clock(struct mmc_host *host)
+{
+ host->clk_old = host->ios.clock;
+ host->ios.clock = 0;
+ mmc_set_ios(host);
+}
+
+/*
+ * This restores the clock from gating by using the cached
+ * clock value.
+ */
+void mmc_ungate_clock(struct mmc_host *host)
+{
+ /*
+* We should previously have gated the clock, so the clock
+* shall be 0 here!
+* The clock may however be 0 during intialization,
+* when some request operations are performed before setting
+* the frequency. When ungate is requested in that situation
+* we just ignore the call.
+*/
+ if (host->clk_old) {
+ BUG_ON(host->ios.clock);
+ mmc_set_clock(host, host->clk_old);
+ }
+}
+#endif
+
/*
* Change the bus mode (open drain/push-pull) of a host.
*/
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index c819eff..ee27f81 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -27,6 +27,8 @@ void mmc_detach_bus(struct mmc_host *host);
void mmc_set_chip_select(struct mmc_host *host, int mode);
void mmc_set_clock(struct mmc_host *host, unsigned int hz);
+void mmc_gate_clock(struct mmc_host *host);
+void mmc_ungate_clock(struct mmc_host *host);
void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode);
void mmc_set_bus_width(struct mmc_host *host, unsigned int width);
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c
index 610dbd1..1a969bd 100644
--- a/drivers/mmc/core/debugfs.c
+++ b/drivers/mmc/core/debugfs.c
@@ -149,11 +149,17 @@ void mmc_add_host_debugfs(struct mmc_host *host)
host->debugfs_root = root;
if (!debugfs_create_file("ios", S_IRUSR, root, host, &mmc_ios_fops))
- goto err_ios;
+