[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]

Add to Google Follow linuxarm on Twitter