[PATCH v5.1 7/8] sunxi: mmc support

Ian Campbell ijc at hellion.org.uk
Fri Mar 7 05:29:39 CET 2014


This adds support for the MMC controller on the Allwinner A20 (sun7i)
processor.

Signed-off-by: Henrik Nordstrom <henrik at henriknordstrom.net>
Signed-off-by: Luke Leighton <lkcl at lkcl.net>
Signed-off-by: Oliver Schinagl <oliver at schinagl.nl>
Signed-off-by: Wills Wang <wills.wang.open at gmail.com>
Signed-off-by: Ian Campbell <ijc at hellion.org.uk>
Reviewed-by: Marek Vasut <marex at denx.de>
Reviewed-by: Stefan Roese <sr at denx.de>
Cc: Tom Cubie <Mr.hipboi at gmail.com>
Cc: Aaron Maoye <leafy.myeh at allwinnertech.com>
Cc: Pantelis Antoniou <panto at antoniou-consulting.com>
---
v5.1: Based on 3f5ff92b1503 "sunxi: add comments to pll1_para array."
  - Drop unused CONFIG_MMC1_PG option
  - Make comments in mmc_pinmux_setup be consistent (always Function-Pin, not
    half Pin-Function).

v5: Based on 3f5ff92b1503 "sunxi: add comments to pll1_para array."
  - No changes required

v4: Based on d9fe0a1e061e "sunxi: mksunxiboot: remove unnecessary casts."
    - Moved pinmux setup from drivers/mmc/sunxi_mmc.c to
      board/sunxi/board.c
    - Use setbits_le32 to manipulate the ahb0 gate.

v3: Based on c89867dca2e9 "sunxi: clocks: clock_get_pll5
prototype and coding style".

v2: Based on u-boot-sunxi.git#sunxi d9aa5dd3d15c "sunxi: mmc:
checkpatch whitespace fixes" with v2014.04-rc2 merged in:
  - use proper gpio interfaces, removing awkward casts and some magic numbers.
  - remove magic numbers
  - other cleanups

v1: Based on u-boot-sunxi.git#sunxi commit d854c4de2f57 "arm: Handle .gnu.hash
section in ldscripts" vs v2014.01.

mmc: drop debug
---
 arch/arm/include/asm/arch-sunxi/mmc.h | 124 +++++++++
 board/sunxi/board.c                   |  63 +++++
 drivers/mmc/Makefile                  |   1 +
 drivers/mmc/sunxi_mmc.c               | 503 ++++++++++++++++++++++++++++++++++
 include/configs/sunxi-common.h        |  11 +
 5 files changed, 702 insertions(+)
 create mode 100644 arch/arm/include/asm/arch-sunxi/mmc.h
 create mode 100644 drivers/mmc/sunxi_mmc.c

