]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/mmc/host/omap_hsmmc.c
ARM: OMAP: hsmmc requires data reset after data timeout
[linux-2.6-omap-h63xx.git] / drivers / mmc / host / omap_hsmmc.c
index d5d82a85ff00690224aec1c6a4a2e2a8ee253382..8fb677e94c469f73aad91c6ff97819f3ebcbb6e7 100644 (file)
 #include <linux/clk.h>
 #include <linux/mmc/host.h>
 #include <linux/io.h>
-#include <asm/semaphore.h>
+#include <linux/semaphore.h>
 #include <asm/dma.h>
-#include <asm/hardware.h>
-#include <asm/arch/board.h>
-#include <asm/arch/mmc.h>
-#include <asm/arch/cpu.h>
+#include <mach/hardware.h>
+#include <mach/board.h>
+#include <mach/mmc.h>
+#include <mach/cpu.h>
 
 /* OMAP HSMMC Host Controller Registers */
 #define OMAP_HSMMC_SYSCONFIG   0x0010
 #define STAT_CLEAR             0xFFFFFFFF
 #define INIT_STREAM_CMD                0x00000000
 #define DUAL_VOLT_OCR_BIT      7
+#define SRC                    (1 << 25)
+#define SRD                    (1 << 26)
+
+/*
+ * 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_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
 #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])
 
@@ -172,6 +182,40 @@ 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)
+{
+       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];
+
+       return sprintf(buf, "slot:%s\n", slot.name);
+}
+
+static DEVICE_ATTR(slot_name, S_IRUGO, mmc_omap_show_slot_name, NULL);
+
 /*
  * Configure the response type and send the cmd.
  */
@@ -294,12 +338,44 @@ static void mmc_dma_cleanup(struct mmc_omap_host *host)
        host->datadir = OMAP_MMC_DATADIR_NONE;
 }
 
+/*
+ * Readable error output
+ */
+#ifdef CONFIG_MMC_DEBUG
+static void mmc_omap_report_irq(struct mmc_omap_host *host, u32 status)
+{
+       /* --- means reserved bit without definition at documentation */
+       static const char *mmc_omap_status_bits[] = {
+               "CC", "TC", "BGE", "---", "BWR", "BRR", "---", "---", "CIRQ",
+               "OBI", "---", "---", "---", "---", "---", "ERRI", "CTO", "CCRC",
+               "CEB", "CIE", "DTO", "DCRC", "DEB", "---", "ACE", "---",
+               "---", "---", "---", "CERR", "CERR", "BADA", "---", "---", "---"
+       };
+       int i;
+
+       dev_dbg(mmc_dev(host->mmc), "MMC IRQ 0x%x :", status);
+
+       for (i = 0; i < ARRAY_SIZE(mmc_omap_status_bits); i++)
+               if (status & (1 << i))
+                       /*
+                        * KERN_* facility is not used here because this should
+                        * print a single line.
+                        */
+                       printk(" %s", mmc_omap_status_bits[i]);
+
+       printk("\n");
+
+}
+#endif  /* CONFIG_MMC_DEBUG */
+
+
 /*
  * MMC controller IRQ handler
  */
 static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
 {
        struct mmc_omap_host *host = dev_id;
+       struct mmc_data *data;
        int end_cmd = 0, end_trans = 0, status;
 
        if (host->cmd == NULL && host->data == NULL) {
@@ -308,17 +384,27 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
                return IRQ_HANDLED;
        }
 
+       data = host->data;
        status = OMAP_HSMMC_READ(host->base, STAT);
        dev_dbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status);
 
        if (status & ERR) {
+#ifdef CONFIG_MMC_DEBUG
+               mmc_omap_report_irq(host, status);
+#endif
                if ((status & CMD_TIMEOUT) ||
                        (status & CMD_CRC)) {
                        if (host->cmd) {
-                               if (status & CMD_TIMEOUT)
+                               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) ;
                                        host->cmd->error = -ETIMEDOUT;
-                               else
+                               } else {
                                        host->cmd->error = -EILSEQ;
+                               }
                                end_cmd = 1;
                        }
                        if (host->data)
@@ -331,6 +417,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;
                        }
                }
@@ -349,7 +440,7 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
        if (end_cmd || (status & CC))
                mmc_omap_cmd_done(host, host->cmd);
        if (end_trans || (status & TC))
-               mmc_omap_xfer_done(host, host->data);
+               mmc_omap_xfer_done(host, data);
 
        return IRQ_HANDLED;
 }
