omap_hsmmc: avoid requesting dma repeatedly
[pandora-kernel.git] / drivers / mmc / host / omap_hsmmc.c
index 364309c..348f577 100644 (file)
@@ -175,6 +175,7 @@ struct omap_hsmmc_host {
        int                     suspended;
        int                     irq;
        int                     use_dma, dma_ch;
+       int                     dma_ch_tx, dma_ch_rx;
        int                     dma_line_tx, dma_line_rx;
        int                     slot_id;
        int                     got_dbclk;
@@ -584,7 +585,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);
 }
 
@@ -1056,7 +1057,6 @@ static void omap_hsmmc_dma_cleanup(struct omap_hsmmc_host *host, int errno)
                dma_unmap_sg(mmc_dev(host->mmc), host->data->sg,
                        host->data->sg_len,
                        omap_hsmmc_get_dma_dir(host, host->data));
-               omap_free_dma(dma_ch);
                host->data->host_cookie = 0;
        }
        host->data = NULL;
@@ -1140,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)) {
@@ -1366,23 +1362,39 @@ static int omap_hsmmc_get_dma_sync_dev(struct omap_hsmmc_host *host,
        return sync_dev;
 }
 
+static void omap_hsmmc_config_dma_params_once(struct omap_hsmmc_host *host,
+                                             struct mmc_data *data,
+                                             int 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_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_16);
+               omap_set_dma_src_data_pack(dma_ch, 1);
+       } else {
+               omap_set_dma_src_params(dma_ch, 0, OMAP_DMA_AMODE_CONSTANT,
+                       (host->mapbase + OMAP_HSMMC_DATA), 0, 0);
+               omap_set_dma_dest_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_16);
+               omap_set_dma_dest_data_pack(dma_ch, 1);
+               omap_set_dma_write_mode(dma_ch, OMAP_DMA_WRITE_LAST_NON_POSTED);
+       }
+}
+
 static void omap_hsmmc_config_dma_params(struct omap_hsmmc_host *host,
                                       struct mmc_data *data,
                                       struct scatterlist *sgl)
 {
-       int blksz, nblk, dma_ch;
+       int blksz, nblk, dma_ch, sync;
 
        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);
+               sync = OMAP_DMA_DST_SYNC_PREFETCH;
        } 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);
+               sync = OMAP_DMA_SRC_SYNC;
        }
 
        blksz = host->data->blksz;
@@ -1390,8 +1402,7 @@ static void omap_hsmmc_config_dma_params(struct omap_hsmmc_host *host,
 
        omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32,
                        blksz / 4, nblk, OMAP_DMA_SYNC_FRAME,
-                       omap_hsmmc_get_dma_sync_dev(host, data),
-                       !(data->flags & MMC_DATA_WRITE));
+                       omap_hsmmc_get_dma_sync_dev(host, data), sync);
 
        omap_start_dma(dma_ch);
 }
