/* * (C) Copyright 2008-2010 Freescale Semiconductor, Inc. * Terry Lv * * Copyright 2007, 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 #undef IMX_SSP_MMC_DEBUG static inline int ssp_mmc_read(struct mmc *mmc, uint reg) { struct imx_ssp_mmc_cfg *cfg = (struct imx_ssp_mmc_cfg *)mmc->priv; return REG_RD(cfg->ssp_mmc_base, reg); } static inline void ssp_mmc_write(struct mmc *mmc, uint reg, uint val) { struct imx_ssp_mmc_cfg *cfg = (struct imx_ssp_mmc_cfg *)mmc->priv; REG_WR(cfg->ssp_mmc_base, reg, val); } static inline void mdelay(unsigned long msec) { unsigned long i; for (i = 0; i < msec; i++) udelay(1000); } static inline void sdelay(unsigned long sec) { unsigned long i; for (i = 0; i < sec; i++) mdelay(1000); } /* * Sends a command out on the bus. Takes the mmc pointer, * a command pointer, and an optional data pointer. */ static int ssp_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { int i; #ifdef IMX_SSP_MMC_DEBUG printf("MMC%d: CMD%d\n", mmc->block_dev.dev, cmd->cmdidx); #endif /* Check bus busy */ i = 0; while (ssp_mmc_read(mmc, HW_SSP_STATUS) & (BM_SSP_STATUS_BUSY | BM_SSP_STATUS_DATA_BUSY | BM_SSP_STATUS_CMD_BUSY)) { mdelay(1); i++; if (i == 1000) { printf("MMC%d: Bus busy timeout!\n", mmc->block_dev.dev); return TIMEOUT; } } /* See if card is present */ if (ssp_mmc_read(mmc, HW_SSP_STATUS) & BM_SSP_STATUS_CARD_DETECT) { printf("MMC%d: No card detected!\n", mmc->block_dev.dev); return NO_CARD_ERR; } /* Clear all control bits except bus width */ ssp_mmc_write(mmc, HW_SSP_CTRL0_CLR, 0xff3fffff); /* Set up command */ if (!(cmd->resp_type & MMC_RSP_CRC)) ssp_mmc_write(mmc, HW_SSP_CTRL0_SET, BM_SSP_CTRL0_IGNORE_CRC); if (cmd->resp_type & MMC_RSP_PRESENT) /* Need to get response */ ssp_mmc_write(mmc, HW_SSP_CTRL0_SET, BM_SSP_CTRL0_GET_RESP); if (cmd->resp_type & MMC_RSP_136) /* It's a 136 bits response */ ssp_mmc_write(mmc, HW_SSP_CTRL0_SET, BM_SSP_CTRL0_LONG_RESP); /* Command index */ ssp_mmc_write(mmc, HW_SSP_CMD0, (ssp_mmc_read(mmc, HW_SSP_CMD0) & ~BM_SSP_CMD0_CMD) | (cmd->cmdidx << BP_SSP_CMD0_CMD)); /* Command argument */ ssp_mmc_write(mmc, HW_SSP_CMD1, cmd->cmdarg); /* Set up data */ if (data) { /* READ or WRITE */ if (data->flags & MMC_DATA_READ) { ssp_mmc_write(mmc, HW_SSP_CTRL0_SET, BM_SSP_CTRL0_READ); } else if (ssp_mmc_is_wp(mmc)) { printf("MMC%d: Can not write a locked card!\n", mmc->block_dev.dev); return UNUSABLE_ERR; } ssp_mmc_write(mmc, HW_SSP_CTRL0_SET, BM_SSP_CTRL0_DATA_XFER); ssp_mmc_write(mmc, HW_SSP_BLOCK_SIZE, ((data->blocks - 1) << BP_SSP_BLOCK_SIZE_BLOCK_COUNT) | ((ffs(data->blocksize) - 1) << BP_SSP_BLOCK_SIZE_BLOCK_SIZE)); ssp_mmc_write(mmc, HW_SSP_XFER_SIZE, data->blocksize * data->blocks); } /* Kick off the command */ ssp_mmc_write(mmc, HW_SSP_CTRL0_SET, BM_SSP_CTRL0_WAIT_FOR_IRQ); ssp_mmc_write(mmc, HW_SSP_CTRL0_SET, BM_SSP_CTRL0_ENABLE); ssp_mmc_write(mmc, HW_SSP_CTRL0_SET, BM_SSP_CTRL0_RUN); /* Wait for the command to complete */ i = 0; do { mdelay(10); if (i++ == 100) { printf("MMC%d: Command %d busy\n", mmc->block_dev.dev, cmd->cmdidx); break; } } while (ssp_mmc_read(mmc, HW_SSP_STATUS) & BM_SSP_STATUS_CMD_BUSY); /* Check command timeout */ if (ssp_mmc_read(mmc, HW_SSP_STATUS) & BM_SSP_STATUS_RESP_TIMEOUT) { #ifdef IMX_SSP_MMC_DEBUG printf("MMC%d: Command %d timeout\n", mmc->block_dev.dev, cmd->cmdidx); #endif return TIMEOUT; } /* Check command errors */ if (ssp_mmc_read(mmc, HW_SSP_STATUS) & (BM_SSP_STATUS_RESP_CRC_ERR | BM_SSP_STATUS_RESP_ERR)) { printf("MMC%d: Command %d error (status 0x%08x)!\n", mmc->block_dev.dev, cmd->cmdidx, ssp_mmc_read(mmc, HW_SSP_STATUS)); return COMM_ERR; } /* Copy response to response buffer */ if (cmd->resp_type & MMC_RSP_136) { cmd->response[3] = ssp_mmc_read(mmc, HW_SSP_SDRESP0); cmd->response[2] = ssp_mmc_read(mmc, HW_SSP_SDRESP1); cmd->response[1] = ssp_mmc_read(mmc, HW_SSP_SDRESP2); cmd->response[0] = ssp_mmc_read(mmc, HW_SSP_SDRESP3); } else cmd->response[0] = ssp_mmc_read(mmc, HW_SSP_SDRESP0); /* Return if no data to process */ if (!data) return 0; /* Process the data */ u32 xfer_cnt = data->blocksize * data->blocks; u32 *tmp_ptr; if (data->flags & MMC_DATA_READ) { tmp_ptr = (u32 *)data->dest; while (xfer_cnt > 0) { if ((ssp_mmc_read(mmc, HW_SSP_STATUS) & BM_SSP_STATUS_FIFO_EMPTY) == 0) { *tmp_ptr++ = ssp_mmc_read(mmc, HW_SSP_DATA); xfer_cnt -= 4; } } } else { tmp_ptr = (u32 *)data->src; while (xfer_cnt > 0) { if ((ssp_mmc_read(mmc, HW_SSP_STATUS) & BM_SSP_STATUS_FIFO_FULL) == 0) { ssp_mmc_write(mmc, HW_SSP_DATA, *tmp_ptr++); xfer_cnt -= 4; } } } /* Check data errors */ if (ssp_mmc_read(mmc, HW_SSP_STATUS) & (BM_SSP_STATUS_TIMEOUT | BM_SSP_STATUS_DATA_CRC_ERR | BM_SSP_STATUS_FIFO_OVRFLW | BM_SSP_STATUS_FIFO_UNDRFLW)) { printf("MMC%d: Data error with command %d (status 0x%08x)!\n", mmc->block_dev.dev, cmd->cmdidx, ssp_mmc_read(mmc, HW_SSP_STATUS)); return COMM_ERR; } return 0; } static void set_bit_clock(struct mmc *mmc, u32 clock) { const u32 sspclk = 480000 * 18 / 29 / 1; /* 297931 KHz */ u32 divide, rate, tgtclk; /* * SSP bit rate = SSPCLK / (CLOCK_DIVIDE * (1 + CLOCK_RATE)), * CLOCK_DIVIDE has to be an even value from 2 to 254, and * CLOCK_RATE could be any integer from 0 to 255. */ clock /= 1000; /* KHz */ for (divide = 2; divide < 254; divide += 2) { rate = sspclk / clock / divide; if (rate <= 256) break; } tgtclk = sspclk / divide / rate; while (tgtclk > clock) { rate++; tgtclk = sspclk / divide / rate; } if (rate > 256) rate = 256; /* Always set timeout the maximum */ ssp_mmc_write(mmc, HW_SSP_TIMING, BM_SSP_TIMING_TIMEOUT | divide << BP_SSP_TIMING_CLOCK_DIVIDE | (rate - 1) << BP_SSP_TIMING_CLOCK_RATE); #ifdef IMX_SSP_MMC_DEBUG printf("MMC%d: Set clock rate to %d KHz (requested %d KHz)\n", mmc->block_dev.dev, tgtclk, clock); #endif } static void ssp_mmc_set_ios(struct mmc *mmc) { u32 regval; /* Set the clock speed */ if (mmc->clock) set_bit_clock(mmc, mmc->clock); /* Set the bus width */ regval = ssp_mmc_read(mmc, HW_SSP_CTRL0); regval &= ~BM_SSP_CTRL0_BUS_WIDTH; switch (mmc->bus_width) { case 1: regval |= (BV_SSP_CTRL0_BUS_WIDTH__ONE_BIT << BP_SSP_CTRL0_BUS_WIDTH); break; case 4: regval |= (BV_SSP_CTRL0_BUS_WIDTH__FOUR_BIT << BP_SSP_CTRL0_BUS_WIDTH); break; case 8: regval |= (BV_SSP_CTRL0_BUS_WIDTH__EIGHT_BIT << BP_SSP_CTRL0_BUS_WIDTH); } ssp_mmc_write(mmc, HW_SSP_CTRL0, regval); #ifdef IMX_SSP_MMC_DEBUG printf("MMC%d: Set %d bits bus width\n", mmc->block_dev.dev, mmc->bus_width); #endif } static int ssp_mmc_init(struct mmc *mmc) { struct imx_ssp_mmc_cfg *cfg = (struct imx_ssp_mmc_cfg *)mmc->priv; u32 regval; /* * Set up SSPCLK */ /* Set REF_IO0 at 297.731 MHz */ regval = REG_RD(REGS_CLKCTRL_BASE, HW_CLKCTRL_FRAC0); regval &= ~BM_CLKCTRL_FRAC0_IO0FRAC; REG_WR(REGS_CLKCTRL_BASE, HW_CLKCTRL_FRAC0, regval | (29 << BP_CLKCTRL_FRAC0_IO0FRAC)); /* Enable REF_IO0 */ REG_CLR(REGS_CLKCTRL_BASE, HW_CLKCTRL_FRAC0, BM_CLKCTRL_FRAC0_CLKGATEIO0); /* Source SSPCLK from REF_IO0 */ REG_CLR(REGS_CLKCTRL_BASE, HW_CLKCTRL_CLKSEQ, cfg->clkctrl_clkseq_ssp_offset); /* Turn on SSPCLK */ REG_WR(REGS_CLKCTRL_BASE, cfg->clkctrl_ssp_offset, REG_RD(REGS_CLKCTRL_BASE, cfg->clkctrl_ssp_offset) & ~BM_CLKCTRL_SSP_CLKGATE); /* Set SSPCLK divide 1 */ regval = REG_RD(REGS_CLKCTRL_BASE, cfg->clkctrl_ssp_offset); regval &= ~(BM_CLKCTRL_SSP_DIV_FRAC_EN | BM_CLKCTRL_SSP_DIV); REG_WR(REGS_CLKCTRL_BASE, cfg->clkctrl_ssp_offset, regval | (1 << BP_CLKCTRL_SSP_DIV)); /* Wait for new divide ready */ do { udelay(10); } while (REG_RD(REGS_CLKCTRL_BASE, cfg->clkctrl_ssp_offset) & BM_CLKCTRL_SSP_BUSY); /* Prepare for software reset */ ssp_mmc_write(mmc, HW_SSP_CTRL0_CLR, BM_SSP_CTRL0_SFTRST); ssp_mmc_write(mmc, HW_SSP_CTRL0_CLR, BM_SSP_CTRL0_CLKGATE); /* Assert reset */ ssp_mmc_write(mmc, HW_SSP_CTRL0_SET, BM_SSP_CTRL0_SFTRST); /* Wait for confirmation */ while (!(ssp_mmc_read(mmc, HW_SSP_CTRL0) & BM_SSP_CTRL0_CLKGATE)) ; /* Done */ ssp_mmc_write(mmc, HW_SSP_CTRL0_CLR, BM_SSP_CTRL0_SFTRST); ssp_mmc_write(mmc, HW_SSP_CTRL0_CLR, BM_SSP_CTRL0_CLKGATE); /* 8 bits word length in MMC mode */ regval = ssp_mmc_read(mmc, HW_SSP_CTRL1); regval &= ~(BM_SSP_CTRL1_SSP_MODE | BM_SSP_CTRL1_WORD_LENGTH); ssp_mmc_write(mmc, HW_SSP_CTRL1, regval | (BV_SSP_CTRL1_SSP_MODE__SD_MMC << BP_SSP_CTRL1_SSP_MODE) | (BV_SSP_CTRL1_WORD_LENGTH__EIGHT_BITS << BP_SSP_CTRL1_WORD_LENGTH)); /* Set initial bit clock 400 KHz */ set_bit_clock(mmc, 400000); /* Send initial 74 clock cycles (185 us @ 400 KHz)*/ ssp_mmc_write(mmc, HW_SSP_CMD0_SET, BM_SSP_CMD0_CONT_CLKING_EN); udelay(200); ssp_mmc_write(mmc, HW_SSP_CMD0_CLR, BM_SSP_CMD0_CONT_CLKING_EN); return 0; } int imx_ssp_mmc_initialize(bd_t *bis, struct imx_ssp_mmc_cfg *cfg) { struct mmc *mmc; mmc = malloc(sizeof(struct mmc)); sprintf(mmc->name, "IMX_SSP_MMC"); mmc->send_cmd = ssp_mmc_send_cmd; mmc->set_ios = ssp_mmc_set_ios; mmc->init = ssp_mmc_init; mmc->priv = cfg; mmc->voltages = MMC_VDD_32_33 | MMC_VDD_31_32 | MMC_VDD_30_31 | MMC_VDD_29_30 | MMC_VDD_28_29 | MMC_VDD_27_28; mmc->host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS; /* * SSPCLK = 480 * 18 / 29 / 1 = 297.731 MHz * SSP bit rate = SSPCLK / (CLOCK_DIVIDE * (1 + CLOCK_RATE)), * CLOCK_DIVIDE has to be an even value from 2 to 254, and * CLOCK_RATE could be any integer from 0 to 255. */ mmc->f_min = 400000; mmc->f_max = 148000000; /* 297.731 MHz / 2 */ mmc_register(mmc); return 0; }