
Hi Lin,
On 20 October 2015 at 20:37, 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.
Signed-off-by: Lin Huang hl@rock-chips.com
Changes in v1:
- clean copyright announcement
drivers/mmc/Kconfig | 9 + drivers/mmc/Makefile | 1 + drivers/mmc/rockchip_3036_dw_mmc.c | 479 +++++++++++++++++++++++++++++++++++++ 3 files changed, 489 insertions(+) create mode 100644 drivers/mmc/rockchip_3036_dw_mmc.c
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index 6277f92..38bfb9c 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -19,6 +19,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 99d0295..ff3920a 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -30,6 +30,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..2a2df52 --- /dev/null +++ b/drivers/mmc/rockchip_3036_dw_mmc.c @@ -0,0 +1,479 @@ +/*
- (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 <memalign.h> +#include <mmc.h> +#include <dwmmc.h> +#include <asm-generic/errno.h>
+DECLARE_GLOBAL_DATA_PTR;
+#define PAGE_SIZE 4096 +#define MMC_GET_FCNT(x) (((x)>>17) & 0x1FF)
Can we use the SHIFT and MASK enums instead (as for clocks)? I'd like to avoid these sort of macro accessors.
+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;
u32 ctrl;
dwmci_writel(host, DWMCI_CTRL, value);
while (timeout--) {
ctrl = dwmci_readl(host, DWMCI_CTRL);
if (!(ctrl & DWMCI_RESET_ALL))
return 1;
The timeout here is somewhat indeterminate, since it does not reference the timer. Unless there is a special reason to do this (in which case we should have a comment here) we should use something like:
unsigned long start;
start = get_timer(0); do { } while (get_timer(start) < 1000);
}
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 = 1000;
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 = MMC_GET_FCNT(fifo_len);
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 = MMC_GET_FCNT(fifo_len);
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;
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);
Similar here.
if (timeout-- < 0) {
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);
timeout = 10000;
do {
status = dwmci_readl(host, DWMCI_CMD);
if (timeout-- < 0) {
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) {
uint32_t fifo_size;
fifo_size = dwmci_readl(host, DWMCI_FIFOTH);
fifo_size = ((fifo_size & RX_WMARK_MASK) >> RX_WMARK_SHIFT) + 1;
host->fifoth_val = MSIZE(0x2) | RX_WMARK(fifo_size / 2 - 1) |
TX_WMARK(fifo_size / 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
Regards, Simon