]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/mmc/host/omap_hsmmc.c
omap_hsmmc: Fix response type for busy after response
[linux-2.6-omap-h63xx.git] / drivers / mmc / host / omap_hsmmc.c
index db37490f67ec724d68732cd56054735d684a223e..1f84bd4a3b2796840dc42e8ab3ff866449cff376 100644 (file)
@@ -55,6 +55,8 @@
 #define VS30                   (1 << 25)
 #define SDVS18                 (0x5 << 9)
 #define SDVS30                 (0x6 << 9)
+#define SDVS33                 (0x7 << 9)
+#define SDVS_MASK              0x00000E00
 #define SDVSCLR                        0xFFFFF1FF
 #define SDVSDET                        0x00000400
 #define AUTOIDLE               0x1
@@ -148,6 +150,7 @@ struct mmc_omap_host {
        int                     initstr;
        int                     slot_id;
        int                     dbclk_enabled;
+       int                     response_busy;
        struct  omap_mmc_platform_data  *pdata;
 };
 
@@ -242,10 +245,14 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd,
        OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
        OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
 
+       host->response_busy = 0;
        if (cmd->flags & MMC_RSP_PRESENT) {
                if (cmd->flags & MMC_RSP_136)
                        resptype = 1;
-               else
+               else if (cmd->flags & MMC_RSP_BUSY) {
+                       resptype = 3;
+                       host->response_busy = 1;
+               } else
                        resptype = 2;
        }
 
@@ -280,6 +287,15 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd,
 static void
 mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
 {
+       if (!data) {
+               struct mmc_request *mrq = host->mrq;
+
+               host->mrq = NULL;
+               mmc_omap_fclk_lazy_disable(host);
+               mmc_request_done(host->mmc, mrq);
+               return;
+       }
+
        host->data = NULL;
 
        if (host->use_dma && host->dma_ch != -1)
@@ -321,7 +337,7 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
                        cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP10);
                }
        }
-       if (host->data == NULL || cmd->error) {
+       if ((host->data == NULL && !host->response_busy) || cmd->error) {
                host->mrq = NULL;
                mmc_request_done(host->mmc, cmd->mrq);
        }
@@ -330,9 +346,9 @@ 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)
+static void mmc_dma_cleanup(struct mmc_omap_host *host, int errno)
 {
-       host->data->error = -ETIMEDOUT;
+       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,
@@ -375,6 +391,32 @@ static void mmc_omap_report_irq(struct mmc_omap_host *host, u32 status)
 }
 #endif  /* CONFIG_MMC_DEBUG */
 
