From: Kuo-Jung Su <dant...@faraday-tech.com> Faraday FTSDC010 is a MMC/SD host controller. There is already a driver in u-boot, which is modified from eSHDC and contributed by Andes Tech.
However it works extreamly slow in Faraday A36x SoC Platforms, so I turn to implement this new version of driver with 10 times faster speed, and improved stability. Signed-off-by: Kuo-Jung Su <dant...@faraday-tech.com> --- drivers/mmc/Makefile | 1 + drivers/mmc/ftsdc010_mci.c | 362 ++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/ftsdc010_mci.h | 91 +++++++++++ 3 files changed, 454 insertions(+) create mode 100644 drivers/mmc/ftsdc010_mci.c create mode 100644 drivers/mmc/ftsdc010_mci.h diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 65791aa..dfe1b8c 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -33,6 +33,7 @@ COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o COBJS-$(CONFIG_DAVINCI_MMC) += davinci_mmc.o COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o COBJS-$(CONFIG_FTSDC010) += ftsdc010_esdhc.o +COBJS-$(CONFIG_FTSDC010_MCI) += ftsdc010_mci.o COBJS-$(CONFIG_GENERIC_MMC) += mmc.o COBJS-$(CONFIG_GENERIC_ATMEL_MCI) += gen_atmel_mci.o COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o diff --git a/drivers/mmc/ftsdc010_mci.c b/drivers/mmc/ftsdc010_mci.c new file mode 100644 index 0000000..d07e4ff --- /dev/null +++ b/drivers/mmc/ftsdc010_mci.c @@ -0,0 +1,362 @@ +/* + * Faraday MMC/SD Host Controller + * + * (C) Copyright 2010 Faraday Technology + * Dante Su <dant...@faraday-tech.com> + * + * This file is released under the terms of GPL v2 and any later version. + * See the file COPYING in the root directory of the source tree for details. + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> + +#include <asm/io.h> +#include <asm/errno.h> +#include <asm/byteorder.h> + +#include "ftsdc010_mci.h" + +#define SD_REG32(chip, off) \ + *(volatile uint32_t *)((uint8_t *)(chip)->iobase + (off)) + +struct ftsdc010_chip { + uint32_t iobase; + uint32_t wprot; /* write protected (locked) */ + uint32_t rate; /* actual SD clock in Hz */ + uint32_t sclk; /* FTSDC010 source clock in Hz */ + uint32_t fifo; /* fifo depth in bytes */ + uint32_t acmd; +}; + +static inline int +ftsdc010_send_cmd(struct mmc *mmc, struct mmc_cmd *mmc_cmd) +{ + struct ftsdc010_chip *chip = mmc->priv; + uint32_t timeout; + + uint32_t cmd = mmc_cmd->cmdidx; + uint32_t arg = mmc_cmd->cmdarg; + uint32_t flags = mmc_cmd->resp_type; + + cmd |= CMD_EN; + + if (chip->acmd) { + cmd |= CMD_APP; + chip->acmd = 0; + } + + if (flags & MMC_RSP_PRESENT) + cmd |= CMD_WAIT_RSP; + + if (flags & MMC_RSP_136) + cmd |= CMD_LONG_RSP; + + SD_REG32(chip, REG_SCR) = SR_RSP_ERR | SR_RSP | SR_CMD; + + SD_REG32(chip, REG_ARG) = arg; + + SD_REG32(chip, REG_CMD) = cmd; + + if ((flags & (MMC_RSP_PRESENT | MMC_RSP_136)) == 0) { + for (timeout = 250000; timeout > 0; --timeout) { + if (SD_REG32(chip, REG_SR) & SR_CMD) { + SD_REG32(chip, REG_SCR) = SR_CMD; + break; + } + udelay(1); + } + } else { + for (timeout = 250000; timeout > 0; --timeout) { + uint32_t st = SD_REG32(chip, REG_SR); + if (st & SR_RSP) { + SD_REG32(chip, REG_SCR) = SR_RSP; + if (flags & MMC_RSP_136) { + mmc_cmd->response[0] = SD_REG32(chip, REG_RSP3); + mmc_cmd->response[1] = SD_REG32(chip, REG_RSP2); + mmc_cmd->response[2] = SD_REG32(chip, REG_RSP1); + mmc_cmd->response[3] = SD_REG32(chip, REG_RSP0); + } else { + mmc_cmd->response[0] = SD_REG32(chip, REG_RSP0); + } + break; + } else if (st & SR_RSP_ERR) { + SD_REG32(chip, REG_SCR) = SR_RSP_ERR; + debug("ftsdc010: rsp err (cmd=%d, st=0x%x)\n", mmc_cmd->cmdidx, st); + return TIMEOUT; + } + udelay(1); + } + } + + if (timeout == 0) { + debug("ftsdc010: cmd timeout (op code=%d)\n", mmc_cmd->cmdidx); + return TIMEOUT; + } + + if (mmc_cmd->cmdidx == MMC_CMD_APP_CMD) + chip->acmd = 1; + + return 0; +} + +static int +ftsdc010_wait(struct mmc *mmc) +{ + struct ftsdc010_chip *chip = mmc->priv; + uint32_t mask = SR_DAT | SR_DAT_END | SR_DAT_ERR; + uint32_t timeout; + + for (timeout = 250000; timeout; --timeout) { + uint32_t st = SD_REG32(chip, REG_SR); + SD_REG32(chip, REG_SCR) = (st & mask); + + if (st & SR_DAT_ERR) { + printf("ftsdc010: data error!(st=0x%x)\n", st); + return TIMEOUT; + } else if (st & SR_DAT_END) { + break; + } + udelay(1); + } + + if (timeout == 0) { + debug("ftsdc010: wait timeout\n"); + return TIMEOUT; + } + + return 0; +} + +static void +ftsdc010_clkset(struct ftsdc010_chip *chip, uint32_t rate) +{ + uint32_t div; + uint32_t clk = chip->sclk; + + for (div = 0; div < 0x7F; ++div) { + if (rate >= clk / (2 * (div + 1))) + break; + } + SD_REG32(chip, REG_CLK) = CLK_SD | div; + + chip->rate = clk / (2 * (div + 1)); +} + +static inline int +ftsdc010_is_ro(struct mmc *mmc) +{ + struct ftsdc010_chip *chip = mmc->priv; + const uint8_t *csd = (const uint8_t *)mmc->csd; + + if (chip->wprot || (csd[1] & 0x30)) + return 1; + + return 0; +} + +/* + * u-boot mmc api + */ + +static int ftsdc010_request(struct mmc *mmc, + struct mmc_cmd *cmd, + struct mmc_data *data) +{ + int rc; + uint32_t len = 0; + struct ftsdc010_chip *chip = mmc->priv; + + if (data && (data->flags & MMC_DATA_WRITE) && chip->wprot) { + printf("ftsdc010: the card is write protected!\n"); + return UNUSABLE_ERR; + } + + if (data) { + uint32_t dcr; + + len = data->blocksize * data->blocks; + + /* 1. data disable + fifo reset */ + SD_REG32(chip, REG_DCR) = DCR_FIFO_RESET; + + /* 2. clear status register */ + SD_REG32(chip, REG_SCR) = SR_DAT_END | SR_DAT | SR_DAT_ERR | SR_TXRDY | SR_RXRDY; + + /* 3. data timeout (1 sec) */ + SD_REG32(chip, REG_DTR) = chip->rate; + + /* 4. data length (bytes) */ + SD_REG32(chip, REG_DLR) = len; + + /* 5. data enable */ + dcr = (ffs(data->blocksize) - 1) | DCR_EN; + if (data->flags & MMC_DATA_WRITE) + dcr |= DCR_WR; + SD_REG32(chip, REG_DCR) = dcr; + } + + rc = ftsdc010_send_cmd(mmc, cmd); + if (rc) { + printf("ftsdc010: sending CMD%d failed\n", cmd->cmdidx); + return rc; + } + + if (!data) + return rc; + + if (data->flags & MMC_DATA_WRITE) { + const uint8_t *buf = (const uint8_t *)data->src; + + while (len > 0) { + int wlen; + + /* wait data ready */ + while (!(SD_REG32(chip, REG_SR) & SR_TXRDY)) + ; + SD_REG32(chip, REG_SCR) = SR_TXRDY; + + /* write bytes to ftsdc010 */ + for (wlen = 0; wlen < len && wlen < chip->fifo; ) { + SD_REG32(chip, REG_DR) = *(uint32_t *)buf; + buf += 4; + wlen += 4; + } + + len -= wlen; + } + + } else { + uint8_t *buf = (uint8_t *)data->dest; + + while (len > 0) { + int rlen; + + /* wait data ready */ + while (!(SD_REG32(chip, REG_SR) & SR_RXRDY)) + ; + SD_REG32(chip, REG_SCR) = SR_RXRDY; + + /* fetch bytes from ftsdc010 */ + for (rlen = 0; rlen < len && rlen < chip->fifo; ) { + *(uint32_t *)buf = SD_REG32(chip, REG_DR); + buf += 4; + rlen += 4; + } + + len -= rlen; + } + + } + + rc = ftsdc010_wait(mmc); + return rc; +} + +static void ftsdc010_set_ios(struct mmc *mmc) +{ + struct ftsdc010_chip *chip = mmc->priv; + + ftsdc010_clkset(chip, mmc->clock); + + if (mmc->clock > 25000000) + SD_REG32(chip, REG_CLK) |= CLK_HISPD; + else + SD_REG32(chip, REG_CLK) &= ~CLK_HISPD; + + SD_REG32(chip, REG_BUS) &= 0xFFFFFFF8; + switch (mmc->bus_width) { + case 4: + SD_REG32(chip, REG_BUS) |= 0x04; + break; + case 8: + SD_REG32(chip, REG_BUS) |= 0x02; + break; + default: + SD_REG32(chip, REG_BUS) |= 0x01; + break; + } +} + +static int ftsdc010_init(struct mmc *mmc) +{ + struct ftsdc010_chip *chip = mmc->priv; + + if (SD_REG32(chip, REG_SR) & SR_CARD_REMOVED) + return NO_CARD_ERR; + + if (SD_REG32(chip, REG_SR) & SR_WPROT) { + printf("ftsdc010: write protected\n"); + chip->wprot = 1; + } + + chip->fifo = (SD_REG32(chip, REG_FEAR) & 0xFF) << 2; + + /* 1. chip reset */ + SD_REG32(chip, REG_CMD) = CMD_RST; + while (SD_REG32(chip, REG_CMD) & CMD_RST) + ; + + /* 2. enter low speed mode (400k card detection) */ + ftsdc010_clkset(chip, 400000); + + /* 3. interrupt disabled */ + SD_REG32(chip, REG_IER) = 0; + + return 0; +} + +int ftsdc010_mmc_init(int devid) +{ + struct mmc *mmc = NULL; + struct ftsdc010_chip *chip = NULL; + + mmc = malloc(sizeof(struct mmc)); + if (!mmc) + return -ENOMEM; + memset(mmc, 0, sizeof(struct mmc)); + + chip = malloc(sizeof(struct ftsdc010_chip)); + if (!chip) { + free(mmc); + return -ENOMEM; + } + memset(chip, 0, sizeof(struct ftsdc010_chip)); + + chip->iobase = CONFIG_FTSDC010_BASE + (devid << 20); + mmc->priv = chip; + + sprintf(mmc->name, "ftsdc010"); + mmc->send_cmd = ftsdc010_request; + mmc->set_ios = ftsdc010_set_ios; + mmc->init = ftsdc010_init; + + switch ((SD_REG32(chip, REG_BUS) >> 3) & 3) { + case 1: + mmc->host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_4BIT; + break; + case 2: + mmc->host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_4BIT | MMC_MODE_8BIT; + break; + default: + mmc->host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz; + break; + } + +#ifdef CONFIG_SYS_CLK_FREQ + chip->sclk = CONFIG_SYS_CLK_FREQ; +#else + chip->sclk = clk_get_rate("SDC"); +#endif + + mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->f_max = chip->sclk / 2; + mmc->f_min = chip->sclk / 0x100; + mmc->block_dev.part_type = PART_TYPE_DOS; + + mmc_register(mmc); + + return 0; +} diff --git a/drivers/mmc/ftsdc010_mci.h b/drivers/mmc/ftsdc010_mci.h new file mode 100644 index 0000000..86d8c9f --- /dev/null +++ b/drivers/mmc/ftsdc010_mci.h @@ -0,0 +1,91 @@ +/* + * Faraday MMC/SD Host Controller + * + * (C) Copyright 2010 Faraday Technology + * Dante Su <dant...@faraday-tech.com> + * + * This file is released under the terms of GPL v2 and any later version. + * See the file COPYING in the root directory of the source tree for details. + */ + +#ifndef __FTSDC010_MCI_H +#define __FTSDC010_MCI_H + +#include <faraday/ftsdc010.h> + +/* sd controller register */ +#define REG_CMD 0x0000 +#define REG_ARG 0x0004 +#define REG_RSP0 0x0008 /* response */ +#define REG_RSP1 0x000C +#define REG_RSP2 0x0010 +#define REG_RSP3 0x0014 +#define REG_RSPCMD 0x0018 /* responsed command */ +#define REG_DCR 0x001C /* data control */ +#define REG_DTR 0x0020 /* data timeout */ +#define REG_DLR 0x0024 /* data length */ +#define REG_SR 0x0028 /* status register */ +#define REG_SCR 0x002C /* status clear register */ +#define REG_IER 0x0030 /* interrupt mask/enable register */ +#define REG_PWR 0x0034 /* power control */ +#define REG_CLK 0x0038 /* clock control */ +#define REG_BUS 0x003C /* bus width */ +#define REG_DR 0x0040 /* data register */ +#define REG_GPOR 0x0048 /* general purpose output register */ +#define REG_FEAR 0x009C /* feature register */ +#define REG_REVR 0x00A0 /* revision register */ + +/* bit mapping of command register */ +#define CMD_IDX 0x0000003F +#define CMD_WAIT_RSP 0x00000040 +#define CMD_LONG_RSP 0x00000080 +#define CMD_APP 0x00000100 +#define CMD_EN 0x00000200 +#define CMD_RST 0x00000400 + +/* bit mapping of response command register */ +#define RSP_CMDIDX 0x0000003F +#define RSP_CMDAPP 0x00000040 + +/* bit mapping of data control register */ +#define DCR_BKSZ 0x0000000F +#define DCR_WR 0x00000010 +#define DCR_RD 0x00000000 +#define DCR_DMA 0x00000020 +#define DCR_EN 0x00000040 +#define DCR_THRES 0x00000080 +#define DCR_BURST1 0x00000000 +#define DCR_BURST4 0x00000100 +#define DCR_BURST8 0x00000200 +#define DCR_FIFO_RESET 0x00000400 + +/* bit mapping of status register */ +#define SR_RSP_CRC 0x00000001 +#define SR_DAT_CRC 0x00000002 +#define SR_RSP_TIMEOUT 0x00000004 +#define SR_DAT_TIMEOUT 0x00000008 +#define SR_RSP_ERR (SR_RSP_CRC | SR_RSP_TIMEOUT) +#define SR_DAT_ERR (SR_DAT_CRC | SR_DAT_TIMEOUT) +#define SR_RSP 0x00000010 +#define SR_DAT 0x00000020 +#define SR_CMD 0x00000040 +#define SR_DAT_END 0x00000080 +#define SR_TXRDY 0x00000100 +#define SR_RXRDY 0x00000200 +#define SR_CARD_CHANGE 0x00000400 +#define SR_CARD_REMOVED 0x00000800 +#define SR_WPROT 0x00001000 +#define SR_SDIO 0x00010000 +#define SR_DAT0 0x00020000 + +/* bit mapping of clock register */ +#define CLK_HISPD 0x00000200 +#define CLK_OFF 0x00000100 +#define CLK_SD 0x00000080 + +/* bit mapping of bus register */ +#define BUS_CARD_DATA3 0x00000020 +#define BUS_4BITS_SUPP 0x00000008 +#define BUS_8BITS_SUPP 0x00000010 + +#endif -- 1.7.9.5 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot