diff options
Diffstat (limited to 'drivers/mmc/pxa_mmc_gen.c')
| -rw-r--r-- | drivers/mmc/pxa_mmc_gen.c | 442 | 
1 files changed, 442 insertions, 0 deletions
| diff --git a/drivers/mmc/pxa_mmc_gen.c b/drivers/mmc/pxa_mmc_gen.c new file mode 100644 index 000000000..28e37b4fe --- /dev/null +++ b/drivers/mmc/pxa_mmc_gen.c @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.com> + * + * Loosely based on the old code and Linux's PXA MMC driver + * + * 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 <config.h> +#include <common.h> +#include <malloc.h> + +#include <mmc.h> +#include <asm/errno.h> +#include <asm/arch/hardware.h> +#include <asm/arch/regs-mmc.h> +#include <asm/io.h> + +/* PXAMMC Generic default config for various CPUs */ +#if defined(CONFIG_PXA250) +#define PXAMMC_FIFO_SIZE	1 +#define PXAMMC_MIN_SPEED	312500 +#define PXAMMC_MAX_SPEED	20000000 +#define PXAMMC_HOST_CAPS	(0) +#elif defined(CONFIG_PXA27X) +#define PXAMMC_CRC_SKIP +#define PXAMMC_FIFO_SIZE	32 +#define PXAMMC_MIN_SPEED	304000 +#define PXAMMC_MAX_SPEED	19500000 +#define PXAMMC_HOST_CAPS	(MMC_MODE_4BIT) +#elif defined(CONFIG_CPU_MONAHANS) +#define PXAMMC_FIFO_SIZE	32 +#define PXAMMC_MIN_SPEED	304000 +#define PXAMMC_MAX_SPEED	26000000 +#define PXAMMC_HOST_CAPS	(MMC_MODE_4BIT | MMC_MODE_HS) +#else +#error "This CPU isn't supported by PXA MMC!" +#endif + +#define MMC_STAT_ERRORS							\ +	(MMC_STAT_RES_CRC_ERROR | MMC_STAT_SPI_READ_ERROR_TOKEN |	\ +	MMC_STAT_CRC_READ_ERROR | MMC_STAT_TIME_OUT_RESPONSE |		\ +	MMC_STAT_READ_TIME_OUT | MMC_STAT_CRC_WRITE_ERROR) + +/* 1 millisecond (in wait cycles below it's 100 x 10uS waits) */ +#define PXA_MMC_TIMEOUT	100 + +struct pxa_mmc_priv { +	struct pxa_mmc_regs *regs; +}; + +/* Wait for bit to be set */ +static int pxa_mmc_wait(struct mmc *mmc, uint32_t mask) +{ +	struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; +	struct pxa_mmc_regs *regs = priv->regs; +	unsigned int timeout = PXA_MMC_TIMEOUT; + +	/* Wait for bit to be set */ +	while (--timeout) { +		if (readl(®s->stat) & mask) +			break; +		udelay(10); +	} + +	if (!timeout) +		return -ETIMEDOUT; + +	return 0; +} + +static int pxa_mmc_stop_clock(struct mmc *mmc) +{ +	struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; +	struct pxa_mmc_regs *regs = priv->regs; +	unsigned int timeout = PXA_MMC_TIMEOUT; + +	/* If the clock aren't running, exit */ +	if (!(readl(®s->stat) & MMC_STAT_CLK_EN)) +		return 0; + +	/* Tell the controller to turn off the clock */ +	writel(MMC_STRPCL_STOP_CLK, ®s->strpcl); + +	/* Wait until the clock are off */ +	while (--timeout) { +		if (!(readl(®s->stat) & MMC_STAT_CLK_EN)) +			break; +		udelay(10); +	} + +	/* The clock refused to stop, scream and die a painful death */ +	if (!timeout) +		return -ETIMEDOUT; + +	/* The clock stopped correctly */ +	return 0; +} + +static int pxa_mmc_start_cmd(struct mmc *mmc, struct mmc_cmd *cmd, +				uint32_t cmdat) +{ +	struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; +	struct pxa_mmc_regs *regs = priv->regs; +	int ret; + +	/* The card can send a "busy" response */ +	if (cmd->flags & MMC_RSP_BUSY) +		cmdat |= MMC_CMDAT_BUSY; + +	/* Inform the controller about response type */ +	switch (cmd->resp_type) { +	case MMC_RSP_R1: +	case MMC_RSP_R1b: +		cmdat |= MMC_CMDAT_R1; +		break; +	case MMC_RSP_R2: +		cmdat |= MMC_CMDAT_R2; +		break; +	case MMC_RSP_R3: +		cmdat |= MMC_CMDAT_R3; +		break; +	default: +		break; +	} + +	/* Load command and it's arguments into the controller */ +	writel(cmd->cmdidx, ®s->cmd); +	writel(cmd->cmdarg >> 16, ®s->argh); +	writel(cmd->cmdarg & 0xffff, ®s->argl); +	writel(cmdat, ®s->cmdat); + +	/* Start the controller clock and wait until they are started */ +	writel(MMC_STRPCL_START_CLK, ®s->strpcl); + +	ret = pxa_mmc_wait(mmc, MMC_STAT_CLK_EN); +	if (ret) +		return ret; + +	/* Correct and happy end */ +	return 0; +} + +static int pxa_mmc_cmd_done(struct mmc *mmc, struct mmc_cmd *cmd) +{ +	struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; +	struct pxa_mmc_regs *regs = priv->regs; +	uint32_t a, b, c; +	int i; +	int stat; + +	/* Read the controller status */ +	stat = readl(®s->stat); + +	/* +	 * Linux says: +	 * Did I mention this is Sick.  We always need to +	 * discard the upper 8 bits of the first 16-bit word. +	 */ +	a = readl(®s->res) & 0xffff; +	for (i = 0; i < 4; i++) { +		b = readl(®s->res) & 0xffff; +		c = readl(®s->res) & 0xffff; +		cmd->response[i] = (a << 24) | (b << 8) | (c >> 8); +		a = c; +	} + +	/* The command response didn't arrive */ +	if (stat & MMC_STAT_TIME_OUT_RESPONSE) +		return -ETIMEDOUT; +	else if (stat & MMC_STAT_RES_CRC_ERROR && cmd->flags & MMC_RSP_CRC) { +#ifdef	PXAMMC_CRC_SKIP +		if (cmd->flags & MMC_RSP_136 && cmd->response[0] & (1 << 31)) +			printf("Ignoring CRC, this may be dangerous!\n"); +		else +#endif +		return -EILSEQ; +	} + +	/* The command response was successfully read */ +	return 0; +} + +static int pxa_mmc_do_read_xfer(struct mmc *mmc, struct mmc_data *data) +{ +	struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; +	struct pxa_mmc_regs *regs = priv->regs; +	uint32_t len; +	uint32_t *buf = (uint32_t *)data->dest; +	int size; +	int ret; + +	len = data->blocks * data->blocksize; + +	while (len) { +		/* The controller has data ready */ +		if (readl(®s->i_reg) & MMC_I_REG_RXFIFO_RD_REQ) { +			size = min(len, PXAMMC_FIFO_SIZE); +			len -= size; +			size /= 4; + +			/* Read data into the buffer */ +			while (size--) +				*buf++ = readl(®s->rxfifo); + +		} + +		if (readl(®s->stat) & MMC_STAT_ERRORS) +			return -EIO; +	} + +	/* Wait for the transmission-done interrupt */ +	ret = pxa_mmc_wait(mmc, MMC_STAT_DATA_TRAN_DONE); +	if (ret) +		return ret; + +	return 0; +} + +static int pxa_mmc_do_write_xfer(struct mmc *mmc, struct mmc_data *data) +{ +	struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; +	struct pxa_mmc_regs *regs = priv->regs; +	uint32_t len; +	uint32_t *buf = (uint32_t *)data->src; +	int size; +	int ret; + +	len = data->blocks * data->blocksize; + +	while (len) { +		/* The controller is ready to receive data */ +		if (readl(®s->i_reg) & MMC_I_REG_TXFIFO_WR_REQ) { +			size = min(len, PXAMMC_FIFO_SIZE); +			len -= size; +			size /= 4; + +			while (size--) +				writel(*buf++, ®s->txfifo); + +			if (min(len, PXAMMC_FIFO_SIZE) < 32) +				writel(MMC_PRTBUF_BUF_PART_FULL, ®s->prtbuf); +		} + +		if (readl(®s->stat) & MMC_STAT_ERRORS) +			return -EIO; +	} + +	/* Wait for the transmission-done interrupt */ +	ret = pxa_mmc_wait(mmc, MMC_STAT_DATA_TRAN_DONE); +	if (ret) +		return ret; + +	/* Wait until the data are really written to the card */ +	ret = pxa_mmc_wait(mmc, MMC_STAT_PRG_DONE); +	if (ret) +		return ret; + +	return 0; +} + +static int pxa_mmc_request(struct mmc *mmc, struct mmc_cmd *cmd, +				struct mmc_data *data) +{ +	struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; +	struct pxa_mmc_regs *regs = priv->regs; +	uint32_t cmdat = 0; +	int ret; + +	/* Stop the controller */ +	ret = pxa_mmc_stop_clock(mmc); +	if (ret) +		return ret; + +	/* If we're doing data transfer, configure the controller accordingly */ +	if (data) { +		writel(data->blocks, ®s->nob); +		writel(data->blocksize, ®s->blklen); +		/* This delay can be optimized, but stick with max value */ +		writel(0xffff, ®s->rdto); +		cmdat |= MMC_CMDAT_DATA_EN; +		if (data->flags & MMC_DATA_WRITE) +			cmdat |= MMC_CMDAT_WRITE; +	} + +	/* Run in 4bit mode if the card can do it */ +	if (mmc->bus_width == 4) +		cmdat |= MMC_CMDAT_SD_4DAT; + +	/* Execute the command */ +	ret = pxa_mmc_start_cmd(mmc, cmd, cmdat); +	if (ret) +		return ret; + +	/* Wait until the command completes */ +	ret = pxa_mmc_wait(mmc, MMC_STAT_END_CMD_RES); +	if (ret) +		return ret; + +	/* Read back the result */ +	ret = pxa_mmc_cmd_done(mmc, cmd); +	if (ret) +		return ret; + +	/* In case there was a data transfer scheduled, do it */ +	if (data) { +		if (data->flags & MMC_DATA_WRITE) +			pxa_mmc_do_write_xfer(mmc, data); +		else +			pxa_mmc_do_read_xfer(mmc, data); +	} + +	return 0; +} + +static void pxa_mmc_set_ios(struct mmc *mmc) +{ +	struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; +	struct pxa_mmc_regs *regs = priv->regs; +	uint32_t tmp; +	uint32_t pxa_mmc_clock; + +	if (!mmc->clock) { +		pxa_mmc_stop_clock(mmc); +		return; +	} + +	/* PXA3xx can do 26MHz with special settings. */ +	if (mmc->clock == 26000000) { +		writel(0x7, ®s->clkrt); +		return; +	} + +	/* Set clock to the card the usual way. */ +	pxa_mmc_clock = 0; +	tmp = mmc->f_max / mmc->clock; +	tmp += tmp % 2; + +	while (tmp > 1) { +		pxa_mmc_clock++; +		tmp >>= 1; +	} + +	writel(pxa_mmc_clock, ®s->clkrt); +} + +static int pxa_mmc_init(struct mmc *mmc) +{ +	struct pxa_mmc_priv *priv = (struct pxa_mmc_priv *)mmc->priv; +	struct pxa_mmc_regs *regs = priv->regs; + +	/* Make sure the clock are stopped */ +	pxa_mmc_stop_clock(mmc); + +	/* Turn off SPI mode */ +	writel(0, ®s->spi); + +	/* Set up maximum timeout to wait for command response */ +	writel(MMC_RES_TO_MAX_MASK, ®s->resto); + +	/* Mask all interrupts */ +	writel(~(MMC_I_MASK_TXFIFO_WR_REQ | MMC_I_MASK_RXFIFO_RD_REQ), +		®s->i_mask); +	return 0; +} + +int pxa_mmc_register(int card_index) +{ +	struct mmc *mmc; +	struct pxa_mmc_priv *priv; +	uint32_t reg; +	int ret = -ENOMEM; + +	mmc = malloc(sizeof(struct mmc)); +	if (!mmc) +		goto err0; + +	priv = malloc(sizeof(struct pxa_mmc_priv)); +	if (!priv) +		goto err1; + +	switch (card_index) { +	case 0: +		priv->regs = (struct pxa_mmc_regs *)MMC0_BASE; +		break; +	case 1: +		priv->regs = (struct pxa_mmc_regs *)MMC1_BASE; +		break; +	default: +		printf("PXA MMC: Invalid MMC controller ID (card_index = %d)\n", +			card_index); +		goto err2; +	} + +	mmc->priv = priv; + +	sprintf(mmc->name, "PXA MMC"); +	mmc->send_cmd	= pxa_mmc_request; +	mmc->set_ios	= pxa_mmc_set_ios; +	mmc->init	= pxa_mmc_init; + +	mmc->voltages	= MMC_VDD_32_33 | MMC_VDD_33_34; +	mmc->f_max	= PXAMMC_MAX_SPEED; +	mmc->f_min	= PXAMMC_MIN_SPEED; +	mmc->host_caps	= PXAMMC_HOST_CAPS; + +	mmc->b_max = 0; + +#ifndef	CONFIG_CPU_MONAHANS	/* PXA2xx */ +	reg = readl(CKEN); +	reg |= CKEN12_MMC; +	writel(reg, CKEN); +#else				/* PXA3xx */ +	reg = readl(CKENA); +	reg |= CKENA_12_MMC0 | CKENA_13_MMC1; +	writel(reg, CKENA); +#endif + +	mmc_register(mmc); + +	return 0; + +err2: +	free(priv); +err1: +	free(mmc); +err0: +	return ret; +} |