]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/mmc/host/omap_hsmmc.c
OMAP: HSMMC: Implement scatter-gather emulation
[linux-2.6-omap-h63xx.git] / drivers / mmc / host / omap_hsmmc.c
index b5931de315b111deb2e4c71f7ec8d1d0247d5719..6058a70f2c7c799a65d7c457d723e1b31e53c650 100644 (file)
 #define OMAP_HSMMC_ISE         0x0138
 #define OMAP_HSMMC_CAPA                0x0140
 
-#define VS18                   (1<<26)
-#define VS30                   (1<<25)
-#define SDVS18                 (0x5<<9)
-#define SDVS30                 (0x6<<9)
+#define VS18                   (1 << 26)
+#define VS30                   (1 << 25)
+#define SDVS18                 (0x5 << 9)
+#define SDVS30                 (0x6 << 9)
 #define SDVSCLR                        0xFFFFF1FF
 #define SDVSDET                        0x00000400
 #define AUTOIDLE               0x1
-#define SDBP                   (1<<8)
+#define SDBP                   (1 << 8)
 #define DTO                    0xe
 #define ICE                    0x1
 #define ICS                    0x2
-#define CEN                    (1<<2)
+#define CEN                    (1 << 2)
 #define CLKD_MASK              0x0000FFC0
+#define CLKD_SHIFT             6
+#define DTO_MASK               0x000F0000
+#define DTO_SHIFT              16
 #define INT_EN_MASK            0x307F0033
-#define INIT_STREAM            (1<<1)
-#define DP_SELECT              (1<<21)
-#define DDIR                   (1<<4)
+#define INIT_STREAM            (1 << 1)
+#define DP_SELECT              (1 << 21)
+#define DDIR                   (1 << 4)
 #define DMA_EN                 0x1
-#define MSBS                   1<<5
-#define BCE                    1<<1
-#define FOUR_BIT               1 << 1
+#define MSBS                   (1 << 5)
+#define BCE                    (1 << 1)
+#define FOUR_BIT               (1 << 1)
 #define CC                     0x1
 #define TC                     0x02
 #define OD                     0x1
 #define SRC                    (1 << 25)
 #define SRD                    (1 << 26)
 
-#define OMAP_MMC1_DEVID                1
-#define OMAP_MMC2_DEVID                2
-#define OMAP_MMC_DATADIR_NONE  0
-#define OMAP_MMC_DATADIR_READ  1
-#define OMAP_MMC_DATADIR_WRITE 2
+/*
+ * FIXME: Most likely all the data using these _DEVID defines should come
+ * from the platform_data, or implemented in controller and slot specific
+ * functions.
+ */
+#define OMAP_MMC1_DEVID                0
+#define OMAP_MMC2_DEVID                1
+
 #define MMC_TIMEOUT_MS         20
 #define OMAP_MMC_MASTER_CLOCK  96000000
 #define DRIVER_NAME            "mmci-omap"
+
 /*
- * slot_id is device id - 1, device id is a static value
- * of 1 to represent device 1 etc..
+ * 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
+ * omap_hsmmc.c device.
  */
 #define mmc_slot(host)         (host->pdata->slots[host->slot_id])
 
 #define OMAP_HSMMC_WRITE(base, reg, val) \
        __raw_writel((val), (base) + OMAP_HSMMC_##reg)
 
+enum {OFF = 0, ON};
+#define IDLE_TIMEOUT (jiffies_to_msecs(10))
+
 struct mmc_omap_host {
        struct  device          *dev;
        struct  mmc_host        *mmc;
@@ -125,21 +136,62 @@ struct mmc_omap_host {
        resource_size_t         mapbase;
        unsigned int            id;
        unsigned int            dma_len;
-       unsigned int            dma_dir;
+       unsigned int            dma_sg_idx;
        unsigned char           bus_mode;
-       unsigned char           datadir;
        u32                     *buffer;
        u32                     bytesleft;
        int                     suspended;
        int                     irq;
        int                     carddetect;
        int                     use_dma, dma_ch;
-       int                     initstr;
        int                     slot_id;
        int                     dbclk_enabled;
+
+       struct timer_list       idle_timer;
+       spinlock_t              clk_lock;     /* for changing enabled state */
+       unsigned int            fclk_enabled:1;
+
        struct  omap_mmc_platform_data  *pdata;
 };
 
+static int mmc_omap_fclk_state(struct mmc_omap_host *host, unsigned int state)
+{
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&host->clk_lock, flags);
+       del_timer(&host->idle_timer);
+       if (host->fclk_enabled != state) {
+               if (state == ON) {
+                       ret = clk_enable(host->fclk);
+                       if (ret != 0)
+                               goto err_out;
+
+                       dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n");
+               } else {
+                       clk_disable(host->fclk);
+                       dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n");
+               }
+               host->fclk_enabled = state;
+       }
+
+err_out:
+       spin_unlock_irqrestore(&host->clk_lock, flags);
+       return ret;
+}
+
+static void mmc_omap_idle_timer(unsigned long data)
+{
+       struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+
+       mmc_omap_fclk_state(host, OFF);
+}
+
+static void mmc_omap_fclk_lazy_disable(struct mmc_omap_host *host)
+{
+       mod_timer(&host->idle_timer, jiffies + IDLE_TIMEOUT);
+}
+
 /*
  * Stop clock to the card
  */
@@ -174,6 +226,27 @@ static void send_init_stream(struct mmc_omap_host *host)
        enable_irq(host->irq);
 }
 
