diff mbox

[U-Boot,v4,13/20] rockchip: Add an rk3036 MMC driver

Message ID 1447056167-16138-14-git-send-email-hl@rock-chips.com
State Superseded
Delegated to: Simon Glass
Headers show

Commit Message

Lin Huang Nov. 9, 2015, 8:02 a.m. UTC
rk3036 mmc driver is similar to dw_mmc, but use external dma,
this patch implment fifo mode, need to do dma mode in future.

Signed-off-by: Lin Huang <hl@rock-chips.com>
---
Changes in v1:
- clean copyright announcement
Changes in v2:
- modify code suggest by Simon:
- use get_time() to do timeout
Changes in v3:
- extend read and write data timeout time
- fix write data read fifo length bug
Changes in v4: None

 drivers/mmc/Kconfig                |   9 +
 drivers/mmc/Makefile               |   1 +
 drivers/mmc/rockchip_3036_dw_mmc.c | 485 +++++++++++++++++++++++++++++++++++++
 3 files changed, 495 insertions(+)
 create mode 100644 drivers/mmc/rockchip_3036_dw_mmc.c

Comments

Simon Glass Nov. 9, 2015, 8:23 p.m. UTC | #1
Hi Lin,

On 9 November 2015 at 00:02, Lin Huang <hl@rock-chips.com> wrote:
> rk3036 mmc driver is similar to dw_mmc, but use external dma,
> this patch implment fifo mode, need to do dma mode in future.

What is the plan to merge this driver with the dwmmc one? Also the
other code seems very similar to rockchip_dw_mmc.c  - can we combine
these?

>
> Signed-off-by: Lin Huang <hl@rock-chips.com>
> ---
> Changes in v1:
> - clean copyright announcement
> Changes in v2:
> - modify code suggest by Simon:
> - use get_time() to do timeout
> Changes in v3:
> - extend read and write data timeout time
> - fix write data read fifo length bug
> Changes in v4: None
>
>  drivers/mmc/Kconfig                |   9 +
>  drivers/mmc/Makefile               |   1 +
>  drivers/mmc/rockchip_3036_dw_mmc.c | 485 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 495 insertions(+)
>  create mode 100644 drivers/mmc/rockchip_3036_dw_mmc.c

Regards,
Simon
Lin Huang Nov. 10, 2015, 1:02 a.m. UTC | #2
Hi Simon,

On 10/11/15 04:23, Simon Glass wrote:
> Hi Lin,
>
> On 9 November 2015 at 00:02, Lin Huang <hl@rock-chips.com> wrote:
>> rk3036 mmc driver is similar to dw_mmc, but use external dma,
>> this patch implment fifo mode, need to do dma mode in future.
> What is the plan to merge this driver with the dwmmc one? Also the
> other code seems very similar to rockchip_dw_mmc.c  - can we combine
> these?
     i have confirm with my colleague, they have tested that in uboot 
use external dma is
     not more efficient than fifo mode, so i think it not necessary to 
