]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/mmc/host/omap_hsmmc.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-2.6
[mv-sheeva.git] / drivers / mmc / host / omap_hsmmc.c
index 30b863b2c5102ec6e54beb2ed7f8d6df12c47987..4487cc0979112ea71e412f2c722ad45282cd3328 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/timer.h>
 #include <linux/clk.h>
 #include <linux/mmc/host.h>
+#include <linux/mmc/core.h>
 #include <linux/io.h>
 #include <linux/semaphore.h>
 #include <mach/dma.h>
@@ -37,6 +38,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 +98,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
 #define OMAP_MMC1_DEVID                0
 #define OMAP_MMC2_DEVID                1
 #define OMAP_MMC3_DEVID                2
+#define OMAP_MMC4_DEVID                3
+#define OMAP_MMC5_DEVID                4
 
 #define MMC_TIMEOUT_MS         20
 #define OMAP_MMC_MASTER_CLOCK  96000000
 #define DRIVER_NAME            "mmci-omap-hs"
 
+/* Timeouts for entering power saving states on inactivity, msec */
+#define OMAP_MMC_DISABLED_TIMEOUT      100
+#define OMAP_MMC_SLEEP_TIMEOUT         1000
+#define OMAP_MMC_OFF_TIMEOUT           8000
+
 /*
  * One controller can have multiple slots, like on some omap boards using
  * omap.c controller driver. Luckily this is not currently done on any known
 #define OMAP_HSMMC_WRITE(base, reg, val) \
        __raw_writel((val), (base) + OMAP_HSMMC_##reg)
 
-struct mmc_omap_host {
+struct omap_hsmmc_host {
        struct  device          *dev;
        struct  mmc_host        *mmc;
        struct  mmc_request     *mrq;
@@ -139,6 +150,8 @@ struct mmc_omap_host {
        struct  work_struct     mmc_carddetect_work;
        void    __iomem         *base;
        resource_size_t         mapbase;
+       spinlock_t              irq_lock; /* Prevent races with irq handler */
+       unsigned long           flags;
        unsigned int            id;
        unsigned int            dma_len;
        unsigned int            dma_sg_idx;
@@ -148,19 +161,24 @@ struct mmc_omap_host {
        u32                     bytesleft;
        int                     suspended;
        int                     irq;
-       int                     carddetect;
        int                     use_dma, dma_ch;
        int                     dma_line_tx, dma_line_rx;
        int                     slot_id;
-       int                     dbclk_enabled;
+       int                     got_dbclk;
        int                     response_busy;
+       int                     context_loss;
+       int                     dpm_state;
+       int                     vdd;
+       int                     protect_card;
+       int                     reqs_blocked;
+
        struct  omap_mmc_platform_data  *pdata;
 };
 
 /*
  * Stop clock to the card
  */