diff --git a/arch/arm/include/asm/arch-sunxi/mmc.h b/arch/arm/include/asm/arch-sunxi/mmc.h
new file mode 100644
index 0000000..53196e3
--- /dev/null
+++ b/arch/arm/include/asm/arch-sunxi/mmc.h
@@ -0,0 +1,124 @@
+/*
+ * (C) Copyright 2007-2011
+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
+ * Aaron <leafy.myeh at allwinnertech.com>
+ *
+ * MMC register definition for allwinner sunxi platform.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _SUNXI_MMC_H
+#define _SUNXI_MMC_H
+
+#include <linux/types.h>
+
+struct sunxi_mmc {
+	u32 gctrl;		/* 0x00 global control */
+	u32 clkcr;		/* 0x04 clock control */
+	u32 timeout;		/* 0x08 time out */
+	u32 width;		/* 0x0c bus width */
+	u32 blksz;		/* 0x10 block size */
+	u32 bytecnt;		/* 0x14 byte count */
+	u32 cmd;		/* 0x18 command */
+	u32 arg;		/* 0x1c argument */
+	u32 resp0;		/* 0x20 response 0 */
+	u32 resp1;		/* 0x24 response 1 */
+	u32 resp2;		/* 0x28 response 2 */
+	u32 resp3;		/* 0x2c response 3 */
+	u32 imask;		/* 0x30 interrupt mask */
+	u32 mint;		/* 0x34 masked interrupt status */
+	u32 rint;		/* 0x38 raw interrupt status */
+	u32 status;		/* 0x3c status */
+	u32 ftrglevel;		/* 0x40 FIFO threshold watermark*/
+	u32 funcsel;		/* 0x44 function select */
+	u32 cbcr;		/* 0x48 CIU byte count */
+	u32 bbcr;		/* 0x4c BIU byte count */
+	u32 dbgc;		/* 0x50 debug enable */
+	u32 res0[11];
+	u32 dmac;		/* 0x80 internal DMA control */
+	u32 dlba;		/* 0x84 internal DMA descr list base address */
+	u32 idst;		/* 0x88 internal DMA status */
+	u32 idie;		/* 0x8c internal DMA interrupt enable */
+	u32 chda;		/* 0x90 */
+	u32 cbda;		/* 0x94 */
+	u32 res1[26];
+	u32 fifo;		/* 0x100 FIFO access address */
+};
+
+#define SUNXI_MMC_CLK_POWERSAVE		(0x1 << 17)
+#define SUNXI_MMC_CLK_ENABLE		(0x1 << 16)
+#define SUNXI_MMC_CLK_DIVIDER_MASK	(0xff)
+
+#define SUNXI_MMC_GCTRL_SOFT_RESET	(0x1 << 0)
+#define SUNXI_MMC_GCTRL_FIFO_RESET	(0x1 << 1)
+#define SUNXI_MMC_GCTRL_DMA_RESET	(0x1 << 2)
+#define SUNXI_MMC_GCTRL_RESET		(SUNXI_MMC_GCTRL_SOFT_RESET|\
+					 SUNXI_MMC_GCTRL_FIFO_RESET|\
+					 SUNXI_MMC_GCTRL_DMA_RESET)
+#define SUNXI_MMC_GCTRL_DMA_ENABLE	(0x1 << 5)
+#define SUNXI_MMC_GCTRL_ACCESS_BY_AHB   (0x1 << 31)
+
+#define SUNXI_MMC_CMD_RESP_EXPIRE	(0x1 << 6)
+#define SUNXI_MMC_CMD_LONG_RESPONSE	(0x1 << 7)
+#define SUNXI_MMC_CMD_CHK_RESPONSE_CRC	(0x1 << 8)
+#define SUNXI_MMC_CMD_DATA_EXPIRE	(0x1 << 9)
+#define SUNXI_MMC_CMD_WRITE		(0x1 << 10)
+#define SUNXI_MMC_CMD_AUTO_STOP		(0x1 << 12)
+#define SUNXI_MMC_CMD_WAIT_PRE_OVER	(0x1 << 13)
+#define SUNXI_MMC_CMD_SEND_INIT_SEQ	(0x1 << 15)
+#define SUNXI_MMC_CMD_UPCLK_ONLY	(0x1 << 21)
+#define SUNXI_MMC_CMD_START		(0x1 << 31)
+
+#define SUNXI_MMC_RINT_RESP_ERROR		(0x1 << 1)
+#define SUNXI_MMC_RINT_COMMAND_DONE		(0x1 << 2)
+#define SUNXI_MMC_RINT_DATA_OVER		(0x1 << 3)
+#define SUNXI_MMC_RINT_TX_DATA_REQUEST		(0x1 << 4)
+#define SUNXI_MMC_RINT_RX_DATA_REQUEST		(0x1 << 5)
+#define SUNXI_MMC_RINT_RESP_CRC_ERROR		(0x1 << 6)
+#define SUNXI_MMC_RINT_DATA_CRC_ERROR		(0x1 << 7)
+#define SUNXI_MMC_RINT_RESP_TIMEOUT		(0x1 << 8)
+#define SUNXI_MMC_RINT_DATA_TIMEOUT		(0x1 << 9)
+#define SUNXI_MMC_RINT_VOLTAGE_CHANGE_DONE	(0x1 << 10)
+#define SUNXI_MMC_RINT_FIFO_RUN_ERROR		(0x1 << 11)
+#define SUNXI_MMC_RINT_HARD_WARE_LOCKED		(0x1 << 12)
+#define SUNXI_MMC_RINT_START_BIT_ERROR		(0x1 << 13)
+#define SUNXI_MMC_RINT_AUTO_COMMAND_DONE	(0x1 << 14)
+#define SUNXI_MMC_RINT_END_BIT_ERROR		(0x1 << 15)
+#define SUNXI_MMC_RINT_SDIO_INTERRUPT		(0x1 << 16)
+#define SUNXI_MMC_RINT_CARD_INSERT		(0x1 << 30)
+#define SUNXI_MMC_RINT_CARD_REMOVE		(0x1 << 31)
+#define SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT      \
+	(SUNXI_MMC_RINT_RESP_ERROR |		\
+	 SUNXI_MMC_RINT_RESP_CRC_ERROR |	\
+	 SUNXI_MMC_RINT_DATA_CRC_ERROR |	\
+	 SUNXI_MMC_RINT_RESP_TIMEOUT |		\
+	 SUNXI_MMC_RINT_DATA_TIMEOUT |		\
+	 SUNXI_MMC_RINT_VOLTAGE_CHANGE_DONE |	\
+	 SUNXI_MMC_RINT_FIFO_RUN_ERROR |	\
+	 SUNXI_MMC_RINT_HARD_WARE_LOCKED |	\
+	 SUNXI_MMC_RINT_START_BIT_ERROR |	\
+	 SUNXI_MMC_RINT_END_BIT_ERROR) /* 0xbfc2 */
+#define SUNXI_MMC_RINT_INTERRUPT_DONE_BIT	\
+	(SUNXI_MMC_RINT_AUTO_COMMAND_DONE |	\
+	 SUNXI_MMC_RINT_DATA_OVER |		\
+	 SUNXI_MMC_RINT_COMMAND_DONE |		\
+	 SUNXI_MMC_RINT_VOLTAGE_CHANGE_DONE)
+
+#define SUNXI_MMC_STATUS_RXWL_FLAG		(0x1 << 0)
+#define SUNXI_MMC_STATUS_TXWL_FLAG		(0x1 << 1)
+#define SUNXI_MMC_STATUS_FIFO_EMPTY		(0x1 << 2)
+#define SUNXI_MMC_STATUS_FIFO_FULL		(0x1 << 3)
+#define SUNXI_MMC_STATUS_CARD_PRESENT		(0x1 << 8)
+#define SUNXI_MMC_STATUS_CARD_DATA_BUSY		(0x1 << 9)
+#define SUNXI_MMC_STATUS_DATA_FSM_BUSY		(0x1 << 10)
+
+#define SUNXI_MMC_IDMAC_RESET		(0x1 << 0)
+#define SUNXI_MMC_IDMAC_FIXBURST	(0x1 << 1)
+#define SUNXI_MMC_IDMAC_ENABLE		(0x1 << 7)
+
+#define SUNXI_MMC_IDIE_TXIRQ		(0x1 << 0)
+#define SUNXI_MMC_IDIE_RXIRQ		(0x1 << 1)
+
+int sunxi_mmc_init(int sdc_no);
+#endif /* _SUNXI_MMC_H */
diff --git a/board/sunxi/board.c b/board/sunxi/board.c
index 328334a..b05d0b9 100644
--- a/board/sunxi/board.c
+++ b/board/sunxi/board.c
@@ -14,6 +14,8 @@
 #include <common.h>
 #include <asm/arch/clock.h>
 #include <asm/arch/dram.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/mmc.h>
 
 DECLARE_GLOBAL_DATA_PTR;
 