+static inline
+int mmc_omap_cover_is_closed(struct mmc_omap_host *host)
+{
+       if (host->pdata->slots[host->slot_id].get_cover_state)
+               return host->pdata->slots[host->slot_id].get_cover_state(host->dev, host->slot_id);
+       return 1;
+}
+
+static ssize_t
+mmc_omap_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);
+
+       return sprintf(buf, "%s\n", mmc_omap_cover_is_closed(host) ? "closed" :
+                      "open");
+}
+
+static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL);
+
 static ssize_t
 mmc_omap_show_slot_name(struct device *dev, struct device_attribute *attr,
                        char *buf)
@@ -238,6 +311,15 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd,
        OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
 }
 
+static int
+mmc_omap_get_dma_dir(struct mmc_omap_host *host, struct mmc_data *data)
+{
+       if (data->flags & MMC_DATA_WRITE)
+               return DMA_TO_DEVICE;
+       else
+               return DMA_FROM_DEVICE;
+}
+
 /*
  * Notify the transfer complete to MMC core
  */
@@ -248,9 +330,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,
-                       host->dma_dir);
-
-       host->datadir = OMAP_MMC_DATADIR_NONE;
+                       mmc_omap_get_dma_dir(host, data));
 
        if (!data->error)
                data->bytes_xfered += data->blocks * (data->blksz);
@@ -259,6 +339,7 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
 
        if (!data->stop) {
                host->mrq = NULL;
+               mmc_omap_fclk_lazy_disable(host);
                mmc_request_done(host->mmc, data->mrq);
                return;
        }
@@ -287,6 +368,7 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
        }
        if (host->data == NULL || cmd->error) {
                host->mrq = NULL;
+               mmc_omap_fclk_lazy_disable(host);
                mmc_request_done(host->mmc, cmd->mrq);
        }
 }
@@ -300,13 +382,12 @@ static void mmc_dma_cleanup(struct mmc_omap_host *host)
 
        if (host->use_dma && host->dma_ch != -1) {
                dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->dma_len,
-                       host->dma_dir);
+                       mmc_omap_get_dma_dir(host, host->data));
                omap_free_dma(host->dma_ch);
                host->dma_ch = -1;
                up(&host->sem);
        }
        host->data = NULL;
-       host->datadir = OMAP_MMC_DATADIR_NONE;
 }
 
 /*
@@ -388,6 +469,11 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
                                        mmc_dma_cleanup(host);
                                else
                                        host->data->error = -EILSEQ;
+                               OMAP_HSMMC_WRITE(host->base, SYSCTL,
+                                       OMAP_HSMMC_READ(host->base,
+                                                       SYSCTL) | SRD);
+                               while (OMAP_HSMMC_READ(host->base,
+                                                       SYSCTL) & SRD) ;
                                end_trans = 1;
                        }
                }
@@ -420,7 +506,7 @@ static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd)
        int ret;
 
        /* Disable the clocks */
