Message ID | 1445395048-3703-11-git-send-email-hl@rock-chips.com |
---|---|
State | Superseded |
Delegated to: | Simon Glass |
Headers | show |
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
Hi Simon, On 22/10/15 22:08, Simon Glass wrote: > 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. Okay, got it. > >> + >> +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); Thanks to point it, i will modify it next version. > >> + } >> + 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. Got it. > >> + 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 > > >
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) + +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; + } + 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); + 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), +};
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