Merge branch 'master' of git://git.denx.de/u-boot-usb
[oweals/u-boot.git] / drivers / mmc / tegra2_mmc.c
index 159cef1e0cc73a630bb734c1326e9843799a7e70..fb8a57d162fcb2bf6ced82e1ac035f909a105895 100644 (file)
@@ -21,6 +21,7 @@
 
 #include <common.h>
 #include <mmc.h>
+#include <asm/gpio.h>
 #include <asm/io.h>
 #include <asm/arch/clk_rst.h>
 #include <asm/arch/clock.h>
@@ -113,37 +114,35 @@ static void mmc_set_transfer_mode(struct mmc_host *host, struct mmc_data *data)
        if (data->flags & MMC_DATA_READ)
                mode |= TEGRA_MMC_TRNMOD_DATA_XFER_DIR_SEL_READ;
 
+       if (data->flags & MMC_DATA_WRITE) {
+               if ((uintptr_t)data->src & (ARCH_DMA_MINALIGN - 1))
+                       printf("Warning: unaligned write to %p may fail\n",
+                              data->src);
+               flush_dcache_range((ulong)data->src, (ulong)data->src +
+                       data->blocks * data->blocksize);
+       }
+
        writew(mode, &host->reg->trnmod);
 }
 
-static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
-                       struct mmc_data *data)
+static int mmc_wait_inhibit(struct mmc_host *host,
+                           struct mmc_cmd *cmd,
+                           struct mmc_data *data,
+                           unsigned int timeout)
 {
-       struct mmc_host *host = (struct mmc_host *)mmc->priv;
-       int flags, i;
-       unsigned int timeout;
-       unsigned int mask;
-       unsigned int retry = 0x100000;
-       debug(" mmc_send_cmd called\n");
-
-       /* Wait max 10 ms */
-       timeout = 10;
-
        /*
         * PRNSTS
-        * CMDINHDAT[1] : Command Inhibit (DAT)
-        * CMDINHCMD[0] : Command Inhibit (CMD)
+        * CMDINHDAT[1] : Command Inhibit (DAT)
+        * CMDINHCMD[0] : Command Inhibit (CMD)
         */
-       mask = TEGRA_MMC_PRNSTS_CMD_INHIBIT_CMD;
-       if ((data != NULL) || (cmd->resp_type & MMC_RSP_BUSY))
-               mask |= TEGRA_MMC_PRNSTS_CMD_INHIBIT_DAT;
+       unsigned int mask = TEGRA_MMC_PRNSTS_CMD_INHIBIT_CMD;
 
        /*
         * We shouldn't wait for data inhibit for stop commands, even
         * though they might use busy signaling
         */
-       if (data)
-               mask &= ~TEGRA_MMC_PRNSTS_CMD_INHIBIT_DAT;
+       if ((data == NULL) && (cmd->resp_type & MMC_RSP_BUSY))
+               mask |= TEGRA_MMC_PRNSTS_CMD_INHIBIT_DAT;
 
        while (readl(&host->reg->prnsts) & mask) {
                if (timeout == 0) {
@@ -154,6 +153,24 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
                udelay(1000);
        }
 
+       return 0;
+}
+
+static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
+                       struct mmc_data *data)
+{
+       struct mmc_host *host = (struct mmc_host *)mmc->priv;
+       int flags, i;
+       int result;
+       unsigned int mask = 0;
+       unsigned int retry = 0x100000;
+       debug(" mmc_send_cmd called\n");
+
+       result = mmc_wait_inhibit(host, cmd, data, 10 /* ms */);
+
+       if (result < 0)
+               return result;
+
        if (data)
                mmc_prepare_data(host, data);
 
@@ -210,16 +227,19 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
 
        if (i == retry) {
                printf("%s: waiting for status update\n", __func__);
+               writel(mask, &host->reg->norintsts);
                return TIMEOUT;
        }
 
        if (mask & TEGRA_MMC_NORINTSTS_CMD_TIMEOUT) {
                /* Timeout Error */
                debug("timeout: %08x cmd %d\n", mask, cmd->cmdidx);
+               writel(mask, &host->reg->norintsts);
                return TIMEOUT;
        } else if (mask & TEGRA_MMC_NORINTSTS_ERR_INTERRUPT) {
                /* Error Interrupt */
                debug("error: %08x cmd %d\n", mask, cmd->cmdidx);
+               writel(mask, &host->reg->norintsts);
                return -1;
        }
 
@@ -248,6 +268,7 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
 
                        if (i == retry) {
                                printf("%s: card is still busy\n", __func__);
+                               writel(mask, &host->reg->norintsts);
                                return TIMEOUT;
                        }
 
@@ -260,6 +281,8 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
        }
 
        if (data) {
+               unsigned long   start = get_timer(0);
+
                while (1) {
                        mask = readl(&host->reg->norintsts);
 
@@ -284,9 +307,29 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
                                /* Transfer Complete */
                                debug("r/w is done\n");
                                break;
+                       } else if (get_timer(start) > 2000UL) {
+                               writel(mask, &host->reg->norintsts);
+                               printf("%s: MMC Timeout\n"
+                                      "    Interrupt status        0x%08x\n"
+                                      "    Interrupt status enable 0x%08x\n"
+                                      "    Interrupt signal enable 0x%08x\n"
+                                      "    Present status          0x%08x\n",
+                                      __func__, mask,
+                                      readl(&host->reg->norintstsen),
+                                      readl(&host->reg->norintsigen),
+                                      readl(&host->reg->prnsts));
+                               return -1;
                        }
                }
                writel(mask, &host->reg->norintsts);
