diff options
Diffstat (limited to 'drivers/mmc')
| -rw-r--r-- | drivers/mmc/Makefile | 2 | ||||
| -rw-r--r-- | drivers/mmc/ftsdc010_esdhc.c | 687 | ||||
| -rw-r--r-- | drivers/mmc/ftsdc010_mci.c | 377 | 
3 files changed, 378 insertions, 688 deletions
| diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 196ef9b8e..2b581781d 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -29,7 +29,7 @@ LIB	:= $(obj)libmmc.o  COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o  COBJS-$(CONFIG_DAVINCI_MMC) += davinci_mmc.o  COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o -COBJS-$(CONFIG_FTSDC010) += ftsdc010_esdhc.o +COBJS-$(CONFIG_FTSDC010) += ftsdc010_mci.o  COBJS-$(CONFIG_GENERIC_MMC) += mmc.o  COBJS-$(CONFIG_GENERIC_ATMEL_MCI) += gen_atmel_mci.o  COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o diff --git a/drivers/mmc/ftsdc010_esdhc.c b/drivers/mmc/ftsdc010_esdhc.c deleted file mode 100644 index 42f0e0ce5..000000000 --- a/drivers/mmc/ftsdc010_esdhc.c +++ /dev/null @@ -1,687 +0,0 @@ -/* - * Copyright (C) 2011 Andes Technology Corporation - * Macpaul Lin, Andes Technology Corporation <macpaul@andestech.com> - * - * 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 <config.h> -#include <common.h> -#include <mmc.h> - -#include <asm/io.h> -#include <faraday/ftsdc010.h> - -/* - * supported mmc hosts - * setting the number CONFIG_FTSDC010_NUMBER in your configuration file. - */ -static struct mmc ftsdc010_dev[CONFIG_FTSDC010_NUMBER]; -static struct mmc_host ftsdc010_host[CONFIG_FTSDC010_NUMBER]; - -static struct ftsdc010_mmc *ftsdc010_get_base_mmc(int dev_index) -{ -	return (struct ftsdc010_mmc *)CONFIG_FTSDC010_BASE + dev_index; -} - -#ifdef DEBUG -static void ftsdc010_dump_reg(struct mmc_host *host) -{ -	debug("cmd: %08x\n",		readl(&host->reg->cmd)); -	debug("argu: %08x\n",		readl(&host->reg->argu)); -	debug("rsp0: %08x\n",		readl(&host->reg->rsp0)); -	debug("rsp1: %08x\n",		readl(&host->reg->rsp1)); -	debug("rsp2: %08x\n",		readl(&host->reg->rsp2)); -	debug("rsp3: %08x\n",		readl(&host->reg->rsp3)); -	debug("rsp_cmd: %08x\n",	readl(&host->reg->rsp_cmd)); -	debug("dcr: %08x\n",		readl(&host->reg->dcr)); -	debug("dtr: %08x\n",		readl(&host->reg->dtr)); -	debug("dlr: %08x\n",		readl(&host->reg->dlr)); -	debug("status: %08x\n",		readl(&host->reg->status)); -	debug("clr: %08x\n",		readl(&host->reg->clr)); -	debug("int_mask: %08x\n",	readl(&host->reg->int_mask)); -	debug("pcr: %08x\n",		readl(&host->reg->pcr)); -	debug("ccr: %08x\n",		readl(&host->reg->ccr)); -	debug("bwr: %08x\n",		readl(&host->reg->bwr)); -	debug("dwr: %08x\n",		readl(&host->reg->dwr)); -	debug("feature: %08x\n",	readl(&host->reg->feature)); -	debug("rev: %08x\n",		readl(&host->reg->rev)); -} -#endif - -static unsigned int enable_imask(struct ftsdc010_mmc *reg, unsigned int imask) -{ -	unsigned int newmask; - -	newmask = readl(®->int_mask); -	newmask |= imask; - -	writel(newmask, ®->int_mask); - -	return newmask; -} - -static void ftsdc010_pio_read(struct mmc_host *host, char *buf, unsigned int size) -{ -	unsigned int fifo; -	unsigned int fifo_words; -	unsigned int *ptr; -	unsigned int status; -	unsigned int retry = 0; - -	/* get_data_buffer */ -	ptr = (unsigned int *)buf; - -	while (size) { -		status = readl(&host->reg->status); -		debug("%s: size: %08x\n", __func__, size); - -		if (status & FTSDC010_STATUS_FIFO_ORUN) { - -			debug("%s: FIFO OVERRUN: sta: %08x\n", -					__func__, status); - -			fifo = host->fifo_len > size ? -				size : host->fifo_len; - -			size -= fifo; - -			fifo_words = fifo >> 2; - -			while (fifo_words--) -				*ptr++ = readl(&host->reg->dwr); - -			/* -			 * for adding some delays for SD card to put -			 * data into FIFO again -			 */ -			udelay(4*FTSDC010_DELAY_UNIT); - -#ifdef CONFIG_FTSDC010_SDIO -			/* sdio allow non-power-of-2 blksz */ -			if (fifo & 3) { -				unsigned int n = fifo & 3; -				unsigned int data = readl(&host->reg->dwr); - -				unsigned char *p = (unsigned char *)ptr; - -				while (n--) { -					*p++ = data; -					data >>= 8; -				} -			} -#endif -		} else { -			udelay(1); -			if (++retry >= FTSDC010_PIO_RETRY) { -				debug("%s: PIO_RETRY timeout\n", __func__); -				return; -			} -		} -	} -} - -static void ftsdc010_pio_write(struct mmc_host *host, const char *buf, -			unsigned int size) -{ -	unsigned int fifo; -	unsigned int *ptr; -	unsigned int status; -	unsigned int retry = 0; - -	/* get data buffer */ -	ptr = (unsigned int *)buf; - -	while (size) { -		status = readl(&host->reg->status); - -		if (status & FTSDC010_STATUS_FIFO_URUN) { -			fifo = host->fifo_len > size ? -				size : host->fifo_len; - -			size -= fifo; - -			fifo = (fifo + 3) >> 2; - -			while (fifo--) { -				writel(*ptr, &host->reg->dwr); -				ptr++; -			} -		} else { -			udelay(1); -			if (++retry >= FTSDC010_PIO_RETRY) { -				debug("%s: PIO_RETRY timeout\n", __func__); -				return; -			} -		} -	} -} - -static int ftsdc010_check_rsp(struct mmc *mmc, struct mmc_cmd *cmd, -			struct mmc_data *data) -{ -	struct mmc_host *host = mmc->priv; -	unsigned int sta, clear; - -	sta = readl(&host->reg->status); -	debug("%s: sta: %08x cmd %d\n", __func__, sta, cmd->cmdidx); - -	/* check RSP TIMEOUT or FAIL */ -	if (sta & FTSDC010_STATUS_RSP_TIMEOUT) { -		/* RSP TIMEOUT */ -		debug("%s: RSP timeout: sta: %08x\n", __func__, sta); - -		clear |= FTSDC010_CLR_RSP_TIMEOUT; -		writel(clear, &host->reg->clr); - -		return TIMEOUT; -	} else if (sta & FTSDC010_STATUS_RSP_CRC_FAIL) { -		/* clear response fail bit */ -		debug("%s: RSP CRC FAIL: sta: %08x\n", __func__, sta); - -		clear |= FTSDC010_CLR_RSP_CRC_FAIL; -		writel(clear, &host->reg->clr); - -		return COMM_ERR; -	} else if (sta & FTSDC010_STATUS_RSP_CRC_OK) { - -		/* clear response CRC OK bit */ -		clear |= FTSDC010_CLR_RSP_CRC_OK; -	} - -	writel(clear, &host->reg->clr); -	return 0; -} - -static int ftsdc010_check_data(struct mmc *mmc, struct mmc_cmd *cmd, -			struct mmc_data *data) -{ -	struct mmc_host *host = mmc->priv; -	unsigned int sta, clear; - -	sta = readl(&host->reg->status); -	debug("%s: sta: %08x cmd %d\n", __func__, sta, cmd->cmdidx); - -	/* check DATA TIMEOUT or FAIL */ -	if (data) { - -		/* Transfer Complete */ -		if (sta & FTSDC010_STATUS_DATA_END) -			clear |= FTSDC010_STATUS_DATA_END; - -		/* Data CRC_OK */ -		if (sta & FTSDC010_STATUS_DATA_CRC_OK) -			clear |= FTSDC010_STATUS_DATA_CRC_OK; - -		/* DATA TIMEOUT or DATA CRC FAIL */ -		if (sta & FTSDC010_STATUS_DATA_TIMEOUT) { -			/* DATA TIMEOUT */ -			debug("%s: DATA TIMEOUT: sta: %08x\n", __func__, sta); - -			clear |= FTSDC010_STATUS_DATA_TIMEOUT; -			writel(clear, &host->reg->clr); - -			return TIMEOUT; -		} else if (sta & FTSDC010_STATUS_DATA_CRC_FAIL) { -			/* DATA CRC FAIL */ -			debug("%s: DATA CRC FAIL: sta: %08x\n", __func__, sta); - -			clear |= FTSDC010_STATUS_DATA_CRC_FAIL; -			writel(clear, &host->reg->clr); - -			return COMM_ERR; -		} -		writel(clear, &host->reg->clr); -	} -	return 0; -} - -static int ftsdc010_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, -			struct mmc_data *data) -{ -	struct mmc_host *host = mmc->priv; - -#ifdef CONFIG_FTSDC010_SDIO -	unsigned int scon; -#endif -	unsigned int ccon; -	unsigned int mask, tmpmask; -	unsigned int ret; -	unsigned int sta, i; - -	ret = 0; - -	if (data) -		mask = FTSDC010_INT_MASK_RSP_TIMEOUT; -	else if (cmd->resp_type & MMC_RSP_PRESENT) -		mask = FTSDC010_INT_MASK_RSP_TIMEOUT; -	else -		mask = FTSDC010_INT_MASK_CMD_SEND; - -	/* write argu reg */ -	debug("%s: argu: %08x\n", __func__, host->reg->argu); -	writel(cmd->cmdarg, &host->reg->argu); - -	/* setup commnad */ -	ccon = FTSDC010_CMD_IDX(cmd->cmdidx); - -	/* setup command flags */ -	ccon |= FTSDC010_CMD_CMD_EN; - -	/* -	 * This hardware didn't support specific commands for mapping -	 * MMC_RSP_BUSY and MMC_RSP_OPCODE. Hence we don't deal with it. -	 */ -	if (cmd->resp_type & MMC_RSP_PRESENT) { -		ccon |= FTSDC010_CMD_NEED_RSP; -		mask |= FTSDC010_INT_MASK_RSP_CRC_OK | -			FTSDC010_INT_MASK_RSP_CRC_FAIL; -	} - -	if (cmd->resp_type & MMC_RSP_136) -		ccon |= FTSDC010_CMD_LONG_RSP; - -	/* In Linux driver, MMC_CMD_APP_CMD is checked in last_opcode */ -	if (host->last_opcode == MMC_CMD_APP_CMD) -		ccon |= FTSDC010_CMD_APP_CMD; - -#ifdef CONFIG_FTSDC010_SDIO -	scon = readl(&host->reg->sdio_ctrl1); -	if (host->card_type == MMC_TYPE_SDIO) -		scon |= FTSDC010_SDIO_CTRL1_SDIO_ENABLE; -	else -		scon &= ~FTSDC010_SDIO_CTRL1_SDIO_ENABLE; -	writel(scon, &host->reg->sdio_ctrl1); -#endif - -	/* record last opcode for specifing the command type to hardware */ -	host->last_opcode = cmd->cmdidx; - -	/* write int_mask reg */ -	tmpmask = readl(&host->reg->int_mask); -	tmpmask |= mask; -	writel(tmpmask, &host->reg->int_mask); - -	/* write cmd reg */ -	debug("%s: ccon: %08x\n", __func__, ccon); -	writel(ccon, &host->reg->cmd); - -	/* check CMD_SEND */ -	for (i = 0; i < FTSDC010_CMD_RETRY; i++) { -		/* -		 * If we read status register too fast -		 * will lead hardware error and the RSP_TIMEOUT -		 * flag will be raised incorrectly. -		 */ -		udelay(16*FTSDC010_DELAY_UNIT); -		sta = readl(&host->reg->status); - -		/* Command Complete */ -		/* -		 * Note: -		 *	Do not clear FTSDC010_CLR_CMD_SEND flag. -		 *	(by writing FTSDC010_CLR_CMD_SEND bit to clear register) -		 *	It will make the driver becomes very slow. -		 *	If the operation hasn't been finished, hardware will -		 *	clear this bit automatically. -		 *	In origin, the driver will clear this flag if there is -		 *	no data need to be read. -		 */ -		if (sta & FTSDC010_STATUS_CMD_SEND) -			break; -	} - -	if (i > FTSDC010_CMD_RETRY) { -		printf("%s: send command timeout\n", __func__); -		return TIMEOUT; -	} - -	/* check rsp status */ -	ret = ftsdc010_check_rsp(mmc, cmd, data); -	if (ret) -		return ret; - -	/* read response if we have RSP_OK */ -	if (ccon & FTSDC010_CMD_LONG_RSP) { -		cmd->response[0] = readl(&host->reg->rsp3); -		cmd->response[1] = readl(&host->reg->rsp2); -		cmd->response[2] = readl(&host->reg->rsp1); -		cmd->response[3] = readl(&host->reg->rsp0); -	} else { -		cmd->response[0] = readl(&host->reg->rsp0); -	} - -	/* read/write data */ -	if (data && (data->flags & MMC_DATA_READ)) { -		ftsdc010_pio_read(host, data->dest, -				data->blocksize * data->blocks); -	} else if (data && (data->flags & MMC_DATA_WRITE)) { -		ftsdc010_pio_write(host, data->src, -				data->blocksize * data->blocks); -	} - -	/* check data status */ -	if (data) { -		ret = ftsdc010_check_data(mmc, cmd, data); -		if (ret) -			return ret; -	} - -	udelay(FTSDC010_DELAY_UNIT); -	return ret; -} - -static unsigned int cal_blksz(unsigned int blksz) -{ -	unsigned int blksztwo = 0; - -	while (blksz >>= 1) -		blksztwo++; - -	return blksztwo; -} - -static int ftsdc010_setup_data(struct mmc *mmc, struct mmc_data *data) -{ -	struct mmc_host *host = mmc->priv; -	unsigned int dcon, newmask; - -	/* configure data transfer paramter */ -	if (!data) -		return 0; - -	if (((data->blocksize - 1) & data->blocksize) != 0) { -		printf("%s: can't do non-power-of 2 sized block transfers" -			" (blksz %d)\n", __func__, data->blocksize); -		return -1; -	} - -	/* -	 * We cannot deal with unaligned blocks with more than -	 * one block being transfered. -	 */ -	if ((data->blocksize <= 2) && (data->blocks > 1)) { -			printf("%s: can't do non-word sized block transfers" -				" (blksz %d)\n", __func__, data->blocksize); -			return -1; -	} - -	/* data length */ -	dcon = data->blocksize * data->blocks; -	writel(dcon, &host->reg->dlr); - -	/* write data control */ -	dcon = cal_blksz(data->blocksize); - -	/* add to IMASK register */ -	newmask = (FTSDC010_STATUS_RSP_CRC_FAIL | FTSDC010_STATUS_DATA_TIMEOUT); - -	/* -	 * enable UNDERRUN will trigger interrupt immediatedly -	 * So setup it when rsp is received successfully -	 */ -	if (data->flags & MMC_DATA_WRITE) { -		dcon |= FTSDC010_DCR_DATA_WRITE; -	} else { -		dcon &= ~FTSDC010_DCR_DATA_WRITE; -		newmask |= FTSDC010_STATUS_FIFO_ORUN; -	} -	enable_imask(host->reg, newmask); - -#ifdef CONFIG_FTSDC010_SDIO -	/* always reset fifo since last transfer may fail */ -	dcon |= FTSDC010_DCR_FIFO_RST; - -	if (data->blocks > 1) -		dcon |= FTSDC010_SDIO_CTRL1_SDIO_BLK_MODE; -#endif - -	/* enable data transfer which will be pended until cmd is send */ -	dcon |= FTSDC010_DCR_DATA_EN; -	writel(dcon, &host->reg->dcr); - -	return 0; -} - -static int ftsdc010_send_request(struct mmc *mmc, struct mmc_cmd *cmd, -			struct mmc_data *data) -{ -	int ret; - -	if (data) { -		ret = ftsdc010_setup_data(mmc, data); - -		if (ret) { -			printf("%s: setup data error\n", __func__); -			return -1; -		} - -		if ((data->flags & MMC_DATA_BOTH_DIR) == MMC_DATA_BOTH_DIR) { -			printf("%s: data is both direction\n", __func__); -			return -1; -		} -	} - -	/* Send command */ -	ret = ftsdc010_send_cmd(mmc, cmd, data); -	return ret; -} - -static int ftsdc010_card_detect(struct mmc *mmc) -{ -	struct mmc_host *host = mmc->priv; -	unsigned int sta; - -	sta = readl(&host->reg->status); -	debug("%s: card status: %08x\n", __func__, sta); - -	return (sta & FTSDC010_STATUS_CARD_DETECT) ? 0 : 1; -} - -static int ftsdc010_request(struct mmc *mmc, struct mmc_cmd *cmd, -			struct mmc_data *data) -{ -	int ret; - -	if (ftsdc010_card_detect(mmc) == 0) { -		printf("%s: no medium present\n", __func__); -		return -1; -	} else { -		ret = ftsdc010_send_request(mmc, cmd, data); -		return ret; -	} -} - -static void ftsdc010_set_clk(struct mmc *mmc) -{ -	struct mmc_host *host = mmc->priv; -	unsigned char clk_div; -	unsigned int real_rate; -	unsigned int clock; - -	debug("%s: mmc_set_clock: %x\n", __func__, mmc->clock); -	clock = readl(&host->reg->ccr); - -	if (mmc->clock == 0) { -		real_rate = 0; -		clock |= FTSDC010_CCR_CLK_DIS; -	} else { -		debug("%s, mmc->clock: %08x, origin clock: %08x\n", -			 __func__, mmc->clock, clock); - -		for (clk_div = 0; clk_div <= 127; clk_div++) { -			real_rate = (CONFIG_SYS_CLK_FREQ / 2) / -					(2 * (clk_div + 1)); - -			if (real_rate <= mmc->clock) -				break; -		} - -		debug("%s: computed real_rate: %x, clk_div: %x\n", -			 __func__, real_rate, clk_div); - -		if (clk_div > 127) -			debug("%s: no match clock rate, %x\n", -				__func__, mmc->clock); - -		clock = (clock & ~FTSDC010_CCR_CLK_DIV(0x7f)) | -				FTSDC010_CCR_CLK_DIV(clk_div); - -		clock &= ~FTSDC010_CCR_CLK_DIS; -	} - -	debug("%s, set clock: %08x\n", __func__, clock); -	writel(clock, &host->reg->ccr); -} - -static void ftsdc010_set_ios(struct mmc *mmc) -{ -	struct mmc_host *host = mmc->priv; -	unsigned int power; -	unsigned long val; -	unsigned int bus_width; - -	debug("%s: bus_width: %x, clock: %d\n", -		__func__, mmc->bus_width, mmc->clock); - -	/* set pcr: power on */ -	power = readl(&host->reg->pcr); -	power |= FTSDC010_PCR_POWER_ON; -	writel(power, &host->reg->pcr); - -	if (mmc->clock) -		ftsdc010_set_clk(mmc); - -	/* set bwr: bus width reg */ -	bus_width = readl(&host->reg->bwr); -	bus_width &= ~(FTSDC010_BWR_WIDE_8_BUS | FTSDC010_BWR_WIDE_4_BUS | -			FTSDC010_BWR_SINGLE_BUS); - -	if (mmc->bus_width == 8) -		bus_width |= FTSDC010_BWR_WIDE_8_BUS; -	else if (mmc->bus_width == 4) -		bus_width |= FTSDC010_BWR_WIDE_4_BUS; -	else -		bus_width |= FTSDC010_BWR_SINGLE_BUS; - -	writel(bus_width, &host->reg->bwr); - -	/* set fifo depth */ -	val = readl(&host->reg->feature); -	host->fifo_len = FTSDC010_FEATURE_FIFO_DEPTH(val) * 4; /* 4 bytes */ - -	/* set data timeout register */ -	val = -1; -	writel(val, &host->reg->dtr); -} - -static void ftsdc010_reset(struct mmc_host *host) -{ -	unsigned int timeout; -	unsigned int sta; - -	/* Do SDC_RST: Software reset for all register */ -	writel(FTSDC010_CMD_SDC_RST, &host->reg->cmd); - -	host->clock = 0; - -	/* this hardware has no reset finish flag to read */ -	/* wait 100ms maximum */ -	timeout = 100; - -	/* hw clears the bit when it's done */ -	while (readl(&host->reg->dtr) != 0) { -		if (timeout == 0) { -			printf("%s: reset timeout error\n", __func__); -			return; -		} -		timeout--; -		udelay(10*FTSDC010_DELAY_UNIT); -	} - -	sta = readl(&host->reg->status); -	if (sta & FTSDC010_STATUS_CARD_CHANGE) -		writel(FTSDC010_CLR_CARD_CHANGE, &host->reg->clr); -} - -static int ftsdc010_core_init(struct mmc *mmc) -{ -	struct mmc_host *host = mmc->priv; -	unsigned int mask; -	unsigned int major, minor, revision; - -	/* get hardware version */ -	host->version = readl(&host->reg->rev); - -	major = FTSDC010_REV_MAJOR(host->version); -	minor = FTSDC010_REV_MINOR(host->version); -	revision = FTSDC010_REV_REVISION(host->version); - -	printf("ftsdc010 hardware ver: %d_%d_r%d\n", major, minor, revision); - -	/* Interrupt MASK register init - mask all */ -	writel(0x0, &host->reg->int_mask); - -	mask = FTSDC010_INT_MASK_CMD_SEND | -		FTSDC010_INT_MASK_DATA_END | -		FTSDC010_INT_MASK_CARD_CHANGE; -#ifdef CONFIG_FTSDC010_SDIO -	mask |= FTSDC010_INT_MASK_CP_READY | -		FTSDC010_INT_MASK_CP_BUF_READY | -		FTSDC010_INT_MASK_PLAIN_TEXT_READY | -		FTSDC010_INT_MASK_SDIO_IRPT; -#endif - -	writel(mask, &host->reg->int_mask); - -	return 0; -} - -int ftsdc010_mmc_init(int dev_index) -{ -	struct mmc *mmc; -	struct mmc_host *host; - -	mmc = &ftsdc010_dev[dev_index]; - -	sprintf(mmc->name, "FTSDC010 SD/MMC"); -	mmc->priv = &ftsdc010_host[dev_index]; -	mmc->send_cmd = ftsdc010_request; -	mmc->set_ios = ftsdc010_set_ios; -	mmc->init = ftsdc010_core_init; -	mmc->getcd = NULL; -	mmc->getwp = NULL; - -	mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; - -	mmc->host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT; - -	mmc->f_min = CONFIG_SYS_CLK_FREQ / 2 / (2*128); -	mmc->f_max = CONFIG_SYS_CLK_FREQ / 2 / 2; - -	ftsdc010_host[dev_index].clock = 0; -	ftsdc010_host[dev_index].reg = ftsdc010_get_base_mmc(dev_index); -	mmc_register(mmc); - -	/* reset mmc */ -	host = (struct mmc_host *)mmc->priv; -	ftsdc010_reset(host); - -	return 0; -} diff --git a/drivers/mmc/ftsdc010_mci.c b/drivers/mmc/ftsdc010_mci.c new file mode 100644 index 000000000..562b14aff --- /dev/null +++ b/drivers/mmc/ftsdc010_mci.c @@ -0,0 +1,377 @@ +/* + * Faraday MMC/SD Host Controller + * + * (C) Copyright 2010 Faraday Technology + * Dante Su <dantesu@faraday-tech.com> + * + * This file is released under the terms of GPL v2 and any later version. + * See the file COPYING in the root directory of the source tree for details. + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> + +#include <asm/io.h> +#include <asm/errno.h> +#include <asm/byteorder.h> +#include <faraday/ftsdc010.h> + +#define CFG_CMD_TIMEOUT (CONFIG_SYS_HZ >> 4) /* 250 ms */ +#define CFG_RST_TIMEOUT CONFIG_SYS_HZ /* 1 sec reset timeout */ + +struct ftsdc010_chip { +	void __iomem *regs; +	uint32_t wprot;   /* write protected (locked) */ +	uint32_t rate;    /* actual SD clock in Hz */ +	uint32_t sclk;    /* FTSDC010 source clock in Hz */ +	uint32_t fifo;    /* fifo depth in bytes */ +	uint32_t acmd; +}; + +static inline int ftsdc010_send_cmd(struct mmc *mmc, struct mmc_cmd *mmc_cmd) +{ +	struct ftsdc010_chip *chip = mmc->priv; +	struct ftsdc010_mmc __iomem *regs = chip->regs; +	int ret = TIMEOUT; +	uint32_t ts, st; +	uint32_t cmd   = FTSDC010_CMD_IDX(mmc_cmd->cmdidx); +	uint32_t arg   = mmc_cmd->cmdarg; +	uint32_t flags = mmc_cmd->resp_type; + +	cmd |= FTSDC010_CMD_CMD_EN; + +	if (chip->acmd) { +		cmd |= FTSDC010_CMD_APP_CMD; +		chip->acmd = 0; +	} + +	if (flags & MMC_RSP_PRESENT) +		cmd |= FTSDC010_CMD_NEED_RSP; + +	if (flags & MMC_RSP_136) +		cmd |= FTSDC010_CMD_LONG_RSP; + +	writel(FTSDC010_STATUS_RSP_MASK | FTSDC010_STATUS_CMD_SEND, +		®s->clr); +	writel(arg, ®s->argu); +	writel(cmd, ®s->cmd); + +	if (!(flags & (MMC_RSP_PRESENT | MMC_RSP_136))) { +		for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { +			if (readl(®s->status) & FTSDC010_STATUS_CMD_SEND) { +				writel(FTSDC010_STATUS_CMD_SEND, ®s->clr); +				ret = 0; +				break; +			} +		} +	} else { +		st = 0; +		for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { +			st = readl(®s->status); +			writel(st & FTSDC010_STATUS_RSP_MASK, ®s->clr); +			if (st & FTSDC010_STATUS_RSP_MASK) +				break; +		} +		if (st & FTSDC010_STATUS_RSP_CRC_OK) { +			if (flags & MMC_RSP_136) { +				mmc_cmd->response[0] = readl(®s->rsp3); +				mmc_cmd->response[1] = readl(®s->rsp2); +				mmc_cmd->response[2] = readl(®s->rsp1); +				mmc_cmd->response[3] = readl(®s->rsp0); +			} else { +				mmc_cmd->response[0] = readl(®s->rsp0); +			} +			ret = 0; +		} else { +			debug("ftsdc010: rsp err (cmd=%d, st=0x%x)\n", +				mmc_cmd->cmdidx, st); +		} +	} + +	if (ret) { +		debug("ftsdc010: cmd timeout (op code=%d)\n", +			mmc_cmd->cmdidx); +	} else if (mmc_cmd->cmdidx == MMC_CMD_APP_CMD) { +		chip->acmd = 1; +	} + +	return ret; +} + +static void ftsdc010_clkset(struct mmc *mmc, uint32_t rate) +{ +	struct ftsdc010_chip *chip = mmc->priv; +	struct ftsdc010_mmc __iomem *regs = chip->regs; +	uint32_t div; + +	for (div = 0; div < 0x7f; ++div) { +		if (rate >= chip->sclk / (2 * (div + 1))) +			break; +	} +	chip->rate = chip->sclk / (2 * (div + 1)); + +	writel(FTSDC010_CCR_CLK_DIV(div), ®s->ccr); + +	if (IS_SD(mmc)) { +		setbits_le32(®s->ccr, FTSDC010_CCR_CLK_SD); + +		if (chip->rate > 25000000) +			setbits_le32(®s->ccr, FTSDC010_CCR_CLK_HISPD); +		else +			clrbits_le32(®s->ccr, FTSDC010_CCR_CLK_HISPD); +	} +} + +static inline int ftsdc010_is_ro(struct mmc *mmc) +{ +	struct ftsdc010_chip *chip = mmc->priv; +	const uint8_t *csd = (const uint8_t *)mmc->csd; + +	return chip->wprot || (csd[1] & 0x30); +} + +static int ftsdc010_wait(struct ftsdc010_mmc __iomem *regs, uint32_t mask) +{ +	int ret = TIMEOUT; +	uint32_t st, ts; + +	for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { +		st = readl(®s->status); +		if (!(st & mask)) +			continue; +		writel(st & mask, ®s->clr); +		ret = 0; +		break; +	} + +	if (ret) +		debug("ftsdc010: wait st(0x%x) timeout\n", mask); + +	return ret; +} + +/* + * u-boot mmc api + */ + +static int ftsdc010_request(struct mmc *mmc, struct mmc_cmd *cmd, +	struct mmc_data *data) +{ +	int ret = UNUSABLE_ERR; +	uint32_t len = 0; +	struct ftsdc010_chip *chip = mmc->priv; +	struct ftsdc010_mmc __iomem *regs = chip->regs; + +	if (data && (data->flags & MMC_DATA_WRITE) && chip->wprot) { +		printf("ftsdc010: the card is write protected!\n"); +		return ret; +	} + +	if (data) { +		uint32_t dcr; + +		len = data->blocksize * data->blocks; + +		/* 1. data disable + fifo reset */ +		writel(FTSDC010_DCR_FIFO_RST, ®s->dcr); + +		/* 2. clear status register */ +		writel(FTSDC010_STATUS_DATA_MASK | FTSDC010_STATUS_FIFO_URUN +			| FTSDC010_STATUS_FIFO_ORUN, ®s->clr); + +		/* 3. data timeout (1 sec) */ +		writel(chip->rate, ®s->dtr); + +		/* 4. data length (bytes) */ +		writel(len, ®s->dlr); + +		/* 5. data enable */ +		dcr = (ffs(data->blocksize) - 1) | FTSDC010_DCR_DATA_EN; +		if (data->flags & MMC_DATA_WRITE) +			dcr |= FTSDC010_DCR_DATA_WRITE; +		writel(dcr, ®s->dcr); +	} + +	ret = ftsdc010_send_cmd(mmc, cmd); +	if (ret) { +		printf("ftsdc010: CMD%d failed\n", cmd->cmdidx); +		return ret; +	} + +	if (!data) +		return ret; + +	if (data->flags & MMC_DATA_WRITE) { +		const uint8_t *buf = (const uint8_t *)data->src; + +		while (len > 0) { +			int wlen; + +			/* wait for tx ready */ +			ret = ftsdc010_wait(regs, FTSDC010_STATUS_FIFO_URUN); +			if (ret) +				break; + +			/* write bytes to ftsdc010 */ +			for (wlen = 0; wlen < len && wlen < chip->fifo; ) { +				writel(*(uint32_t *)buf, ®s->dwr); +				buf  += 4; +				wlen += 4; +			} + +			len -= wlen; +		} + +	} else { +		uint8_t *buf = (uint8_t *)data->dest; + +		while (len > 0) { +			int rlen; + +			/* wait for rx ready */ +			ret = ftsdc010_wait(regs, FTSDC010_STATUS_FIFO_ORUN); +			if (ret) +				break; + +			/* fetch bytes from ftsdc010 */ +			for (rlen = 0; rlen < len && rlen < chip->fifo; ) { +				*(uint32_t *)buf = readl(®s->dwr); +				buf  += 4; +				rlen += 4; +			} + +			len -= rlen; +		} + +	} + +	if (!ret) { +		ret = ftsdc010_wait(regs, +			FTSDC010_STATUS_DATA_END | FTSDC010_STATUS_DATA_ERROR); +	} + +	return ret; +} + +static void ftsdc010_set_ios(struct mmc *mmc) +{ +	struct ftsdc010_chip *chip = mmc->priv; +	struct ftsdc010_mmc __iomem *regs = chip->regs; + +	ftsdc010_clkset(mmc, mmc->clock); + +	clrbits_le32(®s->bwr, FTSDC010_BWR_MODE_MASK); +	switch (mmc->bus_width) { +	case 4: +		setbits_le32(®s->bwr, FTSDC010_BWR_MODE_4BIT); +		break; +	case 8: +		setbits_le32(®s->bwr, FTSDC010_BWR_MODE_8BIT); +		break; +	default: +		setbits_le32(®s->bwr, FTSDC010_BWR_MODE_1BIT); +		break; +	} +} + +static int ftsdc010_init(struct mmc *mmc) +{ +	struct ftsdc010_chip *chip = mmc->priv; +	struct ftsdc010_mmc __iomem *regs = chip->regs; +	uint32_t ts; + +	if (readl(®s->status) & FTSDC010_STATUS_CARD_DETECT) +		return NO_CARD_ERR; + +	if (readl(®s->status) & FTSDC010_STATUS_WRITE_PROT) { +		printf("ftsdc010: write protected\n"); +		chip->wprot = 1; +	} + +	chip->fifo = (readl(®s->feature) & 0xff) << 2; + +	/* 1. chip reset */ +	writel(FTSDC010_CMD_SDC_RST, ®s->cmd); +	for (ts = get_timer(0); get_timer(ts) < CFG_RST_TIMEOUT; ) { +		if (readl(®s->cmd) & FTSDC010_CMD_SDC_RST) +			continue; +		break; +	} +	if (readl(®s->cmd) & FTSDC010_CMD_SDC_RST) { +		printf("ftsdc010: reset failed\n"); +		return UNUSABLE_ERR; +	} + +	/* 2. enter low speed mode (400k card detection) */ +	ftsdc010_clkset(mmc, 400000); + +	/* 3. interrupt disabled */ +	writel(0, ®s->int_mask); + +	return 0; +} + +int ftsdc010_mmc_init(int devid) +{ +	struct mmc *mmc; +	struct ftsdc010_chip *chip; +	struct ftsdc010_mmc __iomem *regs; +#ifdef CONFIG_FTSDC010_BASE_LIST +	uint32_t base_list[] = CONFIG_FTSDC010_BASE_LIST; + +	if (devid < 0 || devid >= ARRAY_SIZE(base_list)) +		return -1; +	regs = (void __iomem *)base_list[devid]; +#else +	regs = (void __iomem *)(CONFIG_FTSDC010_BASE + (devid << 20)); +#endif + +	mmc = malloc(sizeof(struct mmc)); +	if (!mmc) +		return -ENOMEM; +	memset(mmc, 0, sizeof(struct mmc)); + +	chip = malloc(sizeof(struct ftsdc010_chip)); +	if (!chip) { +		free(mmc); +		return -ENOMEM; +	} +	memset(chip, 0, sizeof(struct ftsdc010_chip)); + +	chip->regs = regs; +	mmc->priv  = chip; + +	sprintf(mmc->name, "ftsdc010"); +	mmc->send_cmd  = ftsdc010_request; +	mmc->set_ios   = ftsdc010_set_ios; +	mmc->init      = ftsdc010_init; + +	mmc->host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz; +	switch (readl(®s->bwr) & FTSDC010_BWR_CAPS_MASK) { +	case FTSDC010_BWR_CAPS_4BIT: +		mmc->host_caps |= MMC_MODE_4BIT; +		break; +	case FTSDC010_BWR_CAPS_8BIT: +		mmc->host_caps |= MMC_MODE_4BIT | MMC_MODE_8BIT; +		break; +	default: +		break; +	} + +#ifdef CONFIG_SYS_CLK_FREQ +	chip->sclk = CONFIG_SYS_CLK_FREQ; +#else +	chip->sclk = clk_get_rate("SDC"); +#endif + +	mmc->voltages  = MMC_VDD_32_33 | MMC_VDD_33_34; +	mmc->f_max     = chip->sclk / 2; +	mmc->f_min     = chip->sclk / 0x100; +	mmc->block_dev.part_type = PART_TYPE_DOS; + +	mmc_register(mmc); + +	return 0; +} |