diff options
Diffstat (limited to 'drivers/mmc')
-rw-r--r-- | drivers/mmc/Makefile | 2 | ||||
-rw-r--r-- | drivers/mmc/bcm2835_sdhci.c | 2 | ||||
-rw-r--r-- | drivers/mmc/dw_mmc.c | 5 | ||||
-rw-r--r-- | drivers/mmc/gen_atmel_mci.c | 63 | ||||
-rw-r--r-- | drivers/mmc/mvebu_mmc.c | 361 | ||||
-rw-r--r-- | drivers/mmc/s3c_sdi.c | 321 |
6 files changed, 737 insertions, 17 deletions
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 34febf52f0..464cee16d1 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_PXA_MMC_GENERIC) += pxa_mmc_gen.o obj-$(CONFIG_SDHCI) += sdhci.o obj-$(CONFIG_BCM2835_SDHCI) += bcm2835_sdhci.o obj-$(CONFIG_KONA_SDHCI) += kona_sdhci.o +obj-$(CONFIG_S3C_SDI) += s3c_sdi.o obj-$(CONFIG_S5P_SDHCI) += s5p_sdhci.o obj-$(CONFIG_SH_MMCIF) += sh_mmcif.o obj-$(CONFIG_SPEAR_SDHCI) += spear_sdhci.o @@ -37,3 +38,4 @@ obj-$(CONFIG_SPL_MMC_BOOT) += fsl_esdhc_spl.o else obj-$(CONFIG_GENERIC_MMC) += mmc_write.o endif +obj-$(CONFIG_MVEBU_MMC) += mvebu_mmc.o diff --git a/drivers/mmc/bcm2835_sdhci.c b/drivers/mmc/bcm2835_sdhci.c index 54cfabfb91..82079d67cd 100644 --- a/drivers/mmc/bcm2835_sdhci.c +++ b/drivers/mmc/bcm2835_sdhci.c @@ -179,7 +179,7 @@ int bcm2835_sdhci_init(u32 regbase, u32 emmc_freq) host->name = "bcm2835_sdhci"; host->ioaddr = (void *)regbase; host->quirks = SDHCI_QUIRK_BROKEN_VOLTAGE | SDHCI_QUIRK_BROKEN_R1B | - SDHCI_QUIRK_WAIT_SEND_CMD; + SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_NO_HISPD_BIT; host->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; host->ops = &bcm2835_ops; diff --git a/drivers/mmc/dw_mmc.c b/drivers/mmc/dw_mmc.c index 5bf36a0309..0df30bc045 100644 --- a/drivers/mmc/dw_mmc.c +++ b/drivers/mmc/dw_mmc.c @@ -245,7 +245,10 @@ static int dwmci_setup_bus(struct dwmci_host *host, u32 freq) return -EINVAL; } - div = DIV_ROUND_UP(sclk, 2 * freq); + if (sclk == freq) + div = 0; /* bypass mode */ + else + div = DIV_ROUND_UP(sclk, 2 * freq); dwmci_writel(host, DWMCI_CLKENA, 0); dwmci_writel(host, DWMCI_CLKSRC, 0); diff --git a/drivers/mmc/gen_atmel_mci.c b/drivers/mmc/gen_atmel_mci.c index a57a9b1faf..45bcffb6b2 100644 --- a/drivers/mmc/gen_atmel_mci.c +++ b/drivers/mmc/gen_atmel_mci.c @@ -58,30 +58,61 @@ static void mci_set_mode(struct mmc *mmc, u32 hz, u32 blklen) atmel_mci_t *mci = mmc->priv; u32 bus_hz = get_mci_clk_rate(); u32 clkdiv = 255; + unsigned int version = atmel_mci_get_version(mci); + u32 clkodd = 0; + u32 mr; debug("mci: bus_hz is %u, setting clock %u Hz, block size %u\n", bus_hz, hz, blklen); if (hz > 0) { - /* find lowest clkdiv yielding a rate <= than requested */ - for (clkdiv=0; clkdiv<255; clkdiv++) { - if ((bus_hz / (clkdiv+1) / 2) <= hz) - break; + if (version >= 0x500) { + clkdiv = DIV_ROUND_UP(bus_hz, hz) - 2; + if (clkdiv > 511) + clkdiv = 511; + + clkodd = clkdiv & 1; + clkdiv >>= 1; + + printf("mci: setting clock %u Hz, block size %u\n", + bus_hz / (clkdiv * 2 + clkodd + 2), blklen); + } else { + /* find clkdiv yielding a rate <= than requested */ + for (clkdiv = 0; clkdiv < 255; clkdiv++) { + if ((bus_hz / (clkdiv + 1) / 2) <= hz) + break; + } + printf("mci: setting clock %u Hz, block size %u\n", + (bus_hz / (clkdiv + 1)) / 2, blklen); + } } - printf("mci: setting clock %u Hz, block size %u\n", - (bus_hz / (clkdiv+1)) / 2, blklen); blklen &= 0xfffc; - /* On some platforms RDPROOF and WRPROOF are ignored */ - writel((MMCI_BF(CLKDIV, clkdiv) - | MMCI_BF(BLKLEN, blklen) - | MMCI_BIT(RDPROOF) - | MMCI_BIT(WRPROOF)), &mci->mr); + + mr = MMCI_BF(CLKDIV, clkdiv); + + /* MCI IP version >= 0x200 has R/WPROOF */ + if (version >= 0x200) + mr |= MMCI_BIT(RDPROOF) | MMCI_BIT(WRPROOF); + /* - * On some new platforms BLKLEN in mci->mr is ignored. - * Should use the BLKLEN in the block register. + * MCI IP version >= 0x500 use bit 16 as clkodd. + * MCI IP version < 0x500 use upper 16 bits for blklen. */ - writel(MMCI_BF(BLKLEN, blklen), &mci->blkr); + if (version >= 0x500) + mr |= MMCI_BF(CLKODD, clkodd); + else + mr |= MMCI_BF(BLKLEN, blklen); + + writel(mr, &mci->mr); + + /* MCI IP version >= 0x200 has blkr */ + if (version >= 0x200) + writel(MMCI_BF(BLKLEN, blklen), &mci->blkr); + + if (mmc->card_caps & mmc->cfg->host_caps & MMC_MODE_HS) + writel(MMCI_BIT(HSMODE), &mci->cfg); + initialized = 1; } @@ -376,8 +407,10 @@ int atmel_mci_init(void *regs) /* need to be able to pass these in on a board by board basis */ cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; version = atmel_mci_get_version(mci); - if ((version & 0xf00) >= 0x300) + if ((version & 0xf00) >= 0x300) { cfg->host_caps = MMC_MODE_8BIT; + cfg->host_caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz; + } cfg->host_caps |= MMC_MODE_4BIT; diff --git a/drivers/mmc/mvebu_mmc.c b/drivers/mmc/mvebu_mmc.c new file mode 100644 index 0000000000..97591983d2 --- /dev/null +++ b/drivers/mmc/mvebu_mmc.c @@ -0,0 +1,361 @@ +/* + * Marvell MMC/SD/SDIO driver + * + * (C) Copyright 2012 + * Marvell Semiconductor <www.marvell.com> + * Written-by: Maen Suleiman, Gerald Kerma + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <asm/io.h> +#include <asm/arch/cpu.h> +#include <asm/arch/kirkwood.h> +#include <mvebu_mmc.h> + +#define DRIVER_NAME "MVEBU_MMC" + +static void mvebu_mmc_write(u32 offs, u32 val) +{ + writel(val, CONFIG_SYS_MMC_BASE + (offs)); +} + +static u32 mvebu_mmc_read(u32 offs) +{ + return readl(CONFIG_SYS_MMC_BASE + (offs)); +} + +static int mvebu_mmc_setup_data(struct mmc_data *data) +{ + u32 ctrl_reg; + + debug("%s, data %s : blocks=%d blksz=%d\n", DRIVER_NAME, + (data->flags & MMC_DATA_READ) ? "read" : "write", + data->blocks, data->blocksize); + + /* default to maximum timeout */ + ctrl_reg = mvebu_mmc_read(SDIO_HOST_CTRL); + ctrl_reg |= SDIO_HOST_CTRL_TMOUT(SDIO_HOST_CTRL_TMOUT_MAX); + mvebu_mmc_write(SDIO_HOST_CTRL, ctrl_reg); + + if (data->flags & MMC_DATA_READ) { + mvebu_mmc_write(SDIO_SYS_ADDR_LOW, (u32)data->dest & 0xffff); + mvebu_mmc_write(SDIO_SYS_ADDR_HI, (u32)data->dest >> 16); + } else { + mvebu_mmc_write(SDIO_SYS_ADDR_LOW, (u32)data->src & 0xffff); + mvebu_mmc_write(SDIO_SYS_ADDR_HI, (u32)data->src >> 16); + } + + mvebu_mmc_write(SDIO_BLK_COUNT, data->blocks); + mvebu_mmc_write(SDIO_BLK_SIZE, data->blocksize); + + return 0; +} + +static int mvebu_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + int timeout = 10; + ushort waittype = 0; + ushort resptype = 0; + ushort xfertype = 0; + ushort resp_indx = 0; + + debug("cmdidx [0x%x] resp_type[0x%x] cmdarg[0x%x]\n", + cmd->cmdidx, cmd->resp_type, cmd->cmdarg); + + udelay(10*1000); + + debug("%s: cmd %d (hw state 0x%04x)\n", DRIVER_NAME, + cmd->cmdidx, mvebu_mmc_read(SDIO_HW_STATE)); + + /* Checking if card is busy */ + while ((mvebu_mmc_read(SDIO_HW_STATE) & CARD_BUSY)) { + if (timeout == 0) { + printf("%s: card busy!\n", DRIVER_NAME); + return -1; + } + timeout--; + udelay(1000); + } + + /* Set up for a data transfer if we have one */ + if (data) { + int err = mvebu_mmc_setup_data(data); + + if (err) + return err; + } + + resptype = SDIO_CMD_INDEX(cmd->cmdidx); + + /* Analyzing resptype/xfertype/waittype for the command */ + if (cmd->resp_type & MMC_RSP_BUSY) + resptype |= SDIO_CMD_RSP_48BUSY; + else if (cmd->resp_type & MMC_RSP_136) + resptype |= SDIO_CMD_RSP_136; + else if (cmd->resp_type & MMC_RSP_PRESENT) + resptype |= SDIO_CMD_RSP_48; + else + resptype |= SDIO_CMD_RSP_NONE; + + if (cmd->resp_type & MMC_RSP_CRC) + resptype |= SDIO_CMD_CHECK_CMDCRC; + + if (cmd->resp_type & MMC_RSP_OPCODE) + resptype |= SDIO_CMD_INDX_CHECK; + + if (cmd->resp_type & MMC_RSP_PRESENT) { + resptype |= SDIO_UNEXPECTED_RESP; + waittype |= SDIO_NOR_UNEXP_RSP; + } + + if (data) { + resptype |= SDIO_CMD_DATA_PRESENT | SDIO_CMD_CHECK_DATACRC16; + xfertype |= SDIO_XFER_MODE_HW_WR_DATA_EN; + if (data->flags & MMC_DATA_READ) { + xfertype |= SDIO_XFER_MODE_TO_HOST; + waittype = SDIO_NOR_DMA_INI; + } else { + waittype |= SDIO_NOR_XFER_DONE; + } + } else { + waittype |= SDIO_NOR_CMD_DONE; + } + + /* Setting cmd arguments */ + mvebu_mmc_write(SDIO_ARG_LOW, cmd->cmdarg & 0xffff); + mvebu_mmc_write(SDIO_ARG_HI, cmd->cmdarg >> 16); + + /* Setting Xfer mode */ + mvebu_mmc_write(SDIO_XFER_MODE, xfertype); + + mvebu_mmc_write(SDIO_NOR_INTR_STATUS, ~SDIO_NOR_CARD_INT); + mvebu_mmc_write(SDIO_ERR_INTR_STATUS, SDIO_POLL_MASK); + + /* Sending command */ + mvebu_mmc_write(SDIO_CMD, resptype); + + mvebu_mmc_write(SDIO_NOR_INTR_EN, SDIO_POLL_MASK); + mvebu_mmc_write(SDIO_ERR_INTR_EN, SDIO_POLL_MASK); + + /* Waiting for completion */ + timeout = 1000000; + + while (!((mvebu_mmc_read(SDIO_NOR_INTR_STATUS)) & waittype)) { + if (mvebu_mmc_read(SDIO_NOR_INTR_STATUS) & SDIO_NOR_ERROR) { + debug("%s: error! cmdidx : %d, err reg: %04x\n", + DRIVER_NAME, cmd->cmdidx, + mvebu_mmc_read(SDIO_ERR_INTR_STATUS)); + if (mvebu_mmc_read(SDIO_ERR_INTR_STATUS) & + (SDIO_ERR_CMD_TIMEOUT | SDIO_ERR_DATA_TIMEOUT)) + return TIMEOUT; + return COMM_ERR; + } + + timeout--; + udelay(1); + if (timeout <= 0) { + printf("%s: command timed out\n", DRIVER_NAME); + return TIMEOUT; + } + } + + /* Handling response */ + if (cmd->resp_type & MMC_RSP_136) { + uint response[8]; + + for (resp_indx = 0; resp_indx < 8; resp_indx++) + response[resp_indx] + = mvebu_mmc_read(SDIO_RSP(resp_indx)); + + cmd->response[0] = ((response[0] & 0x03ff) << 22) | + ((response[1] & 0xffff) << 6) | + ((response[2] & 0xfc00) >> 10); + cmd->response[1] = ((response[2] & 0x03ff) << 22) | + ((response[3] & 0xffff) << 6) | + ((response[4] & 0xfc00) >> 10); + cmd->response[2] = ((response[4] & 0x03ff) << 22) | + ((response[5] & 0xffff) << 6) | + ((response[6] & 0xfc00) >> 10); + cmd->response[3] = ((response[6] & 0x03ff) << 22) | + ((response[7] & 0x3fff) << 8); + } else if (cmd->resp_type & MMC_RSP_PRESENT) { + uint response[3]; + + for (resp_indx = 0; resp_indx < 3; resp_indx++) + response[resp_indx] + = mvebu_mmc_read(SDIO_RSP(resp_indx)); + + cmd->response[0] = ((response[2] & 0x003f) << (8 - 8)) | + ((response[1] & 0xffff) << (14 - 8)) | + ((response[0] & 0x03ff) << (30 - 8)); + cmd->response[1] = ((response[0] & 0xfc00) >> 10); + cmd->response[2] = 0; + cmd->response[3] = 0; + } + + debug("%s: resp[0x%x] ", DRIVER_NAME, cmd->resp_type); + debug("[0x%x] ", cmd->response[0]); + debug("[0x%x] ", cmd->response[1]); + debug("[0x%x] ", cmd->response[2]); + debug("[0x%x] ", cmd->response[3]); + debug("\n"); + + return 0; +} + +static void mvebu_mmc_power_up(void) +{ + debug("%s: power up\n", DRIVER_NAME); + + /* disable interrupts */ + mvebu_mmc_write(SDIO_NOR_INTR_EN, 0); + mvebu_mmc_write(SDIO_ERR_INTR_EN, 0); + + /* SW reset */ + mvebu_mmc_write(SDIO_SW_RESET, SDIO_SW_RESET_NOW); + + mvebu_mmc_write(SDIO_XFER_MODE, 0); + + /* enable status */ + mvebu_mmc_write(SDIO_NOR_STATUS_EN, SDIO_POLL_MASK); + mvebu_mmc_write(SDIO_ERR_STATUS_EN, SDIO_POLL_MASK); + + /* enable interrupts status */ + mvebu_mmc_write(SDIO_NOR_INTR_STATUS, SDIO_POLL_MASK); + mvebu_mmc_write(SDIO_ERR_INTR_STATUS, SDIO_POLL_MASK); +} + +static void mvebu_mmc_set_clk(unsigned int clock) +{ + unsigned int m; + + if (clock == 0) { + debug("%s: clock off\n", DRIVER_NAME); + mvebu_mmc_write(SDIO_XFER_MODE, SDIO_XFER_MODE_STOP_CLK); + mvebu_mmc_write(SDIO_CLK_DIV, MVEBU_MMC_BASE_DIV_MAX); + } else { + m = MVEBU_MMC_BASE_FAST_CLOCK/(2*clock) - 1; + if (m > MVEBU_MMC_BASE_DIV_MAX) + m = MVEBU_MMC_BASE_DIV_MAX; + mvebu_mmc_write(SDIO_CLK_DIV, m & MVEBU_MMC_BASE_DIV_MAX); + } + + udelay(10*1000); +} + +static void mvebu_mmc_set_bus(unsigned int bus) +{ + u32 ctrl_reg = 0; + + ctrl_reg = mvebu_mmc_read(SDIO_HOST_CTRL); + ctrl_reg &= ~SDIO_HOST_CTRL_DATA_WIDTH_4_BITS; + + switch (bus) { + case 4: + ctrl_reg |= SDIO_HOST_CTRL_DATA_WIDTH_4_BITS; + break; + case 1: + default: + ctrl_reg |= SDIO_HOST_CTRL_DATA_WIDTH_1_BIT; + } + + /* default transfer mode */ + ctrl_reg |= SDIO_HOST_CTRL_BIG_ENDIAN; + ctrl_reg &= ~SDIO_HOST_CTRL_LSB_FIRST; + + /* default to maximum timeout */ + ctrl_reg |= SDIO_HOST_CTRL_TMOUT(SDIO_HOST_CTRL_TMOUT_MAX); + + ctrl_reg |= SDIO_HOST_CTRL_PUSH_PULL_EN; + + ctrl_reg |= SDIO_HOST_CTRL_CARD_TYPE_MEM_ONLY; + + debug("%s: ctrl 0x%04x: %s %s %s\n", DRIVER_NAME, ctrl_reg, + (ctrl_reg & SDIO_HOST_CTRL_PUSH_PULL_EN) ? + "push-pull" : "open-drain", + (ctrl_reg & SDIO_HOST_CTRL_DATA_WIDTH_4_BITS) ? + "4bit-width" : "1bit-width", + (ctrl_reg & SDIO_HOST_CTRL_HI_SPEED_EN) ? + "high-speed" : ""); + + mvebu_mmc_write(SDIO_HOST_CTRL, ctrl_reg); + udelay(10*1000); +} + +static void mvebu_mmc_set_ios(struct mmc *mmc) +{ + debug("%s: bus[%d] clock[%d]\n", DRIVER_NAME, + mmc->bus_width, mmc->clock); + mvebu_mmc_set_bus(mmc->bus_width); + mvebu_mmc_set_clk(mmc->clock); +} + +static int mvebu_mmc_initialize(struct mmc *mmc) +{ + debug("%s: mvebu_mmc_initialize", DRIVER_NAME); + + /* + * Setting host parameters + * Initial Host Ctrl : Timeout : max , Normal Speed mode, + * 4-bit data mode, Big Endian, SD memory Card, Push_pull CMD Line + */ + mvebu_mmc_write(SDIO_HOST_CTRL, + SDIO_HOST_CTRL_TMOUT(SDIO_HOST_CTRL_TMOUT_MAX) | + SDIO_HOST_CTRL_DATA_WIDTH_4_BITS | + SDIO_HOST_CTRL_BIG_ENDIAN | + SDIO_HOST_CTRL_PUSH_PULL_EN | + SDIO_HOST_CTRL_CARD_TYPE_MEM_ONLY); + + mvebu_mmc_write(SDIO_CLK_CTRL, 0); + + /* enable status */ + mvebu_mmc_write(SDIO_NOR_STATUS_EN, SDIO_POLL_MASK); + mvebu_mmc_write(SDIO_ERR_STATUS_EN, SDIO_POLL_MASK); + + /* disable interrupts */ + mvebu_mmc_write(SDIO_NOR_INTR_EN, 0); + mvebu_mmc_write(SDIO_ERR_INTR_EN, 0); + + /* SW reset */ + mvebu_mmc_write(SDIO_SW_RESET, SDIO_SW_RESET_NOW); + + udelay(10*1000); + + return 0; +} + +static const struct mmc_ops mvebu_mmc_ops = { + .send_cmd = mvebu_mmc_send_cmd, + .set_ios = mvebu_mmc_set_ios, + .init = mvebu_mmc_initialize, +}; + +static struct mmc_config mvebu_mmc_cfg = { + .name = DRIVER_NAME, + .ops = &mvebu_mmc_ops, + .f_min = MVEBU_MMC_BASE_FAST_CLOCK / MVEBU_MMC_BASE_DIV_MAX, + .f_max = MVEBU_MMC_CLOCKRATE_MAX, + .voltages = MMC_VDD_32_33 | MMC_VDD_33_34, + .host_caps = MMC_MODE_4BIT | MMC_MODE_HS, + .part_type = PART_TYPE_DOS, + .b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT, +}; + +int mvebu_mmc_init(bd_t *bis) +{ + struct mmc *mmc; + + mvebu_mmc_power_up(); + + mmc = mmc_create(&mvebu_mmc_cfg, bis); + if (mmc == NULL) + return -1; + + return 0; +} diff --git a/drivers/mmc/s3c_sdi.c b/drivers/mmc/s3c_sdi.c new file mode 100644 index 0000000000..1b5b70512d --- /dev/null +++ b/drivers/mmc/s3c_sdi.c @@ -0,0 +1,321 @@ +/* + * S3C24xx SD/MMC driver + * + * Based on OpenMoko S3C24xx driver by Harald Welte <laforge@openmoko.org> + * + * Copyright (C) 2014 Marek Vasut <marex@denx.de> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <malloc.h> +#include <mmc.h> +#include <errno.h> +#include <asm/arch/s3c24x0_cpu.h> +#include <asm/io.h> +#include <asm/unaligned.h> + +#define S3C2440_SDICON_SDRESET (1 << 8) +#define S3C2410_SDICON_FIFORESET (1 << 1) +#define S3C2410_SDICON_CLOCKTYPE (1 << 0) + +#define S3C2410_SDICMDCON_LONGRSP (1 << 10) +#define S3C2410_SDICMDCON_WAITRSP (1 << 9) +#define S3C2410_SDICMDCON_CMDSTART (1 << 8) +#define S3C2410_SDICMDCON_SENDERHOST (1 << 6) +#define S3C2410_SDICMDCON_INDEX 0x3f + +#define S3C2410_SDICMDSTAT_CRCFAIL (1 << 12) +#define S3C2410_SDICMDSTAT_CMDSENT (1 << 11) +#define S3C2410_SDICMDSTAT_CMDTIMEOUT (1 << 10) +#define S3C2410_SDICMDSTAT_RSPFIN (1 << 9) + +#define S3C2440_SDIDCON_DS_WORD (2 << 22) +#define S3C2410_SDIDCON_TXAFTERRESP (1 << 20) +#define S3C2410_SDIDCON_RXAFTERCMD (1 << 19) +#define S3C2410_SDIDCON_BLOCKMODE (1 << 17) +#define S3C2410_SDIDCON_WIDEBUS (1 << 16) +#define S3C2440_SDIDCON_DATSTART (1 << 14) +#define S3C2410_SDIDCON_XFER_RXSTART (2 << 12) +#define S3C2410_SDIDCON_XFER_TXSTART (3 << 12) +#define S3C2410_SDIDCON_BLKNUM 0x7ff + +#define S3C2410_SDIDSTA_FIFOFAIL (1 << 8) +#define S3C2410_SDIDSTA_CRCFAIL (1 << 7) +#define S3C2410_SDIDSTA_RXCRCFAIL (1 << 6) +#define S3C2410_SDIDSTA_DATATIMEOUT (1 << 5) +#define S3C2410_SDIDSTA_XFERFINISH (1 << 4) + +#define S3C2410_SDIFSTA_TFHALF (1 << 11) +#define S3C2410_SDIFSTA_COUNTMASK 0x7f + +/* + * WARNING: We only support one SD IP block. + * NOTE: It's not likely there will ever exist an S3C24xx with two, + * at least not in this universe all right. + */ +static int wide_bus; + +static int +s3cmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) +{ + struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi(); + uint32_t sdiccon, sdicsta, sdidcon, sdidsta, sdidat, sdifsta; + uint32_t sdicsta_wait_bit = S3C2410_SDICMDSTAT_CMDSENT; + unsigned int timeout = 100000; + int ret = 0, xfer_len, data_offset = 0; + const uint32_t sdidsta_err_mask = S3C2410_SDIDSTA_FIFOFAIL | + S3C2410_SDIDSTA_CRCFAIL | S3C2410_SDIDSTA_RXCRCFAIL | + S3C2410_SDIDSTA_DATATIMEOUT; + + + writel(0xffffffff, &sdi_regs->sdicsta); + writel(0xffffffff, &sdi_regs->sdidsta); + writel(0xffffffff, &sdi_regs->sdifsta); + + /* Set up data transfer (if applicable). */ + if (data) { + writel(data->blocksize, &sdi_regs->sdibsize); + + sdidcon = data->blocks & S3C2410_SDIDCON_BLKNUM; + sdidcon |= S3C2410_SDIDCON_BLOCKMODE; +#if defined(CONFIG_S3C2440) + sdidcon |= S3C2440_SDIDCON_DS_WORD | S3C2440_SDIDCON_DATSTART; +#endif + if (wide_bus) + sdidcon |= S3C2410_SDIDCON_WIDEBUS; + + if (data->flags & MMC_DATA_READ) { + sdidcon |= S3C2410_SDIDCON_RXAFTERCMD; + sdidcon |= S3C2410_SDIDCON_XFER_RXSTART; + } else { + sdidcon |= S3C2410_SDIDCON_TXAFTERRESP; + sdidcon |= S3C2410_SDIDCON_XFER_TXSTART; + } + + writel(sdidcon, &sdi_regs->sdidcon); + } + + /* Write CMD arg. */ + writel(cmd->cmdarg, &sdi_regs->sdicarg); + + /* Write CMD index. */ + sdiccon = cmd->cmdidx & S3C2410_SDICMDCON_INDEX; + sdiccon |= S3C2410_SDICMDCON_SENDERHOST; + sdiccon |= S3C2410_SDICMDCON_CMDSTART; + + /* Command with short response. */ + if (cmd->resp_type & MMC_RSP_PRESENT) { + sdiccon |= S3C2410_SDICMDCON_WAITRSP; + sdicsta_wait_bit = S3C2410_SDICMDSTAT_RSPFIN; + } + + /* Command with long response. */ + if (cmd->resp_type & MMC_RSP_136) + sdiccon |= S3C2410_SDICMDCON_LONGRSP; + + /* Start the command. */ + writel(sdiccon, &sdi_regs->sdiccon); + + /* Wait for the command to complete or for response. */ + for (timeout = 100000; timeout; timeout--) { + sdicsta = readl(&sdi_regs->sdicsta); + if (sdicsta & sdicsta_wait_bit) + break; + + if (sdicsta & S3C2410_SDICMDSTAT_CMDTIMEOUT) + timeout = 1; + } + + /* Clean the status bits. */ + setbits_le32(&sdi_regs->sdicsta, 0xf << 9); + + if (!timeout) { + puts("S3C SDI: Command timed out!\n"); + ret = TIMEOUT; + goto error; + } + + /* Read out the response. */ + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[0] = readl(&sdi_regs->sdirsp0); + cmd->response[1] = readl(&sdi_regs->sdirsp1); + cmd->response[2] = readl(&sdi_regs->sdirsp2); + cmd->response[3] = readl(&sdi_regs->sdirsp3); + } else { + cmd->response[0] = readl(&sdi_regs->sdirsp0); + } + + /* If there are no data, we're done. */ + if (!data) + return 0; + + xfer_len = data->blocksize * data->blocks; + + while (xfer_len > 0) { + sdidsta = readl(&sdi_regs->sdidsta); + sdifsta = readl(&sdi_regs->sdifsta); + + if (sdidsta & sdidsta_err_mask) { + printf("S3C SDI: Data error (sdta=0x%08x)\n", sdidsta); + ret = -EIO; + goto error; + } + + if (data->flags & MMC_DATA_READ) { + if ((sdifsta & S3C2410_SDIFSTA_COUNTMASK) < 4) + continue; + sdidat = readl(&sdi_regs->sdidat); + put_unaligned_le32(sdidat, data->dest + data_offset); + } else { /* Write */ + /* TX FIFO half full. */ + if (!(sdifsta & S3C2410_SDIFSTA_TFHALF)) + continue; + + /* TX FIFO is below 32b full, write. */ + sdidat = get_unaligned_le32(data->src + data_offset); + writel(sdidat, &sdi_regs->sdidat); + } + data_offset += 4; + xfer_len -= 4; + } + + /* Wait for the command to complete or for response. */ + for (timeout = 100000; timeout; timeout--) { + sdidsta = readl(&sdi_regs->sdidsta); + if (sdidsta & S3C2410_SDIDSTA_XFERFINISH) + break; + + if (sdidsta & S3C2410_SDIDSTA_DATATIMEOUT) + timeout = 1; + } + + /* Clear status bits. */ + writel(0x6f8, &sdi_regs->sdidsta); + + if (!timeout) { + puts("S3C SDI: Command timed out!\n"); + ret = TIMEOUT; + goto error; + } + + writel(0, &sdi_regs->sdidcon); + + return 0; +error: + return ret; +} + +static void s3cmmc_set_ios(struct mmc *mmc) +{ + struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi(); + uint32_t divider = 0; + + wide_bus = (mmc->bus_width == 4); + + if (!mmc->clock) + return; + + divider = DIV_ROUND_UP(get_PCLK(), mmc->clock); + if (divider) + divider--; + + writel(divider, &sdi_regs->sdipre); + mdelay(125); +} + +static int s3cmmc_init(struct mmc *mmc) +{ + struct s3c24x0_clock_power *clk_power = s3c24x0_get_base_clock_power(); + struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi(); + + /* Start the clock. */ + setbits_le32(&clk_power->clkcon, 1 << 9); + +#if defined(CONFIG_S3C2440) + writel(S3C2440_SDICON_SDRESET, &sdi_regs->sdicon); + mdelay(10); + writel(0x7fffff, &sdi_regs->sdidtimer); +#else + writel(0xffff, &sdi_regs->sdidtimer); +#endif + writel(MMC_MAX_BLOCK_LEN, &sdi_regs->sdibsize); + writel(0x0, &sdi_regs->sdiimsk); + + writel(S3C2410_SDICON_FIFORESET | S3C2410_SDICON_CLOCKTYPE, + &sdi_regs->sdicon); + + mdelay(125); + + return 0; +} + +struct s3cmmc_priv { + struct mmc_config cfg; + int (*getcd)(struct mmc *); + int (*getwp)(struct mmc *); +}; + +static int s3cmmc_getcd(struct mmc *mmc) +{ + struct s3cmmc_priv *priv = mmc->priv; + if (priv->getcd) + return priv->getcd(mmc); + else + return 0; +} + +static int s3cmmc_getwp(struct mmc *mmc) +{ + struct s3cmmc_priv *priv = mmc->priv; + if (priv->getwp) + return priv->getwp(mmc); + else + return 0; +} + +static const struct mmc_ops s3cmmc_ops = { + .send_cmd = s3cmmc_send_cmd, + .set_ios = s3cmmc_set_ios, + .init = s3cmmc_init, + .getcd = s3cmmc_getcd, + .getwp = s3cmmc_getwp, +}; + +int s3cmmc_initialize(bd_t *bis, int (*getcd)(struct mmc *), + int (*getwp)(struct mmc *)) +{ + struct s3cmmc_priv *priv; + struct mmc *mmc; + struct mmc_config *cfg; + + priv = calloc(1, sizeof(*priv)); + if (!priv) + return -ENOMEM; + cfg = &priv->cfg; + + cfg->name = "S3C MMC"; + cfg->ops = &s3cmmc_ops; + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HC | MMC_MODE_HS; + cfg->f_min = 400000; + cfg->f_max = get_PCLK() / 2; + cfg->b_max = 0x80; + +#if defined(CONFIG_S3C2410) + /* + * S3C2410 has some bug that prevents reliable + * operation at higher speed + */ + cfg->f_max /= 2; +#endif + + mmc = mmc_create(cfg, priv); + if (!mmc) { + free(priv); + return -ENOMEM; + } + + return 0; +} |