]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/mmc/host/omap_hsmmc.c
Merge branch 'omap_hsmmc'
[linux-2.6-omap-h63xx.git] / drivers / mmc / host / omap_hsmmc.c
index 4ab132546eb18b5ae756ae03c07b109f41b7488e..1fcc5443cf0dd1d9398e6ea0e20d72833a99145d 100644 (file)
@@ -98,9 +98,6 @@
 #define OMAP_MMC1_DEVID                0
 #define OMAP_MMC2_DEVID                1
 
-#define OMAP_MMC_DATADIR_NONE  0
-#define OMAP_MMC_DATADIR_READ  1
-#define OMAP_MMC_DATADIR_WRITE 2
 #define MMC_TIMEOUT_MS         20
 #define OMAP_MMC_MASTER_CLOCK  96000000
 #define DRIVER_NAME            "mmci-omap-hs"
 #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;
@@ -136,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
  */
@@ -270,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
  */
@@ -280,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);
@@ -291,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;
        }
@@ -319,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);
        }
 }
@@ -332,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;
 }
 
 /*
@@ -457,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);
 
@@ -471,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);
 
@@ -486,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);
@@ -513,6 +565,7 @@ static void mmc_omap_detect(struct work_struct *work)
                                                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)) {
                        /*
@@ -530,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);
 }
 
 /*
@@ -545,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
  */
@@ -558,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;
        /*
@@ -567,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
@@ -618,49 +719,22 @@ 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;
 }
 
@@ -710,7 +784,6 @@ 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;
        }
@@ -719,9 +792,6 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
                                        | (req->data->blocks << 16));
        set_data_timeout(host, req);
 
-       host->datadir = (req->data->flags & MMC_DATA_WRITE) ?
-                       OMAP_MMC_DATADIR_WRITE : OMAP_MMC_DATADIR_READ;
-
        if (host->use_dma) {
                ret = mmc_omap_start_dma_transfer(host, req);
                if (ret != 0) {
@@ -741,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)
 {
@@ -754,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);
@@ -824,6 +896,8 @@ 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);
+
+       mmc_omap_fclk_lazy_disable(host);
 }
 
 static int omap_hsmmc_get_cd(struct mmc_host *mmc)
@@ -925,14 +999,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;
@@ -951,10 +1028,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;
@@ -1035,6 +1113,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                if (ret < 0)
                        goto err_cover_switch;
        }
+       mmc_omap_fclk_lazy_disable(host);
 
        return 0;
 
@@ -1047,7 +1126,7 @@ err_irq_cd:
 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);
@@ -1072,14 +1151,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);
@@ -1096,7 +1178,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);
@@ -1126,6 +1208,7 @@ static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state)
                if (ret == 0) {
                        host->suspended = 1;
 
+                       mmc_omap_fclk_state(host, ON);
                        OMAP_HSMMC_WRITE(host->base, ISE, 0);
                        OMAP_HSMMC_WRITE(host->base, IE, 0);
 
@@ -1137,19 +1220,9 @@ static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state)
                                                " level suspend\n");
                        }
 
-                       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);
-                       }
-
-                       clk_disable(host->fclk);
+                       OMAP_HSMMC_WRITE(host->base, HCTL,
+                                        OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
+                       mmc_omap_fclk_state(host, OFF);
                        clk_disable(host->iclk);
                        clk_disable(host->dbclk);
                }
@@ -1168,14 +1241,13 @@ static int omap_mmc_resume(struct platform_device *pdev)
                return 0;
 
        if (host) {
-
-               ret = clk_enable(host->fclk);
-               if (ret)
+               int i;
+               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;
                }
@@ -1184,6 +1256,13 @@ static int omap_mmc_resume(struct platform_device *pdev)
                        dev_dbg(mmc_dev(host->mmc),
                                        "Enabling debounce clk failed\n");
 
+               OMAP_HSMMC_WRITE(host->base, HCTL,
+                                OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+
+               for (i = 0; i < 100; i++)
+                       if (OMAP_HSMMC_READ(host->base, HCTL) & SDBP)
+                               break;
+
                if (host->pdata->resume) {
                        ret = host->pdata->resume(&pdev->dev, host->slot_id);
                        if (ret)
@@ -1195,6 +1274,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;