@@ -43,6 +45,67 @@ int dram_init(void)
 	return 0;
 }
 
+#ifdef CONFIG_GENERIC_MMC
+static void mmc_pinmux_setup(int sdc)
+{
+	unsigned int pin;
+
+	switch (sdc) {
+	case 0:
+		/* D1-PF0, D0-PF1, CLK-PF2, CMD-PF3, D3-PF4, D4-PF5 */
+		for (pin = SUNXI_GPF(0); pin <= SUNXI_GPF(5); pin++) {
+			sunxi_gpio_set_cfgpin(pin, SUNXI_GPF0_SDC0);
+			sunxi_gpio_set_pull(pin, SUNXI_GPIO_PULL_UP);
+			sunxi_gpio_set_drv(pin, 2);
+		}
+		break;
+
+	case 1:
+		/* CMD-PH22, CLK-PH23, D0~D3-PH24~27 : 5 */
+		for (pin = SUNXI_GPH(22); pin <= SUNXI_GPH(27); pin++) {
+			sunxi_gpio_set_cfgpin(pin, SUN4I_GPH22_SDC1);
+			sunxi_gpio_set_pull(pin, SUNXI_GPIO_PULL_UP);
+			sunxi_gpio_set_drv(pin, 2);
+		}
+		break;
+
+	case 2:
+		/* CMD-PC6, CLK-PC7, D0-PC8, D1-PC9, D2-PC10, D3-PC11 */
+		for (pin = SUNXI_GPC(6); pin <= SUNXI_GPC(11); pin++) {
+			sunxi_gpio_set_cfgpin(pin, SUNXI_GPC6_SDC2);
+			sunxi_gpio_set_pull(pin, SUNXI_GPIO_PULL_UP);
+			sunxi_gpio_set_drv(pin, 2);
+		}
+		break;
+
+	case 3:
+		/* CMD-PI4, CLK-PI5, D0~D3-PI6~9 : 2 */
+		for (pin = SUNXI_GPI(4); pin <= SUNXI_GPI(9); pin++) {
+			sunxi_gpio_set_cfgpin(pin, SUN4I_GPI4_SDC3);
+			sunxi_gpio_set_pull(pin, SUNXI_GPIO_PULL_UP);
+			sunxi_gpio_set_drv(pin, 2);
+		}
+		break;
+
+	default:
+		printf("sunxi: invalid MMC slot %d for pinmux setup\n", sdc);
+		break;
+	}
+}
+
+int board_mmc_init(bd_t *bis)
+{
+	mmc_pinmux_setup(CONFIG_MMC_SUNXI_SLOT);
+	sunxi_mmc_init(CONFIG_MMC_SUNXI_SLOT);
+#if !defined (CONFIG_SPL_BUILD) && defined (CONFIG_MMC_SUNXI_SLOT_EXTRA)
+	mmc_pinmux_setup(CONFIG_MMC_SUNXI_SLOT_EXTRA);
+	sunxi_mmc_init(CONFIG_MMC_SUNXI_SLOT_EXTRA);
+#endif
+
+	return 0;
+}
+#endif
+
 #ifdef CONFIG_SPL_BUILD
 void sunxi_board_init(void)
 {
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index 931922b..5e67098 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_SPEAR_SDHCI) += spear_sdhci.o
 obj-$(CONFIG_TEGRA_MMC) += tegra_mmc.o
 obj-$(CONFIG_DWMMC) += dw_mmc.o
 obj-$(CONFIG_EXYNOS_DWMMC) += exynos_dw_mmc.o