-       clk_disable(host->fclk);
+       mmc_omap_fclk_state(host, OFF);
        clk_disable(host->iclk);
        clk_disable(host->dbclk);
 
@@ -434,7 +520,7 @@ static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd)
        if (ret != 0)
                goto err;
 
-       clk_enable(host->fclk);
+       mmc_omap_fclk_state(host, ON);
        clk_enable(host->iclk);
        clk_enable(host->dbclk);
 
@@ -449,10 +535,13 @@ static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd)
         * Only MMC1 supports 3.0V.  MMC2 will not function if SDVS30 is
         * set in HCTL.
         */
-       if (host->id == OMAP_MMC1_DEVID && (((1 << vdd) == MMC_VDD_32_33) ||
-                               ((1 << vdd) == MMC_VDD_33_34)))
-               reg_val |= SDVS30;
-       if ((1 << vdd) == MMC_VDD_165_195)
+       if (host->id == OMAP_MMC1_DEVID) {
+               if (((1 << vdd) == MMC_VDD_32_33) ||
+                   ((1 << vdd) == MMC_VDD_33_34))
+                       reg_val |= SDVS30;
+               else if ((1 << vdd) == MMC_VDD_165_195)
+                       reg_val |= SDVS18;
+       } else
                reg_val |= SDVS18;
 
        OMAP_HSMMC_WRITE(host->base, HCTL, reg_val);
@@ -475,6 +564,8 @@ static void mmc_omap_detect(struct work_struct *work)
        struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
                                                mmc_carddetect_work);
 
+       sysfs_notify(&host->mmc->class_dev.kobj, NULL, "cover_switch");
+       mmc_omap_fclk_state(host, ON);
        if (host->carddetect) {
                if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
                        /*
@@ -492,6 +583,7 @@ static void mmc_omap_detect(struct work_struct *work)
                while (OMAP_HSMMC_READ(host->base, SYSCTL) & SRD) ;
                mmc_detect_change(host->mmc, (HZ * 50) / 1000);
        }
+       mmc_omap_fclk_lazy_disable(host);
 }
 
 /*
@@ -507,6 +599,55 @@ static irqreturn_t omap_mmc_cd_handler(int irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
+static int mmc_omap_get_dma_sync_dev(struct mmc_omap_host *host,
+                                    struct mmc_data *data)
+{
+       int sync_dev;
+
+       if (data->flags & MMC_DATA_WRITE) {
+               if (host->id == OMAP_MMC1_DEVID)
+                       sync_dev = OMAP24XX_DMA_MMC1_TX;
+               else
+                       sync_dev = OMAP24XX_DMA_MMC2_TX;
+       } else {
+               if (host->id == OMAP_MMC1_DEVID)
+                       sync_dev = OMAP24XX_DMA_MMC1_RX;
+               else
+                       sync_dev = OMAP24XX_DMA_MMC2_RX;
+       }
+       return sync_dev;
+}
+
+static void mmc_omap_config_dma_params(struct mmc_omap_host *host,
+                                      struct mmc_data *data,
+                                      struct scatterlist *sgl)
+{
+       int blksz, nblk, dma_ch;
+
+       dma_ch = host->dma_ch;
+       if (data->flags & MMC_DATA_WRITE) {
+               omap_set_dma_dest_params(dma_ch, 0, OMAP_DMA_AMODE_CONSTANT,
+                       (host->mapbase + OMAP_HSMMC_DATA), 0, 0);
+               omap_set_dma_src_params(dma_ch, 0, OMAP_DMA_AMODE_POST_INC,
+                       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);
+               omap_set_dma_dest_params(dma_ch, 0, OMAP_DMA_AMODE_POST_INC,
+                       sg_dma_address(sgl), 0, 0);
+       }
+
+       blksz = host->data->blksz;
+       nblk = sg_dma_len(sgl) / blksz;
+
+       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),
+                       !(data->flags & MMC_DATA_WRITE));
+
+       omap_start_dma(dma_ch);
+}
+
 /*
  * DMA call back function
  */
@@ -520,6 +661,14 @@ static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)
        if (host->dma_ch < 0)
                return;
 
