[U-Boot] [PATCH 10/11] mmc: add an alternative FTSDC010 driver support

Kuo-Jung Su
2013-03-29T07:06:27Z

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 <dantesu@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 <dantesu@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 <dantesu@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