+/*
+ * MMC controller internal state machines reset
+ *
+ * Used to reset command or data internal state machines, using respectively
+ *  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)
+{
+       unsigned long i = 0;
+       unsigned long limit = (loops_per_jiffy *
+                               msecs_to_jiffies(MMC_TIMEOUT_MS));
+
+       OMAP_HSMMC_WRITE(host->base, SYSCTL,
+                        OMAP_HSMMC_READ(host->base, SYSCTL) | bit);
+
+       while ((OMAP_HSMMC_READ(host->base, SYSCTL) & bit) &&
+               (i++ < limit))
+               cpu_relax();
+
+       if (OMAP_HSMMC_READ(host->base, SYSCTL) & bit)
+               dev_err(mmc_dev(host->mmc),
+                       "Timeout waiting on controller reset in %s\n",
+                       __func__);
+}
 
 /*
  * MMC controller IRQ handler
@@ -385,7 +427,7 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
        struct mmc_data *data;
        int end_cmd = 0, end_trans = 0, status;
 
-       if (host->cmd == NULL && host->data == NULL) {
+       if (host->mrq == NULL) {
                OMAP_HSMMC_WRITE(host->base, STAT,
                        OMAP_HSMMC_READ(host->base, STAT));
                return IRQ_HANDLED;
@@ -403,35 +445,32 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
                        (status & CMD_CRC)) {
                        if (host->cmd) {
                                if (status & CMD_TIMEOUT) {
-                                       OMAP_HSMMC_WRITE(host->base, SYSCTL,
-                                               OMAP_HSMMC_READ(host->base,
-                                                               SYSCTL) | SRC);
-                                       while (OMAP_HSMMC_READ(host->base,
-                                                       SYSCTL) & SRC)
-                                               ;
-
+                                       mmc_omap_reset_controller_fsm(host, SRC);
                                        host->cmd->error = -ETIMEDOUT;
                                } else {
                                        host->cmd->error = -EILSEQ;
                                }
                                end_cmd = 1;
                        }
-                       if (host->data)
-                               mmc_dma_cleanup(host);
+                       if (host->data || host->response_busy) {
+                               if (host->data)
+                                       mmc_dma_cleanup(host, -ETIMEDOUT);
+                               host->response_busy = 0;
+                               mmc_omap_reset_controller_fsm(host, SRD);
+                       }
                }
                if ((status & DATA_TIMEOUT) ||
                        (status & DATA_CRC)) {
-                       if (host->data) {
-                               if (status & DATA_TIMEOUT)
-                                       mmc_dma_cleanup(host);
+                       if (host->data || host->response_busy) {
+                               int err = (status & DATA_TIMEOUT) ?
+                                               -ETIMEDOUT : -EILSEQ;
+
+                               if (host->data)
+                                       mmc_dma_cleanup(host, err);
                                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)
-                                       ;
+                                       host->mrq->cmd->error = err;
+                               host->response_busy = 0;
+                               mmc_omap_reset_controller_fsm(host, SRD);
                                end_trans = 1;
                        }
                }
@@ -456,7 +495,11 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
 }
 
 /*
- * Switch MMC operating voltage
+ * Switch MMC interface voltage ... only relevant for MMC1.
+ *
+ * MMC2 and MMC3 use fixed 1.8V levels, and maybe a transceiver.
+ * 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)
 {
@@ -485,19 +528,26 @@ static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd)
        OMAP_HSMMC_WRITE(host->base, HCTL,
                OMAP_HSMMC_READ(host->base, HCTL) & SDVSCLR);
        reg_val = OMAP_HSMMC_READ(host->base, HCTL);
+
        /*
         * 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.
         *
-        * Only MMC1 supports 3.0V.  MMC2 will not function if SDVS30 is
-        * set in HCTL.
+        * 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,
+        *    but recommended values are 1.71V to 1.89V
+        *  - "3.0V" for vdds_mmc1/vdds_mmc1a can be up to 3.5V max,
+        *    but recommended values are 2.7V to 3.3V
+        *
+        * Board setup code shouldn't permit anything very out-of-range.
+        * TWL4030-family VMMC1 and VSIM regulators are fine (avoiding the
+        * middle range) but VSIM can't power DAT4..DAT7 at more than 3V.
         */
-       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 ((1 << vdd) <= MMC_VDD_23_24)
                reg_val |= SDVS18;
+       else
+               reg_val |= SDVS30;
 
        OMAP_HSMMC_WRITE(host->base, HCTL, reg_val);
 
@@ -517,16 +567,15 @@ static void mmc_omap_detect(struct work_struct *work)
 {
        struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
                                                mmc_carddetect_work);
+       struct omap_mmc_slot_data *slot = &mmc_slot(host);
+
+       host->carddetect = slot->card_detect(slot->card_detect_irq);
 
        sysfs_notify(&host->mmc->class_dev.kobj, NULL, "cover_switch");
        if (host->carddetect) {
                mmc_detect_change(host->mmc, (HZ * 200) / 1000);
        } else {
-               OMAP_HSMMC_WRITE(host->base, SYSCTL,
-                       OMAP_HSMMC_READ(host->base, SYSCTL) | SRD);
-               while (OMAP_HSMMC_READ(host->base, SYSCTL) & SRD)
-                       ;
-
+               mmc_omap_reset_controller_fsm(host, SRD);
                mmc_detect_change(host->mmc, (HZ * 50) / 1000);
        }
 }