-static void omap_mmc_stop_clock(struct mmc_omap_host *host)
+static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host)
 {
        OMAP_HSMMC_WRITE(host->base, SYSCTL,
                OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN);
@@ -168,15 +186,178 @@ 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_hsmmc_context_restore(struct omap_hsmmc_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_hsmmc_context_save(struct omap_hsmmc_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_hsmmc_context_restore(struct omap_hsmmc_host *host)
+{
+       return 0;
+}
+
+static void omap_hsmmc_context_save(struct omap_hsmmc_host *host)
+{
+}
+
+#endif
+
 /*
  * Send init stream sequence to card
  * before sending IDLE command
  */
-static void send_init_stream(struct mmc_omap_host *host)
+static void send_init_stream(struct omap_hsmmc_host *host)
 {
        int reg = 0;
        unsigned long timeout;
 
+       if (host->protect_card)
+               return;
+
        disable_irq(host->irq);
        OMAP_HSMMC_WRITE(host->base, CON,
                OMAP_HSMMC_READ(host->base, CON) | INIT_STREAM);
@@ -188,51 +369,53 @@ static void send_init_stream(struct mmc_omap_host *host)
 
        OMAP_HSMMC_WRITE(host->base, CON,
                OMAP_HSMMC_READ(host->base, CON) & ~INIT_STREAM);
+
+       OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+       OMAP_HSMMC_READ(host->base, STAT);
+
        enable_irq(host->irq);
 }
 
 static inline
-int mmc_omap_cover_is_closed(struct mmc_omap_host *host)
+int omap_hsmmc_cover_is_closed(struct omap_hsmmc_host *host)
 {
        int r = 1;
 
-       if (host->pdata->slots[host->slot_id].get_cover_state)
-               r = host->pdata->slots[host->slot_id].get_cover_state(host->dev,
-                       host->slot_id);
+       if (mmc_slot(host).get_cover_state)
+               r = mmc_slot(host).get_cover_state(host->dev, host->slot_id);
        return r;
 }
 
 static ssize_t
-mmc_omap_show_cover_switch(struct device *dev, struct device_attribute *attr,
+omap_hsmmc_show_cover_switch(struct device *dev, struct device_attribute *attr,
                           char *buf)
 {
        struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
-       struct mmc_omap_host *host = mmc_priv(mmc);
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
 
-       return sprintf(buf, "%s\n", mmc_omap_cover_is_closed(host) ? "closed" :
-                      "open");
+       return sprintf(buf, "%s\n",
+                       omap_hsmmc_cover_is_closed(host) ? "closed" : "open");
 }
 
-static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL);
+static DEVICE_ATTR(cover_switch, S_IRUGO, omap_hsmmc_show_cover_switch, NULL);
 
 static ssize_t
-mmc_omap_show_slot_name(struct device *dev, struct device_attribute *attr,
+omap_hsmmc_show_slot_name(struct device *dev, struct device_attribute *attr,
                        char *buf)
 {
        struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
-       struct mmc_omap_host *host = mmc_priv(mmc);
-       struct omap_mmc_slot_data slot = host->pdata->slots[host->slot_id];
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
 
-       return sprintf(buf, "%s\n", slot.name);
+       return sprintf(buf, "%s\n", mmc_slot(host).name);
 }
 
-static DEVICE_ATTR(slot_name, S_IRUGO, mmc_omap_show_slot_name, NULL);
+static DEVICE_ATTR(slot_name, S_IRUGO, omap_hsmmc_show_slot_name, NULL);
 
 /*
  * Configure the response type and send the cmd.
  */
 static void
-mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd,
+omap_hsmmc_start_command(struct omap_hsmmc_host *host, struct mmc_command *cmd,
        struct mmc_data *data)
 {
        int cmdreg = 0, resptype = 0, cmdtype = 0;
@@ -285,12 +468,20 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd,
        if (host->use_dma)
                cmdreg |= DMA_EN;
 
+       /*
+        * In an interrupt context (i.e. STOP command), the spinlock is unlocked
+        * by the interrupt handler, otherwise (i.e. for a new request) it is
+        * unlocked here.
+        */
+       if (!in_interrupt())
+               spin_unlock_irqrestore(&host->irq_lock, host->flags);
+
        OMAP_HSMMC_WRITE(host->base, ARG, cmd->arg);
        OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
 }
 
 static int
-mmc_omap_get_dma_dir(struct mmc_omap_host *host, struct mmc_data *data)
+omap_hsmmc_get_dma_dir(struct omap_hsmmc_host *host, struct mmc_data *data)
 {
        if (data->flags & MMC_DATA_WRITE)
                return DMA_TO_DEVICE;
@@ -302,11 +493,18 @@ mmc_omap_get_dma_dir(struct mmc_omap_host *host, struct mmc_data *data)
  * Notify the transfer complete to MMC core
  */
 static void
-mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
+omap_hsmmc_xfer_done(struct omap_hsmmc_host *host, struct mmc_data *data)
 {
        if (!data) {
                struct mmc_request *mrq = host->mrq;
 
+               /* TC before CC from CMD6 - don't know why, but it happens */
+               if (host->cmd && host->cmd->opcode == 6 &&
+                   host->response_busy) {
+                       host->response_busy = 0;
+                       return;
+               }
+
                host->mrq = NULL;
                mmc_request_done(host->mmc, mrq);
                return;
@@ -316,7 +514,7 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
 
        if (host->use_dma && host->dma_ch != -1)
                dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len,
-                       mmc_omap_get_dma_dir(host, data));
+                       omap_hsmmc_get_dma_dir(host, data));
 
        if (!data->error)
                data->bytes_xfered += data->blocks * (data->blksz);
@@ -328,14 +526,14 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
                mmc_request_done(host->mmc, data->mrq);
                return;
        }
-       mmc_omap_start_command(host, data->stop, NULL);
+       omap_hsmmc_start_command(host, data->stop, NULL);
 }
 
 /*
  * Notify the core about command completion
  */
 static void
-mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
+omap_hsmmc_cmd_done(struct omap_hsmmc_host *host, struct mmc_command *cmd)
 {
        host->cmd = NULL;
 
@@ -360,13 +558,13 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
 /*
  * DMA clean up for command errors
  */
-static void mmc_dma_cleanup(struct mmc_omap_host *host, int errno)
+static void omap_hsmmc_dma_cleanup(struct omap_hsmmc_host *host, int errno)
 {
        host->data->error = errno;
 
        if (host->use_dma && host->dma_ch != -1) {
                dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->dma_len,
-                       mmc_omap_get_dma_dir(host, host->data));
+                       omap_hsmmc_get_dma_dir(host, host->data));
                omap_free_dma(host->dma_ch);
                host->dma_ch = -1;
                up(&host->sem);
@@ -378,10 +576,10 @@ static void mmc_dma_cleanup(struct mmc_omap_host *host, int errno)
  * Readable error output
  */
 #ifdef CONFIG_MMC_DEBUG
-static void mmc_omap_report_irq(struct mmc_omap_host *host, u32 status)
+static void omap_hsmmc_report_irq(struct omap_hsmmc_host *host, u32 status)
 {
        /* --- means reserved bit without definition at documentation */
-       static const char *mmc_omap_status_bits[] = {
+       static const char *omap_hsmmc_status_bits[] = {
                "CC", "TC", "BGE", "---", "BWR", "BRR", "---", "---", "CIRQ",
                "OBI", "---", "---", "---", "---", "---", "ERRI", "CTO", "CCRC",
                "CEB", "CIE", "DTO", "DCRC", "DEB", "---", "ACE", "---",
@@ -394,9 +592,9 @@ static void mmc_omap_report_irq(struct mmc_omap_host *host, u32 status)
        len = sprintf(buf, "MMC IRQ 0x%x :", status);
        buf += len;
 
-       for (i = 0; i < ARRAY_SIZE(mmc_omap_status_bits); i++)
+       for (i = 0; i < ARRAY_SIZE(omap_hsmmc_status_bits); i++)
                if (status & (1 << i)) {
-                       len = sprintf(buf, " %s", mmc_omap_status_bits[i]);
+                       len = sprintf(buf, " %s", omap_hsmmc_status_bits[i]);
                        buf += len;
                }
 
@@ -411,8 +609,8 @@ static void mmc_omap_report_irq(struct mmc_omap_host *host, u32 status)
  *  SRC or SRD bit of SYSCTL register
  * Can be called from interrupt context
  */
-static inline void mmc_omap_reset_controller_fsm(struct mmc_omap_host *host,
-               unsigned long bit)
+static inline void omap_hsmmc_reset_controller_fsm(struct omap_hsmmc_host *host,
+                                                  unsigned long bit)
 {
        unsigned long i = 0;
        unsigned long limit = (loops_per_jiffy *
@@ -434,17 +632,20 @@ static inline void mmc_omap_reset_controller_fsm(struct mmc_omap_host *host,
 /*
  * MMC controller IRQ handler
  */
-static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
+static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
 {
-       struct mmc_omap_host *host = dev_id;
+       struct omap_hsmmc_host *host = dev_id;
        struct mmc_data *data;
        int end_cmd = 0, end_trans = 0, status;
 
+       spin_lock(&host->irq_lock);
+
        if (host->mrq == NULL) {
                OMAP_HSMMC_WRITE(host->base, STAT,
                        OMAP_HSMMC_READ(host->base, STAT));
                /* Flush posted write */
                OMAP_HSMMC_READ(host->base, STAT);
+               spin_unlock(&host->irq_lock);
                return IRQ_HANDLED;
        }
 
@@ -454,13 +655,14 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
 
        if (status & ERR) {
 #ifdef CONFIG_MMC_DEBUG
-               mmc_omap_report_irq(host, status);
+               omap_hsmmc_report_irq(host, status);
 #endif
                if ((status & CMD_TIMEOUT) ||
                        (status & CMD_CRC)) {
                        if (host->cmd) {
                                if (status & CMD_TIMEOUT) {
-                                       mmc_omap_reset_controller_fsm(host, SRC);
+                                       omap_hsmmc_reset_controller_fsm(host,
+                                                                       SRC);
                                        host->cmd->error = -ETIMEDOUT;
                                } else {
                                        host->cmd->error = -EILSEQ;
@@ -469,9 +671,10 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
                        }
                        if (host->data || host->response_busy) {
                                if (host->data)
-                                       mmc_dma_cleanup(host, -ETIMEDOUT);
+                                       omap_hsmmc_dma_cleanup(host,
+                                                               -ETIMEDOUT);
                                host->response_busy = 0;
-                               mmc_omap_reset_controller_fsm(host, SRD);
+                               omap_hsmmc_reset_controller_fsm(host, SRD);
                        }
                }
                if ((status & DATA_TIMEOUT) ||
@@ -481,11 +684,11 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
                                                -ETIMEDOUT : -EILSEQ;
 
                                if (host->data)
-                                       mmc_dma_cleanup(host, err);
+                                       omap_hsmmc_dma_cleanup(host, err);
                                else
                                        host->mrq->cmd->error = err;
                                host->response_busy = 0;
-                               mmc_omap_reset_controller_fsm(host, SRD);
+                               omap_hsmmc_reset_controller_fsm(host, SRD);
                                end_trans = 1;
                        }
                }
@@ -504,14 +707,16 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
        OMAP_HSMMC_READ(host->base, STAT);
 
        if (end_cmd || ((status & CC) && host->cmd))
-               mmc_omap_cmd_done(host, host->cmd);
-       if (end_trans || (status & TC))
-               mmc_omap_xfer_done(host, data);
+               omap_hsmmc_cmd_done(host, host->cmd);
+       if ((end_trans || (status & TC)) && host->mrq)
+               omap_hsmmc_xfer_done(host, data);
+
+       spin_unlock(&host->irq_lock);
 
        return IRQ_HANDLED;
 }
 
-static void set_sd_bus_power(struct mmc_omap_host *host)
+static void set_sd_bus_power(struct omap_hsmmc_host *host)
 {
        unsigned long i;
 
@@ -531,7 +736,7 @@ static void set_sd_bus_power(struct mmc_omap_host *host)
  * The MMC2 transceiver controls are used instead of DAT4..DAT7.
  * Some chips, like eMMC ones, use internal transceivers.
  */
-static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd)
+static int omap_hsmmc_switch_opcond(struct omap_hsmmc_host *host, int vdd)
 {
        u32 reg_val = 0;
        int ret;
@@ -539,22 +744,24 @@ static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd)
        /* Disable the clocks */
        clk_disable(host->fclk);
        clk_disable(host->iclk);
-       clk_disable(host->dbclk);
+       if (host->got_dbclk)
+               clk_disable(host->dbclk);
 
        /* Turn the power off */
        ret = mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
-       if (ret != 0)
-               goto err;
 
        /* Turn the power ON with given VDD 1.8 or 3.0v */
-       ret = mmc_slot(host).set_power(host->dev, host->slot_id, 1, vdd);
+       if (!ret)
+               ret = mmc_slot(host).set_power(host->dev, host->slot_id, 1,
+                                              vdd);
+       clk_enable(host->iclk);
+       clk_enable(host->fclk);
+       if (host->got_dbclk)
+               clk_enable(host->dbclk);
+
        if (ret != 0)
                goto err;
 
-       clk_enable(host->fclk);
-       clk_enable(host->iclk);
-       clk_enable(host->dbclk);
-
        OMAP_HSMMC_WRITE(host->base, HCTL,
                OMAP_HSMMC_READ(host->base, HCTL) & SDVSCLR);
        reg_val = OMAP_HSMMC_READ(host->base, HCTL);
@@ -562,7 +769,7 @@ static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd)
        /*
         * If a MMC dual voltage card is detected, the set_ios fn calls
         * this fn with VDD bit set for 1.8V. Upon card removal from the
-        * slot, omap_mmc_set_ios sets the VDD back to 3V on MMC_POWER_OFF.
+        * slot, omap_hsmmc_set_ios sets the VDD back to 3V on MMC_POWER_OFF.
         *
         * Cope with a bit of slop in the range ... per data sheets:
         *  - "1.8V" for vdds_mmc1/vdds_mmc1a can be up to 2.45V max,
@@ -588,27 +795,59 @@ err:
        return ret;
 }
 
+/* Protect the card while the cover is open */
+static void omap_hsmmc_protect_card(struct omap_hsmmc_host *host)
+{
+       if (!mmc_slot(host).get_cover_state)
+               return;
+
+       host->reqs_blocked = 0;
+       if (mmc_slot(host).get_cover_state(host->dev, host->slot_id)) {
+               if (host->protect_card) {
+                       printk(KERN_INFO "%s: cover is closed, "
+                                        "card is now accessible\n",
+                                        mmc_hostname(host->mmc));
+                       host->protect_card = 0;
+               }
+       } else {
+               if (!host->protect_card) {
+                       printk(KERN_INFO "%s: cover is open, "
+                                        "card is now inaccessible\n",
+                                        mmc_hostname(host->mmc));
+                       host->protect_card = 1;
+               }
+       }
+}
+
 /*
  * Work Item to notify the core about card insertion/removal
  */
-static void mmc_omap_detect(struct work_struct *work)
+static void omap_hsmmc_detect(struct work_struct *work)
 {
-       struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
-                                               mmc_carddetect_work);
+       struct omap_hsmmc_host *host =
+               container_of(work, struct omap_hsmmc_host, mmc_carddetect_work);
        struct omap_mmc_slot_data *slot = &mmc_slot(host);
+       int carddetect;
 
-       if (mmc_slot(host).card_detect)
-               host->carddetect = slot->card_detect(slot->card_detect_irq);
-       else
-               host->carddetect = -ENOSYS;
+       if (host->suspended)
+               return;
 
        sysfs_notify(&host->mmc->class_dev.kobj, NULL, "cover_switch");
-       if (host->carddetect) {
+
+       if (slot->card_detect)
+               carddetect = slot->card_detect(slot->card_detect_irq);
+       else {
+               omap_hsmmc_protect_card(host);
+               carddetect = -ENOSYS;
+       }
+
+       if (carddetect) {
                mmc_detect_change(host->mmc, (HZ * 200) / 1000);
        } else {
                mmc_host_enable(host->mmc);
-               mmc_omap_reset_controller_fsm(host, SRD);
+               omap_hsmmc_reset_controller_fsm(host, SRD);
                mmc_host_lazy_disable(host->mmc);
+
                mmc_detect_change(host->mmc, (HZ * 50) / 1000);
        }
 }
@@ -616,16 +855,18 @@ static void mmc_omap_detect(struct work_struct *work)
 /*
  * ISR for handling card insertion and removal
  */
-static irqreturn_t omap_mmc_cd_handler(int irq, void *dev_id)
+static irqreturn_t omap_hsmmc_cd_handler(int irq, void *dev_id)
 {
-       struct mmc_omap_host *host = (struct mmc_omap_host *)dev_id;
+       struct omap_hsmmc_host *host = (struct omap_hsmmc_host *)dev_id;
 
+       if (host->suspended)
+               return IRQ_HANDLED;
        schedule_work(&host->mmc_carddetect_work);
 
        return IRQ_HANDLED;
 }
 
-static int mmc_omap_get_dma_sync_dev(struct mmc_omap_host *host,
+static int omap_hsmmc_get_dma_sync_dev(struct omap_hsmmc_host *host,
                                     struct mmc_data *data)
 {
        int sync_dev;
@@ -637,7 +878,7 @@ static int mmc_omap_get_dma_sync_dev(struct mmc_omap_host *host,
        return sync_dev;
 }
 
-static void mmc_omap_config_dma_params(struct mmc_omap_host *host,
+static void omap_hsmmc_config_dma_params(struct omap_hsmmc_host *host,
                                       struct mmc_data *data,
                                       struct scatterlist *sgl)
 {
@@ -651,7 +892,7 @@ static void mmc_omap_config_dma_params(struct mmc_omap_host *host,
                        sg_dma_address(sgl), 0, 0);
        } else {
                omap_set_dma_src_params(dma_ch, 0, OMAP_DMA_AMODE_CONSTANT,
-                                       (host->mapbase + OMAP_HSMMC_DATA), 0, 0);
+                       (host->mapbase + OMAP_HSMMC_DATA), 0, 0);
                omap_set_dma_dest_params(dma_ch, 0, OMAP_DMA_AMODE_POST_INC,
                        sg_dma_address(sgl), 0, 0);
        }
@@ -661,7 +902,7 @@ static void mmc_omap_config_dma_params(struct mmc_omap_host *host,
 
        omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32,
                        blksz / 4, nblk, OMAP_DMA_SYNC_FRAME,
-                       mmc_omap_get_dma_sync_dev(host, data),
+                       omap_hsmmc_get_dma_sync_dev(host, data),
                        !(data->flags & MMC_DATA_WRITE));
 
        omap_start_dma(dma_ch);
@@ -670,9 +911,9 @@ static void mmc_omap_config_dma_params(struct mmc_omap_host *host,
 /*
  * DMA call back function
  */
-static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)
+static void omap_hsmmc_dma_cb(int lch, u16 ch_status, void *data)
 {
-       struct mmc_omap_host *host = data;
+       struct omap_hsmmc_host *host = data;
 
        if (ch_status & OMAP2_DMA_MISALIGNED_ERR_IRQ)
                dev_dbg(mmc_dev(host->mmc), "MISALIGNED_ADRS_ERR\n");
@@ -683,7 +924,7 @@ static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)
        host->dma_sg_idx++;
        if (host->dma_sg_idx < host->dma_len) {
                /* Fire up the next transfer. */
-               mmc_omap_config_dma_params(host, host->data,
+               omap_hsmmc_config_dma_params(host, host->data,
                                           host->data->sg + host->dma_sg_idx);
                return;
        }
@@ -700,14 +941,14 @@ static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)
 /*
  * Routine to configure and start DMA for the MMC card
  */
-static int
-mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req)
+static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host,
+                                       struct mmc_request *req)
 {
        int dma_ch = 0, ret = 0, err = 1, i;
        struct mmc_data *data = req->data;
 
        /* Sanity check: all the SG entries must be aligned by block size. */
-       for (i = 0; i < host->dma_len; i++) {
+       for (i = 0; i < data->sg_len; i++) {
                struct scatterlist *sgl;
 
                sgl = data->sg + i;
@@ -738,8 +979,8 @@ mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req)
                        return err;
        }
 
-       ret = omap_request_dma(mmc_omap_get_dma_sync_dev(host, data), "MMC/SD",
-                              mmc_omap_dma_cb,host, &dma_ch);
+       ret = omap_request_dma(omap_hsmmc_get_dma_sync_dev(host, data),
+                              "MMC/SD", omap_hsmmc_dma_cb, host, &dma_ch);
        if (ret != 0) {
                dev_err(mmc_dev(host->mmc),
                        "%s: omap_request_dma() failed with %d\n",
@@ -748,17 +989,18 @@ mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req)
        }
 
        host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
-                       data->sg_len, mmc_omap_get_dma_dir(host, data));
+                       data->sg_len, omap_hsmmc_get_dma_dir(host, data));
        host->dma_ch = dma_ch;
        host->dma_sg_idx = 0;
 
-       mmc_omap_config_dma_params(host, data, data->sg);
+       omap_hsmmc_config_dma_params(host, data, data->sg);
 
        return 0;
 }
 
-static void set_data_timeout(struct mmc_omap_host *host,
-                            struct mmc_request *req)
+static void set_data_timeout(struct omap_hsmmc_host *host,
+                            unsigned int timeout_ns,
+                            unsigned int timeout_clks)
 {
        unsigned int timeout, cycle_ns;
        uint32_t reg, clkd, dto = 0;
@@ -769,8 +1011,8 @@ static void set_data_timeout(struct mmc_omap_host *host,
                clkd = 1;
 
        cycle_ns = 1000000000 / (clk_get_rate(host->fclk) / clkd);
-       timeout = req->data->timeout_ns / cycle_ns;
-       timeout += req->data->timeout_clks;
+       timeout = timeout_ns / cycle_ns;
+       timeout += timeout_clks;
        if (timeout) {
                while ((timeout & 0x80000000) == 0) {
                        dto += 1;
@@ -797,22 +1039,28 @@ static void set_data_timeout(struct mmc_omap_host *host,
  * Configure block length for MMC/SD cards and initiate the transfer.
  */
 static int
-mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
+omap_hsmmc_prepare_data(struct omap_hsmmc_host *host, struct mmc_request *req)
 {
        int ret;
        host->data = req->data;
 
        if (req->data == NULL) {
                OMAP_HSMMC_WRITE(host->base, BLK, 0);
+               /*
+                * Set an arbitrary 100ms data timeout for commands with
+                * busy signal.
+                */
+               if (req->cmd->flags & MMC_RSP_BUSY)
+                       set_data_timeout(host, 100000000U, 0);
                return 0;
        }
 
        OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
                                        | (req->data->blocks << 16));
-       set_data_timeout(host, req);
+       set_data_timeout(host, req->data->timeout_ns, req->data->timeout_clks);
 
        if (host->use_dma) {
-               ret = mmc_omap_start_dma_transfer(host, req);
+               ret = omap_hsmmc_start_dma_transfer(host, req);
                if (ret != 0) {
                        dev_dbg(mmc_dev(host->mmc), "MMC start dma failure\n");
                        return ret;
@@ -821,45 +1069,66 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
        return 0;
 }
 
-static int omap_mmc_enable(struct mmc_host *mmc)
-{
-       struct mmc_omap_host *host = mmc_priv(mmc);
-       int err;
-
-       err = clk_enable(host->fclk);
-       if (err)
-               return err;
-       dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n");
-       return 0;
-}
-
-static int omap_mmc_disable(struct mmc_host *mmc, int lazy)
-{
-       struct mmc_omap_host *host = mmc_priv(mmc);
-
-       clk_disable(host->fclk);
-       dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n");
-       return 0;
-}
-
 /*
  * Request function. for read/write operation
  */
-static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req)
 {
-       struct mmc_omap_host *host = mmc_priv(mmc);
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
+       int err;
 
+       /*
+        * Prevent races with the interrupt handler because of unexpected
+        * interrupts, but not if we are already in interrupt context i.e.
+        * retries.
+        */
+       if (!in_interrupt()) {
+               spin_lock_irqsave(&host->irq_lock, host->flags);
+               /*
+                * Protect the card from I/O if there is a possibility
+                * it can be removed.
+                */
+               if (host->protect_card) {
+                       if (host->reqs_blocked < 3) {
+                               /*
+                                * Ensure the controller is left in a consistent
+                                * state by resetting the command and data state
+                                * machines.
+                                */
+                               omap_hsmmc_reset_controller_fsm(host, SRD);
+                               omap_hsmmc_reset_controller_fsm(host, SRC);
+                               host->reqs_blocked += 1;
+                       }
+                       req->cmd->error = -EBADF;
+                       if (req->data)
+                               req->data->error = -EBADF;
+                       spin_unlock_irqrestore(&host->irq_lock, host->flags);
+                       mmc_request_done(mmc, req);
+                       return;
+               } else if (host->reqs_blocked)
+                       host->reqs_blocked = 0;
+       }
        WARN_ON(host->mrq != NULL);
        host->mrq = req;
-       mmc_omap_prepare_data(host, req);
-       mmc_omap_start_command(host, req->cmd, req->data);
-}
+       err = omap_hsmmc_prepare_data(host, req);
+       if (err) {
+               req->cmd->error = err;
+               if (req->data)
+                       req->data->error = err;
+               host->mrq = NULL;
+               if (!in_interrupt())
+                       spin_unlock_irqrestore(&host->irq_lock, host->flags);
+               mmc_request_done(mmc, req);
+               return;
+       }
 
+       omap_hsmmc_start_command(host, req->cmd, req->data);
+}
 
 /* Routine to configure clock values. Exposed API to core */
-static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
-       struct mmc_omap_host *host = mmc_priv(mmc);
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
        u16 dsor = 0;
        unsigned long regval;
        unsigned long timeout;
@@ -873,10 +1142,12 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                case MMC_POWER_OFF:
                        mmc_slot(host).set_power(host->dev, host->slot_id,
                                                 0, 0);
+                       host->vdd = 0;
                        break;
                case MMC_POWER_UP:
                        mmc_slot(host).set_power(host->dev, host->slot_id,
                                                 1, ios->vdd);
+                       host->vdd = ios->vdd;
                        break;
                case MMC_POWER_ON:
                        do_send_init_stream = 1;
@@ -885,6 +1156,8 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                host->power_mode = ios->power_mode;
        }
 
+       /* FIXME: set registers based only on changes to ios */
+
        con = OMAP_HSMMC_READ(host->base, CON);
        switch (mmc->ios.bus_width) {
        case MMC_BUS_WIDTH_8:
@@ -914,8 +1187,8 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                                 * MMC_POWER_UP upon recalculating the voltage.
                                 * vdd 1.8v.
                                 */
-                               if (omap_mmc_switch_opcond(host, ios->vdd) != 0)
-                                       dev_dbg(mmc_dev(host->mmc),
+                       if (omap_hsmmc_switch_opcond(host, ios->vdd) != 0)
+                               dev_dbg(mmc_dev(host->mmc),
                                                "Switch operation failed\n");
                }
        }
@@ -931,7 +1204,7 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                if (dsor > 250)
                        dsor = 250;
        }
-       omap_mmc_stop_clock(host);
+       omap_hsmmc_stop_clock(host);
        regval = OMAP_HSMMC_READ(host->base, SYSCTL);
        regval = regval & ~(CLKD_MASK);
        regval = regval | (dsor << 6) | (DTO << 16);
@@ -941,7 +1214,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);
 
@@ -951,34 +1224,37 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        if (do_send_init_stream)
                send_init_stream(host);
 
+       con = OMAP_HSMMC_READ(host->base, CON);
        if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
-               OMAP_HSMMC_WRITE(host->base, CON,
-                               OMAP_HSMMC_READ(host->base, CON) | OD);
+               OMAP_HSMMC_WRITE(host->base, CON, con | OD);
+       else
+               OMAP_HSMMC_WRITE(host->base, CON, con & ~OD);
 
-       mmc_host_lazy_disable(host->mmc);
+       if (host->power_mode == MMC_POWER_OFF)
+               mmc_host_disable(host->mmc);
+       else
+               mmc_host_lazy_disable(host->mmc);
 }
 
 static int omap_hsmmc_get_cd(struct mmc_host *mmc)
 {
-       struct mmc_omap_host *host = mmc_priv(mmc);
-       struct omap_mmc_platform_data *pdata = host->pdata;
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
 
-       if (!pdata->slots[0].card_detect)
+       if (!mmc_slot(host).card_detect)
                return -ENOSYS;
-       return pdata->slots[0].card_detect(pdata->slots[0].card_detect_irq);
+       return mmc_slot(host).card_detect(mmc_slot(host).card_detect_irq);
 }
 
 static int omap_hsmmc_get_ro(struct mmc_host *mmc)
 {
-       struct mmc_omap_host *host = mmc_priv(mmc);
-       struct omap_mmc_platform_data *pdata = host->pdata;
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
 
-       if (!pdata->slots[0].get_ro)
+       if (!mmc_slot(host).get_ro)
                return -ENOSYS;
-       return pdata->slots[0].get_ro(host->dev, 0);
+       return mmc_slot(host).get_ro(host->dev, 0);
 }
 
-static void omap_hsmmc_init(struct mmc_omap_host *host)
+static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
 {
        u32 hctl, capa, value;
 
@@ -1005,11 +1281,252 @@ static void omap_hsmmc_init(struct mmc_omap_host *host)
        set_sd_bus_power(host);
 }
 
-static struct mmc_host_ops mmc_omap_ops = {
-       .enable = omap_mmc_enable,
-       .disable = omap_mmc_disable,
-       .request = omap_mmc_request,
-       .set_ios = omap_mmc_set_ios,
+/*
+ * Dynamic power saving handling, FSM:
+ *   ENABLED -> DISABLED -> CARDSLEEP / REGSLEEP -> OFF
+ *     ^___________|          |                      |
+ *     |______________________|______________________|
+ *
+ * ENABLED:   mmc host is fully functional
+ * DISABLED:  fclk is off
+ * CARDSLEEP: fclk is off, card is asleep, voltage regulator is asleep
+ * REGSLEEP:  fclk is off, voltage regulator is asleep
+ * OFF:       fclk is off, voltage regulator is off
+ *
+ * Transition handlers return the timeout for the next state transition
+ * or negative error.
+ */
+
+enum {ENABLED = 0, DISABLED, CARDSLEEP, REGSLEEP, OFF};
+
+/* Handler for [ENABLED -> DISABLED] transition */
+static int omap_hsmmc_enabled_to_disabled(struct omap_hsmmc_host *host)
+{
+       omap_hsmmc_context_save(host);
+       clk_disable(host->fclk);
+       host->dpm_state = DISABLED;
+
+       dev_dbg(mmc_dev(host->mmc), "ENABLED -> DISABLED\n");
+
+       if (host->power_mode == MMC_POWER_OFF)
+               return 0;
+
+       return msecs_to_jiffies(OMAP_MMC_SLEEP_TIMEOUT);
+}
+
+/* Handler for [DISABLED -> REGSLEEP / CARDSLEEP] transition */
+static int omap_hsmmc_disabled_to_sleep(struct omap_hsmmc_host *host)
+{
+       int err, new_state;
+
+       if (!mmc_try_claim_host(host->mmc))
+               return 0;
+
+       clk_enable(host->fclk);
+       omap_hsmmc_context_restore(host);
+       if (mmc_card_can_sleep(host->mmc)) {
+               err = mmc_card_sleep(host->mmc);
+               if (err < 0) {
+                       clk_disable(host->fclk);
+                       mmc_release_host(host->mmc);
+                       return err;
+               }
+               new_state = CARDSLEEP;
+       } else {
+               new_state = REGSLEEP;
+       }
+       if (mmc_slot(host).set_sleep)
+               mmc_slot(host).set_sleep(host->dev, host->slot_id, 1, 0,
+                                        new_state == CARDSLEEP);
+       /* FIXME: turn off bus power and perhaps interrupts too */
+       clk_disable(host->fclk);
+       host->dpm_state = new_state;
+
+       mmc_release_host(host->mmc);
+
+       dev_dbg(mmc_dev(host->mmc), "DISABLED -> %s\n",
+               host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP");
+
+       if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) ||
+           mmc_slot(host).card_detect ||
+           (mmc_slot(host).get_cover_state &&
+            mmc_slot(host).get_cover_state(host->dev, host->slot_id)))
+               return msecs_to_jiffies(OMAP_MMC_OFF_TIMEOUT);
+
+       return 0;
+}
+
+/* Handler for [REGSLEEP / CARDSLEEP -> OFF] transition */
+static int omap_hsmmc_sleep_to_off(struct omap_hsmmc_host *host)
+{
+       if (!mmc_try_claim_host(host->mmc))
+               return 0;
+
+       if (!((host->mmc->caps & MMC_CAP_NONREMOVABLE) ||
+             mmc_slot(host).card_detect ||
+             (mmc_slot(host).get_cover_state &&
+              mmc_slot(host).get_cover_state(host->dev, host->slot_id)))) {
+               mmc_release_host(host->mmc);
+               return 0;
+       }
+
+       mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
+       host->vdd = 0;
+       host->power_mode = MMC_POWER_OFF;
+
+       dev_dbg(mmc_dev(host->mmc), "%s -> OFF\n",
+               host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP");
+
+       host->dpm_state = OFF;
+
+       mmc_release_host(host->mmc);
+
+       return 0;
+}
+
+/* Handler for [DISABLED -> ENABLED] transition */
+static int omap_hsmmc_disabled_to_enabled(struct omap_hsmmc_host *host)
+{
+       int err;
+
+       err = clk_enable(host->fclk);
+       if (err < 0)
+               return err;
+
+       omap_hsmmc_context_restore(host);
+       host->dpm_state = ENABLED;
+
+       dev_dbg(mmc_dev(host->mmc), "DISABLED -> ENABLED\n");
+
+       return 0;
+}
+
+/* Handler for [SLEEP -> ENABLED] transition */
+static int omap_hsmmc_sleep_to_enabled(struct omap_hsmmc_host *host)
+{
+       if (!mmc_try_claim_host(host->mmc))
+               return 0;
+
+       clk_enable(host->fclk);
+       omap_hsmmc_context_restore(host);
+       if (mmc_slot(host).set_sleep)
+               mmc_slot(host).set_sleep(host->dev, host->slot_id, 0,
+                        host->vdd, host->dpm_state == CARDSLEEP);
+       if (mmc_card_can_sleep(host->mmc))
+               mmc_card_awake(host->mmc);
+
+       dev_dbg(mmc_dev(host->mmc), "%s -> ENABLED\n",
+               host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP");
+
+       host->dpm_state = ENABLED;
+
+       mmc_release_host(host->mmc);
+
+       return 0;
+}
+
+/* Handler for [OFF -> ENABLED] transition */
+static int omap_hsmmc_off_to_enabled(struct omap_hsmmc_host *host)
+{
+       clk_enable(host->fclk);
+
+       omap_hsmmc_context_restore(host);
+       omap_hsmmc_conf_bus_power(host);
+       mmc_power_restore_host(host->mmc);
+
+       host->dpm_state = ENABLED;
+
+       dev_dbg(mmc_dev(host->mmc), "OFF -> ENABLED\n");
+
+       return 0;
+}
+
+/*
+ * Bring MMC host to ENABLED from any other PM state.
+ */
+static int omap_hsmmc_enable(struct mmc_host *mmc)
+{
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+       switch (host->dpm_state) {
+       case DISABLED:
+               return omap_hsmmc_disabled_to_enabled(host);
+       case CARDSLEEP:
+       case REGSLEEP:
+               return omap_hsmmc_sleep_to_enabled(host);
+       case OFF:
+               return omap_hsmmc_off_to_enabled(host);
+       default:
+               dev_dbg(mmc_dev(host->mmc), "UNKNOWN state\n");
+               return -EINVAL;
+       }
+}
+
+/*
+ * Bring MMC host in PM state (one level deeper).
+ */
+static int omap_hsmmc_disable(struct mmc_host *mmc, int lazy)
+{
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+       switch (host->dpm_state) {
+       case ENABLED: {
+               int delay;
+
+               delay = omap_hsmmc_enabled_to_disabled(host);
+               if (lazy || delay < 0)
+                       return delay;
+               return 0;
+       }
+       case DISABLED:
+               return omap_hsmmc_disabled_to_sleep(host);
+       case CARDSLEEP:
+       case REGSLEEP:
+               return omap_hsmmc_sleep_to_off(host);
+       default:
+               dev_dbg(mmc_dev(host->mmc), "UNKNOWN state\n");
+               return -EINVAL;
+       }
+}
+
+static int omap_hsmmc_enable_fclk(struct mmc_host *mmc)
+{
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
+       int err;
+
+       err = clk_enable(host->fclk);
+       if (err)
+               return err;
+       dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n");
+       omap_hsmmc_context_restore(host);
+       return 0;
+}
+
+static int omap_hsmmc_disable_fclk(struct mmc_host *mmc, int lazy)
+{
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+       omap_hsmmc_context_save(host);
+       clk_disable(host->fclk);
+       dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n");
+       return 0;
+}
+
+static const struct mmc_host_ops omap_hsmmc_ops = {
+       .enable = omap_hsmmc_enable_fclk,
+       .disable = omap_hsmmc_disable_fclk,
+       .request = omap_hsmmc_request,
+       .set_ios = omap_hsmmc_set_ios,
+       .get_cd = omap_hsmmc_get_cd,
+       .get_ro = omap_hsmmc_get_ro,
+       /* NYET -- enable_sdio_irq */
+};
+
+static const struct mmc_host_ops omap_hsmmc_ps_ops = {
+       .enable = omap_hsmmc_enable,
+       .disable = omap_hsmmc_disable,
+       .request = omap_hsmmc_request,
+       .set_ios = omap_hsmmc_set_ios,
        .get_cd = omap_hsmmc_get_cd,
        .get_ro = omap_hsmmc_get_ro,
        /* NYET -- enable_sdio_irq */
@@ -1017,20 +1534,33 @@ static struct mmc_host_ops mmc_omap_ops = {
 
 #ifdef CONFIG_DEBUG_FS
 
-static int mmc_regs_show(struct seq_file *s, void *data)
+static int omap_hsmmc_regs_show(struct seq_file *s, void *data)
 {
        struct mmc_host *mmc = s->private;
-       struct mmc_omap_host *host = mmc_priv(mmc);
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
+       int context_loss = 0;
+
+       if (host->pdata->get_context_loss_count)
+               context_loss = host->pdata->get_context_loss_count(host->dev);
 
        seq_printf(s, "mmc%d:\n"
                        " enabled:\t%d\n"
+                       " dpm_state:\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,
+                       host->dpm_state, mmc->nesting_cnt,
+                       host->context_loss, context_loss);
+
+       if (host->suspended || host->dpm_state == OFF) {
+               seq_printf(s, "host suspended, can't read registers\n");
+               return 0;
+       }
 
        if (clk_enable(host->fclk) != 0) {
                seq_printf(s, "can't read the regs\n");
-               goto err;
+               return 0;
        }
 
        seq_printf(s, "SYSCONFIG:\t0x%08x\n",
@@ -1049,23 +1579,23 @@ static int mmc_regs_show(struct seq_file *s, void *data)
                        OMAP_HSMMC_READ(host->base, CAPA));
 
        clk_disable(host->fclk);
-err:
+
        return 0;
 }
 
-static int mmc_regs_open(struct inode *inode, struct file *file)
+static int omap_hsmmc_regs_open(struct inode *inode, struct file *file)
 {
-       return single_open(file, mmc_regs_show, inode->i_private);
+       return single_open(file, omap_hsmmc_regs_show, inode->i_private);
 }
 
 static const struct file_operations mmc_regs_fops = {
-       .open           = mmc_regs_open,
+       .open           = omap_hsmmc_regs_open,
        .read           = seq_read,
        .llseek         = seq_lseek,
        .release        = single_release,
 };
 
-static void omap_mmc_debugfs(struct mmc_host *mmc)
+static void omap_hsmmc_debugfs(struct mmc_host *mmc)
 {
        if (mmc->debugfs_root)
                debugfs_create_file("regs", S_IRUSR, mmc->debugfs_root,
@@ -1074,17 +1604,17 @@ static void omap_mmc_debugfs(struct mmc_host *mmc)
 
 #else
 
-static void omap_mmc_debugfs(struct mmc_host *mmc)
+static void omap_hsmmc_debugfs(struct mmc_host *mmc)
 {
 }
 
 #endif
 
-static int __init omap_mmc_probe(struct platform_device *pdev)
+static int __init omap_hsmmc_probe(struct platform_device *pdev)
 {
        struct omap_mmc_platform_data *pdata = pdev->dev.platform_data;
        struct mmc_host *mmc;
-       struct mmc_omap_host *host = NULL;
+       struct omap_hsmmc_host *host = NULL;
        struct resource *res;
        int ret = 0, irq;
 
@@ -1108,7 +1638,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        if (res == NULL)
                return -EBUSY;
 
-       mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev);
+       mmc = mmc_alloc_host(sizeof(struct omap_hsmmc_host), &pdev->dev);
        if (!mmc) {
                ret = -ENOMEM;
                goto err;
@@ -1129,13 +1659,18 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        host->power_mode = -1;
 
        platform_set_drvdata(pdev, host);
-       INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect);
+       INIT_WORK(&host->mmc_carddetect_work, omap_hsmmc_detect);
+
+       if (mmc_slot(host).power_saving)
+               mmc->ops        = &omap_hsmmc_ps_ops;
+       else
+               mmc->ops        = &omap_hsmmc_ops;
 
-       mmc->ops        = &mmc_omap_ops;
        mmc->f_min      = 400000;
        mmc->f_max      = 52000000;
 
        sema_init(&host->sem, 1);
+       spin_lock_init(&host->irq_lock);
 
        host->iclk = clk_get(&pdev->dev, "ick");
        if (IS_ERR(host->iclk)) {
@@ -1151,8 +1686,13 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                goto err1;
        }
 
+       omap_hsmmc_context_save(host);
+
        mmc->caps |= MMC_CAP_DISABLE;
-       mmc_set_disable_delay(mmc, 100);
+       mmc_set_disable_delay(mmc, OMAP_MMC_DISABLED_TIMEOUT);
+       /* we start off in DISABLED state */
+       host->dpm_state = DISABLED;
+
        if (mmc_host_enable(host->mmc) != 0) {
                clk_put(host->iclk);
                clk_put(host->fclk);
@@ -1166,18 +1706,22 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                goto err1;
        }
 
-       host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck");
-       /*
-        * MMC can still work without debounce clock.
-        */
-       if (IS_ERR(host->dbclk))
-               dev_warn(mmc_dev(host->mmc), "Failed to get debounce clock\n");
-       else
-               if (clk_enable(host->dbclk) != 0)
-                       dev_dbg(mmc_dev(host->mmc), "Enabling debounce"
-                                                       " clk failed\n");
+       if (cpu_is_omap2430()) {
+               host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck");
+               /*
+                * MMC can still work without debounce clock.
+                */
+               if (IS_ERR(host->dbclk))
+                       dev_warn(mmc_dev(host->mmc),
+                               "Failed to get debounce clock\n");
                else
-                       host->dbclk_enabled = 1;
+                       host->got_dbclk = 1;
+
+               if (host->got_dbclk)
+                       if (clk_enable(host->dbclk) != 0)
+                               dev_dbg(mmc_dev(host->mmc), "Enabling debounce"
+                                                       " clk failed\n");
+       }
 
        /* Since we do only SG emulation, we can have as many segs
         * as we want. */
@@ -1189,14 +1733,18 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
        mmc->max_seg_size = mmc->max_req_size;
 
-       mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
+       mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED |
+                    MMC_CAP_WAIT_WHILE_BUSY;
 
-       if (pdata->slots[host->slot_id].wires >= 8)
+       if (mmc_slot(host).wires >= 8)
                mmc->caps |= MMC_CAP_8_BIT_DATA;
-       else if (pdata->slots[host->slot_id].wires >= 4)
+       else if (mmc_slot(host).wires >= 4)
                mmc->caps |= MMC_CAP_4_BIT_DATA;
 
-       omap_hsmmc_init(host);
+       if (mmc_slot(host).nonremovable)
+               mmc->caps |= MMC_CAP_NONREMOVABLE;
+
+       omap_hsmmc_conf_bus_power(host);
 
        /* Select DMA lines */
        switch (host->id) {
@@ -1212,13 +1760,21 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                host->dma_line_tx = OMAP34XX_DMA_MMC3_TX;
                host->dma_line_rx = OMAP34XX_DMA_MMC3_RX;
                break;
+       case OMAP_MMC4_DEVID:
+               host->dma_line_tx = OMAP44XX_DMA_MMC4_TX;
+               host->dma_line_rx = OMAP44XX_DMA_MMC4_RX;
+               break;
+       case OMAP_MMC5_DEVID:
+               host->dma_line_tx = OMAP44XX_DMA_MMC5_TX;
+               host->dma_line_rx = OMAP44XX_DMA_MMC5_RX;
+               break;
        default:
                dev_err(mmc_dev(host->mmc), "Invalid MMC id\n");
                goto err_irq;
        }
 
        /* Request IRQ for MMC operations */
-       ret = request_irq(host->irq, mmc_omap_irq, IRQF_DISABLED,
+       ret = request_irq(host->irq, omap_hsmmc_irq, IRQF_DISABLED,
                        mmc_hostname(mmc), host);
        if (ret) {
                dev_dbg(mmc_dev(host->mmc), "Unable to grab HSMMC IRQ\n");
@@ -1228,7 +1784,8 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        /* initialize power supplies, gpios, etc */
        if (pdata->init != NULL) {
                if (pdata->init(&pdev->dev) != 0) {
-                       dev_dbg(mmc_dev(host->mmc), "late init error\n");
+                       dev_dbg(mmc_dev(host->mmc),
+                               "Unable to configure MMC IRQs\n");
                        goto err_irq_cd_init;
                }
        }
@@ -1237,7 +1794,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        /* Request IRQ for card detect */
        if ((mmc_slot(host).card_detect_irq)) {
                ret = request_irq(mmc_slot(host).card_detect_irq,
-                                 omap_mmc_cd_handler,
+                                 omap_hsmmc_cd_handler,
                                  IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
                                          | IRQF_DISABLED,
                                  mmc_hostname(mmc), host);
@@ -1253,22 +1810,23 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
 
        mmc_host_lazy_disable(host->mmc);
 
+       omap_hsmmc_protect_card(host);
+
        mmc_add_host(mmc);
 
-       if (host->pdata->slots[host->slot_id].name != NULL) {
+       if (mmc_slot(host).name != NULL) {
                ret = device_create_file(&mmc->class_dev, &dev_attr_slot_name);
                if (ret < 0)
                        goto err_slot_name;
        }
-       if (mmc_slot(host).card_detect_irq &&
-           host->pdata->slots[host->slot_id].get_cover_state) {
+       if (mmc_slot(host).card_detect_irq && mmc_slot(host).get_cover_state) {
                ret = device_create_file(&mmc->class_dev,
                                        &dev_attr_cover_switch);
                if (ret < 0)
                        goto err_cover_switch;
        }
 
-       omap_mmc_debugfs(mmc);
+       omap_hsmmc_debugfs(mmc);
 
        return 0;
 
@@ -1285,7 +1843,7 @@ err_irq:
        clk_disable(host->iclk);
        clk_put(host->fclk);
        clk_put(host->iclk);
-       if (host->dbclk_enabled) {
+       if (host->got_dbclk) {
                clk_disable(host->dbclk);
                clk_put(host->dbclk);
        }
@@ -1300,9 +1858,9 @@ err:
        return ret;
 }
 
-static int omap_mmc_remove(struct platform_device *pdev)
+static int omap_hsmmc_remove(struct platform_device *pdev)
 {
-       struct mmc_omap_host *host = platform_get_drvdata(pdev);
+       struct omap_hsmmc_host *host = platform_get_drvdata(pdev);
        struct resource *res;
 
        if (host) {
@@ -1319,7 +1877,7 @@ static int omap_mmc_remove(struct platform_device *pdev)
                clk_disable(host->iclk);
                clk_put(host->fclk);
                clk_put(host->iclk);
-               if (host->dbclk_enabled) {
+               if (host->got_dbclk) {
                        clk_disable(host->dbclk);
                        clk_put(host->dbclk);
                }
@@ -1337,70 +1895,80 @@ static int omap_mmc_remove(struct platform_device *pdev)
 }
 
 #ifdef CONFIG_PM
-static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state)
+static int omap_hsmmc_suspend(struct platform_device *pdev, pm_message_t state)
 {
        int ret = 0;
-       struct mmc_omap_host *host = platform_get_drvdata(pdev);
+       struct omap_hsmmc_host *host = platform_get_drvdata(pdev);
 
        if (host && host->suspended)
                return 0;
 
        if (host) {
+               host->suspended = 1;
+               if (host->pdata->suspend) {
+                       ret = host->pdata->suspend(&pdev->dev,
+                                                       host->slot_id);
+                       if (ret) {
+                               dev_dbg(mmc_dev(host->mmc),
+                                       "Unable to handle MMC board"
+                                       " level suspend\n");
+                               host->suspended = 0;
+                               return ret;
+                       }
+               }
+               cancel_work_sync(&host->mmc_carddetect_work);
                mmc_host_enable(host->mmc);
                ret = mmc_suspend_host(host->mmc, state);
                if (ret == 0) {
-                       host->suspended = 1;
-
                        OMAP_HSMMC_WRITE(host->base, ISE, 0);
                        OMAP_HSMMC_WRITE(host->base, IE, 0);
 
-                       if (host->pdata->suspend) {
-                               ret = host->pdata->suspend(&pdev->dev,
-                                                               host->slot_id);
-                               if (ret)
-                                       dev_dbg(mmc_dev(host->mmc),
-                                               "Unable to handle MMC board"
-                                               " level suspend\n");
-                       }
 
                        OMAP_HSMMC_WRITE(host->base, HCTL,
-                                        OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
+                               OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
                        mmc_host_disable(host->mmc);
                        clk_disable(host->iclk);
-                       clk_disable(host->dbclk);
-               } else
+                       if (host->got_dbclk)
+                               clk_disable(host->dbclk);
+               } else {
+                       host->suspended = 0;
+                       if (host->pdata->resume) {
+                               ret = host->pdata->resume(&pdev->dev,
+                                                         host->slot_id);
+                               if (ret)
+                                       dev_dbg(mmc_dev(host->mmc),
+                                               "Unmask interrupt failed\n");
+                       }
                        mmc_host_disable(host->mmc);
+               }
 
        }
        return ret;
 }
 
 /* Routine to resume the MMC device */
-static int omap_mmc_resume(struct platform_device *pdev)
+static int omap_hsmmc_resume(struct platform_device *pdev)
 {
        int ret = 0;
-       struct mmc_omap_host *host = platform_get_drvdata(pdev);
+       struct omap_hsmmc_host *host = platform_get_drvdata(pdev);
 
        if (host && !host->suspended)
                return 0;
 
        if (host) {
-
-               if (mmc_host_enable(host->mmc) != 0)
+               ret = clk_enable(host->iclk);
+               if (ret)
                        goto clk_en_err;
 
-               ret = clk_enable(host->iclk);
-               if (ret) {
-                       mmc_host_disable(host->mmc);
-                       clk_put(host->fclk);
+               if (mmc_host_enable(host->mmc) != 0) {
+                       clk_disable(host->iclk);
                        goto clk_en_err;
                }
 
-               if (clk_enable(host->dbclk) != 0)
-                       dev_dbg(mmc_dev(host->mmc),
-                                       "Enabling debounce clk failed\n");
+               if (host->got_dbclk)
+                       clk_enable(host->dbclk);
 
-               omap_hsmmc_init(host);
+               omap_hsmmc_conf_bus_power(host);
 
                if (host->pdata->resume) {
                        ret = host->pdata->resume(&pdev->dev, host->slot_id);
@@ -1409,10 +1977,13 @@ static int omap_mmc_resume(struct platform_device *pdev)
                                        "Unmask interrupt failed\n");
                }
 
+               omap_hsmmc_protect_card(host);
+
                /* Notify the core to resume the host */
                ret = mmc_resume_host(host->mmc);
                if (ret == 0)
                        host->suspended = 0;
+
                mmc_host_lazy_disable(host->mmc);
        }
 
@@ -1425,34 +1996,34 @@ clk_en_err:
 }
 
 #else
-#define omap_mmc_suspend       NULL
-#define omap_mmc_resume                NULL
+#define omap_hsmmc_suspend     NULL
+#define omap_hsmmc_resume              NULL
 #endif
 
-static struct platform_driver omap_mmc_driver = {
-       .remove         = omap_mmc_remove,
-       .suspend        = omap_mmc_suspend,
-       .resume         = omap_mmc_resume,
+static struct platform_driver omap_hsmmc_driver = {
+       .remove         = omap_hsmmc_remove,
+       .suspend        = omap_hsmmc_suspend,
+       .resume         = omap_hsmmc_resume,
        .driver         = {
                .name = DRIVER_NAME,
                .owner = THIS_MODULE,
        },
 };
 
-static int __init omap_mmc_init(void)
+static int __init omap_hsmmc_init(void)
 {
        /* Register the MMC driver */
-       return platform_driver_probe(&omap_mmc_driver, omap_mmc_probe);
+       return platform_driver_register(&omap_hsmmc_driver);
 }
 
-static void __exit omap_mmc_cleanup(void)
+static void __exit omap_hsmmc_cleanup(void)
 {
        /* Unregister MMC driver */
-       platform_driver_unregister(&omap_mmc_driver);
+       platform_driver_unregister(&omap_hsmmc_driver);
 }
 
-module_init(omap_mmc_init);
-module_exit(omap_mmc_cleanup);
+module_init(omap_hsmmc_init);
+module_exit(omap_hsmmc_cleanup);
 
 MODULE_DESCRIPTION("OMAP High Speed Multimedia Card driver");
 MODULE_LICENSE("GPL");