|
|
|
[PATCH] MMCI Aggressive clocking v2 | |
| [Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] | |
This patch gates the clock on the MMCI agressively, while still
conforming to the MMC spec to leave the clock on for 8 full
bus cycles before gating it off. The disablement is handled by
splitting off a work item which gives some additional slack in
the typical case.
Signed-off-by: Linus Walleij <linus.walleij@xxxxxxxxxxxx>
---
drivers/mmc/host/mmci.c | 90 +++++++++++++++++++++++++++++++++++++++++++---
drivers/mmc/host/mmci.h | 3 ++
2 files changed, 87 insertions(+), 6 deletions(-)
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
index 2909bbc..f882b22 100644
--- a/drivers/mmc/host/mmci.c
+++ b/drivers/mmc/host/mmci.c
@@ -14,6 +14,7 @@
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
+#include <linux/workqueue.h>
#include <linux/err.h>
#include <linux/highmem.h>
#include <linux/log2.h>
@@ -37,6 +38,63 @@
static unsigned int fmax = 515633;
+static void mmci_clk_disable_delayed(struct mmci_host *host)
+{
+ unsigned long tick_ns;
+ unsigned long freq = clk_get_rate(host->clk);
+ int users;
+
+ /*
+ * New users may have appeared while we were scheduling,
+ * then there is no reason to delay clk_disable().
+ */
+ spin_lock(&host->lock);
+ users = host->clk_users;
+ spin_unlock(&host->lock);
+ /*
+ * Delay 8 bus cycles (from MMC spec) before attempting
+ * to disable the MMCI block clock. The reference count
+ * may have gone up again afterwards!
+ */
+ if (!users && freq > 0) {
+ tick_ns = (1000000000 + freq - 1) / freq;
+ ndelay(8 * tick_ns);
+ }
+ spin_lock(&host->lock);
+ if (!host->clk_users)
+ clk_disable(host->clk);
+ host->clk_pending_gate = false;
+ spin_unlock(&host->lock);
+}
+
+/* Disable the clock after some grace time */
+static void mmci_clk_disable_work(struct work_struct *work)
+{
+ struct mmci_host *host = container_of(work, struct mmci_host,
+ clk_disable_work);
+
+ mmci_clk_disable_delayed(host);
+}
+
+
+/* These must be called with host->lock held */
+static void mmci_clk_enable(struct mmci_host *host)
+{
+ /* If a gate is pending the clock is still on */
+ if (!host->clk_users && !host->clk_pending_gate)
+ clk_enable(host->clk);
+ host->clk_users++;
+}
+
+static void mmci_clk_disable(struct mmci_host *host)
+{
+ host->clk_users--;
+ if (!host->clk_users) {
+ host->clk_pending_gate = true;
+ schedule_work(&host->clk_disable_work);
+ }
+}
+
static void
mmci_request_end(struct mmci_host *host, struct mmc_request *mrq)
{
@@ -57,6 +115,7 @@ mmci_request_end(struct mmci_host *host, struct
mmc_request *mrq)
spin_unlock(&host->lock);
mmc_request_done(host->mmc, mrq);
spin_lock(&host->lock);
+ mmci_clk_disable(host);
}
static void mmci_stop_data(struct mmci_host *host)
@@ -405,6 +464,8 @@ static void mmci_request(struct mmc_host *mmc,
struct mmc_request *mrq)
spin_lock_irqsave(&host->lock, flags);
+ mmci_clk_enable(host);
+
host->mrq = mrq;
if (mrq->data && mrq->data->flags & MMC_DATA_READ)
@@ -464,12 +525,18 @@ static void mmci_set_ios(struct mmc_host *mmc,
struct mmc_ios *ios)
}
}
+ spin_lock(&host->lock);
+ mmci_clk_enable(host);
+
writel(clk, host->base + MMCICLOCK);
if (host->pwr != pwr) {
host->pwr = pwr;
writel(pwr, host->base + MMCIPOWER);
}
+
+ mmci_clk_disable(host);
+ spin_unlock(&host->lock);
}
static const struct mmc_host_ops mmci_ops = {
@@ -527,9 +594,10 @@ static int mmci_probe(struct amba_device *dev, void *id)
goto host_free;
}
- ret = clk_enable(host->clk);
- if (ret)
- goto clk_free;
+ host->clk_users = 0;
+ host->clk_pending_gate = false;
+ INIT_WORK(&host->clk_disable_work, mmci_clk_disable_work);
+ mmci_clk_enable(host);
host->plat = plat;
host->mclk = clk_get_rate(host->clk);
@@ -615,6 +683,11 @@ static int mmci_probe(struct amba_device *dev, void *id)
host->timer.expires = jiffies + HZ;
add_timer(&host->timer);
+ spin_lock(&host->lock);
+ mmci_clk_disable(host);
+ spin_unlock(&host->lock);
+
+
return 0;
irq0_free:
@@ -622,8 +695,7 @@ static int mmci_probe(struct amba_device *dev, void *id)
unmap:
iounmap(host->base);
clk_disable:
- clk_disable(host->clk);
- clk_free:
+ mmci_clk_disable(host);
clk_put(host->clk);
host_free:
mmc_free_host(mmc);
@@ -646,17 +718,23 @@ static int mmci_remove(struct amba_device *dev)
mmc_remove_host(mmc);
+ mmci_clk_enable(host);
+
writel(0, host->base + MMCIMASK0);
writel(0, host->base + MMCIMASK1);
writel(0, host->base + MMCICOMMAND);
writel(0, host->base + MMCIDATACTRL);
+ mmci_clk_disable(host);
+ if (cancel_work_sync(&host->clk_disable_work))
+ mmci_clk_disable_delayed(host);
+
free_irq(dev->irq[0], host);
free_irq(dev->irq[1], host);
iounmap(host->base);
- clk_disable(host->clk);
+
clk_put(host->clk);
mmc_free_host(mmc);
diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h
index 0441bac..7d9e5b5 100644
--- a/drivers/mmc/host/mmci.h
+++ b/drivers/mmc/host/mmci.h
@@ -151,6 +151,9 @@ struct mmci_host {
struct mmc_data *data;
struct mmc_host *mmc;
struct clk *clk;
+ int clk_users;
+ bool clk_pending_gate;
+ struct work_struct clk_disable_work;
unsigned int data_xfered;
--
1.6.2.1
-------------------------------------------------------------------
List admin: http://lists.arm.linux.org.uk/mailman/listinfo/linux-arm-kernel
FAQ: http://www.arm.linux.org.uk/mailinglists/faq.php
Etiquette: http://www.arm.linux.org.uk/mailinglists/etiquette.php
[Linux ARM (vger)] [Linux ARM MSM] [Linux Omap] [Linux Arm] [Fedora ARM] [eCos] [Linux Fastboot] [Gcc Help] [Git] [DCCP] [IETF Announce] [Security] [PDAs] [Linux] [Linux Book List] [Linux MIPS] [Yosemite Campsites] [Photos]
![]() |
![]() |