From c6c3afb3ebd37ab36926f660c081f3d5ee16ff81 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 3 Sep 2013 10:30:08 +0200 Subject: [PATCH] mci: add designware mmc controller support Based on the U-Boot driver. Signed-off-by: Sascha Hauer --- drivers/mci/Kconfig | 8 + drivers/mci/Makefile | 1 + drivers/mci/dw_mmc.c | 609 +++++++++++++++++++++++++++++++++ include/platform_data/dw_mmc.h | 9 + 4 files changed, 627 insertions(+) create mode 100644 drivers/mci/dw_mmc.c create mode 100644 include/platform_data/dw_mmc.h diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig index 7aff7df83..f34c119a2 100644 --- a/drivers/mci/Kconfig +++ b/drivers/mci/Kconfig @@ -34,6 +34,14 @@ config MCI_MMC_BOOT_PARTITIONS comment "--- MCI host drivers ---" +config MCI_DW + bool "Synopsys DesignWare Memory Card Interface" + depends on ARM + help + This selects support for the Synopsys DesignWare Mobile Storage IP + block, this provides host support for SD and MMC interfaces, in both + PIO and external DMA modes. + config MCI_MXS bool "i.MX23/i.MX28" depends on ARCH_MXS diff --git a/drivers/mci/Makefile b/drivers/mci/Makefile index df06a08f6..c13dad312 100644 --- a/drivers/mci/Makefile +++ b/drivers/mci/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_MCI_OMAP_HSMMC) += omap_hsmmc.o obj-$(CONFIG_MCI_PXA) += pxamci.o obj-$(CONFIG_MCI_S3C) += s3c.o obj-$(CONFIG_MCI_SPI) += mci_spi.o +obj-$(CONFIG_MCI_DW) += dw_mmc.o diff --git a/drivers/mci/dw_mmc.c b/drivers/mci/dw_mmc.c new file mode 100644 index 000000000..cd2fbb5e9 --- /dev/null +++ b/drivers/mci/dw_mmc.c @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2013 Altera Corporation + * + * (C) Copyright 2012 SAMSUNG Electronics + * Jaehoon Chung + * Rajeshawari Shinde + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DWMCI_CTRL 0x000 +#define DWMCI_PWREN 0x004 +#define DWMCI_CLKDIV 0x008 +#define DWMCI_CLKSRC 0x00C +#define DWMCI_CLKENA 0x010 +#define DWMCI_TMOUT 0x014 +#define DWMCI_CTYPE 0x018 +#define DWMCI_BLKSIZ 0x01C +#define DWMCI_BYTCNT 0x020 +#define DWMCI_INTMASK 0x024 +#define DWMCI_CMDARG 0x028 +#define DWMCI_CMD 0x02C +#define DWMCI_RESP0 0x030 +#define DWMCI_RESP1 0x034 +#define DWMCI_RESP2 0x038 +#define DWMCI_RESP3 0x03C +#define DWMCI_MINTSTS 0x040 +#define DWMCI_RINTSTS 0x044 +#define DWMCI_STATUS 0x048 +#define DWMCI_FIFOTH 0x04C +#define DWMCI_CDETECT 0x050 +#define DWMCI_WRTPRT 0x054 +#define DWMCI_GPIO 0x058 +#define DWMCI_TCMCNT 0x05C +#define DWMCI_TBBCNT 0x060 +#define DWMCI_DEBNCE 0x064 +#define DWMCI_USRID 0x068 +#define DWMCI_VERID 0x06C +#define DWMCI_HCON 0x070 +#define DWMCI_UHS_REG 0x074 +#define DWMCI_BMOD 0x080 +#define DWMCI_PLDMND 0x084 +#define DWMCI_DBADDR 0x088 +#define DWMCI_IDSTS 0x08C +#define DWMCI_IDINTEN 0x090 +#define DWMCI_DSCADDR 0x094 +#define DWMCI_BUFADDR 0x098 +#define DWMCI_DATA 0x200 + +/* Interrupt Mask register */ +#define DWMCI_INTMSK_ALL 0xffffffff +#define DWMCI_INTMSK_RE (1 << 1) +#define DWMCI_INTMSK_CDONE (1 << 2) +#define DWMCI_INTMSK_DTO (1 << 3) +#define DWMCI_INTMSK_TXDR (1 << 4) +#define DWMCI_INTMSK_RXDR (1 << 5) +#define DWMCI_INTMSK_RCRC (1 << 6) +#define DWMCI_INTMSK_DCRC (1 << 7) +#define DWMCI_INTMSK_RTO (1 << 8) +#define DWMCI_INTMSK_DRTO (1 << 9) +#define DWMCI_INTMSK_HTO (1 << 10) +#define DWMCI_INTMSK_FRUN (1 << 11) +#define DWMCI_INTMSK_HLE (1 << 12) +#define DWMCI_INTMSK_SBE (1 << 13) +#define DWMCI_INTMSK_ACD (1 << 14) +#define DWMCI_INTMSK_EBE (1 << 15) + +/* Raw interrupt Register */ +#define DWMCI_DATA_ERR (DWMCI_INTMSK_EBE | DWMCI_INTMSK_SBE | DWMCI_INTMSK_HLE |\ + DWMCI_INTMSK_FRUN | DWMCI_INTMSK_DCRC) +#define DWMCI_DATA_TOUT (DWMCI_INTMSK_HTO | DWMCI_INTMSK_DRTO) + +/* CTRL register */ +#define DWMCI_CTRL_RESET (1 << 0) +#define DWMCI_CTRL_FIFO_RESET (1 << 1) +#define DWMCI_CTRL_DMA_RESET (1 << 2) +#define DWMCI_DMA_EN (1 << 5) +#define DWMCI_CTRL_SEND_AS_CCSD (1 << 10) +#define DWMCI_IDMAC_EN (1 << 25) +#define DWMCI_RESET_ALL (DWMCI_CTRL_RESET | DWMCI_CTRL_FIFO_RESET |\ + DWMCI_CTRL_DMA_RESET) + +/* CMD register */ +#define DWMCI_CMD_RESP_EXP (1 << 6) +#define DWMCI_CMD_RESP_LENGTH (1 << 7) +#define DWMCI_CMD_CHECK_CRC (1 << 8) +#define DWMCI_CMD_DATA_EXP (1 << 9) +#define DWMCI_CMD_RW (1 << 10) +#define DWMCI_CMD_SEND_STOP (1 << 12) +#define DWMCI_CMD_ABORT_STOP (1 << 14) +#define DWMCI_CMD_PRV_DAT_WAIT (1 << 13) +#define DWMCI_CMD_UPD_CLK (1 << 21) +#define DWMCI_CMD_USE_HOLD_REG (1 << 29) +#define DWMCI_CMD_START (1 << 31) + +/* CLKENA register */ +#define DWMCI_CLKEN_ENABLE (1 << 0) +#define DWMCI_CLKEN_LOW_PWR (1 << 16) + +/* Card-type register */ +#define DWMCI_CTYPE_1BIT 0 +#define DWMCI_CTYPE_4BIT (1 << 0) +#define DWMCI_CTYPE_8BIT (1 << 16) + +/* Status Register */ +#define DWMCI_STATUS_BUSY (1 << 9) + +/* FIFOTH Register */ +#define DWMCI_FIFOTH_MSIZE(x) ((x) << 28) +#define DWMCI_FIFOTH_RX_WMARK(x) ((x) << 16) +#define DWMCI_FIFOTH_TX_WMARK(x) (x) +#define DWMCI_FIFOTH_FIFO_DEPTH(x) ((((x) >> 16) & 0x3ff) + 1) + +#define DWMCI_IDMAC_OWN (1 << 31) +#define DWMCI_IDMAC_CH (1 << 4) +#define DWMCI_IDMAC_FS (1 << 3) +#define DWMCI_IDMAC_LD (1 << 2) + +/* Bus Mode Register */ +#define DWMCI_BMOD_IDMAC_RESET (1 << 0) +#define DWMCI_BMOD_IDMAC_FB (1 << 1) +#define DWMCI_BMOD_IDMAC_EN (1 << 7) + +struct dwmci_host { + struct mci_host mci; + struct device_d *dev; + struct clk *clk_biu, *clk_ciu; + void *ioaddr; + unsigned int fifo_size_bytes; + + struct dwmci_idmac *idmac; + unsigned long clkrate; + int ciu_div; +}; + +struct dwmci_idmac { + uint32_t flags; + uint32_t cnt; + uint32_t addr; + uint32_t next_addr; +}; + +static inline void dwmci_writel(struct dwmci_host *host, int reg, uint32_t val) +{ + writel(val, host->ioaddr + reg); +} + +static inline uint32_t dwmci_readl(struct dwmci_host *host, int reg) +{ + return readl(host->ioaddr + reg); +} + +#define DW_MMC_NUM_IDMACS (PAGE_SIZE / sizeof(struct dwmci_idmac)) + +static inline struct dwmci_host *to_dwmci_host(struct mci_host *mci) +{ + return container_of(mci, struct dwmci_host, mci); +} + +static int dwmci_wait_reset(struct dwmci_host *host, uint32_t value) +{ + uint64_t start; + uint32_t ctrl; + + start = get_time_ns(); + + dwmci_writel(host, DWMCI_CTRL, value); + + while (!is_timeout(start, SECOND)) { + ctrl = dwmci_readl(host, DWMCI_CTRL); + if (!(ctrl & DWMCI_RESET_ALL)) + return 0; + } + + return -EIO; +} + +static int dwmci_prepare_data(struct dwmci_host *host, + struct mci_data *data) +{ + unsigned long ctrl; + unsigned int i = 0, flags, cnt, blk_cnt; + unsigned long data_start, start_addr; + struct dwmci_idmac *desc = host->idmac; + + blk_cnt = data->blocks; + + if (blk_cnt > DW_MMC_NUM_IDMACS) + return -EINVAL; + + dwmci_wait_reset(host, DWMCI_CTRL_FIFO_RESET); + + data_start = (uint32_t)desc; + dwmci_writel(host, DWMCI_DBADDR, (uint32_t)desc); + + if (data->flags & MMC_DATA_READ) + start_addr = (uint32_t)data->dest; + else + start_addr = (uint32_t)data->src; + + do { + flags = DWMCI_IDMAC_OWN | DWMCI_IDMAC_CH; + flags |= (i == 0) ? DWMCI_IDMAC_FS : 0; + + if (blk_cnt <= 8) { + flags |= DWMCI_IDMAC_LD; + cnt = data->blocksize * blk_cnt; + } else { + cnt = data->blocksize * 8; + } + + desc->flags = flags; + desc->cnt = cnt; + desc->addr = start_addr + (i * PAGE_SIZE); + desc->next_addr = (uint32_t)(desc + 1); + + dev_dbg(host->dev, "desc@ 0x%p 0x%08x 0x%08x 0x%08x 0x%08x\n", + desc, flags, cnt, desc->addr, desc->next_addr); + if (blk_cnt < 8) + break; + + blk_cnt -= 8; + desc++; + i++; + } while (1); + + ctrl = dwmci_readl(host, DWMCI_CTRL); + ctrl |= DWMCI_IDMAC_EN | DWMCI_DMA_EN; + dwmci_writel(host, DWMCI_CTRL, ctrl); + + ctrl = dwmci_readl(host, DWMCI_BMOD); + ctrl |= DWMCI_BMOD_IDMAC_FB | DWMCI_BMOD_IDMAC_EN; + dwmci_writel(host, DWMCI_BMOD, ctrl); + + dwmci_writel(host, DWMCI_BLKSIZ, data->blocksize); + dwmci_writel(host, DWMCI_BYTCNT, data->blocksize * data->blocks); + + return 0; +} + +static int dwmci_set_transfer_mode(struct dwmci_host *host, + struct mci_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_cmd(struct mci_host *mci, struct mci_cmd *cmd, struct mci_data *data) +{ + struct dwmci_host *host = to_dwmci_host(mci); + int flags = 0; + uint32_t mask, ctrl; + uint64_t start; + int ret; + unsigned int num_bytes = 0; + const void *writebuf = NULL; + + start = get_time_ns(); + while (1) { + if (!(dwmci_readl(host, DWMCI_STATUS) & DWMCI_STATUS_BUSY)) + break; + + if (is_timeout(start, 100 * MSECOND)) { + dev_dbg(host->dev, "Timeout on data busy\n"); + return -ETIMEDOUT; + } + } + + dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMSK_ALL); + + if (data) { + num_bytes = data->blocks * data->blocksize; + + if (data->flags & MMC_DATA_WRITE) { + dma_flush_range((unsigned long)data->src, + (unsigned long)(data->src + data->blocks * 512)); + + writebuf = data->src; + } + + ret = dwmci_prepare_data(host, data); + if (ret) + return ret; + } + + 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 -EINVAL; + + 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); + + dev_dbg(host->dev, "Sending CMD%d\n", cmd->cmdidx); + + dwmci_writel(host, DWMCI_CMD, flags); + + start = get_time_ns(); + while (1) { + mask = dwmci_readl(host, DWMCI_RINTSTS); + if (mask & DWMCI_INTMSK_CDONE) { + if (!data) + dwmci_writel(host, DWMCI_RINTSTS, mask); + break; + } + if (is_timeout(start, 100 * MSECOND)) + return -ETIMEDOUT; + } + + if (mask & DWMCI_INTMSK_RTO) { + dev_dbg(host->dev, "Response Timeout..\n"); + return -ETIMEDOUT; + } else if (mask & DWMCI_INTMSK_RE) { + dev_dbg(host->dev, "Response Error..\n"); + 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) { + start = get_time_ns(); + do { + mask = dwmci_readl(host, DWMCI_RINTSTS); + if (mask & (DWMCI_DATA_ERR | DWMCI_DATA_TOUT)) { + dev_dbg(host->dev, "DATA ERROR!\n"); + return -EIO; + } + if (is_timeout(start, SECOND)) + return -ETIMEDOUT; + } while (!(mask & DWMCI_INTMSK_DTO)); + + dwmci_writel(host, DWMCI_RINTSTS, mask); + + ctrl = dwmci_readl(host, DWMCI_CTRL); + ctrl &= ~(DWMCI_DMA_EN); + dwmci_writel(host, DWMCI_CTRL, ctrl); + + if (data->flags & MMC_DATA_READ) { + dma_inv_range((unsigned long)data->dest, + (unsigned long)(data->dest + data->blocks * 512)); + } + } + + udelay(100); + + return 0; +} + +static int dwmci_send_cmd(struct dwmci_host *host, u32 cmd, u32 arg) +{ + uint64_t start = get_time_ns(); + uint32_t status; + + dwmci_writel(host, DWMCI_CMDARG, arg); + + dwmci_writel(host, DWMCI_CMD, cmd | DWMCI_CMD_START); + + while (1) { + status = dwmci_readl(host, DWMCI_CMD); + if (!(status & DWMCI_CMD_START)) + return 0; + + if (is_timeout(start, 100 * MSECOND)) { + dev_err(host->dev, "TIMEOUT error!!\n"); + return -ETIMEDOUT; + } + } +} + +static int dwmci_setup_bus(struct dwmci_host *host, uint32_t freq) +{ + uint32_t div; + unsigned long sclk; + + sclk = host->clkrate / host->ciu_div; + + div = DIV_ROUND_UP(sclk, freq); + if (div > 510) + div = 510; + + div >>= 1; + + dwmci_writel(host, DWMCI_CLKENA, 0); + dwmci_writel(host, DWMCI_CLKSRC, 0); + + dwmci_writel(host, DWMCI_CLKDIV, div); + + dwmci_send_cmd(host, DWMCI_CMD_PRV_DAT_WAIT | DWMCI_CMD_UPD_CLK, 0); + + dwmci_writel(host, DWMCI_CLKENA, DWMCI_CLKEN_ENABLE); + + dwmci_send_cmd(host, DWMCI_CMD_PRV_DAT_WAIT | DWMCI_CMD_UPD_CLK, 0); + + return 0; +} + +static void dwmci_set_ios(struct mci_host *mci, struct mci_ios *ios) +{ + struct dwmci_host *host = to_dwmci_host(mci); + uint32_t ctype; + + dev_dbg(host->dev, "Buswidth = %d, clock: %d\n", ios->bus_width, ios->clock); + + if (ios->clock) + dwmci_setup_bus(host, ios->clock); + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_8: + ctype = DWMCI_CTYPE_8BIT; + break; + case MMC_BUS_WIDTH_4: + ctype = DWMCI_CTYPE_4BIT; + break; + default: + ctype = DWMCI_CTYPE_1BIT; + break; + } + + dwmci_writel(host, DWMCI_CTYPE, ctype); +} + +static int dwmci_card_present(struct mci_host *mci) +{ + return 1; +} + +static int dwmci_init(struct mci_host *mci, struct device_d *dev) +{ + struct dwmci_host *host = to_dwmci_host(mci); + uint32_t fifo_size, fifoth_val; + + dwmci_writel(host, DWMCI_PWREN, 1); + + if (dwmci_wait_reset(host, DWMCI_RESET_ALL)) { + dev_err(host->dev, "reset failed\n"); + return -EIO; + } + + 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); + + fifo_size = dwmci_readl(host, DWMCI_FIFOTH); + + /* + * Use reset default of the rx_wmark field to determine the + * fifo depth. + */ + fifo_size = DWMCI_FIFOTH_FIFO_DEPTH(fifo_size); + host->fifo_size_bytes = fifo_size * 4; + + fifoth_val = DWMCI_FIFOTH_MSIZE(0x2) | + DWMCI_FIFOTH_RX_WMARK(fifo_size / 2 - 1) | + DWMCI_FIFOTH_TX_WMARK(fifo_size / 2); + + dwmci_writel(host, DWMCI_FIFOTH, fifoth_val); + + dwmci_writel(host, DWMCI_CLKENA, 0); + dwmci_writel(host, DWMCI_CLKSRC, 0); + + return 0; +} + +static int dw_mmc_detect(struct device_d *dev) +{ + struct dwmci_host *host = dev->priv; + + return mci_detect_card(&host->mci); +} + +static int dw_mmc_probe(struct device_d *dev) +{ + struct dwmci_host *host; + struct mci_host *mci; + struct dw_mmc_platform_data *pdata = dev->platform_data; + + host = xzalloc(sizeof(*host)); + mci = &host->mci; + + host->clk_biu = clk_get(dev, "biu"); + if (IS_ERR(host->clk_biu)) + return PTR_ERR(host->clk_biu); + + host->clk_ciu = clk_get(dev, "ciu"); + if (IS_ERR(host->clk_ciu)) + return PTR_ERR(host->clk_ciu); + + clk_enable(host->clk_biu); + clk_enable(host->clk_ciu); + + host->dev = dev; + host->ioaddr = dev_request_mem_region(dev, 0); + if (!host->ioaddr) + return -EBUSY; + + if (pdata) { + mci->devname = pdata->devname; + host->ciu_div = pdata->ciu_div; + } else if (dev->device_node) { + const char *alias = of_alias_get(dev->device_node); + if (alias) + mci->devname = xstrdup(alias); + of_property_read_u32(dev->device_node, "dw-mshc-ciu-div", + &host->ciu_div); + } + + /* divider is 0 based in pdata and 1 based in our private struct */ + host->ciu_div++; + + host->idmac = dma_alloc_coherent(sizeof(*host->idmac) * DW_MMC_NUM_IDMACS); + + host->mci.send_cmd = dwmci_cmd; + host->mci.set_ios = dwmci_set_ios; + host->mci.init = dwmci_init; + host->mci.card_present = dwmci_card_present; + host->mci.hw_dev = dev; + host->mci.voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + host->mci.host_caps = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA; + + dev->detect = dw_mmc_detect; + + host->clkrate = clk_get_rate(host->clk_ciu); + host->mci.f_min = host->clkrate / 510 / host->ciu_div; + if (host->mci.f_min < 200000) + host->mci.f_min = 200000; + host->mci.f_max = host->clkrate / host->ciu_div; + + mci_of_parse(&host->mci); + + dev->priv = host; + + return mci_register(&host->mci); +} + +static __maybe_unused struct of_device_id dw_mmc_compatible[] = { + { + .compatible = "altr,socfpga-dw-mshc", + }, { + /* sentinel */ + } +}; + +static struct driver_d dw_mmc_driver = { + .name = "dw_mmc", + .probe = dw_mmc_probe, + .of_compatible = DRV_OF_COMPAT(dw_mmc_compatible), +}; +device_platform_driver(dw_mmc_driver); diff --git a/include/platform_data/dw_mmc.h b/include/platform_data/dw_mmc.h new file mode 100644 index 000000000..48faa76c6 --- /dev/null +++ b/include/platform_data/dw_mmc.h @@ -0,0 +1,9 @@ +#ifndef __INCLUDE_PLATFORM_DATA_DW_MMC_H +#define __INCLUDE_PLATFORM_DATA_DW_MMC_H + +struct dw_mmc_platform_data { + char *devname; + int ciu_div; +}; + +#endif /* __INCLUDE_PLATFORM_DATA_DW_MMC_H */