+       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,
+                                          host->data->sg + host->dma_sg_idx);
+               return;
+       }
+
        omap_free_dma(host->dma_ch);
        host->dma_ch = -1;
        /*
@@ -529,39 +678,29 @@ static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)
        up(&host->sem);
 }
 
-/*
- * Configure dma src and destination parameters
- */
-static int mmc_omap_config_dma_param(int sync_dir, struct mmc_omap_host *host,
-                               struct mmc_data *data)
-{
-       if (sync_dir == 0) {
-               omap_set_dma_dest_params(host->dma_ch, 0,
-                       OMAP_DMA_AMODE_CONSTANT,
-                       (host->mapbase + OMAP_HSMMC_DATA), 0, 0);
-               omap_set_dma_src_params(host->dma_ch, 0,
-                       OMAP_DMA_AMODE_POST_INC,
-                       sg_dma_address(&data->sg[0]), 0, 0);
-       } else {
-               omap_set_dma_src_params(host->dma_ch, 0,
-                       OMAP_DMA_AMODE_CONSTANT,
-                       (host->mapbase + OMAP_HSMMC_DATA), 0, 0);
-               omap_set_dma_dest_params(host->dma_ch, 0,
-                       OMAP_DMA_AMODE_POST_INC,
-                       sg_dma_address(&data->sg[0]), 0, 0);
-       }
-       return 0;
-}
 /*
  * 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)
 {
-       int sync_dev, sync_dir = 0;
-       int dma_ch = 0, ret = 0, err = 1;
+       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++) {
+               struct scatterlist *sgl;
+
+               sgl = data->sg + i;
+               if (sgl->length % data->blksz)
+                       return -EINVAL;
+       }
+       if ((data->blksz % 4) != 0)
+               /* REVISIT: The MMC buffer increments only when MSB is written.
+                * Return error for blksz which is non multiple of four.
+                */
+               return -EINVAL;
+
        /*
         * If for some reason the DMA transfer is still active,
         * we wait for timeout period and free the dma
@@ -580,52 +719,61 @@ mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req)
                        return err;
        }
 
-       if (!(data->flags & MMC_DATA_WRITE)) {
-               host->dma_dir = DMA_FROM_DEVICE;
-               if (host->id == OMAP_MMC1_DEVID)
-                       sync_dev = OMAP24XX_DMA_MMC1_RX;
-               else
-                       sync_dev = OMAP24XX_DMA_MMC2_RX;
-       } else {
-               host->dma_dir = DMA_TO_DEVICE;
-               if (host->id == OMAP_MMC1_DEVID)
-                       sync_dev = OMAP24XX_DMA_MMC1_TX;
-               else
-                       sync_dev = OMAP24XX_DMA_MMC2_TX;
-       }
-
-       ret = omap_request_dma(sync_dev, "MMC/SD", mmc_omap_dma_cb,
-                       host, &dma_ch);
+       ret = omap_request_dma(mmc_omap_get_dma_sync_dev(host, data), "MMC/SD",
+                              mmc_omap_dma_cb,host, &dma_ch);
        if (ret != 0) {
-               dev_dbg(mmc_dev(host->mmc),
+               dev_err(mmc_dev(host->mmc),
                        "%s: omap_request_dma() failed with %d\n",
                        mmc_hostname(host->mmc), ret);
                return ret;
        }
 
        host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
-                       data->sg_len, host->dma_dir);
+                       data->sg_len, mmc_omap_get_dma_dir(host, data));
        host->dma_ch = dma_ch;
+       host->dma_sg_idx = 0;
 
-       if (!(data->flags & MMC_DATA_WRITE))
-               mmc_omap_config_dma_param(1, host, data);
-       else
-               mmc_omap_config_dma_param(0, host, data);
+       mmc_omap_config_dma_params(host, data, data->sg);
 
-       if ((data->blksz % 4) == 0)
-               omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32,
-                       (data->blksz / 4), data->blocks, OMAP_DMA_SYNC_FRAME,
-                       sync_dev, sync_dir);
-       else
-               /* REVISIT: The MMC buffer increments only when MSB is written.
-                * Return error for blksz which is non multiple of four.
-                */
-               return -EINVAL;
-
-       omap_start_dma(dma_ch);
        return 0;
 }
 
