mirror of
				https://xff.cz/git/u-boot/
				synced 2025-10-31 10:26:10 +01:00 
			
		
		
		
	Check for card detect each time an MMC/SD device is initialized. If card detection is not implemented, this code behaves as before and continues assuming a card is present. If no card is detected, has_init is reset for the MMC/SD device (to force initialization next time) and an error is returned. Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de> Tested-by: Jason Liu <jason.hui@linaro.org>
		
			
				
	
	
		
			687 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			687 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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->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;
 | |
| }
 |