mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-12-27 05:24:14 +02:00
212 lines
5.8 KiB
Diff
212 lines
5.8 KiB
Diff
|
From ff31834cfaac386f94ddd65fab8b87d090c69bcc Mon Sep 17 00:00:00 2001
|
||
|
From: Mike Westerhof <mwester@dls.net>
|
||
|
Date: Thu, 13 Nov 2008 20:38:35 +0000
|
||
|
Subject: [PATCH] fix-gta01-s3c-mci-stop-clock-when-idle.patch
|
||
|
|
||
|
This patch, based on the work done by Andy Green for the Glamo mci
|
||
|
driver, makes sure that the SD clock only runs during data transfers.
|
||
|
This can be overridden on the kernel command line if desired. Also
|
||
|
added is the ability for the maximum SD clock speed to be limited.
|
||
|
|
||
|
Signed-off-by: Mike Westerhof (mwester@dls.net)
|
||
|
---
|
||
|
drivers/mmc/host/s3cmci.c | 113 +++++++++++++++++++++++++++++++++++++++++++--
|
||
|
1 files changed, 109 insertions(+), 4 deletions(-)
|
||
|
|
||
|
diff --git a/drivers/mmc/host/s3cmci.c b/drivers/mmc/host/s3cmci.c
|
||
|
index edba055..8f88721 100644
|
||
|
--- a/drivers/mmc/host/s3cmci.c
|
||
|
+++ b/drivers/mmc/host/s3cmci.c
|
||
|
@@ -15,6 +15,8 @@
|
||
|
#include <linux/mmc/host.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/irq.h>
|
||
|
+#include <linux/delay.h>
|
||
|
+#include <linux/spinlock.h>
|
||
|
|
||
|
#include <asm/dma.h>
|
||
|
#include <asm/dma-mapping.h>
|
||
|
@@ -29,6 +31,37 @@
|
||
|
|
||
|
#define DRIVER_NAME "s3c-mci"
|
||
|
|
||
|
+static spinlock_t clock_lock;
|
||
|
+
|
||
|
+/*
|
||
|
+ * Max SD clock rate (in Hz)
|
||
|
+ *
|
||
|
+ * you can override this on the kernel command line using
|
||
|
+ *
|
||
|
+ * s3cmci.sd_max_clk=10000000
|
||
|
+ *
|
||
|
+ * for example.
|
||
|
+ */
|
||
|
+
|
||
|
+static int sd_max_clk = 25000000;
|
||
|
+module_param(sd_max_clk, int, 0644);
|
||
|
+
|
||
|
+/*
|
||
|
+ * SD allow SD clock to run while idle
|
||
|
+ *
|
||
|
+ * you can override this on kernel commandline using
|
||
|
+ *
|
||
|
+ * s3cmci.sd_idleclk=0
|
||
|
+ *
|
||
|
+ * for example.
|
||
|
+ */
|
||
|
+
|
||
|
+static int sd_idleclk; /* disallow idle clock by default */
|
||
|
+module_param(sd_idleclk, int, 0644);
|
||
|
+
|
||
|
+/* used to stash real idleclk state in suspend: we force it to run in there */
|
||
|
+static int suspend_sd_idleclk;
|
||
|
+
|
||
|
enum dbg_channels {
|
||
|
dbg_err = (1 << 0),
|
||
|
dbg_debug = (1 << 1),
|
||
|
@@ -368,6 +401,40 @@ static void pio_tasklet(unsigned long data)
|
||
|
enable_irq(host->irq);
|
||
|
}
|
||
|
|
||
|
+static void __s3cmci_enable_clock(struct s3cmci_host *host)
|
||
|
+{
|
||
|
+ u32 mci_con;
|
||
|
+ unsigned long flags;
|
||
|
+
|
||
|
+ /* enable the clock if clock rate is > 0 */
|
||
|
+ if (host->real_rate) {
|
||
|
+ spin_lock_irqsave(&clock_lock, flags);
|
||
|
+
|
||
|
+ mci_con = readl(host->base + S3C2410_SDICON);
|
||
|
+ mci_con |= S3C2410_SDICON_CLOCKTYPE;
|
||
|
+ writel(mci_con, host->base + S3C2410_SDICON);
|
||
|
+
|
||
|
+ spin_unlock_irqrestore(&clock_lock, flags);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void __s3cmci_disable_clock(struct s3cmci_host *host)
|
||
|
+{
|
||
|
+ u32 mci_con;
|
||
|
+ unsigned long flags;
|
||
|
+
|
||
|
+ if (!sd_idleclk) {
|
||
|
+ spin_lock_irqsave(&clock_lock, flags);
|
||
|
+
|
||
|
+ mci_con = readl(host->base + S3C2410_SDICON);
|
||
|
+ mci_con &= ~S3C2410_SDICON_CLOCKTYPE;
|
||
|
+ writel(mci_con, host->base + S3C2410_SDICON);
|
||
|
+
|
||
|
+ spin_unlock_irqrestore(&clock_lock, flags);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
/*
|
||
|
* ISR for SDI Interface IRQ
|
||
|
* Communication between driver and ISR works as follows:
|
||
|
@@ -749,6 +816,7 @@ static void finalize_request(struct s3cmci_host *host)
|
||
|
}
|
||
|
|
||
|
request_done:
|
||
|
+ __s3cmci_disable_clock(host);
|
||
|
host->complete_what = COMPLETION_NONE;
|
||
|
host->mrq = NULL;
|
||
|
mmc_request_done(host->mmc, mrq);
|
||
|
@@ -1005,6 +1073,7 @@ static void s3cmci_send_request(struct mmc_host *mmc)
|
||
|
|
||
|
}
|
||
|
|
||
|
+ __s3cmci_enable_clock(host);
|
||
|
s3cmci_send_command(host, cmd);
|
||
|
enable_irq(host->irq);
|
||
|
}
|
||
|
@@ -1087,14 +1156,30 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||
|
if ((ios->power_mode == MMC_POWER_ON)
|
||
|
|| (ios->power_mode == MMC_POWER_UP)) {
|
||
|
|
||
|
- dbg(host, dbg_conf, "running at %lukHz (requested: %ukHz).\n",
|
||
|
- host->real_rate/1000, ios->clock/1000);
|
||
|
+ dbg(host, dbg_conf,
|
||
|
+ "powered (vdd: %d), clk: %lukHz div=%lu (req: %ukHz),"
|
||
|
+ " bus width: %d\n", ios->vdd, host->real_rate / 1000,
|
||
|
+ host->clk_div * (host->prescaler + 1),
|
||
|
+ ios->clock / 1000, ios->bus_width);
|
||
|
+
|
||
|
+ /* After power-up, we need to give the card 74 clocks to
|
||
|
+ * initialize, so sleep just a moment before we disable
|
||
|
+ * the clock again.
|
||
|
+ */
|
||
|
+ if (ios->clock)
|
||
|
+ msleep(1);
|
||
|
+
|
||
|
} else {
|
||
|
dbg(host, dbg_conf, "powered down.\n");
|
||
|
}
|
||
|
|
||
|
host->bus_width = ios->bus_width;
|
||
|
|
||
|
+ /* No need to run the clock until we have data to move */
|
||
|
+ if (!sd_idleclk) {
|
||
|
+ __s3cmci_disable_clock(host);
|
||
|
+ dbg(host, dbg_conf, "SD clock disabled when idle.\n");
|
||
|
+ }
|
||
|
}
|
||
|
|
||
|
static void s3cmci_reset(struct s3cmci_host *host)
|
||
|
@@ -1267,7 +1352,7 @@ static int s3cmci_probe(struct platform_device *pdev, int is2440)
|
||
|
mmc->ocr_avail = host->pdata->ocr_avail;
|
||
|
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
|
||
|
mmc->f_min = host->clk_rate / (host->clk_div * 256);
|
||
|
- mmc->f_max = host->clk_rate / host->clk_div;
|
||
|
+ mmc->f_max = sd_max_clk;
|
||
|
|
||
|
mmc->max_blk_count = 4095;
|
||
|
mmc->max_blk_size = 4095;
|
||
|
@@ -1354,14 +1439,33 @@ static int s3cmci_probe_2440(struct platform_device *dev)
|
||
|
static int s3cmci_suspend(struct platform_device *dev, pm_message_t state)
|
||
|
{
|
||
|
struct mmc_host *mmc = platform_get_drvdata(dev);
|
||
|
+ struct s3cmci_host *host = mmc_priv(mmc);
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ /* Ensure clock is running so it will be running on resume */
|
||
|
+ __s3cmci_enable_clock(host);
|
||
|
|
||
|
- return mmc_suspend_host(mmc, state);
|
||
|
+ /* We will do more commands, make sure the clock stays running,
|
||
|
+ * and save our state so that we can restore it on resume.
|
||
|
+ */
|
||
|
+ suspend_sd_idleclk = sd_idleclk;
|
||
|
+ sd_idleclk = 1;
|
||
|
+
|
||
|
+ ret = mmc_suspend_host(mmc, state);
|
||
|
+
|
||
|
+ /* so that when we resume, we use any modified max rate */
|
||
|
+ mmc->f_max = sd_max_clk;
|
||
|
+
|
||
|
+ return ret;
|
||
|
}
|
||
|
|
||
|
static int s3cmci_resume(struct platform_device *dev)
|
||
|
{
|
||
|
struct mmc_host *mmc = platform_get_drvdata(dev);
|
||
|
|
||
|
+ /* Put the sd_idleclk state back to what it was */
|
||
|
+ sd_idleclk = suspend_sd_idleclk;
|
||
|
+
|
||
|
return mmc_resume_host(mmc);
|
||
|
}
|
||
|
|
||
|
@@ -1398,6 +1502,7 @@ static struct platform_driver s3cmci_driver_2440 = {
|
||
|
|
||
|
static int __init s3cmci_init(void)
|
||
|
{
|
||
|
+ spin_lock_init(&clock_lock);
|
||
|
platform_driver_register(&s3cmci_driver_2410);
|
||
|
platform_driver_register(&s3cmci_driver_2412);
|
||
|
platform_driver_register(&s3cmci_driver_2440);
|
||
|
--
|
||
|
1.5.6.5
|
||
|
|