+static void set_data_timeout(struct mmc_omap_host *host,
+                            struct mmc_request *req)
+{
+       unsigned int timeout, cycle_ns;
+       uint32_t reg, clkd, dto = 0;
+
+       reg = OMAP_HSMMC_READ(host->base, SYSCTL);
+       clkd = (reg & CLKD_MASK) >> CLKD_SHIFT;
+       if (clkd == 0)
+               clkd = 1;
+
+       cycle_ns = 1000000000 / (clk_get_rate(host->fclk) / clkd);
+       timeout = req->data->timeout_ns / cycle_ns;
+       timeout += req->data->timeout_clks;
+       if (timeout) {
+               while ((timeout & 0x80000000) == 0) {
+                       dto += 1;
+                       timeout <<= 1;
+               }
+               dto = 31 - dto;
+               timeout <<= 1;
+               if (timeout && dto)
+                       dto += 1;
+               if (dto >= 13)
+                       dto -= 13;
+               else
+                       dto = 0;
+               if (dto > 14)
+                       dto = 14;
+       }
+
+       reg &= ~DTO_MASK;
+       reg |= dto << DTO_SHIFT;
+       OMAP_HSMMC_WRITE(host->base, SYSCTL, reg);
+}
+
 /*
  * Configure block length for MMC/SD cards and initiate the transfer.
  */
@@ -636,16 +784,13 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
        host->data = req->data;
 
        if (req->data == NULL) {
-               host->datadir = OMAP_MMC_DATADIR_NONE;
                OMAP_HSMMC_WRITE(host->base, BLK, 0);
                return 0;
        }
 
        OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
                                        | (req->data->blocks << 16));
-
-       host->datadir = (req->data->flags & MMC_DATA_WRITE) ?
-                       OMAP_MMC_DATADIR_WRITE : OMAP_MMC_DATADIR_READ;
+       set_data_timeout(host, req);
 
        if (host->use_dma) {
                ret = mmc_omap_start_dma_transfer(host, req);
@@ -666,11 +811,11 @@ static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
 
        WARN_ON(host->mrq != NULL);
        host->mrq = req;
+       mmc_omap_fclk_state(host, ON);
        mmc_omap_prepare_data(host, req);
        mmc_omap_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)
 {
@@ -679,6 +824,8 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        unsigned long regval;
        unsigned long timeout;
 
+       mmc_omap_fclk_state(host, ON);
+
        switch (ios->power_mode) {
        case MMC_POWER_OFF:
                mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
@@ -749,11 +896,39 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
                OMAP_HSMMC_WRITE(host->base, CON,
                                OMAP_HSMMC_READ(host->base, CON) | OD);
+
+       if (ios->power_mode == MMC_POWER_OFF)
+               mmc_omap_fclk_state(host, OFF);
+       else
+               mmc_omap_fclk_lazy_disable(host);
+}
+
+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;
+
+       if (!pdata->slots[0].card_detect)
+               return -ENOSYS;
+       return pdata->slots[0].card_detect(pdata->slots[0].card_detect_irq);
 }
-/* NOTE: Read only switch not supported yet */
+
+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;
+
+       if (!pdata->slots[0].get_ro)
+               return -ENOSYS;
+       return pdata->slots[0].get_ro(host->dev, 0);
+}
+
 static struct mmc_host_ops mmc_omap_ops = {
        .request = omap_mmc_request,
        .set_ios = omap_mmc_set_ios,
+       .get_cd = omap_hsmmc_get_cd,
+       .get_ro = omap_hsmmc_get_ro,
+       /* NYET -- enable_sdio_irq */
 };
 
 static int __init omap_mmc_probe(struct platform_device *pdev)
@@ -794,13 +969,19 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        host            = mmc_priv(mmc);
        host->mmc       = mmc;
        host->pdata     = pdata;
+       host->dev       = &pdev->dev;
        host->use_dma   = 1;
+       host->dev->dma_mask = &pdata->dma_mask;
        host->dma_ch    = -1;
        host->irq       = irq;
        host->id        = pdev->id;
        host->slot_id   = 0;
        host->mapbase   = res->start;
        host->base      = ioremap(host->mapbase, SZ_4K);
+
+       platform_set_drvdata(pdev, host);
+       INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect);
+
        mmc->ops        = &mmc_omap_ops;
        mmc->f_min      = 400000;
        mmc->f_max      = 52000000;
