diff options
| author | Jaehoon Chung <jh80.chung@samsung.com> | 2012-10-15 19:10:29 +0000 | 
|---|---|---|
| committer | Andy Fleming <afleming@freescale.com> | 2012-10-22 02:56:25 -0500 | 
| commit | 757bff49ba3159d71ccacabdb68f8309b1eb6613 (patch) | |
| tree | e786063d85ecf6fbf7fbdc73d190137e92db3366 | |
| parent | 48cf9dc63c9353febf8b2c27ff20f0d6dc56d6b6 (diff) | |
| download | olio-uboot-2014.01-757bff49ba3159d71ccacabdb68f8309b1eb6613.tar.xz olio-uboot-2014.01-757bff49ba3159d71ccacabdb68f8309b1eb6613.zip | |
mmc: dw-mmc: support DesignWare MMC Controller
Support the DesginWare MMC Controller.
Signed-off-by: Jaehoon Chung <jh80.chung@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Rajeshawari Shinde <rajeshwari.s@samsung.com>
Signed-off-by: Andy Fleming <afleming@freescale.com>
| -rw-r--r-- | drivers/mmc/Makefile | 1 | ||||
| -rw-r--r-- | drivers/mmc/dw_mmc.c | 385 | ||||
| -rw-r--r-- | include/dwmmc.h | 191 | 
3 files changed, 577 insertions, 0 deletions
| diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index ac3cb0b1e..a1dd7302b 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -46,6 +46,7 @@ COBJS-$(CONFIG_SDHCI) += sdhci.o  COBJS-$(CONFIG_S5P_SDHCI) += s5p_sdhci.o  COBJS-$(CONFIG_SH_MMCIF) += sh_mmcif.o  COBJS-$(CONFIG_TEGRA_MMC) += tegra_mmc.o +COBJS-$(CONFIG_DWMMC) += dw_mmc.o  COBJS	:= $(COBJS-y)  SRCS	:= $(COBJS:.o=.c) diff --git a/drivers/mmc/dw_mmc.c b/drivers/mmc/dw_mmc.c new file mode 100644 index 000000000..4070d4ea5 --- /dev/null +++ b/drivers/mmc/dw_mmc.c @@ -0,0 +1,385 @@ +/* + * (C) Copyright 2012 SAMSUNG Electronics + * Jaehoon Chung <jh80.chung@samsung.com> + * Rajeshawari Shinde <rajeshwari.s@samsung.com> + * + * 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 <common.h> +#include <malloc.h> +#include <mmc.h> +#include <dwmmc.h> +#include <asm/arch/clk.h> +#include <asm-generic/errno.h> + +#define PAGE_SIZE 4096 + +static int dwmci_wait_reset(struct dwmci_host *host, u32 value) +{ +	unsigned long timeout = 1000; +	u32 ctrl; + +	dwmci_writel(host, DWMCI_CTRL, value); + +	while (timeout--) { +		ctrl = dwmci_readl(host, DWMCI_CTRL); +		if (!(ctrl & DWMCI_RESET_ALL)) +			return 1; +	} +	return 0; +} + +static void dwmci_set_idma_desc(struct dwmci_idmac *idmac, +		u32 desc0, u32 desc1, u32 desc2) +{ +	struct dwmci_idmac *desc = idmac; + +	desc->flags = desc0; +	desc->cnt = desc1; +	desc->addr = desc2; +	desc->next_addr = (unsigned int)desc + sizeof(struct dwmci_idmac); +} + +static void dwmci_prepare_data(struct dwmci_host *host, +		struct mmc_data *data) +{ +	unsigned long ctrl; +	unsigned int i = 0, flags, cnt, blk_cnt; +	ulong data_start, data_end, start_addr; +	ALLOC_CACHE_ALIGN_BUFFER(struct dwmci_idmac, cur_idmac, data->blocks); + + +	blk_cnt = data->blocks; + +	dwmci_wait_reset(host, DWMCI_CTRL_FIFO_RESET); + +	data_start = (ulong)cur_idmac; +	dwmci_writel(host, DWMCI_DBADDR, (unsigned int)cur_idmac); + +	if (data->flags == MMC_DATA_READ) +		start_addr = (unsigned int)data->dest; +	else +		start_addr = (unsigned int)data->src; + +	do { +		flags = DWMCI_IDMAC_OWN | DWMCI_IDMAC_CH ; +		flags |= (i == 0) ? DWMCI_IDMAC_FS : 0; +		if (blk_cnt <= 8) { +			flags |= DWMCI_IDMAC_LD; +			cnt = data->blocksize * blk_cnt; +		} else +			cnt = data->blocksize * 8; + +		dwmci_set_idma_desc(cur_idmac, flags, cnt, +				start_addr + (i * PAGE_SIZE)); + +		if(blk_cnt < 8) +			break; +		blk_cnt -= 8; +		cur_idmac++; +		i++; +	} while(1); + +	data_end = (ulong)cur_idmac; +	flush_dcache_range(data_start, data_end + ARCH_DMA_MINALIGN); + +	ctrl = dwmci_readl(host, DWMCI_CTRL); +	ctrl |= DWMCI_IDMAC_EN | DWMCI_DMA_EN; +	dwmci_writel(host, DWMCI_CTRL, ctrl); + +	ctrl = dwmci_readl(host, DWMCI_BMOD); +	ctrl |= DWMCI_BMOD_IDMAC_FB | DWMCI_BMOD_IDMAC_EN; +	dwmci_writel(host, DWMCI_BMOD, ctrl); + +	dwmci_writel(host, DWMCI_BLKSIZ, data->blocksize); +	dwmci_writel(host, DWMCI_BYTCNT, data->blocksize * data->blocks); +} + +static int dwmci_set_transfer_mode(struct dwmci_host *host, +		struct mmc_data *data) +{ +	unsigned long mode; + +	mode = DWMCI_CMD_DATA_EXP; +	if (data->flags & MMC_DATA_WRITE) +		mode |= DWMCI_CMD_RW; + +	return mode; +} + +static int dwmci_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, +		struct mmc_data *data) +{ +	struct dwmci_host *host = (struct dwmci_host *)mmc->priv; +	int flags = 0, i; +	unsigned int timeout = 100000; +	u32 retry = 10000; +	u32 mask, ctrl; + +	while (dwmci_readl(host, DWMCI_STATUS) & DWMCI_BUSY) { +		if (timeout == 0) { +			printf("Timeout on data busy\n"); +			return TIMEOUT; +		} +		timeout--; +	} + +	dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMSK_ALL); + +	if (data) +		dwmci_prepare_data(host, data); + + +	dwmci_writel(host, DWMCI_CMDARG, cmd->cmdarg); + +	if (data) +		flags = dwmci_set_transfer_mode(host, data); + +	if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) +		return -1; + +	if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) +		flags |= DWMCI_CMD_ABORT_STOP; +	else +		flags |= DWMCI_CMD_PRV_DAT_WAIT; + +	if (cmd->resp_type & MMC_RSP_PRESENT) { +		flags |= DWMCI_CMD_RESP_EXP; +		if (cmd->resp_type & MMC_RSP_136) +			flags |= DWMCI_CMD_RESP_LENGTH; +	} + +	if (cmd->resp_type & MMC_RSP_CRC) +		flags |= DWMCI_CMD_CHECK_CRC; + +	flags |= (cmd->cmdidx | DWMCI_CMD_START | DWMCI_CMD_USE_HOLD_REG); + +	debug("Sending CMD%d\n",cmd->cmdidx); + +	dwmci_writel(host, DWMCI_CMD, flags); + +	for (i = 0; i < retry; i++) { +		mask = dwmci_readl(host, DWMCI_RINTSTS); +		if (mask & DWMCI_INTMSK_CDONE) { +			if (!data) +				dwmci_writel(host, DWMCI_RINTSTS, mask); +			break; +		} +	} + +	if (i == retry) +		return TIMEOUT; + +	if (mask & DWMCI_INTMSK_RTO) { +		debug("Response Timeout..\n"); +		return TIMEOUT; +	} else if (mask & DWMCI_INTMSK_RE) { +		debug("Response Error..\n"); +		return -1; +	} + + +	if (cmd->resp_type & MMC_RSP_PRESENT) { +		if (cmd->resp_type & MMC_RSP_136) { +			cmd->response[0] = dwmci_readl(host, DWMCI_RESP3); +			cmd->response[1] = dwmci_readl(host, DWMCI_RESP2); +			cmd->response[2] = dwmci_readl(host, DWMCI_RESP1); +			cmd->response[3] = dwmci_readl(host, DWMCI_RESP0); +		} else { +			cmd->response[0] = dwmci_readl(host, DWMCI_RESP0); +		} +	} + +	if (data) { +		do { +			mask = dwmci_readl(host, DWMCI_RINTSTS); +			if (mask & (DWMCI_DATA_ERR | DWMCI_DATA_TOUT)) { +				debug("DATA ERROR!\n"); +				return -1; +			} +		} while (!(mask & DWMCI_INTMSK_DTO)); + +		dwmci_writel(host, DWMCI_RINTSTS, mask); + +		ctrl = dwmci_readl(host, DWMCI_CTRL); +		ctrl &= ~(DWMCI_DMA_EN); +		dwmci_writel(host, DWMCI_CTRL, ctrl); +	} + +	udelay(100); + +	return 0; +} + +static int dwmci_setup_bus(struct dwmci_host *host, u32 freq) +{ +	u32 div, status; +	int timeout = 10000; +	unsigned long sclk; + +	if (freq == host->clock) +		return 0; + +	/* +	 * If host->mmc_clk didn't define, +	 * then assume that host->bus_hz is source clock value. +	 * host->bus_hz should be set from user. +	 */ +	if (host->mmc_clk) +		sclk = host->mmc_clk(host->dev_index); +	else if (host->bus_hz) +		sclk = host->bus_hz; +	else { +		printf("Didn't get source clock value..\n"); +		return -EINVAL; +	} + +	div = DIV_ROUND_UP(sclk, 2 * freq); + +	dwmci_writel(host, DWMCI_CLKENA, 0); +	dwmci_writel(host, DWMCI_CLKSRC, 0); + +	dwmci_writel(host, DWMCI_CLKDIV, div); +	dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_PRV_DAT_WAIT | +			DWMCI_CMD_UPD_CLK | DWMCI_CMD_START); + +	do { +		status = dwmci_readl(host, DWMCI_CMD); +		if (timeout-- < 0) { +			printf("TIMEOUT error!!\n"); +			return -ETIMEDOUT; +		} +	} while (status & DWMCI_CMD_START); + +	dwmci_writel(host, DWMCI_CLKENA, DWMCI_CLKEN_ENABLE | +			DWMCI_CLKEN_LOW_PWR); + +	dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_PRV_DAT_WAIT | +			DWMCI_CMD_UPD_CLK | DWMCI_CMD_START); + +	timeout = 10000; +	do { +		status = dwmci_readl(host, DWMCI_CMD); +		if (timeout-- < 0) { +			printf("TIMEOUT error!!\n"); +			return -ETIMEDOUT; +		} +	} while (status & DWMCI_CMD_START); + +	host->clock = freq; + +	return 0; +} + +static void dwmci_set_ios(struct mmc *mmc) +{ +	struct dwmci_host *host = (struct dwmci_host *)mmc->priv; +	u32 ctype; + +	debug("Buswidth = %d, clock: %d\n",mmc->bus_width, mmc->clock); + +	dwmci_setup_bus(host, mmc->clock); +	switch (mmc->bus_width) { +	case 8: +		ctype = DWMCI_CTYPE_8BIT; +		break; +	case 4: +		ctype = DWMCI_CTYPE_4BIT; +		break; +	default: +		ctype = DWMCI_CTYPE_1BIT; +		break; +	} + +	dwmci_writel(host, DWMCI_CTYPE, ctype); + +	if (host->clksel) +		host->clksel(host); +} + +static int dwmci_init(struct mmc *mmc) +{ +	struct dwmci_host *host = (struct dwmci_host *)mmc->priv; +	u32 fifo_size, fifoth_val; + +	dwmci_writel(host, DWMCI_PWREN, 1); + +	if (!dwmci_wait_reset(host, DWMCI_RESET_ALL)) { +		debug("%s[%d] Fail-reset!!\n",__func__,__LINE__); +		return -1; +	} + +	dwmci_writel(host, DWMCI_RINTSTS, 0xFFFFFFFF); +	dwmci_writel(host, DWMCI_INTMASK, 0); + +	dwmci_writel(host, DWMCI_TMOUT, 0xFFFFFFFF); + +	dwmci_writel(host, DWMCI_IDINTEN, 0); +	dwmci_writel(host, DWMCI_BMOD, 1); + +	fifo_size = dwmci_readl(host, DWMCI_FIFOTH); +	if (host->fifoth_val) +		fifoth_val = host->fifoth_val; +	else +		fifoth_val = MSIZE(0x2) | RX_WMARK(fifo_size/2 -1) | +			TX_WMARK(fifo_size/2); +	dwmci_writel(host, DWMCI_FIFOTH, fifoth_val); + +	dwmci_writel(host, DWMCI_CLKENA, 0); +	dwmci_writel(host, DWMCI_CLKSRC, 0); + +	return 0; +} + +int add_dwmci(struct dwmci_host *host, u32 max_clk, u32 min_clk) +{ +	struct mmc *mmc; +	int err = 0; + +	mmc = malloc(sizeof(struct mmc)); +	if (!mmc) { +		printf("mmc malloc fail!\n"); +		return -1; +	} + +	mmc->priv = host; +	host->mmc = mmc; + +	sprintf(mmc->name, "%s", host->name); +	mmc->send_cmd = dwmci_send_cmd; +	mmc->set_ios = dwmci_set_ios; +	mmc->init = dwmci_init; +	mmc->f_min = min_clk; +	mmc->f_max = max_clk; + +	mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + +	mmc->host_caps = host->caps; + +	if (host->buswidth == 8) { +		mmc->host_caps |= MMC_MODE_8BIT; +		mmc->host_caps &= ~MMC_MODE_4BIT; +	} else { +		mmc->host_caps |= MMC_MODE_4BIT; +		mmc->host_caps &= ~MMC_MODE_8BIT; +	} +	mmc->host_caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_HC; + +	err = mmc_register(mmc); + +	return err; +} diff --git a/include/dwmmc.h b/include/dwmmc.h new file mode 100644 index 000000000..c8b1d408e --- /dev/null +++ b/include/dwmmc.h @@ -0,0 +1,191 @@ +/* + * (C) Copyright 2012 SAMSUNG Electronics + * Jaehoon Chung <jh80.chung@samsung.com> + * + * 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 + * + */ + +#ifndef __DWMMC_HW_H +#define __DWMMC_HW_H + +#include <asm/io.h> +#include <mmc.h> + +#define DWMCI_CTRL		0x000 +#define	DWMCI_PWREN		0x004 +#define DWMCI_CLKDIV		0x008 +#define DWMCI_CLKSRC		0x00C +#define DWMCI_CLKENA		0x010 +#define DWMCI_TMOUT		0x014 +#define DWMCI_CTYPE		0x018 +#define DWMCI_BLKSIZ		0x01C +#define DWMCI_BYTCNT		0x020 +#define DWMCI_INTMASK		0x024 +#define DWMCI_CMDARG		0x028 +#define DWMCI_CMD		0x02C +#define DWMCI_RESP0		0x030 +#define DWMCI_RESP1		0x034 +#define DWMCI_RESP2		0x038 +#define DWMCI_RESP3		0x03C +#define DWMCI_MINTSTS		0x040 +#define DWMCI_RINTSTS		0x044 +#define DWMCI_STATUS		0x048 +#define DWMCI_FIFOTH		0x04C +#define DWMCI_CDETECT		0x050 +#define DWMCI_WRTPRT		0x054 +#define DWMCI_GPIO		0x058 +#define DWMCI_TCMCNT		0x05C +#define DWMCI_TBBCNT		0x060 +#define DWMCI_DEBNCE		0x064 +#define DWMCI_USRID		0x068 +#define DWMCI_VERID		0x06C +#define DWMCI_HCON		0x070 +#define DWMCI_UHS_REG		0x074 +#define DWMCI_BMOD		0x080 +#define DWMCI_PLDMND		0x084 +#define DWMCI_DBADDR		0x088 +#define DWMCI_IDSTS		0x08C +#define DWMCI_IDINTEN		0x090 +#define DWMCI_DSCADDR		0x094 +#define DWMCI_BUFADDR		0x098 +#define DWMCI_DATA		0x200 + +/* Interrupt Mask register */ +#define DWMCI_INTMSK_ALL	0xffffffff +#define DWMCI_INTMSK_RE		(1 << 1) +#define DWMCI_INTMSK_CDONE	(1 << 2) +#define DWMCI_INTMSK_DTO	(1 << 3) +#define DWMCI_INTMSK_TXDR	(1 << 4) +#define DWMCI_INTMSK_RXDR	(1 << 5) +#define DWMCI_INTMSK_DCRC	(1 << 7) +#define DWMCI_INTMSK_RTO	(1 << 8) +#define DWMCI_INTMSK_DRTO	(1 << 9) +#define DWMCI_INTMSK_HTO	(1 << 10) +#define DWMCI_INTMSK_FRUN	(1 << 11) +#define DWMCI_INTMSK_HLE	(1 << 12) +#define DWMCI_INTMSK_SBE	(1 << 13) +#define DWMCI_INTMSK_ACD	(1 << 14) +#define DWMCI_INTMSK_EBE	(1 << 15) + +/* Raw interrupt Regsiter */ +#define DWMCI_DATA_ERR	(DWMCI_INTMSK_EBE | DWMCI_INTMSK_SBE | DWMCI_INTMSK_HLE |\ +			DWMCI_INTMSK_FRUN | DWMCI_INTMSK_EBE | DWMCI_INTMSK_DCRC) +#define DWMCI_DATA_TOUT	(DWMCI_INTMSK_HTO | DWMCI_INTMSK_DRTO) +/* CTRL register */ +#define DWMCI_CTRL_RESET	(1 << 0) +#define DWMCI_CTRL_FIFO_RESET	(1 << 1) +#define DWMCI_CTRL_DMA_RESET	(1 << 2) +#define DWMCI_DMA_EN		(1 << 5) +#define DWMCI_CTRL_SEND_AS_CCSD	(1 << 10) +#define DWMCI_IDMAC_EN		(1 << 25) +#define DWMCI_RESET_ALL		(DWMCI_CTRL_RESET | DWMCI_CTRL_FIFO_RESET |\ +				DWMCI_CTRL_DMA_RESET) + +/* CMD register */ +#define DWMCI_CMD_RESP_EXP	(1 << 6) +#define DWMCI_CMD_RESP_LENGTH	(1 << 7) +#define DWMCI_CMD_CHECK_CRC	(1 << 8) +#define DWMCI_CMD_DATA_EXP	(1 << 9) +#define DWMCI_CMD_RW		(1 << 10) +#define DWMCI_CMD_SEND_STOP	(1 << 12) +#define DWMCI_CMD_ABORT_STOP	(1 << 14) +#define DWMCI_CMD_PRV_DAT_WAIT	(1 << 13) +#define DWMCI_CMD_UPD_CLK	(1 << 21) +#define DWMCI_CMD_USE_HOLD_REG	(1 << 29) +#define DWMCI_CMD_START		(1 << 31) + +/* CLKENA register */ +#define DWMCI_CLKEN_ENABLE	(1 << 0) +#define DWMCI_CLKEN_LOW_PWR	(1 << 16) + +/* Card-type registe */ +#define DWMCI_CTYPE_1BIT	0 +#define DWMCI_CTYPE_4BIT	(1 << 0) +#define DWMCI_CTYPE_8BIT	(1 << 16) + +/* Status Register */ +#define DWMCI_BUSY		(1 << 9) + +/* FIFOTH Register */ +#define MSIZE(x)		((x) << 28) +#define RX_WMARK(x)		((x) << 16) +#define TX_WMARK(x)		(x) + +#define DWMCI_IDMAC_OWN		(1 << 31) +#define DWMCI_IDMAC_CH		(1 << 4) +#define DWMCI_IDMAC_FS		(1 << 3) +#define DWMCI_IDMAC_LD		(1 << 2) + +/*  Bus Mode Register */ +#define DWMCI_BMOD_IDMAC_RESET	(1 << 0) +#define DWMCI_BMOD_IDMAC_FB	(1 << 1) +#define DWMCI_BMOD_IDMAC_EN	(1 << 7) + +struct dwmci_host { +	char *name; +	void *ioaddr; +	unsigned int quirks; +	unsigned int caps; +	unsigned int version; +	unsigned int clock; +	unsigned int bus_hz; +	int dev_index; +	int buswidth; +	u32 fifoth_val; +	struct mmc *mmc; + +	void (*clksel)(struct dwmci_host *host); +	unsigned int (*mmc_clk)(int dev_index); +}; + +struct dwmci_idmac { +	u32 flags; +	u32 cnt; +	u32 addr; +	u32 next_addr; +}; + +static inline void dwmci_writel(struct dwmci_host *host, int reg, u32 val) +{ +	writel(val, host->ioaddr + reg); +} + +static inline void dwmci_writew(struct dwmci_host *host, int reg, u16 val) +{ +	writew(val, host->ioaddr + reg); +} + +static inline void dwmci_writeb(struct dwmci_host *host, int reg, u8 val) +{ +	writeb(val, host->ioaddr + reg); +} +static inline u32 dwmci_readl(struct dwmci_host *host, int reg) +{ +	return readl(host->ioaddr + reg); +} + +static inline u16 dwmci_readw(struct dwmci_host *host, int reg) +{ +	return readw(host->ioaddr + reg); +} + +static inline u8 dwmci_readb(struct dwmci_host *host, int reg) +{ +	return readb(host->ioaddr + reg); +} + +int add_dwmci(struct dwmci_host *host, u32 max_clk, u32 min_clk); +#endif	/* __DWMMC_HW_H */ |