]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/mmc/host/msm_sdcc.c
Merge tag 'v2.6.38' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / drivers / mmc / host / msm_sdcc.c
index 1290d14c5839572a27f6354f3ab227ec6ff79c93..153ab977a013638a7d1a187d3bdb73a33c8d2345 100644 (file)
@@ -44,6 +44,7 @@
 #include <mach/mmc.h>
 #include <mach/msm_iomap.h>
 #include <mach/dma.h>
+#include <mach/clk.h>
 
 #include "msm_sdcc.h"
 
@@ -126,6 +127,40 @@ static void
 msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd,
                      u32 c);
 
+static void msmsdcc_reset_and_restore(struct msmsdcc_host *host)
+{
+       u32     mci_clk = 0;
+       u32     mci_mask0 = 0;
+       int     ret = 0;
+
+       /* Save the controller state */
+       mci_clk = readl(host->base + MMCICLOCK);
+       mci_mask0 = readl(host->base + MMCIMASK0);
+
+       /* Reset the controller */
+       ret = clk_reset(host->clk, CLK_RESET_ASSERT);
+       if (ret)
+               pr_err("%s: Clock assert failed at %u Hz with err %d\n",
+                               mmc_hostname(host->mmc), host->clk_rate, ret);
+
+       ret = clk_reset(host->clk, CLK_RESET_DEASSERT);
+       if (ret)
+               pr_err("%s: Clock deassert failed at %u Hz with err %d\n",
+                               mmc_hostname(host->mmc), host->clk_rate, ret);
+
+       pr_info("%s: Controller has been re-initialiazed\n",
+                       mmc_hostname(host->mmc));
+
+       /* Restore the contoller state */
+       writel(host->pwr, host->base + MMCIPOWER);
+       writel(mci_clk, host->base + MMCICLOCK);
+       writel(mci_mask0, host->base + MMCIMASK0);
+       ret = clk_set_rate(host->clk, host->clk_rate);
+       if (ret)
+               pr_err("%s: Failed to set clk rate %u Hz (%d)\n",
+                               mmc_hostname(host->mmc), host->clk_rate, ret);
+}
+
 static void
 msmsdcc_request_end(struct msmsdcc_host *host, struct mmc_request *mrq)
 {
@@ -155,7 +190,7 @@ static void
 msmsdcc_stop_data(struct msmsdcc_host *host)
 {
        host->curr.data = NULL;
-       host->curr.got_dataend = host->curr.got_datablkend = 0;
+       host->curr.got_dataend = 0;
 }
 
 uint32_t msmsdcc_fifo_addr(struct msmsdcc_host *host)
@@ -189,42 +224,42 @@ msmsdcc_dma_exec_func(struct msm_dmov_cmd *cmd)
 }
 
 static void
