]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/mmc/host/omap_hsmmc.c
hsmmc: Fix comments from Pierre
[linux-2.6-omap-h63xx.git] / drivers / mmc / host / omap_hsmmc.c
index 7744669d203dfb54ab859c414f8baffb78a64ebc..59c594615da38fed7dcca4146d48d8d3da72ed14 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
+/*
+ * 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 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"
+#define DRIVER_NAME            "mmci-omap-hs"
+
 /*
- * 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;
@@ -137,9 +151,52 @@ struct mmc_omap_host {
        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
  */
@@ -237,9 +294,10 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd,
 
        /*
         * Unlike OMAP1 controller, the cmdtype does not seem to be based on
-        * ac, bc, adtc, bcr. Only CMD12 needs a val of 0x3, rest 0x0.
+        * ac, bc, adtc, bcr. Only commands ending an open ended transfer need
+        * a val of 0x3, rest 0x0.
         */
-       if (cmd->opcode == 12)
+       if (cmd == host->mrq->stop)
                cmdtype = 0x3;
 
        cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22);
@@ -280,6 +338,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;
        }
@@ -308,6 +367,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);
        }
 }
@@ -409,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;
                        }
                }
@@ -441,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);
 
@@ -455,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);
 
@@ -470,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);
@@ -497,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)) {
                        /*
@@ -514,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);
 }
 
 /*
@@ -648,6 +718,42 @@ mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req)
        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.
  */
@@ -665,6 +771,7 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
 
        OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
                                        | (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;
@@ -688,11 +795,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)
 {
@@ -701,6 +808,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);
@@ -771,11 +880,36 @@ 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);
 }
-/* NOTE: Read only switch not supported yet */
+
+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);
+}
+
+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)
@@ -784,7 +918,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        struct mmc_host *mmc;
        struct mmc_omap_host *host = NULL;
        struct resource *res;
-       int ret = 0, irq;
+       int ret = 0, irq, reg;
        u32 hctl, capa;
 
        if (pdata == NULL) {
@@ -816,13 +950,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;
@@ -843,14 +983,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;
@@ -861,7 +1004,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
         * MMC can still work without debounce clock.
         */
        if (IS_ERR(host->dbclk))
-               dev_dbg(mmc_dev(host->mmc), "Failed to get debounce clock\n");
+               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"
@@ -881,7 +1024,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->slots[host->slot_id].wire4)
+       if (pdata->slots[host->slot_id].wires >= 4)
                mmc->caps |= MMC_CAP_4_BIT_DATA;
 
        /* Only MMC1 supports 3.0V */
@@ -915,10 +1058,20 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
                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,
+                                 omap_mmc_cd_handler,
+                                 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
+                                         | IRQF_DISABLED,
                                  mmc_hostname(mmc), host);
                if (ret) {
                        dev_dbg(mmc_dev(host->mmc),
@@ -927,19 +1080,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) {
@@ -953,6 +1096,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;
 
@@ -960,12 +1104,12 @@ 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);
@@ -988,23 +1132,7 @@ static int omap_mmc_remove(struct platform_device *pdev)
 {
        struct mmc_omap_host *host = platform_get_drvdata(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;
-       }
-
-       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       if (res)
-               release_mem_region(res->start, res->end - res->start + 1);
-
-       platform_set_drvdata(pdev, NULL);
        if (host) {
                mmc_remove_host(host->mmc);
                if (host->pdata->cleanup)
@@ -1014,7 +1142,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);
@@ -1027,6 +1155,11 @@ static int omap_mmc_remove(struct platform_device *pdev)
                iounmap(host->base);
        }
 
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (res)
+               release_mem_region(res->start, res->end - res->start + 1);
+       platform_set_drvdata(pdev, NULL);
+
        return 0;
 }
 
@@ -1044,6 +1177,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);
 
@@ -1055,19 +1189,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);
                }
@@ -1086,14 +1210,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;
                }
@@ -1102,6 +1225,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)
@@ -1113,6 +1243,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;