+               if (data->flags & MMC_DATA_READ) {
+                       if ((uintptr_t)data->dest & (ARCH_DMA_MINALIGN - 1))
+                               printf("Warning: unaligned read from %p "
+                                       "may fail\n", data->dest);
+                       invalidate_dcache_range((ulong)data->dest,
+                               (ulong)data->dest +
+                                       data->blocks * data->blocksize);
+               }
        }
 
        udelay(1000);
@@ -451,20 +494,49 @@ static int mmc_core_init(struct mmc *mmc)
        return 0;
 }
 
-static int tegra2_mmc_initialize(int dev_index, int bus_width)
+int tegra2_mmc_getcd(struct mmc *mmc)
+{
+       struct mmc_host *host = (struct mmc_host *)mmc->priv;
+
+       debug("tegra2_mmc_getcd called\n");
+
+       if (host->cd_gpio >= 0)
+               return !gpio_get_value(host->cd_gpio);
+
+       return 1;
+}
+
+int tegra2_mmc_init(int dev_index, int bus_width, int pwr_gpio, int cd_gpio)
 {
        struct mmc_host *host;
+       char gpusage[12]; /* "SD/MMCn PWR" or "SD/MMCn CD" */
        struct mmc *mmc;
 
-       debug(" mmc_initialize called\n");
+       debug(" tegra2_mmc_init: index %d, bus width %d "
+               "pwr_gpio %d cd_gpio %d\n",
+               dev_index, bus_width, pwr_gpio, cd_gpio);
 
        host = &mmc_host[dev_index];
 
        host->clock = 0;
+       host->pwr_gpio = pwr_gpio;
+       host->cd_gpio = cd_gpio;
        tegra2_get_setup(host, dev_index);
 
        clock_start_periph_pll(host->mmc_id, CLOCK_ID_PERIPH, 20000000);
 
+       if (host->pwr_gpio >= 0) {
+               sprintf(gpusage, "SD/MMC%d PWR", dev_index);
+               gpio_request(host->pwr_gpio, gpusage);
+               gpio_direction_output(host->pwr_gpio, 1);
+       }
+
+       if (host->cd_gpio >= 0) {
+               sprintf(gpusage, "SD/MMC%d CD", dev_index);
+               gpio_request(host->cd_gpio, gpusage);
+               gpio_direction_input(host->cd_gpio);
+       }
+
        mmc = &mmc_dev[dev_index];
 
        sprintf(mmc->name, "Tegra2 SD/MMC");
@@ -472,6 +544,7 @@ static int tegra2_mmc_initialize(int dev_index, int bus_width)
        mmc->send_cmd = mmc_send_cmd;
        mmc->set_ios = mmc_set_ios;
        mmc->init = mmc_core_init;
+       mmc->getcd = tegra2_mmc_getcd;
 
        mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
        if (bus_width == 8)
@@ -495,10 +568,3 @@ static int tegra2_mmc_initialize(int dev_index, int bus_width)
 
        return 0;
 }
-
-int tegra2_mmc_init(int dev_index, int bus_width)
-{
-       debug(" tegra2_mmc_init: index %d, bus width %d\n",
-               dev_index, bus_width);
-       return tegra2_mmc_initialize(dev_index, bus_width);
-}