-msmsdcc_dma_complete_func(struct msm_dmov_cmd *cmd,
-                         unsigned int result,
-                         struct msm_dmov_errdata *err)
+msmsdcc_dma_complete_tlet(unsigned long data)
 {
-       struct msmsdcc_dma_data *dma_data =
-               container_of(cmd, struct msmsdcc_dma_data, hdr);
-       struct msmsdcc_host     *host = dma_data->host;
+       struct msmsdcc_host *host = (struct msmsdcc_host *)data;
        unsigned long           flags;
        struct mmc_request      *mrq;
+       struct msm_dmov_errdata err;
 
        spin_lock_irqsave(&host->lock, flags);
        host->dma.active = 0;
 
+       err = host->dma.err;
        mrq = host->curr.mrq;
        BUG_ON(!mrq);
        WARN_ON(!mrq->data);
 
-       if (!(result & DMOV_RSLT_VALID)) {
+       if (!(host->dma.result & DMOV_RSLT_VALID)) {
                pr_err("msmsdcc: Invalid DataMover result\n");
                goto out;
        }
 
-       if (result & DMOV_RSLT_DONE) {
+       if (host->dma.result & DMOV_RSLT_DONE) {
                host->curr.data_xfered = host->curr.xfer_size;
        } else {
                /* Error or flush  */
-               if (result & DMOV_RSLT_ERROR)
+               if (host->dma.result & DMOV_RSLT_ERROR)
                        pr_err("%s: DMA error (0x%.8x)\n",
-                              mmc_hostname(host->mmc), result);
-               if (result & DMOV_RSLT_FLUSH)
+                              mmc_hostname(host->mmc), host->dma.result);
+               if (host->dma.result & DMOV_RSLT_FLUSH)
                        pr_err("%s: DMA channel flushed (0x%.8x)\n",
-                              mmc_hostname(host->mmc), result);
-               if (err)
-                       pr_err("Flush data: %.8x %.8x %.8x %.8x %.8x %.8x\n",
-                              err->flush[0], err->flush[1], err->flush[2],
-                              err->flush[3], err->flush[4], err->flush[5]);
+                              mmc_hostname(host->mmc), host->dma.result);
+
+               pr_err("Flush data: %.8x %.8x %.8x %.8x %.8x %.8x\n",
+                      err.flush[0], err.flush[1], err.flush[2],
+                      err.flush[3], err.flush[4], err.flush[5]);
+
+               msmsdcc_reset_and_restore(host);
                if (!mrq->data->error)
                        mrq->data->error = -EIO;
        }
@@ -242,8 +277,7 @@ msmsdcc_dma_complete_func(struct msm_dmov_cmd *cmd,
        host->dma.sg = NULL;
        host->dma.busy = 0;
 
-       if ((host->curr.got_dataend && host->curr.got_datablkend)
-            || mrq->data->error) {
+       if (host->curr.got_dataend || mrq->data->error) {
 
                /*
                 * If we've already gotten our DATAEND / DATABLKEND
@@ -273,6 +307,22 @@ out:
        return;
 }
 
+static void
+msmsdcc_dma_complete_func(struct msm_dmov_cmd *cmd,
+                         unsigned int result,
+                         struct msm_dmov_errdata *err)
+{
+       struct msmsdcc_dma_data *dma_data =
+               container_of(cmd, struct msmsdcc_dma_data, hdr);
+       struct msmsdcc_host *host = dma_data->host;
+
+       dma_data->result = result;
+       if (err)
+               memcpy(&dma_data->err, err, sizeof(struct msm_dmov_errdata));
+
+       tasklet_schedule(&host->dma_tlet);
+}
+
 static int validate_dma(struct msmsdcc_host *host, struct mmc_data *data)
 {
        if (host->dma.channel == -1)
@@ -333,14 +383,30 @@ static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data)
        host->curr.user_pages = 0;
 
        box = &nc->cmd[0];
-       for (i = 0; i < host->dma.num_ents; i++) {
-               box->cmd = CMD_MODE_BOX;
 
-       /* Initialize sg dma address */
-       sg->dma_address = page_to_dma(mmc_dev(host->mmc), sg_page(sg))
-                               + sg->offset;
+       /* location of command block must be 64 bit aligned */
+       BUG_ON(host->dma.cmd_busaddr & 0x07);
+
+       nc->cmdptr = (host->dma.cmd_busaddr >> 3) | CMD_PTR_LP;
+       host->dma.hdr.cmdptr = DMOV_CMD_PTR_LIST |
+                              DMOV_CMD_ADDR(host->dma.cmdptr_busaddr);
+       host->dma.hdr.complete_func = msmsdcc_dma_complete_func;
 
-       if (i == (host->dma.num_ents - 1))
+       n = dma_map_sg(mmc_dev(host->mmc), host->dma.sg,
+                       host->dma.num_ents, host->dma.dir);
+       if (n == 0) {
+               printk(KERN_ERR "%s: Unable to map in all sg elements\n",
+                       mmc_hostname(host->mmc));
+               host->dma.sg = NULL;
+               host->dma.num_ents = 0;
+               return -ENOMEM;
+       }
+
+       for_each_sg(host->dma.sg, sg, n, i) {
+
+               box->cmd = CMD_MODE_BOX;
+
+               if (i == n - 1)
                        box->cmd |= CMD_LC;
                rows = (sg_dma_len(sg) % MCI_FIFOSIZE) ?
                        (sg_dma_len(sg) / MCI_FIFOSIZE) + 1 :
@@ -368,27 +434,6 @@ static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data)
                        box->cmd |= CMD_DST_CRCI(crci);
                }
                box++;
-               sg++;
-       }
-
-       /* location of command block must be 64 bit aligned */
-       BUG_ON(host->dma.cmd_busaddr & 0x07);
-
-       nc->cmdptr = (host->dma.cmd_busaddr >> 3) | CMD_PTR_LP;
-       host->dma.hdr.cmdptr = DMOV_CMD_PTR_LIST |
-                              DMOV_CMD_ADDR(host->dma.cmdptr_busaddr);
-       host->dma.hdr.complete_func = msmsdcc_dma_complete_func;
-
-       n = dma_map_sg(mmc_dev(host->mmc), host->dma.sg,
-                       host->dma.num_ents, host->dma.dir);
-/* dsb inside dma_map_sg will write nc out to mem as well */
-
-       if (n != host->dma.num_ents) {
-               printk(KERN_ERR "%s: Unable to map in all sg elements\n",
-                       mmc_hostname(host->mmc));
-               host->dma.sg = NULL;
-               host->dma.num_ents = 0;
-               return -ENOMEM;
        }
 
        return 0;
@@ -424,6 +469,11 @@ msmsdcc_start_command_deferred(struct msmsdcc_host *host,
              (cmd->opcode == 53))
                *c |= MCI_CSPM_DATCMD;
 
+       if (host->prog_scan && (cmd->opcode == 12)) {
+               *c |= MCI_CPSM_PROGENA;
+               host->prog_enable = true;
+       }
+
        if (cmd == cmd->mrq->stop)
                *c |= MCI_CSPM_MCIABORT;
 
@@ -450,7 +500,6 @@ msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data,
        host->curr.xfer_remain = host->curr.xfer_size;
        host->curr.data_xfered = 0;
        host->curr.got_dataend = 0;
-       host->curr.got_datablkend = 0;
 
        memset(&host->pio, 0, sizeof(host->pio));
 
@@ -494,6 +543,8 @@ msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data,
                        host->cmd_c = c;
                }
                msm_dmov_enqueue_cmd(host->dma.channel, &host->dma.hdr);
+               if (data->flags & MMC_DATA_WRITE)
+                       host->prog_scan = true;
        } else {
                msmsdcc_writel(host, timeout, MMCIDATATIMER);
 
@@ -555,6 +606,9 @@ msmsdcc_pio_read(struct msmsdcc_host *host, char *buffer, unsigned int remain)
        uint32_t        *ptr = (uint32_t *) buffer;
        int             count = 0;
 
+       if (remain % 4)
+               remain = ((remain >> 2) + 1) << 2;
+
        while (msmsdcc_readl(host, MMCISTATUS) & MCI_RXDATAAVLBL) {
                *ptr = msmsdcc_readl(host, MMCIFIFO + (count % MCI_FIFOSIZE));
                ptr++;
@@ -575,13 +629,14 @@ msmsdcc_pio_write(struct msmsdcc_host *host, char *buffer,
        char *ptr = buffer;
 
        do {
-               unsigned int count, maxcnt;
+               unsigned int count, maxcnt, sz;
 
                maxcnt = status & MCI_TXFIFOEMPTY ? MCI_FIFOSIZE :
                                                    MCI_FIFOHALFSIZE;
                count = min(remain, maxcnt);
 
-               writesl(base + MMCIFIFO, ptr, count >> 2);
+               sz = count % 4 ? (count >> 2) + 1 : (count >> 2);
+               writesl(base + MMCIFIFO, ptr, sz);
                ptr += count;
                remain -= count;
 
@@ -702,10 +757,26 @@ static void msmsdcc_do_cmdirq(struct msmsdcc_host *host, uint32_t status)
                        msm_dmov_stop_cmd(host->dma.channel,
                                          &host->dma.hdr, 0);
                else if (host->curr.data) { /* Non DMA */
+                       msmsdcc_reset_and_restore(host);
                        msmsdcc_stop_data(host);
                        msmsdcc_request_end(host, cmd->mrq);
-               } else /* host->data == NULL */
-                       msmsdcc_request_end(host, cmd->mrq);
+               } else { /* host->data == NULL */
+                       if (!cmd->error && host->prog_enable) {
+                               if (status & MCI_PROGDONE) {
+                                       host->prog_scan = false;
+                                       host->prog_enable = false;
+                                       msmsdcc_request_end(host, cmd->mrq);
+                               } else {
+                                       host->curr.cmd = cmd;
+                               }
+                       } else {
+                               if (host->prog_enable) {
+                                       host->prog_scan = false;
+                                       host->prog_enable = false;
+                               }
+                               msmsdcc_request_end(host, cmd->mrq);
+                       }
+               }
        } else if (cmd->data)
                if (!(cmd->data->flags & MMC_DATA_READ))
                        msmsdcc_start_data(host, cmd->data,
@@ -719,7 +790,7 @@ msmsdcc_handle_irq_data(struct msmsdcc_host *host, u32 status,
        struct mmc_data *data = host->curr.data;
 
        if (status & (MCI_CMDSENT | MCI_CMDRESPEND | MCI_CMDCRCFAIL |
-                     MCI_CMDTIMEOUT) && host->curr.cmd) {
+                       MCI_CMDTIMEOUT | MCI_PROGDONE) && host->curr.cmd) {
                msmsdcc_do_cmdirq(host, status);
        }
 
@@ -735,6 +806,7 @@ msmsdcc_handle_irq_data(struct msmsdcc_host *host, u32 status,
                        msm_dmov_stop_cmd(host->dma.channel,
                                          &host->dma.hdr, 0);
                else {
+                       msmsdcc_reset_and_restore(host);
                        if (host->curr.data)
                                msmsdcc_stop_data(host);
                        if (!data->stop)
@@ -748,14 +820,10 @@ msmsdcc_handle_irq_data(struct msmsdcc_host *host, u32 status,
        if (!host->curr.got_dataend && (status & MCI_DATAEND))
                host->curr.got_dataend = 1;
 
-       if (!host->curr.got_datablkend && (status & MCI_DATABLOCKEND))
-               host->curr.got_datablkend = 1;
-
        /*
         * If DMA is still in progress, we complete via the completion handler
         */
-       if (host->curr.got_dataend && host->curr.got_datablkend &&
-           !host->dma.busy) {
+       if (host->curr.got_dataend && !host->dma.busy) {
                /*
                 * There appears to be an issue in the controller where
                 * if you request a small block transfer (< fifo size),
@@ -792,8 +860,7 @@ msmsdcc_irq(int irq, void *dev_id)
 
        do {
                status = msmsdcc_readl(host, MMCISTATUS);
-               status &= (msmsdcc_readl(host, MMCIMASK0) |
-                                             MCI_DATABLOCKENDMASK);
+               status &= msmsdcc_readl(host, MMCIMASK0);
                msmsdcc_writel(host, status, MMCICLEAR);
 
                if (status & MCI_SDIOINTR)
@@ -1118,6 +1185,9 @@ msmsdcc_probe(struct platform_device *pdev)
        host->dmares = dmares;
        spin_lock_init(&host->lock);
 
+       tasklet_init(&host->dma_tlet, msmsdcc_dma_complete_tlet,
+                       (unsigned long)host);
+
        /*
         * Setup DMA
         */
@@ -1256,9 +1326,6 @@ msmsdcc_probe(struct platform_device *pdev)
        if (host->timer.function)
                pr_info("%s: Polling status mode enabled\n", mmc_hostname(mmc));
 
-#if BUSCLK_PWRSAVE
-       msmsdcc_disable_clocks(host, 1);
-#endif
        return 0;
  cmd_irq_free:
        free_irq(cmd_irqres->start, host);