diff options
| author | Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> | 2011-07-04 22:21:22 +0000 | 
|---|---|---|
| committer | Andy Fleming <afleming@freescale.com> | 2011-07-15 20:29:21 -0500 | 
| commit | afb35666da107ccac076b30aed1ad90e7bbcffae (patch) | |
| tree | 21b83fb58836d09fed4a786e8c9aad5f3df50621 /drivers/mmc/sh_mmcif.c | |
| parent | 639b7827d1caf28801775e222fe1d1dedece58f0 (diff) | |
| download | olio-uboot-2014.01-afb35666da107ccac076b30aed1ad90e7bbcffae.tar.xz olio-uboot-2014.01-afb35666da107ccac076b30aed1ad90e7bbcffae.zip | |
mmc: sh_mmcif: add support for Renesas MMCIF
Some Renesas SuperH have MMCIF module. This driver supports it.
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Acked-by: Andy Fleming <afleming@freescale.com>
Diffstat (limited to 'drivers/mmc/sh_mmcif.c')
| -rw-r--r-- | drivers/mmc/sh_mmcif.c | 608 | 
1 files changed, 608 insertions, 0 deletions
| diff --git a/drivers/mmc/sh_mmcif.c b/drivers/mmc/sh_mmcif.c new file mode 100644 index 000000000..567e2cb61 --- /dev/null +++ b/drivers/mmc/sh_mmcif.c @@ -0,0 +1,608 @@ +/* + * MMCIF driver. + * + * Copyright (C)  2011 Renesas Solutions Corp. + * + * 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. + */ + +#include <config.h> +#include <common.h> +#include <watchdog.h> +#include <command.h> +#include <mmc.h> +#include <malloc.h> +#include <asm/errno.h> +#include <asm/io.h> +#include "sh_mmcif.h" + +#define DRIVER_NAME	"sh_mmcif" + +static void *mmc_priv(struct mmc *mmc) +{ +	return (void *)mmc->priv; +} + +static int sh_mmcif_intr(void *dev_id) +{ +	struct sh_mmcif_host *host = dev_id; +	u32 state = 0; + +	state = sh_mmcif_read(&host->regs->ce_int); +	state &= sh_mmcif_read(&host->regs->ce_int_mask); + +	if (state & INT_RBSYE) { +		sh_mmcif_write(~(INT_RBSYE | INT_CRSPE), &host->regs->ce_int); +		sh_mmcif_bitclr(MASK_MRBSYE, &host->regs->ce_int_mask); +		goto end; +	} else if (state & INT_CRSPE) { +		sh_mmcif_write(~INT_CRSPE, &host->regs->ce_int); +		sh_mmcif_bitclr(MASK_MCRSPE, &host->regs->ce_int_mask); +		/* one more interrupt (INT_RBSYE) */ +		if (sh_mmcif_read(&host->regs->ce_cmd_set) & CMD_SET_RBSY) +			return -EAGAIN; +		goto end; +	} else if (state & INT_BUFREN) { +		sh_mmcif_write(~INT_BUFREN, &host->regs->ce_int); +		sh_mmcif_bitclr(MASK_MBUFREN, &host->regs->ce_int_mask); +		goto end; +	} else if (state & INT_BUFWEN) { +		sh_mmcif_write(~INT_BUFWEN, &host->regs->ce_int); +		sh_mmcif_bitclr(MASK_MBUFWEN, &host->regs->ce_int_mask); +		goto end; +	} else if (state & INT_CMD12DRE) { +		sh_mmcif_write(~(INT_CMD12DRE | INT_CMD12RBE | INT_CMD12CRE | +				  INT_BUFRE), &host->regs->ce_int); +		sh_mmcif_bitclr(MASK_MCMD12DRE, &host->regs->ce_int_mask); +		goto end; +	} else if (state & INT_BUFRE) { +		sh_mmcif_write(~INT_BUFRE, &host->regs->ce_int); +		sh_mmcif_bitclr(MASK_MBUFRE, &host->regs->ce_int_mask); +		goto end; +	} else if (state & INT_DTRANE) { +		sh_mmcif_write(~INT_DTRANE, &host->regs->ce_int); +		sh_mmcif_bitclr(MASK_MDTRANE, &host->regs->ce_int_mask); +		goto end; +	} else if (state & INT_CMD12RBE) { +		sh_mmcif_write(~(INT_CMD12RBE | INT_CMD12CRE), +				&host->regs->ce_int); +		sh_mmcif_bitclr(MASK_MCMD12RBE, &host->regs->ce_int_mask); +		goto end; +	} else if (state & INT_ERR_STS) { +		/* err interrupts */ +		sh_mmcif_write(~state, &host->regs->ce_int); +		sh_mmcif_bitclr(state, &host->regs->ce_int_mask); +		goto err; +	} else +		return -EAGAIN; + +err: +	host->sd_error = 1; +	debug("%s: int err state = %08x\n", DRIVER_NAME, state); +end: +	host->wait_int = 1; +	return 0; +} + +static int mmcif_wait_interrupt_flag(struct sh_mmcif_host *host) +{ +	int timeout = 10000000; + +	while (1) { +		timeout--; +		if (timeout < 0) { +			printf("timeout\n"); +			return 0; +		} + +		if (!sh_mmcif_intr(host)) +			break; + +		udelay(1);	/* 1 usec */ +	} + +	return 1;	/* Return value: NOT 0 = complete waiting */ +} + +static void sh_mmcif_clock_control(struct sh_mmcif_host *host, unsigned int clk) +{ +	int i; + +	sh_mmcif_bitclr(CLK_ENABLE, &host->regs->ce_clk_ctrl); +	sh_mmcif_bitclr(CLK_CLEAR, &host->regs->ce_clk_ctrl); + +	if (!clk) +		return; +	if (clk == CLKDEV_EMMC_DATA) { +		sh_mmcif_bitset(CLK_PCLK, &host->regs->ce_clk_ctrl); +	} else { +		for (i = 1; (unsigned int)host->clk / (1 << i) >= clk; i++) +			; +		sh_mmcif_bitset((i - 1) << 16, &host->regs->ce_clk_ctrl); +	} +	sh_mmcif_bitset(CLK_ENABLE, &host->regs->ce_clk_ctrl); +} + +static void sh_mmcif_sync_reset(struct sh_mmcif_host *host) +{ +	u32 tmp; + +	tmp = sh_mmcif_read(&host->regs->ce_clk_ctrl) & (CLK_ENABLE | +							 CLK_CLEAR); + +	sh_mmcif_write(SOFT_RST_ON, &host->regs->ce_version); +	sh_mmcif_write(SOFT_RST_OFF, &host->regs->ce_version); +	sh_mmcif_bitset(tmp | SRSPTO_256 | SRBSYTO_29 | SRWDTO_29 | SCCSTO_29, +			&host->regs->ce_clk_ctrl); +	/* byte swap on */ +	sh_mmcif_bitset(BUF_ACC_ATYP, &host->regs->ce_buf_acc); +} + +static int sh_mmcif_error_manage(struct sh_mmcif_host *host) +{ +	u32 state1, state2; +	int ret, timeout = 10000000; + +	host->sd_error = 0; +	host->wait_int = 0; + +	state1 = sh_mmcif_read(&host->regs->ce_host_sts1); +	state2 = sh_mmcif_read(&host->regs->ce_host_sts2); +	debug("%s: ERR HOST_STS1 = %08x\n", \ +			DRIVER_NAME, sh_mmcif_read(&host->regs->ce_host_sts1)); +	debug("%s: ERR HOST_STS2 = %08x\n", \ +			DRIVER_NAME, sh_mmcif_read(&host->regs->ce_host_sts2)); + +	if (state1 & STS1_CMDSEQ) { +		debug("%s: Forced end of command sequence\n", DRIVER_NAME); +		sh_mmcif_bitset(CMD_CTRL_BREAK, &host->regs->ce_cmd_ctrl); +		sh_mmcif_bitset(~CMD_CTRL_BREAK, &host->regs->ce_cmd_ctrl); +		while (1) { +			timeout--; +			if (timeout < 0) { +				printf(DRIVER_NAME": Forceed end of " \ +					"command sequence timeout err\n"); +				return -EILSEQ; +			} +			if (!(sh_mmcif_read(&host->regs->ce_host_sts1) +								& STS1_CMDSEQ)) +				break; +		} +		sh_mmcif_sync_reset(host); +		return -EILSEQ; +	} + +	if (state2 & STS2_CRC_ERR) +		ret = -EILSEQ; +	else if (state2 & STS2_TIMEOUT_ERR) +		ret = TIMEOUT; +	else +		ret = -EILSEQ; +	return ret; +} + +static int sh_mmcif_single_read(struct sh_mmcif_host *host, +				struct mmc_data *data) +{ +	long time; +	u32 blocksize, i; +	unsigned long *p = (unsigned long *)data->dest; + +	if ((unsigned long)p & 0x00000001) { +		printf("%s: The data pointer is unaligned.", __func__); +		return -EIO; +	} + +	host->wait_int = 0; + +	/* buf read enable */ +	sh_mmcif_bitset(MASK_MBUFREN, &host->regs->ce_int_mask); +	time = mmcif_wait_interrupt_flag(host); +	if (time == 0 || host->sd_error != 0) +		return sh_mmcif_error_manage(host); + +	host->wait_int = 0; +	blocksize = (BLOCK_SIZE_MASK & +			sh_mmcif_read(&host->regs->ce_block_set)) + 3; +	for (i = 0; i < blocksize / 4; i++) +		*p++ = sh_mmcif_read(&host->regs->ce_data); + +	/* buffer read end */ +	sh_mmcif_bitset(MASK_MBUFRE, &host->regs->ce_int_mask); +	time = mmcif_wait_interrupt_flag(host); +	if (time == 0 || host->sd_error != 0) +		return sh_mmcif_error_manage(host); + +	host->wait_int = 0; +	return 0; +} + +static int sh_mmcif_multi_read(struct sh_mmcif_host *host, +				struct mmc_data *data) +{ +	long time; +	u32 blocksize, i, j; +	unsigned long *p = (unsigned long *)data->dest; + +	if ((unsigned long)p & 0x00000001) { +		printf("%s: The data pointer is unaligned.", __func__); +		return -EIO; +	} + +	host->wait_int = 0; +	blocksize = BLOCK_SIZE_MASK & sh_mmcif_read(&host->regs->ce_block_set); +	for (j = 0; j < data->blocks; j++) { +		sh_mmcif_bitset(MASK_MBUFREN, &host->regs->ce_int_mask); +		time = mmcif_wait_interrupt_flag(host); +		if (time == 0 || host->sd_error != 0) +			return sh_mmcif_error_manage(host); + +		host->wait_int = 0; +		for (i = 0; i < blocksize / 4; i++) +			*p++ = sh_mmcif_read(&host->regs->ce_data); + +		WATCHDOG_RESET(); +	} +	return 0; +} + +static int sh_mmcif_single_write(struct sh_mmcif_host *host, +				 struct mmc_data *data) +{ +	long time; +	u32 blocksize, i; +	const unsigned long *p = (unsigned long *)data->dest; + +	if ((unsigned long)p & 0x00000001) { +		printf("%s: The data pointer is unaligned.", __func__); +		return -EIO; +	} + +	host->wait_int = 0; +	sh_mmcif_bitset(MASK_MBUFWEN, &host->regs->ce_int_mask); + +	time = mmcif_wait_interrupt_flag(host); +	if (time == 0 || host->sd_error != 0) +		return sh_mmcif_error_manage(host); + +	host->wait_int = 0; +	blocksize = (BLOCK_SIZE_MASK & +			sh_mmcif_read(&host->regs->ce_block_set)) + 3; +	for (i = 0; i < blocksize / 4; i++) +		sh_mmcif_write(*p++, &host->regs->ce_data); + +	/* buffer write end */ +	sh_mmcif_bitset(MASK_MDTRANE, &host->regs->ce_int_mask); + +	time = mmcif_wait_interrupt_flag(host); +	if (time == 0 || host->sd_error != 0) +		return sh_mmcif_error_manage(host); + +	host->wait_int = 0; +	return 0; +} + +static int sh_mmcif_multi_write(struct sh_mmcif_host *host, +				struct mmc_data *data) +{ +	long time; +	u32 i, j, blocksize; +	const unsigned long *p = (unsigned long *)data->dest; + +	if ((unsigned long)p & 0x00000001) { +		printf("%s: The data pointer is unaligned.", __func__); +		return -EIO; +	} + +	host->wait_int = 0; +	blocksize = BLOCK_SIZE_MASK & sh_mmcif_read(&host->regs->ce_block_set); +	for (j = 0; j < data->blocks; j++) { +		sh_mmcif_bitset(MASK_MBUFWEN, &host->regs->ce_int_mask); + +		time = mmcif_wait_interrupt_flag(host); + +		if (time == 0 || host->sd_error != 0) +			return sh_mmcif_error_manage(host); + +		host->wait_int = 0; +		for (i = 0; i < blocksize / 4; i++) +			sh_mmcif_write(*p++, &host->regs->ce_data); + +		WATCHDOG_RESET(); +	} +	return 0; +} + +static void sh_mmcif_get_response(struct sh_mmcif_host *host, +					struct mmc_cmd *cmd) +{ +	if (cmd->resp_type & MMC_RSP_136) { +		cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp3); +		cmd->response[1] = sh_mmcif_read(&host->regs->ce_resp2); +		cmd->response[2] = sh_mmcif_read(&host->regs->ce_resp1); +		cmd->response[3] = sh_mmcif_read(&host->regs->ce_resp0); +		debug(" RESP %08x, %08x, %08x, %08x\n", cmd->response[0], +			 cmd->response[1], cmd->response[2], cmd->response[3]); +	} else { +		cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp0); +	} +} + +static void sh_mmcif_get_cmd12response(struct sh_mmcif_host *host, +					struct mmc_cmd *cmd) +{ +	cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp_cmd12); +} + +static u32 sh_mmcif_set_cmd(struct sh_mmcif_host *host, +				struct mmc_data *data, struct mmc_cmd *cmd) +{ +	u32 tmp = 0; +	u32 opc = cmd->cmdidx; + +	/* Response Type check */ +	switch (cmd->resp_type) { +	case MMC_RSP_NONE: +		tmp |= CMD_SET_RTYP_NO; +		break; +	case MMC_RSP_R1: +	case MMC_RSP_R1b: +	case MMC_RSP_R3: +		tmp |= CMD_SET_RTYP_6B; +		break; +	case MMC_RSP_R2: +		tmp |= CMD_SET_RTYP_17B; +		break; +	default: +		printf(DRIVER_NAME": Not support type response.\n"); +		break; +	} + +	/* RBSY */ +	if (opc == MMC_CMD_SWITCH) +		tmp |= CMD_SET_RBSY; + +	/* WDAT / DATW */ +	if (host->data) { +		tmp |= CMD_SET_WDAT; +		switch (host->bus_width) { +		case MMC_BUS_WIDTH_1: +			tmp |= CMD_SET_DATW_1; +			break; +		case MMC_BUS_WIDTH_4: +			tmp |= CMD_SET_DATW_4; +			break; +		case MMC_BUS_WIDTH_8: +			tmp |= CMD_SET_DATW_8; +			break; +		default: +			printf(DRIVER_NAME": Not support bus width.\n"); +			break; +		} +	} +	/* DWEN */ +	if (opc == MMC_CMD_WRITE_SINGLE_BLOCK || +	    opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) +		tmp |= CMD_SET_DWEN; +	/* CMLTE/CMD12EN */ +	if (opc == MMC_CMD_READ_MULTIPLE_BLOCK || +	    opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) { +		tmp |= CMD_SET_CMLTE | CMD_SET_CMD12EN; +		sh_mmcif_bitset(data->blocks << 16, &host->regs->ce_block_set); +	} +	/* RIDXC[1:0] check bits */ +	if (opc == MMC_CMD_SEND_OP_COND || opc == MMC_CMD_ALL_SEND_CID || +	    opc == MMC_CMD_SEND_CSD || opc == MMC_CMD_SEND_CID) +		tmp |= CMD_SET_RIDXC_BITS; +	/* RCRC7C[1:0] check bits */ +	if (opc == MMC_CMD_SEND_OP_COND) +		tmp |= CMD_SET_CRC7C_BITS; +	/* RCRC7C[1:0] internal CRC7 */ +	if (opc == MMC_CMD_ALL_SEND_CID || +		opc == MMC_CMD_SEND_CSD || opc == MMC_CMD_SEND_CID) +		tmp |= CMD_SET_CRC7C_INTERNAL; + +	return opc = ((opc << 24) | tmp); +} + +static u32 sh_mmcif_data_trans(struct sh_mmcif_host *host, +				struct mmc_data *data, u16 opc) +{ +	u32 ret; + +	switch (opc) { +	case MMC_CMD_READ_MULTIPLE_BLOCK: +		ret = sh_mmcif_multi_read(host, data); +		break; +	case MMC_CMD_WRITE_MULTIPLE_BLOCK: +		ret = sh_mmcif_multi_write(host, data); +		break; +	case MMC_CMD_WRITE_SINGLE_BLOCK: +		ret = sh_mmcif_single_write(host, data); +		break; +	case MMC_CMD_READ_SINGLE_BLOCK: +	case MMC_CMD_SEND_EXT_CSD: +		ret = sh_mmcif_single_read(host, data); +		break; +	default: +		printf(DRIVER_NAME": NOT SUPPORT CMD = d'%08d\n", opc); +		ret = -EINVAL; +		break; +	} +	return ret; +} + +static int sh_mmcif_start_cmd(struct sh_mmcif_host *host, +				struct mmc_data *data, struct mmc_cmd *cmd) +{ +	long time; +	int ret = 0, mask = 0; +	u32 opc = cmd->cmdidx; + +	if (opc == MMC_CMD_STOP_TRANSMISSION) { +		/* MMCIF sends the STOP command automatically */ +		if (host->last_cmd == MMC_CMD_READ_MULTIPLE_BLOCK) +			sh_mmcif_bitset(MASK_MCMD12DRE, +					&host->regs->ce_int_mask); +		else +			sh_mmcif_bitset(MASK_MCMD12RBE, +					&host->regs->ce_int_mask); + +		time = mmcif_wait_interrupt_flag(host); +		if (time == 0 || host->sd_error != 0) +			return sh_mmcif_error_manage(host); + +		sh_mmcif_get_cmd12response(host, cmd); +		return 0; +	} +	if (opc == MMC_CMD_SWITCH) +		mask = MASK_MRBSYE; +	else +		mask = MASK_MCRSPE; + +	mask |=	MASK_MCMDVIO | MASK_MBUFVIO | MASK_MWDATERR | +		MASK_MRDATERR | MASK_MRIDXERR | MASK_MRSPERR | +		MASK_MCCSTO | MASK_MCRCSTO | MASK_MWDATTO | +		MASK_MRDATTO | MASK_MRBSYTO | MASK_MRSPTO; + +	if (host->data) { +		sh_mmcif_write(0, &host->regs->ce_block_set); +		sh_mmcif_write(data->blocksize, &host->regs->ce_block_set); +	} +	opc = sh_mmcif_set_cmd(host, data, cmd); + +	sh_mmcif_write(INT_START_MAGIC, &host->regs->ce_int); +	sh_mmcif_write(mask, &host->regs->ce_int_mask); + +	debug("CMD%d ARG:%08x\n", cmd->cmdidx, cmd->cmdarg); +	/* set arg */ +	sh_mmcif_write(cmd->cmdarg, &host->regs->ce_arg); +	host->wait_int = 0; +	/* set cmd */ +	sh_mmcif_write(opc, &host->regs->ce_cmd_set); + +	time = mmcif_wait_interrupt_flag(host); +	if (time == 0) +		return sh_mmcif_error_manage(host); + +	if (host->sd_error) { +		switch (cmd->cmdidx) { +		case MMC_CMD_ALL_SEND_CID: +		case MMC_CMD_SELECT_CARD: +		case MMC_CMD_APP_CMD: +			ret = TIMEOUT; +			break; +		default: +			printf(DRIVER_NAME": Cmd(d'%d) err\n", cmd->cmdidx); +			ret = sh_mmcif_error_manage(host); +			break; +		} +		host->sd_error = 0; +		host->wait_int = 0; +		return ret; +	} + +	/* if no response */ +	if (!(opc & 0x00C00000)) +		return 0; + +	if (host->wait_int == 1) { +		sh_mmcif_get_response(host, cmd); +		host->wait_int = 0; +	} +	if (host->data) +		ret = sh_mmcif_data_trans(host, data, cmd->cmdidx); +	host->last_cmd = cmd->cmdidx; + +	return ret; +} + +static int sh_mmcif_request(struct mmc *mmc, struct mmc_cmd *cmd, +			    struct mmc_data *data) +{ +	struct sh_mmcif_host *host = mmc_priv(mmc); +	int ret; + +	WATCHDOG_RESET(); + +	switch (cmd->cmdidx) { +	case MMC_CMD_APP_CMD: +		return TIMEOUT; +	case MMC_CMD_SEND_EXT_CSD: /* = SD_SEND_IF_COND (8) */ +		if (data) +			/* ext_csd */ +			break; +		else +			/* send_if_cond cmd (not support) */ +			return TIMEOUT; +	default: +		break; +	} +	host->sd_error = 0; +	host->data = data; +	ret = sh_mmcif_start_cmd(host, data, cmd); +	host->data = NULL; + +	return ret; +} + +static void sh_mmcif_set_ios(struct mmc *mmc) +{ +	struct sh_mmcif_host *host = mmc_priv(mmc); + +	if (mmc->clock) +		sh_mmcif_clock_control(host, mmc->clock); + +	if (mmc->bus_width == 8) +		host->bus_width = MMC_BUS_WIDTH_8; +	else if (mmc->bus_width == 4) +		host->bus_width = MMC_BUS_WIDTH_4; +	else +		host->bus_width = MMC_BUS_WIDTH_1; + +	debug("clock = %d, buswidth = %d\n", mmc->clock, mmc->bus_width); +} + +static int sh_mmcif_init(struct mmc *mmc) +{ +	struct sh_mmcif_host *host = mmc_priv(mmc); + +	sh_mmcif_sync_reset(host); +	sh_mmcif_write(MASK_ALL, &host->regs->ce_int_mask); +	return 0; +} + +int mmcif_mmc_init(void) +{ +	int ret = 0; +	struct mmc *mmc; +	struct sh_mmcif_host *host = NULL; + +	mmc = malloc(sizeof(struct mmc)); +	if (!mmc) +		ret = -ENOMEM; +	memset(mmc, 0, sizeof(*mmc)); +	host = malloc(sizeof(struct sh_mmcif_host)); +	if (!host) +		ret = -ENOMEM; +	memset(host, 0, sizeof(*host)); + +	mmc->f_min = CLKDEV_MMC_INIT; +	mmc->f_max = CLKDEV_EMMC_DATA; +	mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; +	mmc->host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_4BIT | +			 MMC_MODE_8BIT; +	memcpy(mmc->name, DRIVER_NAME, sizeof(DRIVER_NAME)); +	mmc->send_cmd = sh_mmcif_request; +	mmc->set_ios = sh_mmcif_set_ios; +	mmc->init = sh_mmcif_init; +	host->regs = (struct sh_mmcif_regs *)CONFIG_SH_MMCIF_ADDR; +	host->clk = CONFIG_SH_MMCIF_CLK; +	mmc->priv = host; + +	mmc_register(mmc); + +	return ret; +} |