diff options
| author | Marek Vasut <marek.vasut@gmail.com> | 2011-11-08 23:18:09 +0000 | 
|---|---|---|
| committer | Stefano Babic <sbabic@denx.de> | 2011-11-11 11:36:56 +0100 | 
| commit | 71a758e158e4610349dd1cabd80b8dbda8251eb1 (patch) | |
| tree | 7a2201201a1987e088c1043d556224df08e13df9 /drivers | |
| parent | 6e9a0a396718a72301e27c6b7adc1efe51b6239b (diff) | |
| download | olio-uboot-2014.01-71a758e158e4610349dd1cabd80b8dbda8251eb1.tar.xz olio-uboot-2014.01-71a758e158e4610349dd1cabd80b8dbda8251eb1.zip | |
iMX28: Add SSP MMC driver
Signed-off-by: Marek Vasut <marek.vasut@gmail.com>
Cc: Andy Fleming <afleming@gmail.com>
Cc: Stefano Babic <sbabic@denx.de>
Cc: Wolfgang Denk <wd@denx.de>
Cc: Detlev Zundel <dzu@denx.de>
Acked-by: Andy Fleming <afleming@gmail.com>
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/mmc/Makefile | 1 | ||||
| -rw-r--r-- | drivers/mmc/mxsmmc.c | 351 | 
2 files changed, 352 insertions, 0 deletions
| diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 9f9db7516..506f1d6ef 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -36,6 +36,7 @@ COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o  COBJS-$(CONFIG_ARM_PL180_MMCI) += arm_pl180_mmci.o  COBJS-$(CONFIG_MV_SDHCI) += mv_sdhci.o  COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o +COBJS-$(CONFIG_MXS_MMC) += mxsmmc.o  COBJS-$(CONFIG_OMAP_HSMMC) += omap_hsmmc.o  COBJS-$(CONFIG_PXA_MMC) += pxa_mmc.o  COBJS-$(CONFIG_PXA_MMC_GENERIC) += pxa_mmc_gen.o diff --git a/drivers/mmc/mxsmmc.c b/drivers/mmc/mxsmmc.c new file mode 100644 index 000000000..2a9949eb9 --- /dev/null +++ b/drivers/mmc/mxsmmc.c @@ -0,0 +1,351 @@ +/* + * Freescale i.MX28 SSP MMC driver + * + * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com> + * on behalf of DENX Software Engineering GmbH + * + * Based on code from LTIB: + * (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/errno.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/imx-regs.h> +#include <asm/arch/sys_proto.h> + +struct mxsmmc_priv { +	int			id; +	struct mx28_ssp_regs	*regs; +	uint32_t		clkseq_bypass; +	uint32_t		*clkctrl_ssp; +	uint32_t		buswidth; +	int			(*mmc_is_wp)(int); +}; + +#define	MXSMMC_MAX_TIMEOUT	10000 + +/* + * Sends a command out on the bus.  Takes the mmc pointer, + * a command pointer, and an optional data pointer. + */ +static int +mxsmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) +{ +	struct mxsmmc_priv *priv = (struct mxsmmc_priv *)mmc->priv; +	struct mx28_ssp_regs *ssp_regs = priv->regs; +	uint32_t reg; +	int timeout; +	uint32_t data_count; +	uint32_t *data_ptr; +	uint32_t ctrl0; + +	debug("MMC%d: CMD%d\n", mmc->block_dev.dev, cmd->cmdidx); + +	/* Check bus busy */ +	timeout = MXSMMC_MAX_TIMEOUT; +	while (--timeout) { +		udelay(1000); +		reg = readl(&ssp_regs->hw_ssp_status); +		if (!(reg & +			(SSP_STATUS_BUSY | SSP_STATUS_DATA_BUSY | +			SSP_STATUS_CMD_BUSY))) { +			break; +		} +	} + +	if (!timeout) { +		printf("MMC%d: Bus busy timeout!\n", mmc->block_dev.dev); +		return TIMEOUT; +	} + +	/* See if card is present */ +	if (readl(&ssp_regs->hw_ssp_status) & SSP_STATUS_CARD_DETECT) { +		printf("MMC%d: No card detected!\n", mmc->block_dev.dev); +		return NO_CARD_ERR; +	} + +	/* Start building CTRL0 contents */ +	ctrl0 = priv->buswidth; + +	/* Set up command */ +	if (!(cmd->resp_type & MMC_RSP_CRC)) +		ctrl0 |= SSP_CTRL0_IGNORE_CRC; +	if (cmd->resp_type & MMC_RSP_PRESENT)	/* Need to get response */ +		ctrl0 |= SSP_CTRL0_GET_RESP; +	if (cmd->resp_type & MMC_RSP_136)	/* It's a 136 bits response */ +		ctrl0 |= SSP_CTRL0_LONG_RESP; + +	/* Command index */ +	reg = readl(&ssp_regs->hw_ssp_cmd0); +	reg &= ~(SSP_CMD0_CMD_MASK | SSP_CMD0_APPEND_8CYC); +	reg |= cmd->cmdidx << SSP_CMD0_CMD_OFFSET; +	if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) +		reg |= SSP_CMD0_APPEND_8CYC; +	writel(reg, &ssp_regs->hw_ssp_cmd0); + +	/* Command argument */ +	writel(cmd->cmdarg, &ssp_regs->hw_ssp_cmd1); + +	/* Set up data */ +	if (data) { +		/* READ or WRITE */ +		if (data->flags & MMC_DATA_READ) { +			ctrl0 |= SSP_CTRL0_READ; +		} else if (priv->mmc_is_wp(mmc->block_dev.dev)) { +			printf("MMC%d: Can not write a locked card!\n", +				mmc->block_dev.dev); +			return UNUSABLE_ERR; +		} + +		ctrl0 |= SSP_CTRL0_DATA_XFER; +		reg = ((data->blocks - 1) << +			SSP_BLOCK_SIZE_BLOCK_COUNT_OFFSET) | +			((ffs(data->blocksize) - 1) << +			SSP_BLOCK_SIZE_BLOCK_SIZE_OFFSET); +		writel(reg, &ssp_regs->hw_ssp_block_size); + +		reg = data->blocksize * data->blocks; +		writel(reg, &ssp_regs->hw_ssp_xfer_size); +	} + +	/* Kick off the command */ +	ctrl0 |= SSP_CTRL0_WAIT_FOR_IRQ | SSP_CTRL0_ENABLE | SSP_CTRL0_RUN; +	writel(ctrl0, &ssp_regs->hw_ssp_ctrl0); + +	/* Wait for the command to complete */ +	timeout = MXSMMC_MAX_TIMEOUT; +	while (--timeout) { +		udelay(1000); +		reg = readl(&ssp_regs->hw_ssp_status); +		if (!(reg & SSP_STATUS_CMD_BUSY)) +			break; +	} + +	if (!timeout) { +		printf("MMC%d: Command %d busy\n", +			mmc->block_dev.dev, cmd->cmdidx); +		return TIMEOUT; +	} + +	/* Check command timeout */ +	if (reg & SSP_STATUS_RESP_TIMEOUT) { +		printf("MMC%d: Command %d timeout (status 0x%08x)\n", +			mmc->block_dev.dev, cmd->cmdidx, reg); +		return TIMEOUT; +	} + +	/* Check command errors */ +	if (reg & (SSP_STATUS_RESP_CRC_ERR | SSP_STATUS_RESP_ERR)) { +		printf("MMC%d: Command %d error (status 0x%08x)!\n", +			mmc->block_dev.dev, cmd->cmdidx, reg); +		return COMM_ERR; +	} + +	/* Copy response to response buffer */ +	if (cmd->resp_type & MMC_RSP_136) { +		cmd->response[3] = readl(&ssp_regs->hw_ssp_sdresp0); +		cmd->response[2] = readl(&ssp_regs->hw_ssp_sdresp1); +		cmd->response[1] = readl(&ssp_regs->hw_ssp_sdresp2); +		cmd->response[0] = readl(&ssp_regs->hw_ssp_sdresp3); +	} else +		cmd->response[0] = readl(&ssp_regs->hw_ssp_sdresp0); + +	/* Return if no data to process */ +	if (!data) +		return 0; + +	/* Process the data */ +	data_count = data->blocksize * data->blocks; +	timeout = MXSMMC_MAX_TIMEOUT; +	if (data->flags & MMC_DATA_READ) { +		data_ptr = (uint32_t *)data->dest; +		while (data_count && --timeout) { +			reg = readl(&ssp_regs->hw_ssp_status); +			if (!(reg & SSP_STATUS_FIFO_EMPTY)) { +				*data_ptr++ = readl(&ssp_regs->hw_ssp_data); +				data_count -= 4; +				timeout = MXSMMC_MAX_TIMEOUT; +			} else +				udelay(1000); +		} +	} else { +		data_ptr = (uint32_t *)data->src; +		timeout *= 100; +		while (data_count && --timeout) { +			reg = readl(&ssp_regs->hw_ssp_status); +			if (!(reg & SSP_STATUS_FIFO_FULL)) { +				writel(*data_ptr++, &ssp_regs->hw_ssp_data); +				data_count -= 4; +				timeout = MXSMMC_MAX_TIMEOUT; +			} else +				udelay(1000); +		} +	} + +	if (!timeout) { +		printf("MMC%d: Data timeout with command %d (status 0x%08x)!\n", +			mmc->block_dev.dev, cmd->cmdidx, reg); +		return COMM_ERR; +	} + +	/* Check data errors */ +	reg = readl(&ssp_regs->hw_ssp_status); +	if (reg & +		(SSP_STATUS_TIMEOUT | SSP_STATUS_DATA_CRC_ERR | +		SSP_STATUS_FIFO_OVRFLW | SSP_STATUS_FIFO_UNDRFLW)) { +		printf("MMC%d: Data error with command %d (status 0x%08x)!\n", +			mmc->block_dev.dev, cmd->cmdidx, reg); +		return COMM_ERR; +	} + +	return 0; +} + +static void mxsmmc_set_ios(struct mmc *mmc) +{ +	struct mxsmmc_priv *priv = (struct mxsmmc_priv *)mmc->priv; +	struct mx28_ssp_regs *ssp_regs = priv->regs; + +	/* Set the clock speed */ +	if (mmc->clock) +		mx28_set_ssp_busclock(priv->id, mmc->clock / 1000); + +	switch (mmc->bus_width) { +	case 1: +		priv->buswidth = SSP_CTRL0_BUS_WIDTH_ONE_BIT; +		break; +	case 4: +		priv->buswidth = SSP_CTRL0_BUS_WIDTH_FOUR_BIT; +		break; +	case 8: +		priv->buswidth = SSP_CTRL0_BUS_WIDTH_EIGHT_BIT; +		break; +	} + +	/* Set the bus width */ +	clrsetbits_le32(&ssp_regs->hw_ssp_ctrl0, +			SSP_CTRL0_BUS_WIDTH_MASK, priv->buswidth); + +	debug("MMC%d: Set %d bits bus width\n", +		mmc->block_dev.dev, mmc->bus_width); +} + +static int mxsmmc_init(struct mmc *mmc) +{ +	struct mxsmmc_priv *priv = (struct mxsmmc_priv *)mmc->priv; +	struct mx28_ssp_regs *ssp_regs = priv->regs; + +	/* Reset SSP */ +	mx28_reset_block(&ssp_regs->hw_ssp_ctrl0_reg); + +	/* 8 bits word length in MMC mode */ +	clrsetbits_le32(&ssp_regs->hw_ssp_ctrl1, +		SSP_CTRL1_SSP_MODE_MASK | SSP_CTRL1_WORD_LENGTH_MASK, +		SSP_CTRL1_SSP_MODE_SD_MMC | SSP_CTRL1_WORD_LENGTH_EIGHT_BITS); + +	/* Set initial bit clock 400 KHz */ +	mx28_set_ssp_busclock(priv->id, 400); + +	/* Send initial 74 clock cycles (185 us @ 400 KHz)*/ +	writel(SSP_CMD0_CONT_CLKING_EN, &ssp_regs->hw_ssp_cmd0_set); +	udelay(200); +	writel(SSP_CMD0_CONT_CLKING_EN, &ssp_regs->hw_ssp_cmd0_clr); + +	return 0; +} + +int mxsmmc_initialize(bd_t *bis, int id, int (*wp)(int)) +{ +	struct mx28_clkctrl_regs *clkctrl_regs = +		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE; +	struct mmc *mmc = NULL; +	struct mxsmmc_priv *priv = NULL; + +	mmc = malloc(sizeof(struct mmc)); +	if (!mmc) +		return -ENOMEM; + +	priv = malloc(sizeof(struct mxsmmc_priv)); +	if (!priv) { +		free(mmc); +		return -ENOMEM; +	} + +	priv->mmc_is_wp = wp; +	priv->id = id; +	switch (id) { +	case 0: +		priv->regs = (struct mx28_ssp_regs *)MXS_SSP0_BASE; +		priv->clkseq_bypass = CLKCTRL_CLKSEQ_BYPASS_SSP0; +		priv->clkctrl_ssp = &clkctrl_regs->hw_clkctrl_ssp0; +		break; +	case 1: +		priv->regs = (struct mx28_ssp_regs *)MXS_SSP1_BASE; +		priv->clkseq_bypass = CLKCTRL_CLKSEQ_BYPASS_SSP1; +		priv->clkctrl_ssp = &clkctrl_regs->hw_clkctrl_ssp1; +		break; +	case 2: +		priv->regs = (struct mx28_ssp_regs *)MXS_SSP2_BASE; +		priv->clkseq_bypass = CLKCTRL_CLKSEQ_BYPASS_SSP2; +		priv->clkctrl_ssp = &clkctrl_regs->hw_clkctrl_ssp2; +		break; +	case 3: +		priv->regs = (struct mx28_ssp_regs *)MXS_SSP3_BASE; +		priv->clkseq_bypass = CLKCTRL_CLKSEQ_BYPASS_SSP3; +		priv->clkctrl_ssp = &clkctrl_regs->hw_clkctrl_ssp3; +		break; +	} + +	sprintf(mmc->name, "MXS MMC"); +	mmc->send_cmd = mxsmmc_send_cmd; +	mmc->set_ios = mxsmmc_set_ios; +	mmc->init = mxsmmc_init; +	mmc->priv = priv; + +	mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + +	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 = mxc_get_clock(MXC_SSP0_CLK + id) * 1000 / 2; +	mmc->b_max = 0; + +	mmc_register(mmc); +	return 0; +} |