@@ -821,14 +1002,17 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                goto err1;
        }
 
-       if (clk_enable(host->fclk) != 0) {
+       spin_lock_init(&host->clk_lock);
+       setup_timer(&host->idle_timer, mmc_omap_idle_timer,
+                   (unsigned long) host);
+
+       if (mmc_omap_fclk_state(host, ON) != 0) {
                clk_put(host->iclk);
                clk_put(host->fclk);
                goto err1;
        }
-
        if (clk_enable(host->iclk) != 0) {
-               clk_disable(host->fclk);
+               mmc_omap_fclk_state(host, OFF);
                clk_put(host->iclk);
                clk_put(host->fclk);
                goto err1;
@@ -847,10 +1031,11 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                else
                        host->dbclk_enabled = 1;
 
-#ifdef CONFIG_MMC_BLOCK_BOUNCE
-       mmc->max_phys_segs = 1;
-       mmc->max_hw_segs = 1;
-#endif
+       /* Since we do only SG emulation, we can have as many segs
+        * as we want. */
+       mmc->max_phys_segs = 1024;
+       mmc->max_hw_segs = 1024;
+
        mmc->max_blk_size = 512;       /* Block Length at max can be 1024 */
        mmc->max_blk_count = 0xFFFF;    /* No. of Blocks is 16 bits */
        mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
@@ -859,7 +1044,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        mmc->ocr_avail = mmc_slot(host).ocr_mask;
        mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
 
-       if (pdata->conf.wire4)
+       if (pdata->slots[host->slot_id].wires >= 4)
                mmc->caps |= MMC_CAP_4_BIT_DATA;
 
        /* Only MMC1 supports 3.0V */
@@ -886,18 +1071,28 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                        OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
 
        /* Request IRQ for MMC operations */
-       ret = request_irq(host->irq, mmc_omap_irq, IRQF_DISABLED, pdev->name,
-                        host);
+       ret = request_irq(host->irq, mmc_omap_irq, IRQF_DISABLED,
+                       mmc_hostname(mmc), host);
        if (ret) {
                dev_dbg(mmc_dev(host->mmc), "Unable to grab HSMMC IRQ\n");
                goto err_irq;
        }
 
+       if (pdata->init != NULL) {
+               if (pdata->init(&pdev->dev) != 0) {
+                       dev_dbg(mmc_dev(host->mmc),
+                               "Unable to configure MMC IRQs\n");
+                       goto err_irq_cd_init;
+               }
+       }
+
        /* Request IRQ for card detect */
        if ((mmc_slot(host).card_detect_irq) && (mmc_slot(host).card_detect)) {
                ret = request_irq(mmc_slot(host).card_detect_irq,
-                                 omap_mmc_cd_handler, IRQF_DISABLED, "MMC CD",
-                                 host);
+                                 omap_mmc_cd_handler,
+                                 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
+                                         | IRQF_DISABLED,
+                                 mmc_hostname(mmc), host);
                if (ret) {
                        dev_dbg(mmc_dev(host->mmc),
                                "Unable to grab MMC CD IRQ\n");
@@ -905,19 +1100,9 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                }
        }
 
-       INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect);
-       if (pdata->init != NULL) {
-               if (pdata->init(&pdev->dev) != 0) {
-                       dev_dbg(mmc_dev(host->mmc),
-                               "Unable to configure MMC IRQs\n");
-                       goto err_irq_cd_init;
-               }
-       }
-
        OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
        OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
 
-       platform_set_drvdata(pdev, host);
        mmc_add_host(mmc);
 
        if (host->pdata->slots[host->slot_id].name != NULL) {
@@ -925,17 +1110,26 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                if (ret < 0)
                        goto err_slot_name;
        }
+       if (mmc_slot(host).card_detect_irq && mmc_slot(host).card_detect &&
+                       host->pdata->slots[host->slot_id].get_cover_state) {
+               ret = device_create_file(&mmc->class_dev, &dev_attr_cover_switch);
+               if (ret < 0)
+                       goto err_cover_switch;
+       }
+       mmc_omap_fclk_lazy_disable(host);
 
        return 0;
 