support external dma in future.

      And about the mmc driver, i think there is two way to combine:
     1. add fifo mode in dw_mmc.c(i don't know weather is it allow), 
combine the rockchip_dw_mmc.c and rockchip_3036_dw_mmc.c
     2. combine  the probe(), get_mmc_clk(), platdata() function, 
implement the 3036_dw_mmc.c use for 3036, and 3288 use dw_mmc.c
     which do you prefer?
>
>> Signed-off-by: Lin Huang <hl@rock-chips.com>
>> ---
>> Changes in v1:
>> - clean copyright announcement
>> Changes in v2:
>> - modify code suggest by Simon:
>> - use get_time() to do timeout
>> Changes in v3:
>> - extend read and write data timeout time
>> - fix write data read fifo length bug
>> Changes in v4: None
>>
>>   drivers/mmc/Kconfig                |   9 +
>>   drivers/mmc/Makefile               |   1 +
>>   drivers/mmc/rockchip_3036_dw_mmc.c | 485 +++++++++++++++++++++++++++++++++++++
>>   3 files changed, 495 insertions(+)
>>   create mode 100644 drivers/mmc/rockchip_3036_dw_mmc.c
> Regards,
> Simon
>
>
>
diff mbox

Patch

diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
index ceae7bc..a4f9c8d 100644
--- a/drivers/mmc/Kconfig
+++ b/drivers/mmc/Kconfig
@@ -25,6 +25,15 @@  config ROCKCHIP_DWMMC
 	  SD 3.0, SDIO 3.0 and MMC 4.5 and supports common eMMC chips as well
 	  as removeable SD and micro-SD cards.
 
+config ROCKCHIP_3036_DWMMC
+	bool "Rockchip 3036 SD/MMC controller support"
+	depends on DM_MMC && OF_CONTROL
+	help
+	  This enables support for the Rockchip 3036 SD/MMM controller, which is
+	  based on Designware IP. The device is compatible with at least
+	  SD 3.0, SDIO 3.0 and MMC 4.5 and supports common eMMC chips as well
+	  as removeable SD and micro-SD cards.
+
 config SH_SDHI
 	bool "SuperH/Renesas ARM SoCs on-chip SDHI host controller support"
 	depends on RMOBILE
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index 5d35705..bb50e3a 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -31,6 +31,7 @@  obj-$(CONFIG_OMAP_HSMMC) += omap_hsmmc.o
 obj-$(CONFIG_X86) += pci_mmc.o
 obj-$(CONFIG_PXA_MMC_GENERIC) += pxa_mmc_gen.o
 obj-$(CONFIG_ROCKCHIP_DWMMC) += rockchip_dw_mmc.o
+obj-$(CONFIG_ROCKCHIP_3036_DWMMC) += rockchip_3036_dw_mmc.o
 obj-$(CONFIG_SUPPORT_EMMC_RPMB) += rpmb.o
 obj-$(CONFIG_S3C_SDI) += s3c_sdi.o
 obj-$(CONFIG_S5P_SDHCI) += s5p_sdhci.o
diff --git a/drivers/mmc/rockchip_3036_dw_mmc.c b/drivers/mmc/rockchip_3036_dw_mmc.c
new file mode 100644
index 0000000..61569ab
--- /dev/null
+++ b/drivers/mmc/rockchip_3036_dw_mmc.c
@@ -0,0 +1,485 @@ 
+/*
+ * (C) Copyright 2015 Rockchip Electronics Co., Ltd
+ *
+ * SPDX-License-Identifier:     GPL-2.0+
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <dwmmc.h>
+#include <errno.h>
+#include <syscon.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/periph.h>
+#include <linux/err.h>
+#include <bouncebuf.h>
+#include <common.h>
+#include <errno.h>
+#include <malloc.h>
+#include <mmc.h>
+#include <dwmmc.h>
+#include <asm-generic/errno.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define PAGE_SIZE	4096
+#define FIFO_DETH	256
+
+/* SDMMC_STATUS */
+#define MMC_FIFO_MASK	0x1ff
+#define	MMC_FIFO_SHIFT	17
+
+struct rockchip_dwmmc_priv {
+	struct udevice *clk;
+	struct dwmci_host host;
+};
+
+static int dwmci_wait_reset(struct dwmci_host *host, u32 value)
+{
+	unsigned long timeout = 1000;
+	unsigned long start = get_timer(0);
+	u32 ctrl;
+
+	dwmci_writel(host, DWMCI_CTRL, value);
+
+	do {
+		ctrl = dwmci_readl(host, DWMCI_CTRL);
+		if (!(ctrl & DWMCI_RESET_ALL))
+			return 1;
+	} while (get_timer(start) < timeout);
+
+	return 0;
+}
+
+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 = mmc->priv;
+	int ret = 0, flags = 0, i;
+	unsigned int timeout = 100000;
+	u32 retry = 10000;
+	u32 mask;
+	ulong start = get_timer(0);
+	int size;
+	unsigned int fifo_len;
+	unsigned int *buf = 0;
+
+	while (dwmci_readl(host, DWMCI_STATUS) & DWMCI_BUSY) {
+		if (get_timer(start) > timeout) {
+			debug("%s: Timeout on data busy\n", __func__);
+			return TIMEOUT;
+		}
+	}
+
+	dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMSK_ALL);
+
+	if (data) {
+		/*
+		 * TODO: rk3036 use external DMA,
+		 * need to support DMA mode in future
+		 */
+		if (data->flags == MMC_DATA_READ)
+			buf = (unsigned int *)data->dest;
+		else
+			buf = (unsigned int *)data->src;
+		dwmci_writel(host, DWMCI_BLKSIZ, data->blocksize);
+		dwmci_writel(host, DWMCI_BYTCNT,
+			     data->blocksize * data->blocks);
+		dwmci_wait_reset(host, DWMCI_CTRL_FIFO_RESET);
+	}
+
+	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) {
+		debug("%s: Timeout.\n", __func__);
+		return TIMEOUT;
+	}
+
+	if (mask & DWMCI_INTMSK_RTO) {
+		/*
+		 * Timeout here is not necessarily fatal. (e)MMC cards
+		 * will splat here when they receive CMD55 as they do
+		 * not support this command and that is exactly the way
+		 * to tell them apart from SD cards. Thus, this output
+		 * below shall be debug(). eMMC cards also do not favor
+		 * CMD8, please keep that in mind.
+		 */
+		debug("%s: Response Timeout.\n", __func__);
+		return TIMEOUT;
+	} else if (mask & DWMCI_INTMSK_RE) {
+		debug("%s: Response Error.\n", __func__);
+		return -EIO;
+	}
+
+	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) {
+		size = data->blocksize * data->blocks / 4;
+		start = get_timer(0);
+		timeout = 10000;
+		for (;;) {
+			mask = dwmci_readl(host, DWMCI_RINTSTS);
+			/* Error during data transfer. */
+			if (mask & (DWMCI_DATA_ERR | DWMCI_DATA_TOUT)) {
+				debug("%s: DATA ERROR!\n", __func__);
+				ret = -EINVAL;
+				break;
+			}
+
+			/*
+			 * TODO: rk3036 use external DMA,
+			 * need to support DMA mode in future
+			 */
+			if (data->flags == MMC_DATA_READ) {
+				if ((dwmci_readl(host, DWMCI_RINTSTS) &&
+				     DWMCI_INTMSK_RXDR) && size) {
+					fifo_len = dwmci_readl(host,
+							       DWMCI_STATUS);
+					fifo_len = (fifo_len >> MMC_FIFO_SHIFT)
+						    & MMC_FIFO_MASK;
+					for (i = 0; i < fifo_len; i++)
+						*buf++ = dwmci_readl(host,
+								DWMCI_DATA);
+					dwmci_writel(host, DWMCI_RINTSTS,
+						     DWMCI_INTMSK_RXDR);
+					size = size > fifo_len ?
+							(size - fifo_len) : 0;
+				}
+			} else {
+				if ((dwmci_readl(host, DWMCI_RINTSTS) &&
+				     DWMCI_INTMSK_TXDR) && size) {
+					fifo_len = dwmci_readl(host,
+							DWMCI_STATUS);
+					fifo_len = FIFO_DETH -
+						((fifo_len >> MMC_FIFO_SHIFT)
+						& MMC_FIFO_MASK);
+					for (i = 0; i < fifo_len; i++)
+						dwmci_writel(host, DWMCI_DATA,
+							     *buf++);
+					dwmci_writel(host, DWMCI_RINTSTS,
+						     DWMCI_INTMSK_TXDR);
+					size = size > fifo_len ?
+							(size - fifo_len) : 0;
+				}
+			}
+
+			/* Data arrived correctly. */
+			if (mask & DWMCI_INTMSK_DTO) {
+				ret = 0;
+				break;
+			}
+
+			/* Check for timeout. */
+			if (get_timer(start) > timeout) {
+				debug("%s: Timeout waiting for data!\n",
+				      __func__);
+				ret = TIMEOUT;
+				break;
+			}
+		}
+		dwmci_writel(host, DWMCI_RINTSTS, mask);
+	}
+	udelay(100);
+
+	return ret;
+}
+
+static int dwmci_setup_bus(struct dwmci_host *host, u32 freq)
+{
+	u32 div, status;
+	int timeout = 10000;
+	unsigned long sclk;
+	unsigned long start = get_timer(0);
+
+	if ((freq == host->clock) || (freq == 0))
+		return 0;
+	/*
+	 * If host->get_mmc_clk isn't defined,
+	 * then assume that host->bus_hz is source clock value.
+	 * host->bus_hz should be set by user.
+	 */
+	if (host->get_mmc_clk) {
+		sclk = host->get_mmc_clk(host, freq);
+	} else if (host->bus_hz) {
+		sclk = host->bus_hz;
+	} else {
+		debug("%s: Didn't get source clock value.\n", __func__);
+		return -EINVAL;
+	}
+
+	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);
+
+	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 (get_timer(start) > timeout) {
+			debug("%s: Timeout!\n", __func__);
+			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);
+
+	start = get_timer(0);
+	do {
+		status = dwmci_readl(host, DWMCI_CMD);
+		if (get_timer(start) > timeout) {
+			debug("%s: Timeout!\n", __func__);
+			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, regs;
+
+	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);
+
+	regs = dwmci_readl(host, DWMCI_UHS_REG);
+	if (mmc->ddr_mode)
+		regs |= DWMCI_DDR_MODE;
+	else
+		regs &= ~DWMCI_DDR_MODE;
+
+	dwmci_writel(host, DWMCI_UHS_REG, regs);
+
+	if (host->clksel)
+		host->clksel(host);
+}
+
+static int dwmci_init(struct mmc *mmc)
+{
+	struct dwmci_host *host = mmc->priv;
+
+	if (host->board_init)
+		host->board_init(host);
+
+	dwmci_writel(host, DWMCI_PWREN, 1);
+
+	if (!dwmci_wait_reset(host, DWMCI_RESET_ALL)) {
+		debug("%s[%d] Fail-reset!!\n", __func__, __LINE__);
+		return -EIO;
+	}
+
+	/* Enumerate at 400KHz */
+	dwmci_setup_bus(host, mmc->cfg->f_min);
+
+	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);
+
+	if (!host->fifoth_val) {
+		host->fifoth_val = MSIZE(0x2) | RX_WMARK(FIFO_DETH / 2 - 1) |
+				TX_WMARK(FIFO_DETH / 2);
+	}
+	dwmci_writel(host, DWMCI_FIFOTH, host->fifoth_val);
+
+	dwmci_writel(host, DWMCI_CLKENA, 0);
+	dwmci_writel(host, DWMCI_CLKSRC, 0);
+
+	return 0;
+}
+
+static const struct mmc_ops dwmci_ops = {
+	.send_cmd	= dwmci_send_cmd,
+	.set_ios	= dwmci_set_ios,
+	.init		= dwmci_init,
+};
+
+int add_dwmci(struct dwmci_host *host, u32 max_clk, u32 min_clk)
+{
+	host->cfg.name = host->name;
+	host->cfg.ops = &dwmci_ops;
+	host->cfg.f_min = min_clk;
+	host->cfg.f_max = max_clk;
+
+	host->cfg.voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
+
+	host->cfg.host_caps = host->caps;
+
+	if (host->buswidth == 8) {
+		host->cfg.host_caps |= MMC_MODE_8BIT;
+		host->cfg.host_caps &= ~MMC_MODE_4BIT;
+	} else {
+		host->cfg.host_caps |= MMC_MODE_4BIT;
+		host->cfg.host_caps &= ~MMC_MODE_8BIT;
+	}
+	host->cfg.host_caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz;
+
+	host->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
+
+	host->mmc = mmc_create(&host->cfg, host);
+	if (host->mmc == NULL)
+		return -1;
+
+	return 0;
+}
+
+static uint rockchip_dwmmc_get_mmc_clk(struct dwmci_host *host, uint freq)
+{
+	struct udevice *dev = host->priv;
+	struct rockchip_dwmmc_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = clk_set_periph_rate(priv->clk, PERIPH_ID_SDMMC0 + host->dev_index,
+				  freq);
+	if (ret < 0) {
+		debug("%s: err=%d\n", __func__, ret);
+		return ret;
+	}
+
+	return freq;
+}
+
+static int rockchip_dwmmc_ofdata_to_platdata(struct udevice *dev)
+{
+	struct rockchip_dwmmc_priv *priv = dev_get_priv(dev);
+	struct dwmci_host *host = &priv->host;
+
+	host->name = dev->name;
+	host->ioaddr = (void *)dev_get_addr(dev);
+	host->buswidth = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
+					"bus-width", 4);
+	host->get_mmc_clk = rockchip_dwmmc_get_mmc_clk;
+	host->priv = dev;
+
+	/* use non-removeable as sdcard and emmc as judgement */
+	if (fdtdec_lookup_phandle(gd->fdt_blob, dev->of_offset, "non-removable")
+					== -FDT_ERR_NOTFOUND)
+		host->dev_index = (ulong)host->ioaddr == 1;
+
+	return 0;
+}
+
+static int rockchip_dwmmc_probe(struct udevice *dev)
+{
+	struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
+	struct rockchip_dwmmc_priv *priv = dev_get_priv(dev);
+	struct dwmci_host *host = &priv->host;
+	u32 minmax[2];
+	int ret;
+
+	ret = uclass_get_device(UCLASS_CLK, CLK_GENERAL, &priv->clk);
+	if (ret)
+		return ret;
+
+	ret = fdtdec_get_int_array(gd->fdt_blob, dev->of_offset,
+				   "clock-freq-min-max", minmax, 2);
+	if (!ret)
+		ret = add_dwmci(host, minmax[1], minmax[0]);
+	if (ret)
+		return ret;
+
+	upriv->mmc = host->mmc;
+
+	return 0;
+}
+
+static const struct udevice_id rockchip_dwmmc_ids[] = {
+	{ .compatible = "rockchip,rk3288-dw-mshc" },
+	{ }
+};
+
+U_BOOT_DRIVER(rockchip_dwmmc_drv) = {
+	.name		= "rockchip_3036_dwmmc",
+	.id		= UCLASS_MMC,
+	.of_match	= rockchip_dwmmc_ids,
+	.ofdata_to_platdata = rockchip_dwmmc_ofdata_to_platdata,
+	.probe		= rockchip_dwmmc_probe,
+	.priv_auto_alloc_size = sizeof(struct rockchip_dwmmc_priv),
+};