diff options
Diffstat (limited to 'drivers/mmc')
-rw-r--r-- | drivers/mmc/Makefile | 1 | ||||
-rw-r--r-- | drivers/mmc/imx_ssp_mmc.c | 371 |
2 files changed, 372 insertions, 0 deletions
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 2902ba5..39a8913 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -34,6 +34,7 @@ COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o COBJS-$(CONFIG_PXA_MMC) += pxa_mmc.o COBJS-$(CONFIG_FSL_MMC) += fsl_esdhc.o COBJS-$(CONFIG_IMX_MMC) += imx_esdhc.o +COBJS-$(CONFIG_IMX_SSP_MMC) += imx_ssp_mmc.o COBJS := $(COBJS-y) SRCS := $(COBJS:.o=.c) diff --git a/drivers/mmc/imx_ssp_mmc.c b/drivers/mmc/imx_ssp_mmc.c new file mode 100644 index 0000000..61a1d85 --- /dev/null +++ b/drivers/mmc/imx_ssp_mmc.c @@ -0,0 +1,371 @@ +/* + * (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 <common.h> +#include <malloc.h> +#include <mmc.h> +#include <asm/arch/mx28.h> +#include <asm/arch/regs-ssp.h> +#include <asm/arch/regs-clkctrl.h> + +#undef IMX_SSP_MMC_DEBUG + +extern void ssp_mmc_board_init(void); +extern u32 ssp_mmc_is_wp(void); + +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: CMD%d\n", cmd->cmdidx); +#endif + + /* Check bus busy */ + i = 0; + while (REG_RD(REGS_SSP0_BASE, 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: Bus busy timeout!\n"); + return TIMEOUT; + } + } + + /* See if card is present */ + if (REG_RD(REGS_SSP0_BASE, HW_SSP_STATUS) & BM_SSP_STATUS_CARD_DETECT) { + printf("MMC: No card detected!\n"); + return NO_CARD_ERR; + } + + /* Clear all control bits except bus width */ + REG_CLR(REGS_SSP0_BASE, HW_SSP_CTRL0, 0xff3fffff); + + /* Set up command */ + if (!(cmd->resp_type & MMC_RSP_CRC)) + REG_SET(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_IGNORE_CRC); + if (cmd->resp_type & MMC_RSP_PRESENT) /* Need to get response */ + REG_SET(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_GET_RESP); + if (cmd->resp_type & MMC_RSP_136) /* It's a 136 bits response */ + REG_SET(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_LONG_RESP); + + /* Command index */ + REG_WR(REGS_SSP0_BASE, HW_SSP_CMD0, + (REG_RD(REGS_SSP0_BASE, HW_SSP_CMD0) & ~BM_SSP_CMD0_CMD) | + (cmd->cmdidx << BP_SSP_CMD0_CMD)); + /* Command argument */ + REG_WR(REGS_SSP0_BASE, HW_SSP_CMD1, cmd->cmdarg); + + /* Set up data */ + if (data) { + /* READ or WRITE */ + if (data->flags & MMC_DATA_READ) { + REG_SET(REGS_SSP0_BASE, HW_SSP_CTRL0, + BM_SSP_CTRL0_READ); + } else if (ssp_mmc_is_wp()) { + printf("MMC: Can not write a locked card!\n"); + return UNUSABLE_ERR; + } + REG_SET(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_DATA_XFER); + REG_WR(REGS_SSP0_BASE, HW_SSP_BLOCK_SIZE, + ((data->blocks - 1) << + BP_SSP_BLOCK_SIZE_BLOCK_COUNT) | + ((ffs(data->blocksize) - 1) << + BP_SSP_BLOCK_SIZE_BLOCK_SIZE)); + REG_WR(REGS_SSP0_BASE, HW_SSP_XFER_SIZE, + data->blocksize * data->blocks); + } + + /* Kick off the command */ + REG_SET(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_WAIT_FOR_IRQ); + REG_SET(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_ENABLE); + REG_SET(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_RUN); + + /* Wait for the command to complete */ + i = 0; + do { + mdelay(10); + if (i++ == 100) { + printf("MMC: Command %d busy\n", cmd->cmdidx); + break; + } + } while (REG_RD(REGS_SSP0_BASE, HW_SSP_STATUS) & + BM_SSP_STATUS_CMD_BUSY); + + /* Check command timeout */ + if (REG_RD(REGS_SSP0_BASE, HW_SSP_STATUS) & + BM_SSP_STATUS_RESP_TIMEOUT) { +#ifdef IMX_SSP_MMC_DEBUG + printf("MMC: Command %d timeout\n", cmd->cmdidx); +#endif + return TIMEOUT; + } + + /* Check command errors */ + if (REG_RD(REGS_SSP0_BASE, HW_SSP_STATUS) & + (BM_SSP_STATUS_RESP_CRC_ERR | BM_SSP_STATUS_RESP_ERR)) { + printf("MMC: Command %d error (status 0x%08x)!\n", + cmd->cmdidx, REG_RD(REGS_SSP0_BASE, HW_SSP_STATUS)); + return COMM_ERR; + } + + /* Copy response to response buffer */ + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[3] = REG_RD(REGS_SSP0_BASE, HW_SSP_SDRESP0); + cmd->response[2] = REG_RD(REGS_SSP0_BASE, HW_SSP_SDRESP1); + cmd->response[1] = REG_RD(REGS_SSP0_BASE, HW_SSP_SDRESP2); + cmd->response[0] = REG_RD(REGS_SSP0_BASE, HW_SSP_SDRESP3); + } else + cmd->response[0] = REG_RD(REGS_SSP0_BASE, 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 ((REG_RD(REGS_SSP0_BASE, HW_SSP_STATUS) & + BM_SSP_STATUS_FIFO_EMPTY) == 0) { + *tmp_ptr++ = REG_RD(REGS_SSP0_BASE, HW_SSP_DATA); + xfer_cnt -= 4; + } + } + } else { + tmp_ptr = (u32 *)data->src; + while (xfer_cnt > 0) { + if ((REG_RD(REGS_SSP0_BASE, HW_SSP_STATUS) & + BM_SSP_STATUS_FIFO_FULL) == 0) { + REG_WR(REGS_SSP0_BASE, HW_SSP_DATA, *tmp_ptr++); + xfer_cnt -= 4; + } + } + } + + /* Check data errors */ + if (REG_RD(REGS_SSP0_BASE, 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: Data error with command %d (status 0x%08x)!\n", + cmd->cmdidx, REG_RD(REGS_SSP0_BASE, HW_SSP_STATUS)); + return COMM_ERR; + } + + return 0; +} + +static void set_bit_clock(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 */ + REG_WR(REGS_SSP0_BASE, 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: Set clock rate to %d KHz (requested %d KHz)\n", + 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->clock); + + /* Set the bus width */ + regval = REG_RD(REGS_SSP0_BASE, 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); + } + REG_WR(REGS_SSP0_BASE, HW_SSP_CTRL0, regval); + +#ifdef IMX_SSP_MMC_DEBUG + printf("MMC: Set %d bits bus width\n", mmc->bus_width); +#endif +} + +static int ssp_mmc_init(struct mmc *mmc) +{ + u32 regval; + + /* Board level init */ + ssp_mmc_board_init(); + + /* + * Set up SSPCLK + */ + /* Set REF_IO0 at 480 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, + BM_CLKCTRL_CLKSEQ_BYPASS_SSP0); + /* Turn on SSPCLK */ + REG_WR(REGS_CLKCTRL_BASE, HW_CLKCTRL_SSP0, + REG_RD(REGS_CLKCTRL_BASE, HW_CLKCTRL_SSP0) & + ~BM_CLKCTRL_SSP0_CLKGATE); + /* Set SSPCLK divide 1 */ + regval = REG_RD(REGS_CLKCTRL_BASE, HW_CLKCTRL_SSP0); + regval &= ~(BM_CLKCTRL_SSP0_DIV_FRAC_EN | BM_CLKCTRL_SSP0_DIV); + REG_WR(REGS_CLKCTRL_BASE, HW_CLKCTRL_SSP0, + regval | (1 << BP_CLKCTRL_SSP0_DIV)); + /* Wait for new divide ready */ + do { + udelay(10); + } while (REG_RD(REGS_CLKCTRL_BASE, HW_CLKCTRL_SSP0) & + BM_CLKCTRL_SSP0_BUSY); + + /* Prepare for software reset */ + REG_CLR(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_SFTRST); + REG_CLR(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_CLKGATE); + /* Assert reset */ + REG_SET(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_SFTRST); + /* Wait for confirmation */ + while (!(REG_RD(REGS_SSP0_BASE, HW_SSP_CTRL0) & BM_SSP_CTRL0_CLKGATE)) + ; + /* Done */ + REG_CLR(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_SFTRST); + REG_CLR(REGS_SSP0_BASE, HW_SSP_CTRL0, BM_SSP_CTRL0_CLKGATE); + + /* 8 bits word length in MMC mode */ + regval = REG_RD(REGS_SSP0_BASE, HW_SSP_CTRL1); + regval &= ~(BM_SSP_CTRL1_SSP_MODE | BM_SSP_CTRL1_WORD_LENGTH); + REG_WR(REGS_SSP0_BASE, 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(400000); + + /* Send initial 74 clock cycles (185 us @ 400 KHz)*/ + REG_SET(REGS_SSP0_BASE, HW_SSP_CMD0, BM_SSP_CMD0_CONT_CLKING_EN); + udelay(200); + REG_CLR(REGS_SSP0_BASE, HW_SSP_CMD0, BM_SSP_CMD0_CONT_CLKING_EN); + + return 0; +} + +int imx_ssp_mmc_initialize(bd_t *bis) +{ + 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->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; +} + |