diff options
Diffstat (limited to 'drivers/mmc')
| -rw-r--r-- | drivers/mmc/Makefile | 1 | ||||
| -rw-r--r-- | drivers/mmc/davinci_mmc.c | 404 | 
2 files changed, 405 insertions, 0 deletions
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 68afd30ef..3496f0aa0 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -27,6 +27,7 @@ LIB	:= $(obj)libmmc.o  COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o  COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o +COBJS-$(CONFIG_DAVINCI_MMC) += davinci_mmc.o  COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o  COBJS-$(CONFIG_GENERIC_MMC) += mmc.o  COBJS-$(CONFIG_GENERIC_ATMEL_MCI) += gen_atmel_mci.o diff --git a/drivers/mmc/davinci_mmc.c b/drivers/mmc/davinci_mmc.c new file mode 100644 index 000000000..4e572dc53 --- /dev/null +++ b/drivers/mmc/davinci_mmc.c @@ -0,0 +1,404 @@ +/* + * Davinci MMC Controller Driver + * + * Copyright (C) 2010 Texas Instruments Incorporated + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> +#include <common.h> +#include <command.h> +#include <mmc.h> +#include <part.h> +#include <malloc.h> +#include <asm/io.h> +#include <asm/arch/sdmmc_defs.h> + +#define DAVINCI_MAX_BLOCKS	(32) +#define WATCHDOG_COUNT		(100000) + +#define get_val(addr)		REG(addr) +#define set_val(addr, val)	REG(addr) = (val) +#define set_bit(addr, val)	set_val((addr), (get_val(addr) | (val))) +#define clear_bit(addr, val)	set_val((addr), (get_val(addr) & ~(val))) + +/* Set davinci clock prescalar value based on the required clock in HZ */ +static void dmmc_set_clock(struct mmc *mmc, uint clock) +{ +	struct davinci_mmc *host = mmc->priv; +	struct davinci_mmc_regs *regs = host->reg_base; +	uint clkrt, sysclk2, act_clock; + +	if (clock < mmc->f_min) +		clock = mmc->f_min; +	if (clock > mmc->f_max) +		clock = mmc->f_max; + +	set_val(®s->mmcclk, 0); +	sysclk2 = host->input_clk; +	clkrt = (sysclk2 / (2 * clock)) - 1; + +	/* Calculate the actual clock for the divider used */ +	act_clock = (sysclk2 / (2 * (clkrt + 1))); + +	/* Adjust divider if actual clock exceeds the required clock */ +	if (act_clock > clock) +		clkrt++; + +	/* check clock divider boundary and correct it */ +	if (clkrt > 0xFF) +		clkrt = 0xFF; + +	set_val(®s->mmcclk, (clkrt | MMCCLK_CLKEN)); +} + +/* Status bit wait loop for MMCST1 */ +static int +dmmc_wait_fifo_status(volatile struct davinci_mmc_regs *regs, uint status) +{ +	uint mmcstatus1, wdog = WATCHDOG_COUNT; +	mmcstatus1 = get_val(®s->mmcst1); +	while (--wdog && ((get_val(®s->mmcst1) & status) != status)) +		udelay(10); + +	if (!(get_val(®s->mmcctl) & MMCCTL_WIDTH_4_BIT)) +		udelay(100); + +	if (wdog == 0) +		return COMM_ERR; + +	return 0; +} + +/* Busy bit wait loop for MMCST1 */ +static int dmmc_busy_wait(volatile struct davinci_mmc_regs *regs) +{ +	uint mmcstatus1, wdog = WATCHDOG_COUNT; + +	mmcstatus1 = get_val(®s->mmcst1); +	while (--wdog && (get_val(®s->mmcst1) & MMCST1_BUSY)) +		udelay(10); + +	if (wdog == 0) +		return COMM_ERR; + +	return 0; +} + +/* Status bit wait loop for MMCST0 - Checks for error bits as well */ +static int dmmc_check_status(volatile struct davinci_mmc_regs *regs, +		uint *cur_st, uint st_ready, uint st_error) +{ +	uint wdog = WATCHDOG_COUNT; +	uint mmcstatus = *cur_st; + +	while (wdog--) { +		if (mmcstatus & st_ready) { +			*cur_st = mmcstatus; +			mmcstatus = get_val(®s->mmcst1); +			return 0; +		} else if (mmcstatus & st_error) { +			if (mmcstatus & MMCST0_TOUTRS) +				return TIMEOUT; +			printf("[ ST0 ERROR %x]\n", mmcstatus); +			/* +			 * Ignore CRC errors as some MMC cards fail to +			 * initialize on DM365-EVM on the SD1 slot +			 */ +			if (mmcstatus & MMCST0_CRCRS) +				return 0; +			return COMM_ERR; +		} +		udelay(10); + +		mmcstatus = get_val(®s->mmcst0); +	} + +	printf("Status %x Timeout ST0:%x ST1:%x\n", st_ready, mmcstatus, +			get_val(®s->mmcst1)); +	return COMM_ERR; +} + +/* + * Sends a command out on the bus.  Takes the mmc pointer, + * a command pointer, and an optional data pointer. + */ +static int +dmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) +{ +	struct davinci_mmc *host = mmc->priv; +	volatile struct davinci_mmc_regs *regs = host->reg_base; +	uint mmcstatus, status_rdy, status_err; +	uint i, cmddata, bytes_left = 0; +	int fifo_words, fifo_bytes, err; +	char *data_buf = NULL; + +	/* Clear status registers */ +	mmcstatus = get_val(®s->mmcst0); +	fifo_words = (host->version == MMC_CTLR_VERSION_2) ? 16 : 8; +	fifo_bytes = fifo_words << 2; + +	/* Wait for any previous busy signal to be cleared */ +	dmmc_busy_wait(regs); + +	cmddata = cmd->cmdidx; +	cmddata |= MMCCMD_PPLEN; + +	/* Send init clock for CMD0 */ +	if (cmd->cmdidx == MMC_CMD_GO_IDLE_STATE) +		cmddata |= MMCCMD_INITCK; + +	switch (cmd->resp_type) { +	case MMC_RSP_R1b: +		cmddata |= MMCCMD_BSYEXP; +		/* Fall-through */ +	case MMC_RSP_R1:    /* R1, R1b, R5, R6, R7 */ +		cmddata |= MMCCMD_RSPFMT_R1567; +		break; +	case MMC_RSP_R2: +		cmddata |= MMCCMD_RSPFMT_R2; +		break; +	case MMC_RSP_R3: /* R3, R4 */ +		cmddata |= MMCCMD_RSPFMT_R3; +		break; +	} + +	set_val(®s->mmcim, 0); + +	if (data) { +		/* clear previous data transfer if any and set new one */ +		bytes_left = (data->blocksize * data->blocks); + +		/* Reset FIFO - Always use 32 byte fifo threshold */ +		set_val(®s->mmcfifoctl, +				(MMCFIFOCTL_FIFOLEV | MMCFIFOCTL_FIFORST)); + +		if (host->version == MMC_CTLR_VERSION_2) +			cmddata |= MMCCMD_DMATRIG; + +		cmddata |= MMCCMD_WDATX; +		if (data->flags == MMC_DATA_READ) { +			set_val(®s->mmcfifoctl, MMCFIFOCTL_FIFOLEV); +		} else if (data->flags == MMC_DATA_WRITE) { +			set_val(®s->mmcfifoctl, +					(MMCFIFOCTL_FIFOLEV | +					 MMCFIFOCTL_FIFODIR)); +			cmddata |= MMCCMD_DTRW; +		} + +		set_val(®s->mmctod, 0xFFFF); +		set_val(®s->mmcnblk, (data->blocks & MMCNBLK_NBLK_MASK)); +		set_val(®s->mmcblen, (data->blocksize & MMCBLEN_BLEN_MASK)); + +		if (data->flags == MMC_DATA_WRITE) { +			uint val; +			data_buf = (char *)data->src; +			/* For write, fill FIFO with data before issue of CMD */ +			for (i = 0; (i < fifo_words) && bytes_left; i++) { +				memcpy((char *)&val, data_buf, 4); +				set_val(®s->mmcdxr, val); +				data_buf += 4; +				bytes_left -= 4; +			} +		} +	} else { +		set_val(®s->mmcblen, 0); +		set_val(®s->mmcnblk, 0); +	} + +	set_val(®s->mmctor, 0x1FFF); + +	/* Send the command */ +	set_val(®s->mmcarghl, cmd->cmdarg); +	set_val(®s->mmccmd, cmddata); + +	status_rdy = MMCST0_RSPDNE; +	status_err = (MMCST0_TOUTRS | MMCST0_TOUTRD | +			MMCST0_CRCWR | MMCST0_CRCRD); +	if (cmd->resp_type & MMC_RSP_CRC) +		status_err |= MMCST0_CRCRS; + +	mmcstatus = get_val(®s->mmcst0); +	err = dmmc_check_status(regs, &mmcstatus, status_rdy, status_err); +	if (err) +		return err; + +	/* For R1b wait for busy done */ +	if (cmd->resp_type == MMC_RSP_R1b) +		dmmc_busy_wait(regs); + +	/* Collect response from controller for specific commands */ +	if (mmcstatus & MMCST0_RSPDNE) { +		/* Copy the response to the response buffer */ +		if (cmd->resp_type & MMC_RSP_136) { +			cmd->response[0] = get_val(®s->mmcrsp67); +			cmd->response[1] = get_val(®s->mmcrsp45); +			cmd->response[2] = get_val(®s->mmcrsp23); +			cmd->response[3] = get_val(®s->mmcrsp01); +		} else if (cmd->resp_type & MMC_RSP_PRESENT) { +			cmd->response[0] = get_val(®s->mmcrsp67); +		} +	} + +	if (data == NULL) +		return 0; + +	if (data->flags == MMC_DATA_READ) { +		/* check for DATDNE along with DRRDY as the controller might +		 * set the DATDNE without DRRDY for smaller transfers with +		 * less than FIFO threshold bytes +		 */ +		status_rdy = MMCST0_DRRDY | MMCST0_DATDNE; +		status_err = MMCST0_TOUTRD | MMCST0_CRCRD; +		data_buf = data->dest; +	} else { +		status_rdy = MMCST0_DXRDY | MMCST0_DATDNE; +		status_err = MMCST0_CRCWR; +	} + +	/* Wait until all of the blocks are transferred */ +	while (bytes_left) { +		err = dmmc_check_status(regs, &mmcstatus, status_rdy, +				status_err); +		if (err) +			return err; + +		if (data->flags == MMC_DATA_READ) { +			/* +			 * MMC controller sets the Data receive ready bit +			 * (DRRDY) in MMCST0 even before the entire FIFO is +			 * full. This results in erratic behavior if we start +			 * reading the FIFO soon after DRRDY.  Wait for the +			 * FIFO full bit in MMCST1 for proper FIFO clearing. +			 */ +			if (bytes_left > fifo_bytes) +				dmmc_wait_fifo_status(regs, 0x4a); +			else if (bytes_left == fifo_bytes) +				dmmc_wait_fifo_status(regs, 0x40); + +			for (i = 0; bytes_left && (i < fifo_words); i++) { +				cmddata = get_val(®s->mmcdrr); +				memcpy(data_buf, (char *)&cmddata, 4); +				data_buf += 4; +				bytes_left -= 4; +			} +		} else { +			/* +			 * MMC controller sets the Data transmit ready bit +			 * (DXRDY) in MMCST0 even before the entire FIFO is +			 * empty. This results in erratic behavior if we start +			 * writing the FIFO soon after DXRDY.  Wait for the +			 * FIFO empty bit in MMCST1 for proper FIFO clearing. +			 */ +			dmmc_wait_fifo_status(regs, MMCST1_FIFOEMP); +			for (i = 0; bytes_left && (i < fifo_words); i++) { +				memcpy((char *)&cmddata, data_buf, 4); +				set_val(®s->mmcdxr, cmddata); +				data_buf += 4; +				bytes_left -= 4; +			} +			dmmc_busy_wait(regs); +		} +	} + +	err = dmmc_check_status(regs, &mmcstatus, MMCST0_DATDNE, status_err); +	if (err) +		return err; + +	return 0; +} + +/* Initialize Davinci MMC controller */ +static int dmmc_init(struct mmc *mmc) +{ +	struct davinci_mmc *host = mmc->priv; +	struct davinci_mmc_regs *regs = host->reg_base; + +	/* Clear status registers explicitly - soft reset doesn't clear it +	 * If Uboot is invoked from UBL with SDMMC Support, the status +	 * registers can have uncleared bits +	 */ +	get_val(®s->mmcst0); +	get_val(®s->mmcst1); + +	/* Hold software reset */ +	set_bit(®s->mmcctl, MMCCTL_DATRST); +	set_bit(®s->mmcctl, MMCCTL_CMDRST); +	udelay(10); + +	set_val(®s->mmcclk, 0x0); +	set_val(®s->mmctor, 0x1FFF); +	set_val(®s->mmctod, 0xFFFF); + +	/* Clear software reset */ +	clear_bit(®s->mmcctl, MMCCTL_DATRST); +	clear_bit(®s->mmcctl, MMCCTL_CMDRST); + +	udelay(10); + +	/* Reset FIFO - Always use the maximum fifo threshold */ +	set_val(®s->mmcfifoctl, (MMCFIFOCTL_FIFOLEV | MMCFIFOCTL_FIFORST)); +	set_val(®s->mmcfifoctl, MMCFIFOCTL_FIFOLEV); + +	return 0; +} + +/* Set buswidth or clock as indicated by the GENERIC_MMC framework */ +static void dmmc_set_ios(struct mmc *mmc) +{ +	struct davinci_mmc *host = mmc->priv; +	struct davinci_mmc_regs *regs = host->reg_base; + +	/* Set the bus width */ +	if (mmc->bus_width == 4) +		set_bit(®s->mmcctl, MMCCTL_WIDTH_4_BIT); +	else +		clear_bit(®s->mmcctl, MMCCTL_WIDTH_4_BIT); + +	/* Set clock speed */ +	if (mmc->clock) +		dmmc_set_clock(mmc, mmc->clock); +} + +/* Called from board_mmc_init during startup. Can be called multiple times + * depending on the number of slots available on board and controller + */ +int davinci_mmc_init(bd_t *bis, struct davinci_mmc *host) +{ +	struct mmc *mmc; + +	mmc = malloc(sizeof(struct mmc)); +	memset(mmc, 0, sizeof(struct mmc)); + +	sprintf(mmc->name, "davinci"); +	mmc->priv = host; +	mmc->send_cmd = dmmc_send_cmd; +	mmc->set_ios = dmmc_set_ios; +	mmc->init = dmmc_init; + +	mmc->f_min = 200000; +	mmc->f_max = 25000000; +	mmc->voltages = host->voltages; +	mmc->host_caps = host->host_caps; + +#ifdef CONFIG_MMC_MBLOCK +	mmc->b_max = DAVINCI_MAX_BLOCKS; +#endif +	mmc_register(mmc); + +	return 0; +} +  |