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 <h...@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 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), +}; -- 1.9.1 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot