omap_hsmmc: context save/restore support
authorDenis Karpov <ext-denis.2.karpov@nokia.com>
Tue, 22 Sep 2009 23:44:43 +0000 (16:44 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 23 Sep 2009 14:39:34 +0000 (07:39 -0700)
Keep the context over PM dynamic OFF states.

Signed-off-by: Denis Karpov <ext-denis.2.karpov@nokia.com>
Signed-off-by: Adrian Hunter <adrian.hunter@nokia.com>
Acked-by: Matt Fleming <matt@console-pimps.org>
Cc: Ian Molton <ian@mnementh.co.uk>
Cc: "Roberto A. Foglietta" <roberto.foglietta@gmail.com>
Cc: Jarkko Lavinen <jarkko.lavinen@nokia.com>
Cc: Denis Karpov <ext-denis.2.karpov@nokia.com>
Cc: Pierre Ossman <pierre@ossman.eu>
Cc: Philip Langdale <philipl@overt.org>
Cc: "Madhusudhan" <madhu.cr@ti.com>
Cc: <linux-mmc@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
drivers/mmc/host/omap_hsmmc.c

index 30b863b2c5102ec6e54beb2ed7f8d6df12c47987..74d506a6ffec705a12a5bbbfeb3ba2a5457e2cff 100644 (file)
@@ -37,6 +37,7 @@
 
 /* OMAP HSMMC Host Controller Registers */
 #define OMAP_HSMMC_SYSCONFIG   0x0010
+#define OMAP_HSMMC_SYSSTATUS   0x0014
 #define OMAP_HSMMC_CON         0x002C
 #define OMAP_HSMMC_BLK         0x0104
 #define OMAP_HSMMC_ARG         0x0108
@@ -96,6 +97,8 @@
 #define DUAL_VOLT_OCR_BIT      7
 #define SRC                    (1 << 25)
 #define SRD                    (1 << 26)
+#define SOFTRESET              (1 << 1)
+#define RESETDONE              (1 << 0)
 
 /*
  * FIXME: Most likely all the data using these _DEVID defines should come
@@ -154,6 +157,8 @@ struct mmc_omap_host {
        int                     slot_id;
        int                     dbclk_enabled;
        int                     response_busy;
+       int                     context_loss;
+
        struct  omap_mmc_platform_data  *pdata;
 };
 
@@ -168,6 +173,166 @@ static void omap_mmc_stop_clock(struct mmc_omap_host *host)
                dev_dbg(mmc_dev(host->mmc), "MMC Clock is not stoped\n");
 }
 
+#ifdef CONFIG_PM
+
+/*
+ * Restore the MMC host context, if it was lost as result of a
+ * power state change.
+ */
+static int omap_mmc_restore_ctx(struct mmc_omap_host *host)
+{
+       struct mmc_ios *ios = &host->mmc->ios;
+       struct omap_mmc_platform_data *pdata = host->pdata;
+       int context_loss = 0;
+       u32 hctl, capa, con;
+       u16 dsor = 0;
+       unsigned long timeout;
+
+       if (pdata->get_context_loss_count) {
+               context_loss = pdata->get_context_loss_count(host->dev);
+               if (context_loss < 0)
+                       return 1;
+       }
+
+       dev_dbg(mmc_dev(host->mmc), "context was %slost\n",
+               context_loss == host->context_loss ? "not " : "");
+       if (host->context_loss == context_loss)
+               return 1;
+
+       /* Wait for hardware reset */
+       timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+       while ((OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) != RESETDONE
+               && time_before(jiffies, timeout))
+               ;
+
+       /* Do software reset */
+       OMAP_HSMMC_WRITE(host->base, SYSCONFIG, SOFTRESET);
+       timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+       while ((OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) != RESETDONE
+               && time_before(jiffies, timeout))
+               ;
+
+       OMAP_HSMMC_WRITE(host->base, SYSCONFIG,
+                       OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE);
+
+       if (host->id == OMAP_MMC1_DEVID) {
+               if (host->power_mode != MMC_POWER_OFF &&
+                   (1 << ios->vdd) <= MMC_VDD_23_24)
+                       hctl = SDVS18;
+               else
+                       hctl = SDVS30;
+               capa = VS30 | VS18;
+       } else {
+               hctl = SDVS18;
+               capa = VS18;
+       }
+
+       OMAP_HSMMC_WRITE(host->base, HCTL,
+                       OMAP_HSMMC_READ(host->base, HCTL) | hctl);
+
+       OMAP_HSMMC_WRITE(host->base, CAPA,
+                       OMAP_HSMMC_READ(host->base, CAPA) | capa);
+
+       OMAP_HSMMC_WRITE(host->base, HCTL,
+                       OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+
+       timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+       while ((OMAP_HSMMC_READ(host->base, HCTL) & SDBP) != SDBP
+               && time_before(jiffies, timeout))
+               ;
+
+       OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+       OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
+       OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
+
+       /* Do not initialize card-specific things if the power is off */
+       if (host->power_mode == MMC_POWER_OFF)
+               goto out;
+
+       con = OMAP_HSMMC_READ(host->base, CON);
+       switch (ios->bus_width) {
+       case MMC_BUS_WIDTH_8:
+               OMAP_HSMMC_WRITE(host->base, CON, con | DW8);
+               break;
+       case MMC_BUS_WIDTH_4:
+               OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8);
+               OMAP_HSMMC_WRITE(host->base, HCTL,
+                       OMAP_HSMMC_READ(host->base, HCTL) | FOUR_BIT);
+               break;
+       case MMC_BUS_WIDTH_1:
+               OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8);
+               OMAP_HSMMC_WRITE(host->base, HCTL,
+                       OMAP_HSMMC_READ(host->base, HCTL) & ~FOUR_BIT);
+               break;
+       }
+
+       if (ios->clock) {
+               dsor = OMAP_MMC_MASTER_CLOCK / ios->clock;
+               if (dsor < 1)
+                       dsor = 1;
+
+               if (OMAP_MMC_MASTER_CLOCK / dsor > ios->clock)
+                       dsor++;
+
+               if (dsor > 250)
+                       dsor = 250;
+       }
+
+       OMAP_HSMMC_WRITE(host->base, SYSCTL,
+               OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN);
+       OMAP_HSMMC_WRITE(host->base, SYSCTL, (dsor << 6) | (DTO << 16));
+       OMAP_HSMMC_WRITE(host->base, SYSCTL,
+               OMAP_HSMMC_READ(host->base, SYSCTL) | ICE);
+
+       timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+       while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != ICS
+               && time_before(jiffies, timeout))
+               ;
+
+       OMAP_HSMMC_WRITE(host->base, SYSCTL,
+               OMAP_HSMMC_READ(host->base, SYSCTL) | CEN);
+
+       con = OMAP_HSMMC_READ(host->base, CON);
+       if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
+               OMAP_HSMMC_WRITE(host->base, CON, con | OD);
+       else
+               OMAP_HSMMC_WRITE(host->base, CON, con & ~OD);
+out:
+       host->context_loss = context_loss;
+
+       dev_dbg(mmc_dev(host->mmc), "context is restored\n");
+       return 0;
+}
+
+/*
+ * Save the MMC host context (store the number of power state changes so far).
+ */
+static void omap_mmc_save_ctx(struct mmc_omap_host *host)
+{
+       struct omap_mmc_platform_data *pdata = host->pdata;
+       int context_loss;
+
+       if (pdata->get_context_loss_count) {
+               context_loss = pdata->get_context_loss_count(host->dev);
+               if (context_loss < 0)
+                       return;
+               host->context_loss = context_loss;
+       }
+}
+
+#else
+
+static int omap_mmc_restore_ctx(struct mmc_omap_host *host)
+{
+       return 0;
+}
+
+static void omap_mmc_save_ctx(struct mmc_omap_host *host)
+{
+}
+
+#endif
+
 /*
  * Send init stream sequence to card
  * before sending IDLE command
@@ -830,6 +995,7 @@ static int omap_mmc_enable(struct mmc_host *mmc)
        if (err)
                return err;
        dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n");
+       omap_mmc_restore_ctx(host);
        return 0;
 }
 
@@ -837,6 +1003,7 @@ static int omap_mmc_disable(struct mmc_host *mmc, int lazy)
 {
        struct mmc_omap_host *host = mmc_priv(mmc);
 
+       omap_mmc_save_ctx(host);
        clk_disable(host->fclk);
        dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n");
        return 0;
@@ -941,7 +1108,7 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 
        /* Wait till the ICS bit is set */
        timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
-       while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != 0x2
+       while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != ICS
                && time_before(jiffies, timeout))
                msleep(1);
 
@@ -1021,12 +1188,19 @@ static int mmc_regs_show(struct seq_file *s, void *data)
 {
        struct mmc_host *mmc = s->private;
        struct mmc_omap_host *host = mmc_priv(mmc);
+       struct omap_mmc_platform_data *pdata = host->pdata;
+       int context_loss = 0;
+
+       if (pdata->get_context_loss_count)
+               context_loss = pdata->get_context_loss_count(host->dev);
 
        seq_printf(s, "mmc%d:\n"
                        " enabled:\t%d\n"
                        " nesting_cnt:\t%d\n"
+                       " ctx_loss:\t%d:%d\n"
                        "\nregs:\n",
-                       mmc->index, mmc->enabled ? 1 : 0, mmc->nesting_cnt);
+                       mmc->index, mmc->enabled ? 1 : 0, mmc->nesting_cnt,
+                       host->context_loss, context_loss);
 
        if (clk_enable(host->fclk) != 0) {
                seq_printf(s, "can't read the regs\n");
@@ -1151,6 +1325,8 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                goto err1;
        }
 
+       omap_mmc_save_ctx(host);
+
        mmc->caps |= MMC_CAP_DISABLE;
        mmc_set_disable_delay(mmc, 100);
        if (mmc_host_enable(host->mmc) != 0) {
@@ -1385,21 +1561,19 @@ static int omap_mmc_resume(struct platform_device *pdev)
                return 0;
 
        if (host) {
-
-               if (mmc_host_enable(host->mmc) != 0)
-                       goto clk_en_err;
-
                ret = clk_enable(host->iclk);
-               if (ret) {
-                       mmc_host_disable(host->mmc);
-                       clk_put(host->fclk);
+               if (ret)
                        goto clk_en_err;
-               }
 
                if (clk_enable(host->dbclk) != 0)
                        dev_dbg(mmc_dev(host->mmc),
                                        "Enabling debounce clk failed\n");
 
+               if (mmc_host_enable(host->mmc) != 0) {
+                       clk_disable(host->iclk);
+                       goto clk_en_err;
+               }
+
                omap_hsmmc_init(host);
 
                if (host->pdata->resume) {