@@ -418,6 +509,7 @@ 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");
        if (host->carddetect) {
                if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
                        /*
@@ -429,8 +521,12 @@ static void mmc_omap_detect(struct work_struct *work)
                                host->mmc->ios.vdd = vdd;
                }
                mmc_detect_change(host->mmc, (HZ * 200) / 1000);
-       } else
+       } else {
+               OMAP_HSMMC_WRITE(host->base, SYSCTL,
+                       OMAP_HSMMC_READ(host->base, SYSCTL) | SRD);
+               while (OMAP_HSMMC_READ(host->base, SYSCTL) & SRD) ;
                mmc_detect_change(host->mmc, (HZ * 50) / 1000);
+       }
 }
 
 /*
@@ -733,7 +829,9 @@ 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;
@@ -750,23 +848,27 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        if (IS_ERR(host->iclk)) {
                ret = PTR_ERR(host->iclk);
                host->iclk = NULL;
-               goto err;
+               goto err1;
        }
        host->fclk = clk_get(&pdev->dev, "mmchs_fck");
        if (IS_ERR(host->fclk)) {
                ret = PTR_ERR(host->fclk);
                host->fclk = NULL;
                clk_put(host->iclk);
-               goto err;
+               goto err1;
        }
 
-       if (clk_enable(host->fclk) != 0)
-               goto err;
+       if (clk_enable(host->fclk) != 0) {
+               clk_put(host->iclk);
+               clk_put(host->fclk);
+               goto err1;
+       }
 
        if (clk_enable(host->iclk) != 0) {
                clk_disable(host->fclk);
+               clk_put(host->iclk);
                clk_put(host->fclk);
-               goto err;
+               goto err1;
        }
 
        host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck");
@@ -782,11 +884,19 @@ 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
+       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;
+       mmc->max_seg_size = mmc->max_req_size;
+
        mmc->ocr_avail = mmc_slot(host).ocr_mask;
-       mmc->caps |= MMC_CAP_MULTIWRITE | MMC_CAP_MMC_HIGHSPEED |
-                               MMC_CAP_SD_HIGHSPEED;
+       mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
 
-       if (pdata->conf.wire4)
+       if (pdata->slots[host->slot_id].wire4)
                mmc->caps |= MMC_CAP_4_BIT_DATA;
 
        /* Only MMC1 supports 3.0V */
@@ -813,32 +923,33 @@ 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 irq_err;
+               goto err_irq;
        }
 
        /* 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");
-                       free_irq(host->irq, host);
-                       goto irq_err;
+                               "Unable to grab MMC CD IRQ\n");
+                       goto err_irq_cd;
                }
        }
 
        INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect);
        if (pdata->init != NULL) {
                if (pdata->init(&pdev->dev) != 0) {
-                       free_irq(mmc_slot(host).card_detect_irq, host);
-                       free_irq(host->irq, host);
-                       goto irq_err;
+                       dev_dbg(mmc_dev(host->mmc),
+                               "Unable to configure MMC IRQs\n");
+                       goto err_irq_cd_init;
                }
        }
 
@@ -848,16 +959,29 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
        platform_set_drvdata(pdev, host);
        mmc_add_host(mmc);
 
-       return 0;
+       if (host->pdata->slots[host->slot_id].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 && 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;
+       }
 
-err:
-       dev_dbg(mmc_dev(host->mmc), "Probe Failed\n");
-       if (host)
-               mmc_free_host(mmc);
-       return ret;
+       return 0;
 
-irq_err:
-       dev_dbg(mmc_dev(host->mmc), "Unable to configure MMC IRQs\n");
+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(host->irq, host);
+err_irq:
        clk_disable(host->fclk);
        clk_disable(host->iclk);
        clk_put(host->fclk);
@@ -867,6 +991,11 @@ irq_err:
                clk_put(host->dbclk);
        }
 
+err1:
+       iounmap(host->base);
+err:
+       dev_dbg(mmc_dev(host->mmc), "Probe Failed\n");
+       release_mem_region(res->start, res->end - res->start + 1);
        if (host)
                mmc_free_host(mmc);
        return ret;
@@ -875,10 +1004,28 @@ irq_err:
 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) {
-               host->pdata->cleanup(&pdev->dev);
+               mmc_remove_host(host->mmc);
+               if (host->pdata->cleanup)
+                       host->pdata->cleanup(&pdev->dev);
                free_irq(host->irq, host);
                if (mmc_slot(host).card_detect_irq)
                        free_irq(mmc_slot(host).card_detect_irq, host);
@@ -894,6 +1041,7 @@ static int omap_mmc_remove(struct platform_device *pdev)
                }
 
                mmc_free_host(host->mmc);
+               iounmap(host->base);
        }
 
        return 0;
@@ -916,11 +1064,13 @@ static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state)
                        OMAP_HSMMC_WRITE(host->base, ISE, 0);
                        OMAP_HSMMC_WRITE(host->base, IE, 0);
 
-                       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");
+                       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");
+                       }
 
                        if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
                                OMAP_HSMMC_WRITE(host->base, HCTL,
@@ -969,10 +1119,12 @@ static int omap_mmc_resume(struct platform_device *pdev)
                        dev_dbg(mmc_dev(host->mmc),
                                        "Enabling debounce clk failed\n");
 
-               ret = host->pdata->resume(&pdev->dev, host->slot_id);
-               if (ret)
-                       dev_dbg(mmc_dev(host->mmc),
+               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");
+               }
 
                /* Notify the core to resume the host */
                ret = mmc_resume_host(host->mmc);
@@ -1000,6 +1152,7 @@ static struct platform_driver omap_mmc_driver = {
        .resume         = omap_mmc_resume,
        .driver         = {
                .name = DRIVER_NAME,
+               .owner = THIS_MODULE,
        },
 };
 
@@ -1020,5 +1173,5 @@ module_exit(omap_mmc_cleanup);
 
 MODULE_DESCRIPTION("OMAP High Speed Multimedia Card driver");
 MODULE_LICENSE("GPL");
-MODULE_ALIAS(DRIVER_NAME);
+MODULE_ALIAS("platform:" DRIVER_NAME);
 MODULE_AUTHOR("Texas Instruments Inc");