@@ -1403,7 +1414,7 @@ static void omap_hsmmc_dma_cb(int lch, u16 ch_status, void *cb_data)
 {
        struct omap_hsmmc_host *host = cb_data;
        struct mmc_data *data;
-       int dma_ch, req_in_progress;
+       int req_in_progress;
 
        if (!(ch_status & OMAP_DMA_BLOCK_IRQ)) {
                dev_warn(mmc_dev(host->mmc), "unexpected dma status %x\n",
@@ -1432,12 +1443,9 @@ static void omap_hsmmc_dma_cb(int lch, u16 ch_status, void *cb_data)
                             omap_hsmmc_get_dma_dir(host, data));
 
        req_in_progress = host->req_in_progress;
-       dma_ch = host->dma_ch;
        host->dma_ch = -1;
        spin_unlock(&host->irq_lock);
 
-       omap_free_dma(dma_ch);
-
        /* If DMA has finished after TC, complete the request */
        if (!req_in_progress) {
                struct mmc_request *mrq = host->mrq;
@@ -1453,8 +1461,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);
@@ -1474,7 +1482,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) {
@@ -1500,10 +1508,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.
                 */
@@ -1511,16 +1519,31 @@ static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host,
 
        BUG_ON(host->dma_ch != -1);
 
-       ret = omap_request_dma(omap_hsmmc_get_dma_sync_dev(host, data),
-                              "MMC/SD", omap_hsmmc_dma_cb, host, &dma_ch);
-       if (ret != 0) {
-               dev_err(mmc_dev(host->mmc),
-                       "%s: omap_request_dma() failed with %d\n",
-                       mmc_hostname(host->mmc), ret);
-               return ret;
+       if (data->flags & MMC_DATA_WRITE)
+               dma_ch = host->dma_ch_tx;
+       else
+               dma_ch = host->dma_ch_rx;
+
+       if (dma_ch == -1) {
+               ret = omap_request_dma(omap_hsmmc_get_dma_sync_dev(host, data),
+                                      "MMC/SD", omap_hsmmc_dma_cb, host, &dma_ch);
+               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;
+               }
+
+               omap_hsmmc_config_dma_params_once(host, data, dma_ch);
+
+               if (data->flags & MMC_DATA_WRITE)
+                       host->dma_ch_tx = dma_ch;
+               else
+                       host->dma_ch_rx = dma_ch;
        }
+
        ret = omap_hsmmc_pre_dma_transfer(host, data, NULL);
-       if (ret)
+       if (unlikely(ret))
                return ret;
 
        host->dma_ch = dma_ch;
@@ -1531,21 +1554,15 @@ static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host,
        return 0;
 }
 
-static void set_data_timeout(struct omap_hsmmc_host *host)
+/* pandora wifi small transfer hack */
+static int check_mmc3_dma_hack(struct omap_hsmmc_host *host,
+                              struct mmc_request *req)
 {
-       uint32_t reg, clkd, dto = 0;
-
-       reg = OMAP_HSMMC_READ(host->base, SYSCTL);
-       clkd = (reg & CLKD_MASK) >> CLKD_SHIFT;
-       if (clkd == 0)
-               clkd = 1;
-
-    /* Use the maximum timeout value allowed in the standard of 14 or 0xE */
-       dto = 14;
-
-       reg &= ~DTO_MASK;
-       reg |= dto << DTO_SHIFT;
-       OMAP_HSMMC_WRITE(host->base, SYSCTL, reg);
+       if (req->data != NULL && req->data->sg_len == 1
+           && req->data->sg->length <= 16)
+               return 0;
+       else
+               return 1;
 }
 
 /*
@@ -1559,18 +1576,11 @@ omap_hsmmc_prepare_data(struct omap_hsmmc_host *host, struct mmc_request *req)
 
        if (req->data == NULL) {
                OMAP_HSMMC_WRITE(host->base, BLK, 0);
-               /*
-                * Set an arbitrary 100ms data timeout for commands with
-                * busy signal.
-                */
-               if (req->cmd->flags & MMC_RSP_BUSY)
-                       set_data_timeout(host);
                return 0;
        }
 
        OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
                                        | (req->data->blocks << 16));
-       set_data_timeout(host);
 
        if (host->use_dma) {
                ret = omap_hsmmc_start_dma_transfer(host, req);
@@ -1601,18 +1611,77 @@ static void omap_hsmmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq,
                               bool is_first_req)
 {
        struct omap_hsmmc_host *host = mmc_priv(mmc);
+       int use_dma = host->use_dma;
 
        if (mrq->data->host_cookie) {
                mrq->data->host_cookie = 0;
                return ;
        }
 
-       if (host->use_dma)
+       if (host->id == OMAP_MMC3_DEVID)
+               use_dma = check_mmc3_dma_hack(host, mrq);
+       if (use_dma)
                if (omap_hsmmc_pre_dma_transfer(host, mrq->data,
                                                &host->next_data))
                        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
  */
@@ -1623,7 +1692,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
@@ -1642,10 +1711,15 @@ 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)
+               host->use_dma = check_mmc3_dma_hack(host, req);
+
        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;
@@ -1655,6 +1729,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 */
@@ -1923,6 +2000,8 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev)
        host->use_dma   = 1;
        host->dev->dma_mask = &pdata->dma_mask;
        host->dma_ch    = -1;
+       host->dma_ch_tx = -1;
+       host->dma_ch_rx = -1;
        host->irq       = irq;
        host->id        = pdev->id;
        host->slot_id   = 0;
@@ -2209,9 +2288,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");
                        }
@@ -2269,9 +2346,19 @@ static int omap_hsmmc_resume(struct device *dev)
 static int omap_hsmmc_runtime_suspend(struct device *dev)
 {
        struct omap_hsmmc_host *host;
+       int dma_ch;
 
        host = platform_get_drvdata(to_platform_device(dev));
        omap_hsmmc_context_save(host);
+
+       dma_ch = xchg(&host->dma_ch_tx, -1);
+       if (dma_ch != -1)
+               omap_free_dma(dma_ch);
+
+       dma_ch = xchg(&host->dma_ch_rx, -1);
+       if (dma_ch != -1)
+               omap_free_dma(dma_ch);
+
        dev_dbg(mmc_dev(host->mmc), "disabled\n");
 
        return 0;