From mboxrd@z Thu Jan 1 00:00:00 1970 From: hl Date: Fri, 23 Oct 2015 09:03:41 +0800 Subject: [U-Boot] [PATCH v1 10/12] rockchip: Add an rk3036 MMC driver In-Reply-To: References: <1445395048-3703-1-git-send-email-hl@rock-chips.com> <1445395048-3703-11-git-send-email-hl@rock-chips.com> Message-ID: <5629876D.6040004@rock-chips.com> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit To: u-boot@lists.denx.de Hi Simon, On 22/10/15 22:08, Simon Glass wrote: > Hi Lin, > > On 20 October 2015 at 20:37, Lin Huang 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 >> --- >> 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 >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +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 > > > -- Lin Huang