@@ -538,7 +587,6 @@ static irqreturn_t omap_mmc_cd_handler(int irq, void *dev_id)
 {
        struct mmc_omap_host *host = (struct mmc_omap_host *)dev_id;
 
-       host->carddetect = mmc_slot(host).card_detect(irq);
        schedule_work(&host->mmc_carddetect_work);
 
        return IRQ_HANDLED;
@@ -756,16 +804,6 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        switch (ios->power_mode) {
        case MMC_POWER_OFF:
                mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
-               /*
-                * Reset bus voltage to 3V if it got set to 1.8V earlier.
-                * REVISIT: If we are able to detect cards after unplugging
-                * a 1.8V card, this code should not be needed.
-                */
-               if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
-                       int vdd = fls(host->mmc->ocr_avail) - 1;
-                       if (omap_mmc_switch_opcond(host, vdd) != 0)
-                               host->mmc->ios.vdd = vdd;
-               }
                break;
        case MMC_POWER_UP:
                mmc_slot(host).set_power(host->dev, host->slot_id, 1, ios->vdd);
@@ -784,7 +822,9 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        }
 
        if (host->id == OMAP_MMC1_DEVID) {
-               /* Only MMC1 can operate at 3V/1.8V */
+               /* Only MMC1 can interface at 3V without some flavor
+                * of external transceiver; but they all handle 1.8V.
+                */
                if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) &&
                        (ios->vdd == DUAL_VOLT_OCR_BIT)) {
                                /*
@@ -855,6 +895,34 @@ static int omap_hsmmc_get_ro(struct mmc_host *mmc)
        return pdata->slots[0].get_ro(host->dev, 0);
 }
 
+static void omap_hsmmc_init(struct mmc_omap_host *host)
+{
+       u32 hctl, capa, value;
+
+       /* Only MMC1 supports 3.0V */
+       if (host->id == OMAP_MMC1_DEVID) {
+               hctl = SDVS30;
+               capa = VS30 | VS18;
+       } else {
+               hctl = SDVS18;
+               capa = VS18;
+       }
+
+       value = OMAP_HSMMC_READ(host->base, HCTL) & ~SDVS_MASK;
+       OMAP_HSMMC_WRITE(host->base, HCTL, value | hctl);
+
+       value = OMAP_HSMMC_READ(host->base, CAPA);
+       OMAP_HSMMC_WRITE(host->base, CAPA, value | capa);
+
+       /* Set the controller to AUTO IDLE mode */
+       value = OMAP_HSMMC_READ(host->base, SYSCONFIG);
+       OMAP_HSMMC_WRITE(host->base, SYSCONFIG, value | AUTOIDLE);
+
+       /* Set SD bus power bit */
+       value = OMAP_HSMMC_READ(host->base, HCTL);
+       OMAP_HSMMC_WRITE(host->base, HCTL, value | SDBP);
+}
+
 static struct mmc_host_ops mmc_omap_ops = {
        .request = omap_mmc_request,
        .set_ios = omap_mmc_set_ios,
@@ -870,7 +938,6 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        struct mmc_omap_host *host = NULL;
        struct resource *res;
        int ret = 0, irq;
-       u32 hctl, capa;
 
        if (pdata == NULL) {
                dev_err(&pdev->dev, "Platform Data is missing\n");
@@ -975,28 +1042,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        if (pdata->slots[host->slot_id].wires >= 4)
                mmc->caps |= MMC_CAP_4_BIT_DATA;
 
-       /* Only MMC1 supports 3.0V */
-       if (host->id == OMAP_MMC1_DEVID) {
-               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);
-
-       /* Set the controller to AUTO IDLE mode */
-       OMAP_HSMMC_WRITE(host->base, SYSCONFIG,
-                       OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE);
-
-       /* Set SD bus power bit */
-       OMAP_HSMMC_WRITE(host->base, HCTL,
-                       OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+       omap_hsmmc_init(host);
 
        /* Request IRQ for MMC operations */
        ret = request_irq(host->irq, mmc_omap_irq, IRQF_DISABLED,
@@ -1137,7 +1183,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)) {
+                       if (host->id == OMAP_MMC1_DEVID
+                                       && !(OMAP_HSMMC_READ(host->base, HCTL)
+                                                       & SDVSDET)) {
                                OMAP_HSMMC_WRITE(host->base, HCTL,
                                        OMAP_HSMMC_READ(host->base, HCTL)
                                        & SDVSCLR);
@@ -1184,6 +1232,8 @@ static int omap_mmc_resume(struct platform_device *pdev)
                        dev_dbg(mmc_dev(host->mmc),
                                        "Enabling debounce clk failed\n");
 
+               omap_hsmmc_init(host);
+
                if (host->pdata->resume) {
                        ret = host->pdata->resume(&pdev->dev, host->slot_id);
                        if (ret)