+obj-$(CONFIG_MMC_SUNXI) += sunxi_mmc.o
 obj-$(CONFIG_ZYNQ_SDHCI) += zynq_sdhci.o
 obj-$(CONFIG_SOCFPGA_DWMMC) += socfpga_dw_mmc.o
 ifdef CONFIG_SPL_BUILD
diff --git a/drivers/mmc/sunxi_mmc.c b/drivers/mmc/sunxi_mmc.c
new file mode 100644
index 0000000..eb7b115
--- /dev/null
+++ b/drivers/mmc/sunxi_mmc.c
@@ -0,0 +1,503 @@
+/*
+ * (C) Copyright 2007-2011
+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
+ * Aaron <leafy.myeh at allwinnertech.com>
+ *
+ * MMC driver for allwinner sunxi platform.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <mmc.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/mmc.h>
+
+struct sunxi_mmc_des {
+	u32 reserved1_1:1;
+	u32 dic:1;		/* disable interrupt on completion */
+	u32 last_des:1;		/* 1-this data buffer is the last buffer */
+	u32 first_des:1;		/* 1-data buffer is the first buffer,
+				   0-data buffer contained in the next
+				   descriptor is 1st buffer */
+	u32 des_chain:1;	/* 1-the 2nd address in the descriptor is the
+				   next descriptor address */
+	u32 end_of_ring:1;	/* 1-last descriptor flag when using dual
+				   data buffer in descriptor */
+	u32 reserved1_2:24;
+	u32 card_err_sum:1;	/* transfer error flag */
+	u32 own:1;		/* des owner:1-idma owns it, 0-host owns it */
+#define SDXC_DES_NUM_SHIFT 16
+#define SDXC_DES_BUFFER_MAX_LEN	(1 << SDXC_DES_NUM_SHIFT)
+	u32 data_buf1_sz:16;
+	u32 data_buf2_sz:16;
+	u32 buf_addr_ptr1;
+	u32 buf_addr_ptr2;
+};
+
+struct sunxi_mmc_host {
+	unsigned mmc_no;
+	uint32_t *mclkreg;
+	unsigned database;
+	unsigned fatal_err;
+	unsigned mod_clk;
+	struct sunxi_mmc *reg;
+	struct mmc_config cfg;
+};
+
+/* support 4 mmc hosts */
+struct sunxi_mmc_host mmc_host[4];
+
+static int mmc_resource_init(int sdc_no)
+{
+	struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
+	struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+
+	debug("init mmc %d resource\n", sdc_no);
+
+	switch (sdc_no) {
+	case 0:
+		mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC0_BASE;
+		mmchost->mclkreg = &ccm->sd0_clk_cfg;
+		break;
+	case 1:
+		mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC1_BASE;
+		mmchost->mclkreg = &ccm->sd1_clk_cfg;
+		break;
+	case 2:
+		mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC2_BASE;
+		mmchost->mclkreg = &ccm->sd2_clk_cfg;
+		break;
+	case 3:
+		mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC3_BASE;
+		mmchost->mclkreg = &ccm->sd3_clk_cfg;
+		break;
+	default:
+		printf("Wrong mmc number %d\n", sdc_no);
+		return -1;
+	}
+	mmchost->database = (unsigned int)mmchost->reg + 0x100;
+	mmchost->mmc_no = sdc_no;
+
+	return 0;
+}
+
+static int mmc_clk_io_on(int sdc_no)
+{
+	unsigned int pll_clk;
+	unsigned int divider;
+	struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
+	struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+
+	debug("init mmc %d clock and io\n", sdc_no);
+
+	/* config ahb clock */
+	setbits_le32(&ccm->ahb_gate0, 1 << AHB_GATE_OFFSET_MMC(sdc_no));
+
+	/* config mod clock */
+	pll_clk = clock_get_pll6();
+	/* should be close to 100 MHz but no more, so round up */
+	divider = ((pll_clk + 99999999) / 100000000) - 1;
+	writel(CCM_MMC_CTRL_ENABLE | CCM_MMC_CTRL_PLL6 | divider,
+	       mmchost->mclkreg);
+	mmchost->mod_clk = pll_clk / (divider + 1);
+
+	return 0;
+}
+
+static int mmc_update_clk(struct mmc *mmc)
+{
+	struct sunxi_mmc_host *mmchost = mmc->priv;
+	unsigned int cmd;
+	unsigned timeout_msecs = 2000;
+
+	cmd = SUNXI_MMC_CMD_START |
+	      SUNXI_MMC_CMD_UPCLK_ONLY |
+	      SUNXI_MMC_CMD_WAIT_PRE_OVER;
+	writel(cmd, &mmchost->reg->cmd);
+	while (readl(&mmchost->reg->cmd) & SUNXI_MMC_CMD_START) {
+		if (!timeout_msecs--)
+			return -1;
+		udelay(1000);
+	}
+
+	/* clock update sets various irq status bits, clear these */
+	writel(readl(&mmchost->reg->rint), &mmchost->reg->rint);
+
+	return 0;
+}
+
+static int mmc_config_clock(struct mmc *mmc, unsigned div)
+{
+	struct sunxi_mmc_host *mmchost = mmc->priv;
+	unsigned rval = readl(&mmchost->reg->clkcr);
+
+	/* Disable Clock */
+	rval &= ~SUNXI_MMC_CLK_ENABLE;
+	writel(rval, &mmchost->reg->clkcr);
+	if (mmc_update_clk(mmc))
+		return -1;
+
+	/* Change Divider Factor */
+	rval &= ~SUNXI_MMC_CLK_DIVIDER_MASK;
+	rval |= div;
+	writel(rval, &mmchost->reg->clkcr);
+	if (mmc_update_clk(mmc))
+		return -1;
+	/* Re-enable Clock */
+	rval |= SUNXI_MMC_CLK_ENABLE;
+	writel(rval, &mmchost->reg->clkcr);
+
+	if (mmc_update_clk(mmc))
+		return -1;
+
+	return 0;
+}
+
+static void mmc_set_ios(struct mmc *mmc)
+{
+	struct sunxi_mmc_host *mmchost = mmc->priv;
+	unsigned int clkdiv = 0;
+
+	debug("set ios: bus_width: %x, clock: %d, mod_clk: %d\n",
+	      mmc->bus_width, mmc->clock, mmchost->mod_clk);
+
+	/* Change clock first */
+	clkdiv = (mmchost->mod_clk + (mmc->clock >> 1)) / mmc->clock / 2;
+	if (mmc->clock) {
+		if (mmc_config_clock(mmc, clkdiv)) {
+			mmchost->fatal_err = 1;
+			return;
+		}
+	}
+
+	/* Change bus width */
+	if (mmc->bus_width == 8)
+		writel(0x2, &mmchost->reg->width);
+	else if (mmc->bus_width == 4)
+		writel(0x1, &mmchost->reg->width);
+	else
+		writel(0x0, &mmchost->reg->width);
+}
+
+static int mmc_core_init(struct mmc *mmc)
+{
+	struct sunxi_mmc_host *mmchost = mmc->priv;
+
+	/* Reset controller */
+	writel(SUNXI_MMC_GCTRL_RESET, &mmchost->reg->gctrl);
+
+	return 0;
+}
+
+static int mmc_trans_data_by_cpu(struct mmc *mmc, struct mmc_data *data)
+{
+	struct sunxi_mmc_host *mmchost = mmc->priv;
+	const int reading = !!(data->flags & MMC_DATA_READ);
+	const uint32_t status_bit = reading ? SUNXI_MMC_STATUS_FIFO_EMPTY :
+					      SUNXI_MMC_STATUS_FIFO_FULL;
+	unsigned i;
+	unsigned byte_cnt = data->blocksize * data->blocks;
+	unsigned timeout_msecs = 2000;
+	unsigned *buff = (unsigned int *)(reading ? data->dest : data->src);
+
+	for (i = 0; i < (byte_cnt >> 2); i++) {
+		while (readl(&mmchost->reg->status) & status_bit) {
+			if (!timeout_msecs--)
+				return -1;
+			udelay(1000);
+		}
+
+		if (reading)
+			buff[i] = readl(mmchost->database);
+		else
+			writel(buff[i], mmchost->database);
+	}
+
+	return 0;
+}
+
+static int mmc_trans_data_by_dma(struct mmc *mmc, struct mmc_data *data)
+{
+	struct sunxi_mmc_host *mmchost = mmc->priv;
+	unsigned byte_cnt = data->blocksize * data->blocks;
+	unsigned char *buff;
+	unsigned des_idx = 0;
+	unsigned buff_frag_num =
+		(byte_cnt + SDXC_DES_BUFFER_MAX_LEN - 1) >> SDXC_DES_NUM_SHIFT;
+	unsigned remain;
+	unsigned i, rval;
+	ALLOC_CACHE_ALIGN_BUFFER(struct sunxi_mmc_des, pdes, buff_frag_num);
+
+	buff = data->flags & MMC_DATA_READ ?
+	    (unsigned char *)data->dest : (unsigned char *)data->src;
+	remain = byte_cnt & (SDXC_DES_BUFFER_MAX_LEN - 1);
+
+	flush_cache((unsigned long)buff, (unsigned long)byte_cnt);
+	for (i = 0; i < buff_frag_num; i++, des_idx++) {
+		memset((void *)&pdes[des_idx], 0, sizeof(struct sunxi_mmc_des));
+		pdes[des_idx].des_chain = 1;
+		pdes[des_idx].own = 1;
+		pdes[des_idx].dic = 1;
+		if (buff_frag_num > 1 && i != buff_frag_num - 1)
+			pdes[des_idx].data_buf1_sz = 0; /* 0 == max_len */
+		else
+			pdes[des_idx].data_buf1_sz = remain;
+
+		pdes[des_idx].buf_addr_ptr1 =
+		    (u32) buff + i * SDXC_DES_BUFFER_MAX_LEN;
+		if (i == 0)
+			pdes[des_idx].first_des = 1;
+
+		if (i == buff_frag_num - 1) {
+			pdes[des_idx].dic = 0;
+			pdes[des_idx].last_des = 1;
+			pdes[des_idx].end_of_ring = 1;
+			pdes[des_idx].buf_addr_ptr2 = 0;
+		} else {
+			pdes[des_idx].buf_addr_ptr2 = (u32)&pdes[des_idx + 1];
+		}
+	}
+	flush_cache((unsigned long)pdes,
+		    sizeof(struct sunxi_mmc_des) * (des_idx + 1));
+
+	rval = readl(&mmchost->reg->gctrl);
+	/* Enable DMA */
+	writel(rval | SUNXI_MMC_GCTRL_DMA_RESET | SUNXI_MMC_GCTRL_DMA_ENABLE,
+	       &mmchost->reg->gctrl);
+	/* Reset iDMA */
+	writel(SUNXI_MMC_IDMAC_RESET, &mmchost->reg->dmac);
+	/* Enable iDMA */
+	writel(SUNXI_MMC_IDMAC_FIXBURST | SUNXI_MMC_IDMAC_ENABLE,
+	       &mmchost->reg->dmac);
+	rval = readl(&mmchost->reg->idie) &
+		~(SUNXI_MMC_IDIE_TXIRQ|SUNXI_MMC_IDIE_RXIRQ);
+	if (data->flags & MMC_DATA_WRITE)
+		rval |= SUNXI_MMC_IDIE_TXIRQ;
+	else
+		rval |= SUNXI_MMC_IDIE_RXIRQ;
+	writel(rval, &mmchost->reg->idie);
+	writel((u32) pdes, &mmchost->reg->dlba);
+	writel((0x2 << 28) | (0x7 << 16) | (0x01 << 3),
+	       &mmchost->reg->ftrglevel);
+
+	return 0;
+}
+
+static void mmc_enable_dma_accesses(struct mmc *mmc, int dma)
+{
+	struct sunxi_mmc_host *mmchost = mmc->priv;
+
+	unsigned int gctrl = readl(&mmchost->reg->gctrl);
+	if (dma)
+		gctrl &= ~SUNXI_MMC_GCTRL_ACCESS_BY_AHB;
+	else
+		gctrl |= SUNXI_MMC_GCTRL_ACCESS_BY_AHB;
+	writel(gctrl, &mmchost->reg->gctrl);
+}
+
+static int mmc_rint_wait(struct mmc *mmc, unsigned int timeout_msecs,
+			 unsigned int done_bit, const char *what)
+{
+	struct sunxi_mmc_host *mmchost = mmc->priv;
+	unsigned int status;
+
+	do {
+		status = readl(&mmchost->reg->rint);
+		if (!timeout_msecs-- ||
+		    (status & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT)) {
+			debug("%s timeout %x\n", what,
+			      status & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT);
+			return TIMEOUT;
+		}
+		udelay(1000);
+	} while (!(status & done_bit));
+
+	return 0;
+}
+
+static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
+			struct mmc_data *data)
+{
+	struct sunxi_mmc_host *mmchost = mmc->priv;
+	unsigned int cmdval = SUNXI_MMC_CMD_START;
+	unsigned int timeout_msecs;
+	int error = 0;
+	unsigned int status = 0;
+	unsigned int usedma = 0;
+	unsigned int bytecnt = 0;
+
+	if (mmchost->fatal_err)
+		return -1;
+	if (cmd->resp_type & MMC_RSP_BUSY)
+		debug("mmc cmd %d check rsp busy\n", cmd->cmdidx);
+	if (cmd->cmdidx == 12)
+		return 0;
+
+	if (!cmd->cmdidx)
+		cmdval |= SUNXI_MMC_CMD_SEND_INIT_SEQ;
+	if (cmd->resp_type & MMC_RSP_PRESENT)
+		cmdval |= SUNXI_MMC_CMD_RESP_EXPIRE;
+	if (cmd->resp_type & MMC_RSP_136)
+		cmdval |= SUNXI_MMC_CMD_LONG_RESPONSE;
+	if (cmd->resp_type & MMC_RSP_CRC)
+		cmdval |= SUNXI_MMC_CMD_CHK_RESPONSE_CRC;
+
+	if (data) {
+		if ((u32) data->dest & 0x3) {
+			error = -1;
+			goto out;
+		}
+
+		cmdval |= SUNXI_MMC_CMD_DATA_EXPIRE|SUNXI_MMC_CMD_WAIT_PRE_OVER;
+		if (data->flags & MMC_DATA_WRITE)
+			cmdval |= SUNXI_MMC_CMD_WRITE;
+		if (data->blocks > 1)
+			cmdval |= SUNXI_MMC_CMD_AUTO_STOP;
+		writel(data->blocksize, &mmchost->reg->blksz);
+		writel(data->blocks * data->blocksize, &mmchost->reg->bytecnt);
+	}
+
+	debug("mmc %d, cmd %d(0x%08x), arg 0x%08x\n", mmchost->mmc_no,
+	      cmd->cmdidx, cmdval | cmd->cmdidx, cmd->cmdarg);
+	writel(cmd->cmdarg, &mmchost->reg->arg);
+
+	if (!data)
+		writel(cmdval | cmd->cmdidx, &mmchost->reg->cmd);
+
+	/*
+	 * transfer data and check status
+	 * STATREG[2] : FIFO empty
+	 * STATREG[3] : FIFO full
+	 */
+	if (data) {
+		int ret = 0;
+
+		bytecnt = data->blocksize * data->blocks;
+		debug("trans data %d bytes\n", bytecnt);
+#if defined(CONFIG_MMC_SUNXI_USE_DMA) && !defined(CONFIG_SPL_BUILD)
+		if (bytecnt > 64) {
+#else
+		if (0) {
+#endif
+			usedma = 1;
+			mmc_enable_dma_accesses(mmc, 1);
+			ret = mmc_trans_data_by_dma(mmc, data);
+			writel(cmdval | cmd->cmdidx, &mmchost->reg->cmd);
+		} else {
+			mmc_enable_dma_accesses(mmc, 0);
+			writel(cmdval | cmd->cmdidx, &mmchost->reg->cmd);
+			ret = mmc_trans_data_by_cpu(mmc, data);
+		}
+		if (ret) {
+			error = readl(&mmchost->reg->rint) & \
+				SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT;
+			error = TIMEOUT;
+			goto out;
+		}
+	}
+
+	error = mmc_rint_wait(mmc, 0xfffff, SUNXI_MMC_RINT_COMMAND_DONE, "cmd");
+	if (error)
+		goto out;
+
+	if (data) {
+		timeout_msecs = usedma ? 120 * bytecnt : 120;
+		debug("cacl timeout %x msec\n", timeout_msecs);
+		error = mmc_rint_wait(mmc, timeout_msecs,
+				      data->blocks > 1 ?
+				      SUNXI_MMC_RINT_AUTO_COMMAND_DONE :
+				      SUNXI_MMC_RINT_DATA_OVER,
+				      "data");
+		if (error)
+			goto out;
+	}
+
+	if (cmd->resp_type & MMC_RSP_BUSY) {
+		timeout_msecs = 2000;
+		do {
+			status = readl(&mmchost->reg->status);
+			if (!timeout_msecs--) {
+				debug("busy timeout\n");
+				error = TIMEOUT;
+				goto out;
+			}
+			udelay(1000);
+		} while (status & SUNXI_MMC_STATUS_CARD_DATA_BUSY);
+	}
+
+	if (cmd->resp_type & MMC_RSP_136) {
+		cmd->response[0] = readl(&mmchost->reg->resp3);
+		cmd->response[1] = readl(&mmchost->reg->resp2);
+		cmd->response[2] = readl(&mmchost->reg->resp1);
+		cmd->response[3] = readl(&mmchost->reg->resp0);
+		debug("mmc resp 0x%08x 0x%08x 0x%08x 0x%08x\n",
+		      cmd->response[3], cmd->response[2],
+		      cmd->response[1], cmd->response[0]);
+	} else {
+		cmd->response[0] = readl(&mmchost->reg->resp0);
+		debug("mmc resp 0x%08x\n", cmd->response[0]);
+	}
+out:
+	if (data && usedma) {
+		/* IDMASTAREG
+		 * IDST[0] : idma tx int
+		 * IDST[1] : idma rx int
+		 * IDST[2] : idma fatal bus error
+		 * IDST[4] : idma descriptor invalid
+		 * IDST[5] : idma error summary
+		 * IDST[8] : idma normal interrupt sumary
+		 * IDST[9] : idma abnormal interrupt sumary
+		 */
+		status = readl(&mmchost->reg->idst);
+		writel(status, &mmchost->reg->idst);
+		writel(0, &mmchost->reg->idie);
+		writel(0, &mmchost->reg->dmac);
+		writel(readl(&mmchost->reg->gctrl) & ~SUNXI_MMC_GCTRL_DMA_ENABLE,
+		       &mmchost->reg->gctrl);
+	}
+	if (error < 0) {
+		writel(SUNXI_MMC_GCTRL_RESET, &mmchost->reg->gctrl);
+		mmc_update_clk(mmc);
+	}
+	writel(0xffffffff, &mmchost->reg->rint);
+	writel(readl(&mmchost->reg->gctrl) | SUNXI_MMC_GCTRL_FIFO_RESET,
+	       &mmchost->reg->gctrl);
+
+	return error;
+}
+
+static const struct mmc_ops sunxi_mmc_ops = {
+	.send_cmd	= mmc_send_cmd,
+	.set_ios	= mmc_set_ios,
+	.init		= mmc_core_init,
+};
+
+int sunxi_mmc_init(int sdc_no)
+{
+	struct mmc_config *cfg = &mmc_host[sdc_no].cfg;
+
+	memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host));
+
+	cfg->name = "SUNXI SD/MMC";
+	cfg->ops  = &sunxi_mmc_ops;
+
+	cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
+	cfg->host_caps = MMC_MODE_4BIT;
+	cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;
+	cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
+
+	cfg->f_min = 400000;
+	cfg->f_max = 52000000;
+
+	mmc_resource_init(sdc_no);
+	mmc_clk_io_on(sdc_no);
+
+	if (mmc_create(cfg, &mmc_host[sdc_no]) == NULL)
+		return -1;
+
+	return 0;
+}
diff --git a/include/configs/sunxi-common.h b/include/configs/sunxi-common.h
index e844110..4253452 100644
--- a/include/configs/sunxi-common.h
+++ b/include/configs/sunxi-common.h
@@ -59,6 +59,16 @@
 #define CONFIG_CMDLINE_TAG
 #define CONFIG_INITRD_TAG
 
+/* mmc config */
+#define CONFIG_MMC
+#define CONFIG_GENERIC_MMC
+#define CONFIG_CMD_MMC
+#define CONFIG_MMC_SUNXI
+#define CONFIG_MMC_SUNXI_SLOT		0
+#define CONFIG_MMC_SUNXI_USE_DMA
+#define CONFIG_ENV_IS_IN_MMC
+#define CONFIG_SYS_MMC_ENV_DEV		0	/* first detected MMC controller */
+
 /* 4MB of malloc() pool */
 #define CONFIG_SYS_MALLOC_LEN		(CONFIG_ENV_SIZE + (4 << 20))
 
@@ -94,6 +104,7 @@
 #define CONFIG_SYS_MONITOR_LEN		(512 << 10)	/* 512 KiB */
 #define CONFIG_IDENT_STRING		" Allwinner Technology"
 
+#define CONFIG_ENV_OFFSET		(544 << 10) /* (8 + 24 + 512) KiB */
 #define CONFIG_ENV_SIZE			(128 << 10)	/* 128 KiB */
 
 #define CONFIG_EXTRA_ENV_SETTINGS \
-- 
1.9.0





More information about the U-Boot mailing list