The attached patch is for wider test and for review, not intended for
the Om repo yet.

It's not been checked to ensure it adheres to proper coding style, and
it's missing a Signed-off-by line -- but that's the least of the worries
with this patch.  Please try it, but please don't try it with an SD card
that has anything on it that you want to keep!

This implements two of the "Andy Glamo SD" features -- the clock to the
SD card is disabled when the card is not transferring data, and you can
set the maximum SD clock speed on the kernel command line.  Note that
the max SD clock speed is 16.625 MHz on the GTA01.

I've tested with a 512MB Sandisk SD card, it suspends and resumes, and
completed a quick pass with bonnie++.

The goal of this, of course, is to see if it changes the GPS performance
at all.  I've done no tests in that area whatsoever, so that would be a
really good bit of information to have.  In particular, if it is
determined that limiting the max speed has a positive effect for GPS
performace, then I'll implement Andy's
automatic-slowdown-when-gps-is-powered-up feature -- but if it doesn't
then I'll forgo that effort (since the limited flash space on the GTA01
will force most everyone to use a rootfs on SD, it would be a shame to
slow performance if it had no benefit, of course!).

I'll send in a properly formatted and signed off version once I get a
little feedback.  Happy GPSing!

Mike (mwester)
diff --git a/drivers/mmc/host/s3cmci.c b/drivers/mmc/host/s3cmci.c
index edba055..bac878d 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 = 0; /* 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);

Reply via email to