+err_cover_switch:
+       device_remove_file(&mmc->class_dev, &dev_attr_cover_switch);
 err_slot_name:
        mmc_remove_host(mmc);
-err_irq_cd_init:
-       free_irq(mmc_slot(host).card_detect_irq, host);
 err_irq_cd:
+       free_irq(mmc_slot(host).card_detect_irq, host);
+err_irq_cd_init:
        free_irq(host->irq, host);
 err_irq:
-       clk_disable(host->fclk);
+       mmc_omap_fclk_state(host, OFF);
        clk_disable(host->iclk);
        clk_put(host->fclk);
        clk_put(host->iclk);
@@ -960,14 +1154,17 @@ static int omap_mmc_remove(struct platform_device *pdev)
        struct resource *res;
        u16 vdd = 0;
 
-       if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
-       /*
-        * Set the vdd back to 3V,
-        * applicable for dual volt support.
-        */
-               vdd = fls(host->mmc->ocr_avail) - 1;
-               if (omap_mmc_switch_opcond(host, vdd) != 0)
-                       host->mmc->ios.vdd = vdd;
+       if (host) {
+               mmc_omap_fclk_state(host, ON);
+               if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
+                       /*
+                        * Set the vdd back to 3V,
+                        * applicable for dual volt support.
+                        */
+                       vdd = fls(host->mmc->ocr_avail) - 1;
+                       if (omap_mmc_switch_opcond(host, vdd) != 0)
+                               host->mmc->ios.vdd = vdd;
+               }
        }
 
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -984,7 +1181,7 @@ static int omap_mmc_remove(struct platform_device *pdev)
                        free_irq(mmc_slot(host).card_detect_irq, host);
                flush_scheduled_work();
 
-               clk_disable(host->fclk);
+               mmc_omap_fclk_state(host, OFF);
                clk_disable(host->iclk);
                clk_put(host->fclk);
                clk_put(host->iclk);
@@ -1010,6 +1207,8 @@ static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state)
                return 0;
 
        if (host) {
+               mmc_omap_fclk_state(host, ON);
+
                ret = mmc_suspend_host(host->mmc, state);
                if (ret == 0) {
                        host->suspended = 1;
@@ -1026,18 +1225,19 @@ static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state)
                        }
 
                        if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
-                               OMAP_HSMMC_WRITE(host->base, HCTL,
-                                       OMAP_HSMMC_READ(host->base, HCTL)
-                                       & SDVSCLR);
-                               OMAP_HSMMC_WRITE(host->base, HCTL,
-                                       OMAP_HSMMC_READ(host->base, HCTL)
-                                       | SDVS30);
-                               OMAP_HSMMC_WRITE(host->base, HCTL,
-                                       OMAP_HSMMC_READ(host->base, HCTL)
-                                       | SDBP);
+                               u32 hctl = OMAP_HSMMC_READ(host->base, HCTL) &
+                                       SDVSCLR;
+
+                               if (host->id == OMAP_MMC1_DEVID)
+                                       hctl |= SDVS30;
+                               else
+                                       hctl |= SDVS18;
+
+                               OMAP_HSMMC_WRITE(host->base, HCTL, hctl);
+                               OMAP_HSMMC_WRITE(host->base, HCTL, hctl | SDBP);
                        }
 
-                       clk_disable(host->fclk);
+                       mmc_omap_fclk_state(host, OFF);
                        clk_disable(host->iclk);
                        clk_disable(host->dbclk);
                }
@@ -1056,14 +1256,12 @@ static int omap_mmc_resume(struct platform_device *pdev)
                return 0;
 
        if (host) {
-
-               ret = clk_enable(host->fclk);
-               if (ret)
+               if (mmc_omap_fclk_state(host, ON) != 0)
                        goto clk_en_err;
 
                ret = clk_enable(host->iclk);
                if (ret) {
-                       clk_disable(host->fclk);
+                       mmc_omap_fclk_state(host, OFF);
                        clk_put(host->fclk);
                        goto clk_en_err;
                }
@@ -1083,6 +1281,8 @@ static int omap_mmc_resume(struct platform_device *pdev)
                ret = mmc_resume_host(host->mmc);
                if (ret == 0)
                        host->suspended = 0;
+
+               mmc_omap_fclk_lazy_disable(host);
        }
 
        return ret;