omap_hsmmc: minor tweaks
[pandora-kernel.git] / drivers / mmc / host / omap_hsmmc.c
index d5fe43d..8a585f6 100644 (file)
@@ -24,7 +24,6 @@
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/platform_device.h>
-#include <linux/workqueue.h>
 #include <linux/timer.h>
 #include <linux/clk.h>
 #include <linux/mmc/host.h>
@@ -163,7 +162,6 @@ struct omap_hsmmc_host {
         */
        struct  regulator       *vcc;
        struct  regulator       *vcc_aux;
-       struct  work_struct     mmc_carddetect_work;
        void    __iomem         *base;
        resource_size_t         mapbase;
        spinlock_t              irq_lock; /* Prevent races with irq handler */
@@ -586,7 +584,7 @@ static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
                irq_mask &= ~DTO_ENABLE;
 
        OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
-       OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+       OMAP_HSMMC_WRITE(host->base, ISE, host->use_dma ? irq_mask : 0);
        OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
 }
 
@@ -854,6 +852,55 @@ omap_hsmmc_show_slot_name(struct device *dev, struct device_attribute *attr,
 
 static DEVICE_ATTR(slot_name, S_IRUGO, omap_hsmmc_show_slot_name, NULL);
 
+/* for hosts with 35xx erratum 2.1.1.128 */
+static ssize_t
+omap_hsmmc_show_unsafe_read(struct device *dev, struct device_attribute *attr,
+                       char *buf)
+{
+       struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
+       int val = 0;
+
+       if (!(mmc->caps2 & MMC_CAP2_NO_MULTI_READ)) {
+               val = 1;
+               if (mmc->f_max == OMAP_MMC_MAX_CLOCK)
+                       val = 2;
+       }
+
+       return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t
+omap_hsmmc_set_unsafe_read(struct device *dev, struct device_attribute *attr,
+               const char *buf, size_t count)
+{
+       struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
+       unsigned long val;
+       int ret;
+
+       ret = strict_strtoul(buf, 0, &val);
+       if (ret)
+               return -EINVAL;
+
+       switch (val) {
+       case 0:
+               mmc->caps2 |= MMC_CAP2_NO_MULTI_READ;
+               mmc->f_max = OMAP_MMC_MAX_CLOCK;
+               break;
+       case 1:
+               mmc->caps2 &= ~MMC_CAP2_NO_MULTI_READ;
+               mmc->f_max = 32000000;
+               break;
+       case 2:
+               mmc->caps2 &= ~MMC_CAP2_NO_MULTI_READ;
+               mmc->f_max = OMAP_MMC_MAX_CLOCK;
+               break;
+       }
+
+       return count;
+}
+static DEVICE_ATTR(unsafe_read, S_IWUSR | S_IRUGO,
+       omap_hsmmc_show_unsafe_read, omap_hsmmc_set_unsafe_read);
+
 /*
  * Configure the response type and send the cmd.
  */
@@ -1093,19 +1140,15 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status)
        struct mmc_data *data;
        int end_cmd = 0, end_trans = 0;
 
-       if (!host->req_in_progress) {
-               do {
-                       OMAP_HSMMC_WRITE(host->base, STAT, status);
-                       /* Flush posted write */
-                       status = OMAP_HSMMC_READ(host->base, STAT);
-               } while (status & INT_EN_MASK);
+       if (unlikely(!host->req_in_progress)) {
+               OMAP_HSMMC_WRITE(host->base, STAT, status);
                return;
        }
 
        data = host->data;
        dev_dbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status);
 
-       if (status & ERR) {
+       if (unlikely(status & ERR)) {
                omap_hsmmc_dbg_report_irq(host, status);
                if ((status & CMD_TIMEOUT) ||
                        (status & CMD_CRC)) {
@@ -1280,17 +1323,16 @@ static void omap_hsmmc_protect_card(struct omap_hsmmc_host *host)
 }
 
 /*
- * Work Item to notify the core about card insertion/removal
+ * irq handler to notify the core about card insertion/removal
  */
-static void omap_hsmmc_detect(struct work_struct *work)
+static irqreturn_t omap_hsmmc_detect(int irq, void *dev_id)
 {
-       struct omap_hsmmc_host *host =
-               container_of(work, struct omap_hsmmc_host, mmc_carddetect_work);
+       struct omap_hsmmc_host *host = dev_id;
        struct omap_mmc_slot_data *slot = &mmc_slot(host);
        int carddetect;
 
        if (host->suspended)
-               return;
+               return IRQ_HANDLED;
 
        sysfs_notify(&host->mmc->class_dev.kobj, NULL, "cover_switch");
 
@@ -1305,19 +1347,6 @@ static void omap_hsmmc_detect(struct work_struct *work)
                mmc_detect_change(host->mmc, (HZ * 200) / 1000);
        else
                mmc_detect_change(host->mmc, (HZ * 50) / 1000);
-}
-
-/*
- * ISR for handling card insertion and removal
- */
-static irqreturn_t omap_hsmmc_cd_handler(int irq, void *dev_id)
-{
-       struct omap_hsmmc_host *host = (struct omap_hsmmc_host *)dev_id;
-
-       if (host->suspended)
-               return IRQ_HANDLED;
-       schedule_work(&host->mmc_carddetect_work);
-
        return IRQ_HANDLED;
 }
 
@@ -1420,8 +1449,8 @@ static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host,
 {
        int dma_len;
 
-       if (!next && data->host_cookie &&
-           data->host_cookie != host->next_data.cookie) {
+       if (unlikely(!next && data->host_cookie &&
+           data->host_cookie != host->next_data.cookie)) {
                pr_warning("[%s] invalid cookie: data->host_cookie %d"
                       " host->next_data.cookie %d\n",
                       __func__, data->host_cookie, host->next_data.cookie);
@@ -1441,7 +1470,7 @@ static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host,
        }
 
 
-       if (dma_len == 0)
+       if (unlikely(dma_len == 0))
                return -EINVAL;
 
        if (next) {
@@ -1467,10 +1496,10 @@ static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host,
                struct scatterlist *sgl;
 
                sgl = data->sg + i;
-               if (sgl->length % data->blksz)
+               if (unlikely(sgl->length % data->blksz))
                        return -EINVAL;
        }
-       if ((data->blksz % 4) != 0)
+       if (unlikely((data->blksz % 4) != 0))
                /* REVISIT: The MMC buffer increments only when MSB is written.
                 * Return error for blksz which is non multiple of four.
                 */
@@ -1480,14 +1509,14 @@ static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host,
 
        ret = omap_request_dma(omap_hsmmc_get_dma_sync_dev(host, data),
                               "MMC/SD", omap_hsmmc_dma_cb, host, &dma_ch);
-       if (ret != 0) {
+       if (unlikely(ret != 0)) {
                dev_err(mmc_dev(host->mmc),
                        "%s: omap_request_dma() failed with %d\n",
                        mmc_hostname(host->mmc), ret);
                return ret;
        }
        ret = omap_hsmmc_pre_dma_transfer(host, data, NULL);
-       if (ret)
+       if (unlikely(ret))
                return ret;
 
        host->dma_ch = dma_ch;
@@ -1498,11 +1527,8 @@ static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host,
        return 0;
 }
 
-static void set_data_timeout(struct omap_hsmmc_host *host,
-                            unsigned int timeout_ns,
-                            unsigned int timeout_clks)
+static void set_data_timeout(struct omap_hsmmc_host *host)
 {
-       unsigned int timeout, cycle_ns;
        uint32_t reg, clkd, dto = 0;
 
        reg = OMAP_HSMMC_READ(host->base, SYSCTL);
@@ -1510,25 +1536,8 @@ static void set_data_timeout(struct omap_hsmmc_host *host,
        if (clkd == 0)
                clkd = 1;
 
-       cycle_ns = 1000000000 / (clk_get_rate(host->fclk) / clkd);
-       timeout = timeout_ns / cycle_ns;
-       timeout += 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;
-       }
+    /* Use the maximum timeout value allowed in the standard of 14 or 0xE */
+       dto = 14;
 
        reg &= ~DTO_MASK;
        reg |= dto << DTO_SHIFT;
@@ -1551,13 +1560,13 @@ omap_hsmmc_prepare_data(struct omap_hsmmc_host *host, struct mmc_request *req)
                 * busy signal.
                 */
                if (req->cmd->flags & MMC_RSP_BUSY)
-                       set_data_timeout(host, 100000000U, 0);
+                       set_data_timeout(host);
                return 0;
        }
 
        OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
                                        | (req->data->blocks << 16));
-       set_data_timeout(host, req->data->timeout_ns, req->data->timeout_clks);
+       set_data_timeout(host);
 
        if (host->use_dma) {
                ret = omap_hsmmc_start_dma_transfer(host, req);
@@ -1600,6 +1609,62 @@ static void omap_hsmmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq,
                        mrq->data->host_cookie = 0;
 }
 
+#define BWR (1 << 4)
+#define BRR (1 << 5)
+
+static noinline void omap_hsmmc_request_do_pio(struct mmc_host *mmc,
+       struct mmc_request *req)
+{
+       struct omap_hsmmc_host *host = mmc_priv(mmc);
+       u32 *data = sg_virt(req->data->sg);
+       u32 len = req->data->sg->length;
+       int stat;
+       int i;
+
+       for (i = 0; i < 10000000; i++) {
+               stat = OMAP_HSMMC_READ(host->base, STAT);
+               if (stat == 0)
+                       continue;
+
+               //dev_err(mmc_dev(host->mmc), "stat %x, l %d\n", stat, i);
+
+               if (stat & (DATA_TIMEOUT | DATA_CRC))
+                       omap_hsmmc_reset_controller_fsm(host, SRD);
+
+               if (stat & ERR) {
+                       req->cmd->error =
+                       req->data->error = -EINVAL; // ?
+                       omap_hsmmc_xfer_done(host, host->data);
+                       return;
+               }
+       
+               if (req->data->flags & MMC_DATA_WRITE) {
+                       while (len > 0 && (stat & BWR)) {
+                               OMAP_HSMMC_WRITE(host->base, DATA, *data++);
+                               len -= 4;
+                       }
+               } else {
+                       while (len > 0 && (stat & BRR)) {
+                               *data++ = OMAP_HSMMC_READ(host->base, DATA);
+                               len -= 4;
+                       }
+               }
+
+               if ((stat & CC) && host->cmd)
+                       omap_hsmmc_cmd_done(host, host->cmd);
+               if ((stat & TC) && host->mrq) {
+                       omap_hsmmc_xfer_done(host, host->data);
+                       break;
+               }
+       }
+
+       if (len > 0) {
+               req->cmd->error =
+               req->data->error = -ETIMEDOUT;
+               omap_hsmmc_xfer_done(host, req->data);
+       }
+}
+
 /*
  * Request function. for read/write operation
  */
@@ -1610,7 +1675,7 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req)
 
        BUG_ON(host->req_in_progress);
        BUG_ON(host->dma_ch != -1);
-       if (host->protect_card) {
+       if (unlikely(host->protect_card)) {
                if (host->reqs_blocked < 3) {
                        /*
                         * Ensure the controller is left in a consistent
@@ -1629,10 +1694,19 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req)
                return;
        } else if (host->reqs_blocked)
                host->reqs_blocked = 0;
+
+       /* pandora wifi hack.. */
+       if (host->id == OMAP_MMC3_DEVID && req->data != NULL
+           && req->data->sg_len == 1 && req->data->sg->length <= 16) {
+               host->use_dma = 0;
+       } else {
+               host->use_dma = 1;
+       }
+
        WARN_ON(host->mrq != NULL);
        host->mrq = req;
        err = omap_hsmmc_prepare_data(host, req);
-       if (err) {
+       if (unlikely(err)) {
                req->cmd->error = err;
                if (req->data)
                        req->data->error = err;
@@ -1642,6 +1716,9 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req)
        }
 
        omap_hsmmc_start_command(host, req->cmd, req->data);
+
+       if (host->use_dma == 0)
+               omap_hsmmc_request_do_pio(mmc, req);
 }
 
 /* Routine to configure clock values. Exposed API to core */
@@ -1919,7 +1996,6 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev)
        host->next_data.cookie = 1;
 
        platform_set_drvdata(pdev, host);
-       INIT_WORK(&host->mmc_carddetect_work, omap_hsmmc_detect);
 
        mmc->ops        = &omap_hsmmc_ops;
 
@@ -1945,6 +2021,7 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev)
        omap_hsmmc_context_save(host);
 
        mmc->caps |= MMC_CAP_DISABLE;
+
        if (host->pdata->controller_flags & OMAP_HSMMC_BROKEN_MULTIBLOCK_READ) {
                dev_info(&pdev->dev, "multiblock reads disabled due to 35xx erratum 2.1.1.128; MMC read performance may suffer\n");
                mmc->caps2 |= MMC_CAP2_NO_MULTI_READ;
@@ -2047,10 +2124,11 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev)
 
        /* Request IRQ for card detect */
        if ((mmc_slot(host).card_detect_irq)) {
-               ret = request_irq(mmc_slot(host).card_detect_irq,
-                                 omap_hsmmc_cd_handler,
-                                 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
-                                 mmc_hostname(mmc), host);
+               ret = request_threaded_irq(mmc_slot(host).card_detect_irq,
+                                          NULL,
+                                          omap_hsmmc_detect,
+                                          IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+                                          mmc_hostname(mmc), host);
                if (ret) {
                        dev_dbg(mmc_dev(host->mmc),
                                "Unable to grab MMC CD IRQ\n");
@@ -2078,6 +2156,15 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev)
                        goto err_slot_name;
        }
 
+       if (host->pdata->controller_flags & OMAP_HSMMC_BROKEN_MULTIBLOCK_READ) {
+               ret = device_create_file(&mmc->class_dev, &dev_attr_unsafe_read);
+
+               /* MMC_CAP2_NO_MULTI_READ makes it crawl, try a different workaround */
+               mmc->caps2 &= ~MMC_CAP2_NO_MULTI_READ;
+               mmc->max_segs = 1;
+               mmc->f_max = 32000000;
+       }
+
        omap_hsmmc_debugfs(mmc);
        pm_runtime_mark_last_busy(host->dev);
        pm_runtime_put_autosuspend(host->dev);
@@ -2129,7 +2216,6 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
                free_irq(host->irq, host);
                if (mmc_slot(host).card_detect_irq)
                        free_irq(mmc_slot(host).card_detect_irq, host);
-               flush_work_sync(&host->mmc_carddetect_work);
 
                pm_runtime_put_sync(host->dev);
                pm_runtime_disable(host->dev);
@@ -2176,7 +2262,6 @@ static int omap_hsmmc_suspend(struct device *dev)
                                return ret;
                        }
                }
-               cancel_work_sync(&host->mmc_carddetect_work);
                ret = mmc_suspend_host(host->mmc);
 
                if (ret == 0) {
@@ -2188,9 +2273,7 @@ static int omap_hsmmc_suspend(struct device *dev)
                } else {
                        host->suspended = 0;
                        if (host->pdata->resume) {
-                               ret = host->pdata->resume(&pdev->dev,
-                                                         host->slot_id);
-                               if (ret)
+                               if (host->pdata->resume(&pdev->dev, host->slot_id))
                                        dev_dbg(mmc_dev(host->mmc),
                                                "Unmask interrupt failed\n");
                        }