13f82d89dSTom Warren /* 23f82d89dSTom Warren * (C) Copyright 2009 SAMSUNG Electronics 33f82d89dSTom Warren * Minkyu Kang <mk7.kang@samsung.com> 43f82d89dSTom Warren * Jaehoon Chung <jh80.chung@samsung.com> 56a474db4STom Warren * Portions Copyright 2011-2016 NVIDIA Corporation 63f82d89dSTom Warren * 71a459660SWolfgang Denk * SPDX-License-Identifier: GPL-2.0+ 83f82d89dSTom Warren */ 93f82d89dSTom Warren 1019815399SStephen Warren #include <bouncebuf.h> 113f82d89dSTom Warren #include <common.h> 12c0493076SStephen Warren #include <dm/device.h> 13915ffa52SJaehoon Chung #include <errno.h> 143f82d89dSTom Warren #include <asm/gpio.h> 153f82d89dSTom Warren #include <asm/io.h> 16150c2493STom Warren #include <asm/arch-tegra/tegra_mmc.h> 17150c2493STom Warren #include <mmc.h> 183f82d89dSTom Warren 19c9aa831eSTom Warren DECLARE_GLOBAL_DATA_PTR; 203f82d89dSTom Warren 21f53c4e4bSStephen Warren struct tegra_mmc_priv { 22f53c4e4bSStephen Warren struct tegra_mmc *reg; 23f53c4e4bSStephen Warren struct reset_ctl reset_ctl; 24f53c4e4bSStephen Warren struct clk clk; 25f53c4e4bSStephen Warren struct gpio_desc cd_gpio; /* Change Detect GPIO */ 26f53c4e4bSStephen Warren struct gpio_desc pwr_gpio; /* Power GPIO */ 27f53c4e4bSStephen Warren struct gpio_desc wp_gpio; /* Write Protect GPIO */ 28f53c4e4bSStephen Warren unsigned int version; /* SDHCI spec. version */ 29f53c4e4bSStephen Warren unsigned int clock; /* Current clock (MHz) */ 30f53c4e4bSStephen Warren struct mmc_config cfg; /* mmc configuration */ 316a474db4STom Warren struct mmc *mmc; 32f53c4e4bSStephen Warren }; 33f53c4e4bSStephen Warren 34f53c4e4bSStephen Warren static void tegra_mmc_set_power(struct tegra_mmc_priv *priv, 35f53c4e4bSStephen Warren unsigned short power) 362d348a16STom Warren { 372d348a16STom Warren u8 pwr = 0; 382d348a16STom Warren debug("%s: power = %x\n", __func__, power); 392d348a16STom Warren 402d348a16STom Warren if (power != (unsigned short)-1) { 412d348a16STom Warren switch (1 << power) { 422d348a16STom Warren case MMC_VDD_165_195: 432d348a16STom Warren pwr = TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V1_8; 442d348a16STom Warren break; 452d348a16STom Warren case MMC_VDD_29_30: 462d348a16STom Warren case MMC_VDD_30_31: 472d348a16STom Warren pwr = TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V3_0; 482d348a16STom Warren break; 492d348a16STom Warren case MMC_VDD_32_33: 502d348a16STom Warren case MMC_VDD_33_34: 512d348a16STom Warren pwr = TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V3_3; 522d348a16STom Warren break; 532d348a16STom Warren } 542d348a16STom Warren } 552d348a16STom Warren debug("%s: pwr = %X\n", __func__, pwr); 562d348a16STom Warren 572d348a16STom Warren /* Set the bus voltage first (if any) */ 58f53c4e4bSStephen Warren writeb(pwr, &priv->reg->pwrcon); 592d348a16STom Warren if (pwr == 0) 602d348a16STom Warren return; 612d348a16STom Warren 622d348a16STom Warren /* Now enable bus power */ 632d348a16STom Warren pwr |= TEGRA_MMC_PWRCTL_SD_BUS_POWER; 64f53c4e4bSStephen Warren writeb(pwr, &priv->reg->pwrcon); 652d348a16STom Warren } 662d348a16STom Warren 67f53c4e4bSStephen Warren static void tegra_mmc_prepare_data(struct tegra_mmc_priv *priv, 68f53c4e4bSStephen Warren struct mmc_data *data, 6919815399SStephen Warren struct bounce_buffer *bbstate) 703f82d89dSTom Warren { 713f82d89dSTom Warren unsigned char ctrl; 723f82d89dSTom Warren 733f82d89dSTom Warren 7419815399SStephen Warren debug("buf: %p (%p), data->blocks: %u, data->blocksize: %u\n", 7519815399SStephen Warren bbstate->bounce_buffer, bbstate->user_buffer, data->blocks, 7619815399SStephen Warren data->blocksize); 7719815399SStephen Warren 78f53c4e4bSStephen Warren writel((u32)(unsigned long)bbstate->bounce_buffer, &priv->reg->sysad); 793f82d89dSTom Warren /* 803f82d89dSTom Warren * DMASEL[4:3] 813f82d89dSTom Warren * 00 = Selects SDMA 823f82d89dSTom Warren * 01 = Reserved 833f82d89dSTom Warren * 10 = Selects 32-bit Address ADMA2 843f82d89dSTom Warren * 11 = Selects 64-bit Address ADMA2 853f82d89dSTom Warren */ 86f53c4e4bSStephen Warren ctrl = readb(&priv->reg->hostctl); 873f82d89dSTom Warren ctrl &= ~TEGRA_MMC_HOSTCTL_DMASEL_MASK; 883f82d89dSTom Warren ctrl |= TEGRA_MMC_HOSTCTL_DMASEL_SDMA; 89f53c4e4bSStephen Warren writeb(ctrl, &priv->reg->hostctl); 903f82d89dSTom Warren 913f82d89dSTom Warren /* We do not handle DMA boundaries, so set it to max (512 KiB) */ 92f53c4e4bSStephen Warren writew((7 << 12) | (data->blocksize & 0xFFF), &priv->reg->blksize); 93f53c4e4bSStephen Warren writew(data->blocks, &priv->reg->blkcnt); 943f82d89dSTom Warren } 953f82d89dSTom Warren 96f53c4e4bSStephen Warren static void tegra_mmc_set_transfer_mode(struct tegra_mmc_priv *priv, 97f53c4e4bSStephen Warren struct mmc_data *data) 983f82d89dSTom Warren { 993f82d89dSTom Warren unsigned short mode; 1003f82d89dSTom Warren debug(" mmc_set_transfer_mode called\n"); 1013f82d89dSTom Warren /* 1023f82d89dSTom Warren * TRNMOD 1033f82d89dSTom Warren * MUL1SIN0[5] : Multi/Single Block Select 1043f82d89dSTom Warren * RD1WT0[4] : Data Transfer Direction Select 1053f82d89dSTom Warren * 1 = read 1063f82d89dSTom Warren * 0 = write 1073f82d89dSTom Warren * ENACMD12[2] : Auto CMD12 Enable 1083f82d89dSTom Warren * ENBLKCNT[1] : Block Count Enable 1093f82d89dSTom Warren * ENDMA[0] : DMA Enable 1103f82d89dSTom Warren */ 1113f82d89dSTom Warren mode = (TEGRA_MMC_TRNMOD_DMA_ENABLE | 1123f82d89dSTom Warren TEGRA_MMC_TRNMOD_BLOCK_COUNT_ENABLE); 1133f82d89dSTom Warren 1143f82d89dSTom Warren if (data->blocks > 1) 1153f82d89dSTom Warren mode |= TEGRA_MMC_TRNMOD_MULTI_BLOCK_SELECT; 1163f82d89dSTom Warren 1173f82d89dSTom Warren if (data->flags & MMC_DATA_READ) 1183f82d89dSTom Warren mode |= TEGRA_MMC_TRNMOD_DATA_XFER_DIR_SEL_READ; 1193f82d89dSTom Warren 120f53c4e4bSStephen Warren writew(mode, &priv->reg->trnmod); 1213f82d89dSTom Warren } 1223f82d89dSTom Warren 123f53c4e4bSStephen Warren static int tegra_mmc_wait_inhibit(struct tegra_mmc_priv *priv, 1243f82d89dSTom Warren struct mmc_cmd *cmd, 1253f82d89dSTom Warren struct mmc_data *data, 1263f82d89dSTom Warren unsigned int timeout) 1273f82d89dSTom Warren { 1283f82d89dSTom Warren /* 1293f82d89dSTom Warren * PRNSTS 1303f82d89dSTom Warren * CMDINHDAT[1] : Command Inhibit (DAT) 1313f82d89dSTom Warren * CMDINHCMD[0] : Command Inhibit (CMD) 1323f82d89dSTom Warren */ 1333f82d89dSTom Warren unsigned int mask = TEGRA_MMC_PRNSTS_CMD_INHIBIT_CMD; 1343f82d89dSTom Warren 1353f82d89dSTom Warren /* 1363f82d89dSTom Warren * We shouldn't wait for data inhibit for stop commands, even 1373f82d89dSTom Warren * though they might use busy signaling 1383f82d89dSTom Warren */ 1393f82d89dSTom Warren if ((data == NULL) && (cmd->resp_type & MMC_RSP_BUSY)) 1403f82d89dSTom Warren mask |= TEGRA_MMC_PRNSTS_CMD_INHIBIT_DAT; 1413f82d89dSTom Warren 142f53c4e4bSStephen Warren while (readl(&priv->reg->prnsts) & mask) { 1433f82d89dSTom Warren if (timeout == 0) { 1443f82d89dSTom Warren printf("%s: timeout error\n", __func__); 1453f82d89dSTom Warren return -1; 1463f82d89dSTom Warren } 1473f82d89dSTom Warren timeout--; 1483f82d89dSTom Warren udelay(1000); 1493f82d89dSTom Warren } 1503f82d89dSTom Warren 1513f82d89dSTom Warren return 0; 1523f82d89dSTom Warren } 1533f82d89dSTom Warren 154f53c4e4bSStephen Warren static int tegra_mmc_send_cmd_bounced(struct mmc *mmc, struct mmc_cmd *cmd, 155f53c4e4bSStephen Warren struct mmc_data *data, 156f53c4e4bSStephen Warren struct bounce_buffer *bbstate) 1573f82d89dSTom Warren { 158f53c4e4bSStephen Warren struct tegra_mmc_priv *priv = mmc->priv; 1593f82d89dSTom Warren int flags, i; 1603f82d89dSTom Warren int result; 1613f82d89dSTom Warren unsigned int mask = 0; 1623f82d89dSTom Warren unsigned int retry = 0x100000; 1633f82d89dSTom Warren debug(" mmc_send_cmd called\n"); 1643f82d89dSTom Warren 165f53c4e4bSStephen Warren result = tegra_mmc_wait_inhibit(priv, cmd, data, 10 /* ms */); 1663f82d89dSTom Warren 1673f82d89dSTom Warren if (result < 0) 1683f82d89dSTom Warren return result; 1693f82d89dSTom Warren 1703f82d89dSTom Warren if (data) 171f53c4e4bSStephen Warren tegra_mmc_prepare_data(priv, data, bbstate); 1723f82d89dSTom Warren 1733f82d89dSTom Warren debug("cmd->arg: %08x\n", cmd->cmdarg); 174f53c4e4bSStephen Warren writel(cmd->cmdarg, &priv->reg->argument); 1753f82d89dSTom Warren 1763f82d89dSTom Warren if (data) 177f53c4e4bSStephen Warren tegra_mmc_set_transfer_mode(priv, data); 1783f82d89dSTom Warren 1793f82d89dSTom Warren if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) 1803f82d89dSTom Warren return -1; 1813f82d89dSTom Warren 1823f82d89dSTom Warren /* 1833f82d89dSTom Warren * CMDREG 1843f82d89dSTom Warren * CMDIDX[13:8] : Command index 1853f82d89dSTom Warren * DATAPRNT[5] : Data Present Select 1863f82d89dSTom Warren * ENCMDIDX[4] : Command Index Check Enable 1873f82d89dSTom Warren * ENCMDCRC[3] : Command CRC Check Enable 1883f82d89dSTom Warren * RSPTYP[1:0] 1893f82d89dSTom Warren * 00 = No Response 1903f82d89dSTom Warren * 01 = Length 136 1913f82d89dSTom Warren * 10 = Length 48 1923f82d89dSTom Warren * 11 = Length 48 Check busy after response 1933f82d89dSTom Warren */ 1943f82d89dSTom Warren if (!(cmd->resp_type & MMC_RSP_PRESENT)) 1953f82d89dSTom Warren flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_NO_RESPONSE; 1963f82d89dSTom Warren else if (cmd->resp_type & MMC_RSP_136) 1973f82d89dSTom Warren flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_136; 1983f82d89dSTom Warren else if (cmd->resp_type & MMC_RSP_BUSY) 1993f82d89dSTom Warren flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_48_BUSY; 2003f82d89dSTom Warren else 2013f82d89dSTom Warren flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_48; 2023f82d89dSTom Warren 2033f82d89dSTom Warren if (cmd->resp_type & MMC_RSP_CRC) 2043f82d89dSTom Warren flags |= TEGRA_MMC_TRNMOD_CMD_CRC_CHECK; 2053f82d89dSTom Warren if (cmd->resp_type & MMC_RSP_OPCODE) 2063f82d89dSTom Warren flags |= TEGRA_MMC_TRNMOD_CMD_INDEX_CHECK; 2073f82d89dSTom Warren if (data) 2083f82d89dSTom Warren flags |= TEGRA_MMC_TRNMOD_DATA_PRESENT_SELECT_DATA_TRANSFER; 2093f82d89dSTom Warren 2103f82d89dSTom Warren debug("cmd: %d\n", cmd->cmdidx); 2113f82d89dSTom Warren 212f53c4e4bSStephen Warren writew((cmd->cmdidx << 8) | flags, &priv->reg->cmdreg); 2133f82d89dSTom Warren 2143f82d89dSTom Warren for (i = 0; i < retry; i++) { 215f53c4e4bSStephen Warren mask = readl(&priv->reg->norintsts); 2163f82d89dSTom Warren /* Command Complete */ 2173f82d89dSTom Warren if (mask & TEGRA_MMC_NORINTSTS_CMD_COMPLETE) { 2183f82d89dSTom Warren if (!data) 219f53c4e4bSStephen Warren writel(mask, &priv->reg->norintsts); 2203f82d89dSTom Warren break; 2213f82d89dSTom Warren } 2223f82d89dSTom Warren } 2233f82d89dSTom Warren 2243f82d89dSTom Warren if (i == retry) { 2253f82d89dSTom Warren printf("%s: waiting for status update\n", __func__); 226f53c4e4bSStephen Warren writel(mask, &priv->reg->norintsts); 227915ffa52SJaehoon Chung return -ETIMEDOUT; 2283f82d89dSTom Warren } 2293f82d89dSTom Warren 2303f82d89dSTom Warren if (mask & TEGRA_MMC_NORINTSTS_CMD_TIMEOUT) { 2313f82d89dSTom Warren /* Timeout Error */ 2323f82d89dSTom Warren debug("timeout: %08x cmd %d\n", mask, cmd->cmdidx); 233f53c4e4bSStephen Warren writel(mask, &priv->reg->norintsts); 234915ffa52SJaehoon Chung return -ETIMEDOUT; 2353f82d89dSTom Warren } else if (mask & TEGRA_MMC_NORINTSTS_ERR_INTERRUPT) { 2363f82d89dSTom Warren /* Error Interrupt */ 2373f82d89dSTom Warren debug("error: %08x cmd %d\n", mask, cmd->cmdidx); 238f53c4e4bSStephen Warren writel(mask, &priv->reg->norintsts); 2393f82d89dSTom Warren return -1; 2403f82d89dSTom Warren } 2413f82d89dSTom Warren 2423f82d89dSTom Warren if (cmd->resp_type & MMC_RSP_PRESENT) { 2433f82d89dSTom Warren if (cmd->resp_type & MMC_RSP_136) { 2443f82d89dSTom Warren /* CRC is stripped so we need to do some shifting. */ 2453f82d89dSTom Warren for (i = 0; i < 4; i++) { 246f53c4e4bSStephen Warren unsigned long offset = (unsigned long) 247f53c4e4bSStephen Warren (&priv->reg->rspreg3 - i); 2483f82d89dSTom Warren cmd->response[i] = readl(offset) << 8; 2493f82d89dSTom Warren 2503f82d89dSTom Warren if (i != 3) { 2513f82d89dSTom Warren cmd->response[i] |= 2523f82d89dSTom Warren readb(offset - 1); 2533f82d89dSTom Warren } 2543f82d89dSTom Warren debug("cmd->resp[%d]: %08x\n", 2553f82d89dSTom Warren i, cmd->response[i]); 2563f82d89dSTom Warren } 2573f82d89dSTom Warren } else if (cmd->resp_type & MMC_RSP_BUSY) { 2583f82d89dSTom Warren for (i = 0; i < retry; i++) { 2593f82d89dSTom Warren /* PRNTDATA[23:20] : DAT[3:0] Line Signal */ 260f53c4e4bSStephen Warren if (readl(&priv->reg->prnsts) 2613f82d89dSTom Warren & (1 << 20)) /* DAT[0] */ 2623f82d89dSTom Warren break; 2633f82d89dSTom Warren } 2643f82d89dSTom Warren 2653f82d89dSTom Warren if (i == retry) { 2663f82d89dSTom Warren printf("%s: card is still busy\n", __func__); 267f53c4e4bSStephen Warren writel(mask, &priv->reg->norintsts); 268915ffa52SJaehoon Chung return -ETIMEDOUT; 2693f82d89dSTom Warren } 2703f82d89dSTom Warren 271f53c4e4bSStephen Warren cmd->response[0] = readl(&priv->reg->rspreg0); 2723f82d89dSTom Warren debug("cmd->resp[0]: %08x\n", cmd->response[0]); 2733f82d89dSTom Warren } else { 274f53c4e4bSStephen Warren cmd->response[0] = readl(&priv->reg->rspreg0); 2753f82d89dSTom Warren debug("cmd->resp[0]: %08x\n", cmd->response[0]); 2763f82d89dSTom Warren } 2773f82d89dSTom Warren } 2783f82d89dSTom Warren 2793f82d89dSTom Warren if (data) { 2803f82d89dSTom Warren unsigned long start = get_timer(0); 2813f82d89dSTom Warren 2823f82d89dSTom Warren while (1) { 283f53c4e4bSStephen Warren mask = readl(&priv->reg->norintsts); 2843f82d89dSTom Warren 2853f82d89dSTom Warren if (mask & TEGRA_MMC_NORINTSTS_ERR_INTERRUPT) { 2863f82d89dSTom Warren /* Error Interrupt */ 287f53c4e4bSStephen Warren writel(mask, &priv->reg->norintsts); 2883f82d89dSTom Warren printf("%s: error during transfer: 0x%08x\n", 2893f82d89dSTom Warren __func__, mask); 2903f82d89dSTom Warren return -1; 2913f82d89dSTom Warren } else if (mask & TEGRA_MMC_NORINTSTS_DMA_INTERRUPT) { 2923f82d89dSTom Warren /* 2933f82d89dSTom Warren * DMA Interrupt, restart the transfer where 2943f82d89dSTom Warren * it was interrupted. 2953f82d89dSTom Warren */ 296f53c4e4bSStephen Warren unsigned int address = readl(&priv->reg->sysad); 2973f82d89dSTom Warren 2983f82d89dSTom Warren debug("DMA end\n"); 2993f82d89dSTom Warren writel(TEGRA_MMC_NORINTSTS_DMA_INTERRUPT, 300f53c4e4bSStephen Warren &priv->reg->norintsts); 301f53c4e4bSStephen Warren writel(address, &priv->reg->sysad); 3023f82d89dSTom Warren } else if (mask & TEGRA_MMC_NORINTSTS_XFER_COMPLETE) { 3033f82d89dSTom Warren /* Transfer Complete */ 3043f82d89dSTom Warren debug("r/w is done\n"); 3053f82d89dSTom Warren break; 30609fb7361SMarcel Ziswiler } else if (get_timer(start) > 8000UL) { 307f53c4e4bSStephen Warren writel(mask, &priv->reg->norintsts); 3083f82d89dSTom Warren printf("%s: MMC Timeout\n" 3093f82d89dSTom Warren " Interrupt status 0x%08x\n" 3103f82d89dSTom Warren " Interrupt status enable 0x%08x\n" 3113f82d89dSTom Warren " Interrupt signal enable 0x%08x\n" 3123f82d89dSTom Warren " Present status 0x%08x\n", 3133f82d89dSTom Warren __func__, mask, 314f53c4e4bSStephen Warren readl(&priv->reg->norintstsen), 315f53c4e4bSStephen Warren readl(&priv->reg->norintsigen), 316f53c4e4bSStephen Warren readl(&priv->reg->prnsts)); 3173f82d89dSTom Warren return -1; 3183f82d89dSTom Warren } 3193f82d89dSTom Warren } 320f53c4e4bSStephen Warren writel(mask, &priv->reg->norintsts); 3213f82d89dSTom Warren } 3223f82d89dSTom Warren 3233f82d89dSTom Warren udelay(1000); 3243f82d89dSTom Warren return 0; 3253f82d89dSTom Warren } 3263f82d89dSTom Warren 327ab769f22SPantelis Antoniou static int tegra_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, 32819815399SStephen Warren struct mmc_data *data) 32919815399SStephen Warren { 33019815399SStephen Warren void *buf; 33119815399SStephen Warren unsigned int bbflags; 33219815399SStephen Warren size_t len; 33319815399SStephen Warren struct bounce_buffer bbstate; 33419815399SStephen Warren int ret; 33519815399SStephen Warren 33619815399SStephen Warren if (data) { 33719815399SStephen Warren if (data->flags & MMC_DATA_READ) { 33819815399SStephen Warren buf = data->dest; 33919815399SStephen Warren bbflags = GEN_BB_WRITE; 34019815399SStephen Warren } else { 34119815399SStephen Warren buf = (void *)data->src; 34219815399SStephen Warren bbflags = GEN_BB_READ; 34319815399SStephen Warren } 34419815399SStephen Warren len = data->blocks * data->blocksize; 34519815399SStephen Warren 34619815399SStephen Warren bounce_buffer_start(&bbstate, buf, len, bbflags); 34719815399SStephen Warren } 34819815399SStephen Warren 349f53c4e4bSStephen Warren ret = tegra_mmc_send_cmd_bounced(mmc, cmd, data, &bbstate); 35019815399SStephen Warren 35119815399SStephen Warren if (data) 35219815399SStephen Warren bounce_buffer_stop(&bbstate); 35319815399SStephen Warren 35419815399SStephen Warren return ret; 35519815399SStephen Warren } 35619815399SStephen Warren 357f53c4e4bSStephen Warren static void tegra_mmc_change_clock(struct tegra_mmc_priv *priv, uint clock) 3583f82d89dSTom Warren { 359e8adca9eSStephen Warren ulong rate; 3603f82d89dSTom Warren int div; 3613f82d89dSTom Warren unsigned short clk; 3623f82d89dSTom Warren unsigned long timeout; 3633f82d89dSTom Warren 3643f82d89dSTom Warren debug(" mmc_change_clock called\n"); 3653f82d89dSTom Warren 3663f82d89dSTom Warren /* 3672d348a16STom Warren * Change Tegra SDMMCx clock divisor here. Source is PLLP_OUT0 3683f82d89dSTom Warren */ 3693f82d89dSTom Warren if (clock == 0) 3703f82d89dSTom Warren goto out; 371e8adca9eSStephen Warren 372e8adca9eSStephen Warren rate = clk_set_rate(&priv->clk, clock); 373c0493076SStephen Warren div = (rate + clock - 1) / clock; 3743f82d89dSTom Warren debug("div = %d\n", div); 3753f82d89dSTom Warren 376f53c4e4bSStephen Warren writew(0, &priv->reg->clkcon); 3773f82d89dSTom Warren 3783f82d89dSTom Warren /* 3793f82d89dSTom Warren * CLKCON 3803f82d89dSTom Warren * SELFREQ[15:8] : base clock divided by value 3813f82d89dSTom Warren * ENSDCLK[2] : SD Clock Enable 3823f82d89dSTom Warren * STBLINTCLK[1] : Internal Clock Stable 3833f82d89dSTom Warren * ENINTCLK[0] : Internal Clock Enable 3843f82d89dSTom Warren */ 3853f82d89dSTom Warren div >>= 1; 3863f82d89dSTom Warren clk = ((div << TEGRA_MMC_CLKCON_SDCLK_FREQ_SEL_SHIFT) | 3873f82d89dSTom Warren TEGRA_MMC_CLKCON_INTERNAL_CLOCK_ENABLE); 388f53c4e4bSStephen Warren writew(clk, &priv->reg->clkcon); 3893f82d89dSTom Warren 3903f82d89dSTom Warren /* Wait max 10 ms */ 3913f82d89dSTom Warren timeout = 10; 392f53c4e4bSStephen Warren while (!(readw(&priv->reg->clkcon) & 3933f82d89dSTom Warren TEGRA_MMC_CLKCON_INTERNAL_CLOCK_STABLE)) { 3943f82d89dSTom Warren if (timeout == 0) { 3953f82d89dSTom Warren printf("%s: timeout error\n", __func__); 3963f82d89dSTom Warren return; 3973f82d89dSTom Warren } 3983f82d89dSTom Warren timeout--; 3993f82d89dSTom Warren udelay(1000); 4003f82d89dSTom Warren } 4013f82d89dSTom Warren 4023f82d89dSTom Warren clk |= TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE; 403f53c4e4bSStephen Warren writew(clk, &priv->reg->clkcon); 4043f82d89dSTom Warren 4053f82d89dSTom Warren debug("mmc_change_clock: clkcon = %08X\n", clk); 4063f82d89dSTom Warren 4073f82d89dSTom Warren out: 408f53c4e4bSStephen Warren priv->clock = clock; 4093f82d89dSTom Warren } 4103f82d89dSTom Warren 411*07b0b9c0SJaehoon Chung static int tegra_mmc_set_ios(struct mmc *mmc) 4123f82d89dSTom Warren { 413f53c4e4bSStephen Warren struct tegra_mmc_priv *priv = mmc->priv; 4143f82d89dSTom Warren unsigned char ctrl; 4153f82d89dSTom Warren debug(" mmc_set_ios called\n"); 4163f82d89dSTom Warren 4173f82d89dSTom Warren debug("bus_width: %x, clock: %d\n", mmc->bus_width, mmc->clock); 4183f82d89dSTom Warren 4193f82d89dSTom Warren /* Change clock first */ 420f53c4e4bSStephen Warren tegra_mmc_change_clock(priv, mmc->clock); 4213f82d89dSTom Warren 422f53c4e4bSStephen Warren ctrl = readb(&priv->reg->hostctl); 4233f82d89dSTom Warren 4243f82d89dSTom Warren /* 4253f82d89dSTom Warren * WIDE8[5] 4263f82d89dSTom Warren * 0 = Depend on WIDE4 4273f82d89dSTom Warren * 1 = 8-bit mode 4283f82d89dSTom Warren * WIDE4[1] 4293f82d89dSTom Warren * 1 = 4-bit mode 4303f82d89dSTom Warren * 0 = 1-bit mode 4313f82d89dSTom Warren */ 4323f82d89dSTom Warren if (mmc->bus_width == 8) 4333f82d89dSTom Warren ctrl |= (1 << 5); 4343f82d89dSTom Warren else if (mmc->bus_width == 4) 4353f82d89dSTom Warren ctrl |= (1 << 1); 4363f82d89dSTom Warren else 4373f82d89dSTom Warren ctrl &= ~(1 << 1); 4383f82d89dSTom Warren 439f53c4e4bSStephen Warren writeb(ctrl, &priv->reg->hostctl); 4403f82d89dSTom Warren debug("mmc_set_ios: hostctl = %08X\n", ctrl); 441*07b0b9c0SJaehoon Chung 442*07b0b9c0SJaehoon Chung return 0; 4433f82d89dSTom Warren } 4443f82d89dSTom Warren 445f53c4e4bSStephen Warren static void tegra_mmc_pad_init(struct tegra_mmc_priv *priv) 4466b83588eSStephen Warren { 4476b83588eSStephen Warren #if defined(CONFIG_TEGRA30) 4486b83588eSStephen Warren u32 val; 4496b83588eSStephen Warren 450f53c4e4bSStephen Warren debug("%s: sdmmc address = %08x\n", __func__, (unsigned int)priv->reg); 4516b83588eSStephen Warren 4526b83588eSStephen Warren /* Set the pad drive strength for SDMMC1 or 3 only */ 453f53c4e4bSStephen Warren if (priv->reg != (void *)0x78000000 && 454f53c4e4bSStephen Warren priv->reg != (void *)0x78000400) { 4556b83588eSStephen Warren debug("%s: settings are only valid for SDMMC1/SDMMC3!\n", 4566b83588eSStephen Warren __func__); 4576b83588eSStephen Warren return; 4586b83588eSStephen Warren } 4596b83588eSStephen Warren 460f53c4e4bSStephen Warren val = readl(&priv->reg->sdmemcmppadctl); 4616b83588eSStephen Warren val &= 0xFFFFFFF0; 4626b83588eSStephen Warren val |= MEMCOMP_PADCTRL_VREF; 463f53c4e4bSStephen Warren writel(val, &priv->reg->sdmemcmppadctl); 4646b83588eSStephen Warren 465f53c4e4bSStephen Warren val = readl(&priv->reg->autocalcfg); 4666b83588eSStephen Warren val &= 0xFFFF0000; 4676b83588eSStephen Warren val |= AUTO_CAL_PU_OFFSET | AUTO_CAL_PD_OFFSET | AUTO_CAL_ENABLED; 468f53c4e4bSStephen Warren writel(val, &priv->reg->autocalcfg); 4696b83588eSStephen Warren #endif 4706b83588eSStephen Warren } 4716b83588eSStephen Warren 472f53c4e4bSStephen Warren static void tegra_mmc_reset(struct tegra_mmc_priv *priv, struct mmc *mmc) 4733f82d89dSTom Warren { 4743f82d89dSTom Warren unsigned int timeout; 4753f82d89dSTom Warren debug(" mmc_reset called\n"); 4763f82d89dSTom Warren 4773f82d89dSTom Warren /* 4783f82d89dSTom Warren * RSTALL[0] : Software reset for all 4793f82d89dSTom Warren * 1 = reset 4803f82d89dSTom Warren * 0 = work 4813f82d89dSTom Warren */ 482f53c4e4bSStephen Warren writeb(TEGRA_MMC_SWRST_SW_RESET_FOR_ALL, &priv->reg->swrst); 4833f82d89dSTom Warren 484f53c4e4bSStephen Warren priv->clock = 0; 4853f82d89dSTom Warren 4863f82d89dSTom Warren /* Wait max 100 ms */ 4873f82d89dSTom Warren timeout = 100; 4883f82d89dSTom Warren 4893f82d89dSTom Warren /* hw clears the bit when it's done */ 490f53c4e4bSStephen Warren while (readb(&priv->reg->swrst) & TEGRA_MMC_SWRST_SW_RESET_FOR_ALL) { 4913f82d89dSTom Warren if (timeout == 0) { 4923f82d89dSTom Warren printf("%s: timeout error\n", __func__); 4933f82d89dSTom Warren return; 4943f82d89dSTom Warren } 4953f82d89dSTom Warren timeout--; 4963f82d89dSTom Warren udelay(1000); 4973f82d89dSTom Warren } 4982d348a16STom Warren 4992d348a16STom Warren /* Set SD bus voltage & enable bus power */ 500f53c4e4bSStephen Warren tegra_mmc_set_power(priv, fls(mmc->cfg->voltages) - 1); 5012d348a16STom Warren debug("%s: power control = %02X, host control = %02X\n", __func__, 502f53c4e4bSStephen Warren readb(&priv->reg->pwrcon), readb(&priv->reg->hostctl)); 5032d348a16STom Warren 5042d348a16STom Warren /* Make sure SDIO pads are set up */ 505f53c4e4bSStephen Warren tegra_mmc_pad_init(priv); 5063f82d89dSTom Warren } 5073f82d89dSTom Warren 5086a474db4STom Warren static int tegra_mmc_init(struct mmc *mmc) 5093f82d89dSTom Warren { 510f53c4e4bSStephen Warren struct tegra_mmc_priv *priv = mmc->priv; 5113f82d89dSTom Warren unsigned int mask; 5126a474db4STom Warren debug(" tegra_mmc_init called\n"); 5133f82d89dSTom Warren 514f53c4e4bSStephen Warren tegra_mmc_reset(priv, mmc); 5153f82d89dSTom Warren 516f53c4e4bSStephen Warren priv->version = readw(&priv->reg->hcver); 517f53c4e4bSStephen Warren debug("host version = %x\n", priv->version); 5183f82d89dSTom Warren 5193f82d89dSTom Warren /* mask all */ 520f53c4e4bSStephen Warren writel(0xffffffff, &priv->reg->norintstsen); 521f53c4e4bSStephen Warren writel(0xffffffff, &priv->reg->norintsigen); 5223f82d89dSTom Warren 523f53c4e4bSStephen Warren writeb(0xe, &priv->reg->timeoutcon); /* TMCLK * 2^27 */ 5243f82d89dSTom Warren /* 5253f82d89dSTom Warren * NORMAL Interrupt Status Enable Register init 5263f82d89dSTom Warren * [5] ENSTABUFRDRDY : Buffer Read Ready Status Enable 5273f82d89dSTom Warren * [4] ENSTABUFWTRDY : Buffer write Ready Status Enable 5283f82d89dSTom Warren * [3] ENSTADMAINT : DMA boundary interrupt 5293f82d89dSTom Warren * [1] ENSTASTANSCMPLT : Transfre Complete Status Enable 5303f82d89dSTom Warren * [0] ENSTACMDCMPLT : Command Complete Status Enable 5313f82d89dSTom Warren */ 532f53c4e4bSStephen Warren mask = readl(&priv->reg->norintstsen); 5333f82d89dSTom Warren mask &= ~(0xffff); 5343f82d89dSTom Warren mask |= (TEGRA_MMC_NORINTSTSEN_CMD_COMPLETE | 5353f82d89dSTom Warren TEGRA_MMC_NORINTSTSEN_XFER_COMPLETE | 5363f82d89dSTom Warren TEGRA_MMC_NORINTSTSEN_DMA_INTERRUPT | 5373f82d89dSTom Warren TEGRA_MMC_NORINTSTSEN_BUFFER_WRITE_READY | 5383f82d89dSTom Warren TEGRA_MMC_NORINTSTSEN_BUFFER_READ_READY); 539f53c4e4bSStephen Warren writel(mask, &priv->reg->norintstsen); 5403f82d89dSTom Warren 5413f82d89dSTom Warren /* 5423f82d89dSTom Warren * NORMAL Interrupt Signal Enable Register init 5433f82d89dSTom Warren * [1] ENSTACMDCMPLT : Transfer Complete Signal Enable 5443f82d89dSTom Warren */ 545f53c4e4bSStephen Warren mask = readl(&priv->reg->norintsigen); 5463f82d89dSTom Warren mask &= ~(0xffff); 5473f82d89dSTom Warren mask |= TEGRA_MMC_NORINTSIGEN_XFER_COMPLETE; 548f53c4e4bSStephen Warren writel(mask, &priv->reg->norintsigen); 5493f82d89dSTom Warren 5503f82d89dSTom Warren return 0; 5513f82d89dSTom Warren } 5523f82d89dSTom Warren 55319d7bf3dSJeroen Hofstee static int tegra_mmc_getcd(struct mmc *mmc) 5543f82d89dSTom Warren { 555f53c4e4bSStephen Warren struct tegra_mmc_priv *priv = mmc->priv; 5563f82d89dSTom Warren 55729f3e3f2STom Warren debug("tegra_mmc_getcd called\n"); 5583f82d89dSTom Warren 559f53c4e4bSStephen Warren if (dm_gpio_is_valid(&priv->cd_gpio)) 560f53c4e4bSStephen Warren return dm_gpio_get_value(&priv->cd_gpio); 5613f82d89dSTom Warren 5623f82d89dSTom Warren return 1; 5633f82d89dSTom Warren } 5643f82d89dSTom Warren 565ab769f22SPantelis Antoniou static const struct mmc_ops tegra_mmc_ops = { 566ab769f22SPantelis Antoniou .send_cmd = tegra_mmc_send_cmd, 567ab769f22SPantelis Antoniou .set_ios = tegra_mmc_set_ios, 5686a474db4STom Warren .init = tegra_mmc_init, 569ab769f22SPantelis Antoniou .getcd = tegra_mmc_getcd, 570ab769f22SPantelis Antoniou }; 571ab769f22SPantelis Antoniou 5726a474db4STom Warren static int tegra_mmc_probe(struct udevice *dev) 5733f82d89dSTom Warren { 5746a474db4STom Warren struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); 5756a474db4STom Warren struct tegra_mmc_priv *priv = dev_get_priv(dev); 576e8adca9eSStephen Warren int bus_width, ret; 5773f82d89dSTom Warren 578f53c4e4bSStephen Warren priv->cfg.name = "Tegra SD/MMC"; 579f53c4e4bSStephen Warren priv->cfg.ops = &tegra_mmc_ops; 5803f82d89dSTom Warren 5816a474db4STom Warren bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset, "bus-width", 5826a474db4STom Warren 1); 5836a474db4STom Warren 584f53c4e4bSStephen Warren priv->cfg.voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; 585f53c4e4bSStephen Warren priv->cfg.host_caps = 0; 5866a474db4STom Warren if (bus_width == 8) 587f53c4e4bSStephen Warren priv->cfg.host_caps |= MMC_MODE_8BIT; 5886a474db4STom Warren if (bus_width >= 4) 589f53c4e4bSStephen Warren priv->cfg.host_caps |= MMC_MODE_4BIT; 590f53c4e4bSStephen Warren priv->cfg.host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; 5913f82d89dSTom Warren 5923f82d89dSTom Warren /* 5933f82d89dSTom Warren * min freq is for card identification, and is the highest 5943f82d89dSTom Warren * low-speed SDIO card frequency (actually 400KHz) 5953f82d89dSTom Warren * max freq is highest HS eMMC clock as per the SD/MMC spec 5963f82d89dSTom Warren * (actually 52MHz) 5973f82d89dSTom Warren */ 598f53c4e4bSStephen Warren priv->cfg.f_min = 375000; 599f53c4e4bSStephen Warren priv->cfg.f_max = 48000000; 6003f82d89dSTom Warren 601f53c4e4bSStephen Warren priv->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; 60293bfd616SPantelis Antoniou 6036a474db4STom Warren priv->reg = (void *)dev_get_addr(dev); 604c9aa831eSTom Warren 6056a474db4STom Warren ret = reset_get_by_name(dev, "sdhci", &priv->reset_ctl); 606c0493076SStephen Warren if (ret) { 607eb3f68afSStephen Warren debug("reset_get_by_name() failed: %d\n", ret); 608c0493076SStephen Warren return ret; 609c0493076SStephen Warren } 6106a474db4STom Warren ret = clk_get_by_index(dev, 0, &priv->clk); 611c0493076SStephen Warren if (ret) { 612c0493076SStephen Warren debug("clk_get_by_index() failed: %d\n", ret); 613c0493076SStephen Warren return ret; 614c0493076SStephen Warren } 6156a474db4STom Warren 6166a474db4STom Warren ret = reset_assert(&priv->reset_ctl); 6176a474db4STom Warren if (ret) 6186a474db4STom Warren return ret; 6196a474db4STom Warren ret = clk_enable(&priv->clk); 6206a474db4STom Warren if (ret) 6216a474db4STom Warren return ret; 6226a474db4STom Warren ret = clk_set_rate(&priv->clk, 20000000); 6236a474db4STom Warren if (IS_ERR_VALUE(ret)) 6246a474db4STom Warren return ret; 6256a474db4STom Warren ret = reset_deassert(&priv->reset_ctl); 6266a474db4STom Warren if (ret) 6276a474db4STom Warren return ret; 628c9aa831eSTom Warren 629c9aa831eSTom Warren /* These GPIOs are optional */ 6306a474db4STom Warren gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio, 6310347960bSSimon Glass GPIOD_IS_IN); 6326a474db4STom Warren gpio_request_by_name(dev, "wp-gpios", 0, &priv->wp_gpio, 6330347960bSSimon Glass GPIOD_IS_IN); 6346a474db4STom Warren gpio_request_by_name(dev, "power-gpios", 0, 635f53c4e4bSStephen Warren &priv->pwr_gpio, GPIOD_IS_OUT); 6366a474db4STom Warren if (dm_gpio_is_valid(&priv->pwr_gpio)) 6376a474db4STom Warren dm_gpio_set_value(&priv->pwr_gpio, 1); 638c9aa831eSTom Warren 6396a474db4STom Warren priv->mmc = mmc_create(&priv->cfg, priv); 6406a474db4STom Warren if (priv->mmc == NULL) 641c9aa831eSTom Warren return -1; 6426a474db4STom Warren 6436a474db4STom Warren priv->mmc->dev = dev; 6446a474db4STom Warren upriv->mmc = priv->mmc; 6456a474db4STom Warren 646c9aa831eSTom Warren return 0; 647c9aa831eSTom Warren } 648c9aa831eSTom Warren 6496a474db4STom Warren static const struct udevice_id tegra_mmc_ids[] = { 6506a474db4STom Warren { .compatible = "nvidia,tegra20-sdhci" }, 6516a474db4STom Warren { .compatible = "nvidia,tegra30-sdhci" }, 6526a474db4STom Warren { .compatible = "nvidia,tegra114-sdhci" }, 6536a474db4STom Warren { .compatible = "nvidia,tegra124-sdhci" }, 6546a474db4STom Warren { .compatible = "nvidia,tegra210-sdhci" }, 6556a474db4STom Warren { .compatible = "nvidia,tegra186-sdhci" }, 6566a474db4STom Warren { } 6576a474db4STom Warren }; 658c9aa831eSTom Warren 6596a474db4STom Warren U_BOOT_DRIVER(tegra_mmc_drv) = { 6606a474db4STom Warren .name = "tegra_mmc", 6616a474db4STom Warren .id = UCLASS_MMC, 6626a474db4STom Warren .of_match = tegra_mmc_ids, 6636a474db4STom Warren .probe = tegra_mmc_probe, 6646a474db4STom Warren .priv_auto_alloc_size = sizeof(struct tegra_mmc_priv), 6656a474db4STom Warren }; 666