/* * Copyright 2007, 2010-2011 Freescale Semiconductor, Inc * Andy Fleming * * Based vaguely on the pxa mmc code: * (C) Copyright 2003 * Kyle Harris, Nexus Technologies, Inc. kharris@nexus-tech.net * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include DECLARE_GLOBAL_DATA_PTR; struct fsl_esdhc { uint dsaddr; uint blkattr; uint cmdarg; uint xfertyp; uint cmdrsp0; uint cmdrsp1; uint cmdrsp2; uint cmdrsp3; uint datport; uint prsstat; uint proctl; uint sysctl; uint irqstat; uint irqstaten; uint irqsigen; uint autoc12err; uint hostcapblt; uint wml; char reserved1[8]; uint fevt; char reserved2[168]; uint hostver; char reserved3[780]; uint scr; }; /* Return the XFERTYP flags for a given command and data packet */ uint esdhc_xfertyp(struct mmc_cmd *cmd, struct mmc_data *data) { uint xfertyp = 0; if (data) { xfertyp |= XFERTYP_DPSEL; #ifndef CONFIG_SYS_FSL_ESDHC_USE_PIO xfertyp |= XFERTYP_DMAEN; #endif if (data->blocks > 1) { xfertyp |= XFERTYP_MSBSEL; xfertyp |= XFERTYP_BCEN; #ifdef CONFIG_SYS_FSL_ERRATUM_ESDHC111 xfertyp |= XFERTYP_AC12EN; #endif } if (data->flags & MMC_DATA_READ) xfertyp |= XFERTYP_DTDSEL; } if (cmd->resp_type & MMC_RSP_CRC) xfertyp |= XFERTYP_CCCEN; if (cmd->resp_type & MMC_RSP_OPCODE) xfertyp |= XFERTYP_CICEN; if (cmd->resp_type & MMC_RSP_136) xfertyp |= XFERTYP_RSPTYP_136; else if (cmd->resp_type & MMC_RSP_BUSY) xfertyp |= XFERTYP_RSPTYP_48_BUSY; else if (cmd->resp_type & MMC_RSP_PRESENT) xfertyp |= XFERTYP_RSPTYP_48; return XFERTYP_CMD(cmd->cmdidx) | xfertyp; } #ifdef CONFIG_SYS_FSL_ESDHC_USE_PIO /* * PIO Read/Write Mode reduce the performace as DMA is not used in this mode. */ static void esdhc_pio_read_write(struct mmc *mmc, struct mmc_data *data) { struct fsl_esdhc *regs = mmc->priv; uint blocks; char *buffer; uint databuf; uint size; uint irqstat; uint timeout; if (data->flags & MMC_DATA_READ) { blocks = data->blocks; buffer = data->dest; while (blocks) { timeout = PIO_TIMEOUT; size = data->blocksize; irqstat = esdhc_read32(®s->irqstat); while (!(esdhc_read32(®s->prsstat) & PRSSTAT_BREN) && --timeout); if (timeout <= 0) { printf("\nData Read Failed in PIO Mode."); return; } while (size && (!(irqstat & IRQSTAT_TC))) { udelay(100); /* Wait before last byte transfer complete */ irqstat = esdhc_read32(®s->irqstat); databuf = in_le32(®s->datport); *((uint *)buffer) = databuf; buffer += 4; size -= 4; } blocks--; } } else { blocks = data->blocks; buffer = (char *)data->src; while (blocks) { timeout = PIO_TIMEOUT; size = data->blocksize; irqstat = esdhc_read32(®s->irqstat); while (!(esdhc_read32(®s->prsstat) & PRSSTAT_BWEN) && --timeout); if (timeout <= 0) { printf("\nData Write Failed in PIO Mode."); return; } while (size && (!(irqstat & IRQSTAT_TC))) { udelay(100); /* Wait before last byte transfer complete */ databuf = *((uint *)buffer); buffer += 4; size -= 4; irqstat = esdhc_read32(®s->irqstat); out_le32(®s->datport, databuf); } blocks--; } } } #endif static int esdhc_setup_data(struct mmc *mmc, struct mmc_data *data) { int timeout; struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv; struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base; #ifndef CONFIG_SYS_FSL_ESDHC_USE_PIO uint wml_value; wml_value = data->blocksize/4; if (data->flags & MMC_DATA_READ) { if (wml_value > 0x10) wml_value = 0x10; esdhc_clrsetbits32(®s->wml, WML_RD_WML_MASK, wml_value); esdhc_write32(®s->dsaddr, (u32)data->dest); } else { if (wml_value > 0x80) wml_value = 0x80; if ((esdhc_read32(®s->prsstat) & PRSSTAT_WPSPL) == 0) { printf("\nThe SD card is locked. Can not write to a locked card.\n\n"); return TIMEOUT; } esdhc_clrsetbits32(®s->wml, WML_WR_WML_MASK, wml_value << 16); esdhc_write32(®s->dsaddr, (u32)data->src); } #else /* CONFIG_SYS_FSL_ESDHC_USE_PIO */ if (!(data->flags & MMC_DATA_READ)) { if ((esdhc_read32(®s->prsstat) & PRSSTAT_WPSPL) == 0) { printf("\nThe SD card is locked. " "Can not write to a locked card.\n\n"); return TIMEOUT; } esdhc_write32(®s->dsaddr, (u32)data->src); } else esdhc_write32(®s->dsaddr, (u32)data->dest); #endif /* CONFIG_SYS_FSL_ESDHC_USE_PIO */ esdhc_write32(®s->blkattr, data->blocks << 16 | data->blocksize); /* Calculate the timeout period for data transactions */ timeout = fls(mmc->tran_speed/10) - 1; timeout -= 13; if (timeout > 14) timeout = 14; if (timeout < 0) timeout = 0; #ifdef CONFIG_SYS_FSL_ERRATUM_ESDHC_A001 if ((timeout == 4) || (timeout == 8) || (timeout == 12)) timeout++; #endif esdhc_clrsetbits32(®s->sysctl, SYSCTL_TIMEOUT_MASK, timeout << 16); return 0; } /* * Sends a command out on the bus. Takes the mmc pointer, * a command pointer, and an optional data pointer. */ static int esdhc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { uint xfertyp; uint irqstat; struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv; volatile struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base; #ifdef CONFIG_SYS_FSL_ERRATUM_ESDHC111 if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) return 0; #endif esdhc_write32(®s->irqstat, -1); sync(); /* Wait for the bus to be idle */ while ((esdhc_read32(®s->prsstat) & PRSSTAT_CICHB) || (esdhc_read32(®s->prsstat) & PRSSTAT_CIDHB)) ; while (esdhc_read32(®s->prsstat) & PRSSTAT_DLA) ; /* Wait at least 8 SD clock cycles before the next command */ /* * Note: This is way more than 8 cycles, but 1ms seems to * resolve timing issues with some cards */ udelay(1000); /* Set up for a data transfer if we have one */ if (data) { int err; err = esdhc_setup_data(mmc, data); if(err) return err; } /* Figure out the transfer arguments */ xfertyp = esdhc_xfertyp(cmd, data); /* Send the command */ esdhc_write32(®s->cmdarg, cmd->cmdarg); esdhc_write32(®s->xfertyp, xfertyp); /* Wait for the command to complete */ while (!(esdhc_read32(®s->irqstat) & IRQSTAT_CC)) ; irqstat = esdhc_read32(®s->irqstat); esdhc_write32(®s->irqstat, irqstat); if (irqstat & CMD_ERR) return COMM_ERR; if (irqstat & IRQSTAT_CTOE) return TIMEOUT; /* Copy the response to the response buffer */ if (cmd->resp_type & MMC_RSP_136) { u32 cmdrsp3, cmdrsp2, cmdrsp1, cmdrsp0; cmdrsp3 = esdhc_read32(®s->cmdrsp3); cmdrsp2 = esdhc_read32(®s->cmdrsp2); cmdrsp1 = esdhc_read32(®s->cmdrsp1); cmdrsp0 = esdhc_read32(®s->cmdrsp0); cmd->response[0] = (cmdrsp3 << 8) | (cmdrsp2 >> 24); cmd->response[1] = (cmdrsp2 << 8) | (cmdrsp1 >> 24); cmd->response[2] = (cmdrsp1 << 8) | (cmdrsp0 >> 24); cmd->response[3] = (cmdrsp0 << 8); } else cmd->response[0] = esdhc_read32(®s->cmdrsp0); /* Wait until all of the blocks are transferred */ if (data) { #ifdef CONFIG_SYS_FSL_ESDHC_USE_PIO esdhc_pio_read_write(mmc, data); #else do { irqstat = esdhc_read32(®s->irqstat); if (irqstat & DATA_ERR) return COMM_ERR; if (irqstat & IRQSTAT_DTOE) return TIMEOUT; } while (!(irqstat & IRQSTAT_TC) && (esdhc_read32(®s->prsstat) & PRSSTAT_DLA)); #endif } esdhc_write32(®s->irqstat, -1); return 0; } void set_sysctl(struct mmc *mmc, uint clock) { int sdhc_clk = gd->sdhc_clk; int div, pre_div; struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv; volatile struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base; uint clk; if (clock < mmc->f_min) clock = mmc->f_min; if (sdhc_clk / 16 > clock) { for (pre_div = 2; pre_div < 256; pre_div *= 2) if ((sdhc_clk / pre_div) <= (clock * 16)) break; } else pre_div = 2; for (div = 1; div <= 16; div++) if ((sdhc_clk / (div * pre_div)) <= clock) break; pre_div >>= 1; div -= 1; clk = (pre_div << 8) | (div << 4); esdhc_clrbits32(®s->sysctl, SYSCTL_CKEN); esdhc_clrsetbits32(®s->sysctl, SYSCTL_CLOCK_MASK, clk); udelay(10000); clk = SYSCTL_PEREN | SYSCTL_CKEN; esdhc_setbits32(®s->sysctl, clk); } static void esdhc_set_ios(struct mmc *mmc) { struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv; struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base; /* Set the clock speed */ set_sysctl(mmc, mmc->clock); /* Set the bus width */ esdhc_clrbits32(®s->proctl, PROCTL_DTW_4 | PROCTL_DTW_8); if (mmc->bus_width == 4) esdhc_setbits32(®s->proctl, PROCTL_DTW_4); else if (mmc->bus_width == 8) esdhc_setbits32(®s->proctl, PROCTL_DTW_8); } static int esdhc_init(struct mmc *mmc) { struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv; struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base; int timeout = 1000; int ret = 0; u8 card_absent; /* Reset the entire host controller */ esdhc_write32(®s->sysctl, SYSCTL_RSTA); /* Wait until the controller is available */ while ((esdhc_read32(®s->sysctl) & SYSCTL_RSTA) && --timeout) udelay(1000); /* Enable cache snooping */ if (cfg && !cfg->no_snoop) esdhc_write32(®s->scr, 0x00000040); esdhc_write32(®s->sysctl, SYSCTL_HCKEN | SYSCTL_IPGEN); /* Set the initial clock speed */ mmc_set_clock(mmc, 400000); /* Disable the BRR and BWR bits in IRQSTAT */ esdhc_clrbits32(®s->irqstaten, IRQSTATEN_BRR | IRQSTATEN_BWR); /* Put the PROCTL reg back to the default */ esdhc_write32(®s->proctl, PROCTL_INIT); /* Set timout to the maximum value */ esdhc_clrsetbits32(®s->sysctl, SYSCTL_TIMEOUT_MASK, 14 << 16); /* Check if there is a callback for detecting the card */ if (board_mmc_getcd(&card_absent, mmc)) { timeout = 1000; while (!(esdhc_read32(®s->prsstat) & PRSSTAT_CINS) && --timeout) udelay(1000); if (timeout <= 0) ret = NO_CARD_ERR; } else { if (card_absent) ret = NO_CARD_ERR; } return ret; } static void esdhc_reset(struct fsl_esdhc *regs) { unsigned long timeout = 100; /* wait max 100 ms */ /* reset the controller */ esdhc_write32(®s->sysctl, SYSCTL_RSTA); /* hardware clears the bit when it is done */ while ((esdhc_read32(®s->sysctl) & SYSCTL_RSTA) && --timeout) udelay(1000); if (!timeout) printf("MMC/SD: Reset never completed.\n"); } int fsl_esdhc_initialize(bd_t *bis, struct fsl_esdhc_cfg *cfg) { struct fsl_esdhc *regs; struct mmc *mmc; u32 caps, voltage_caps; if (!cfg) return -1; mmc = malloc(sizeof(struct mmc)); sprintf(mmc->name, "FSL_ESDHC"); regs = (struct fsl_esdhc *)cfg->esdhc_base; /* First reset the eSDHC controller */ esdhc_reset(regs); mmc->priv = cfg; mmc->send_cmd = esdhc_send_cmd; mmc->set_ios = esdhc_set_ios; mmc->init = esdhc_init; voltage_caps = 0; caps = regs->hostcapblt; #ifdef CONFIG_SYS_FSL_ERRATUM_ESDHC135 caps = caps & ~(ESDHC_HOSTCAPBLT_SRS | ESDHC_HOSTCAPBLT_VS18 | ESDHC_HOSTCAPBLT_VS30); #endif if (caps & ESDHC_HOSTCAPBLT_VS18) voltage_caps |= MMC_VDD_165_195; if (caps & ESDHC_HOSTCAPBLT_VS30) voltage_caps |= MMC_VDD_29_30 | MMC_VDD_30_31; if (caps & ESDHC_HOSTCAPBLT_VS33) voltage_caps |= MMC_VDD_32_33 | MMC_VDD_33_34; #ifdef CONFIG_SYS_SD_VOLTAGE mmc->voltages = CONFIG_SYS_SD_VOLTAGE; #else mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; #endif if ((mmc->voltages & voltage_caps) == 0) { printf("voltage not supported by controller\n"); return -1; } mmc->host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT; if (caps & ESDHC_HOSTCAPBLT_HSS) mmc->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; mmc->f_min = 400000; mmc->f_max = MIN(gd->sdhc_clk, 52000000); mmc_register(mmc); return 0; } int fsl_esdhc_mmc_init(bd_t *bis) { struct fsl_esdhc_cfg *cfg; cfg = malloc(sizeof(struct fsl_esdhc_cfg)); memset(cfg, 0, sizeof(struct fsl_esdhc_cfg)); cfg->esdhc_base = CONFIG_SYS_FSL_ESDHC_ADDR; return fsl_esdhc_initialize(bis, cfg); } #ifdef CONFIG_OF_LIBFDT void fdt_fixup_esdhc(void *blob, bd_t *bd) { const char *compat = "fsl,esdhc"; #ifdef CONFIG_FSL_ESDHC_PIN_MUX if (!hwconfig("esdhc")) { do_fixup_by_compat(blob, compat, "status", "disabled", 8 + 1, 1); return; } #endif do_fixup_by_compat_u32(blob, compat, "clock-frequency", gd->sdhc_clk, 1); do_fixup_by_compat(blob, compat, "status", "okay", 4 + 1, 1); } #endif