From c26c8b3daf543fb9fc8fd9cbbec83c0a68f425dc Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E6=9F=AF=E9=A3=9E=E9=9B=84?= Date: Tue, 25 May 2010 02:25:32 +0000 Subject: [PATCH] add sd driver --- drivers/mmc/host/Kconfig | 23 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/rk2818-sdmmc.c | 1387 +++++++++++++++++++++++++++++++ drivers/mmc/host/rk2818-sdmmc.h | 113 +++ 4 files changed, 1524 insertions(+) create mode 100644 drivers/mmc/host/rk2818-sdmmc.c create mode 100755 drivers/mmc/host/rk2818-sdmmc.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 432ae8358c86..c1dca8a8e443 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -4,6 +4,29 @@ comment "MMC/SD/SDIO Host Controller Drivers" +config SDMMC_RK2818 + tristate "RK2818 SDMMC controller suppport" + depends on ARCH_RK2818 + help + This selects the RK2818 SDMMC controller. + SDMMC0 used for sd/mmc card, and SDMMC1 used for sdio. +if SDMMC_RK2818 + comment "Now, there are two SDMMC controllers selected, SDMMC0 and SDMMC1." + config SDMMC0_RK2818 + tristate "RK2818 SDMMC0 controller support" + default y + depends on ARCH_RK2818 + help + This supports the use of the SDMMC0 controller on rk2818 processors. + + config SDMMC1_RK2818 + tristate "RK2818 SDMMC1 controller support" + default y + depends on ARCH_RK2818 + help + This supports the use of the SDMMC1 controller on rk2818 processors. +endif + config MMC_ARMMMCI tristate "ARM AMBA Multimedia Card Interface support" depends on ARM_AMBA diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index abcb0400e06d..cf31850676a4 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -6,6 +6,7 @@ ifeq ($(CONFIG_MMC_DEBUG),y) EXTRA_CFLAGS += -DDEBUG endif +obj-$(CONFIG_SDMMC_RK2818) += rk2818-sdmmc.o obj-$(CONFIG_MMC_ARMMMCI) += mmci.o obj-$(CONFIG_MMC_PXA) += pxamci.o obj-$(CONFIG_MMC_IMX) += imxmmc.o diff --git a/drivers/mmc/host/rk2818-sdmmc.c b/drivers/mmc/host/rk2818-sdmmc.c new file mode 100644 index 000000000000..2761a0c8b25b --- /dev/null +++ b/drivers/mmc/host/rk2818-sdmmc.c @@ -0,0 +1,1387 @@ +/* drivers/mmc/host/rk2818-sdmmc.c + * + * Copyright (C) 2010 ROCKCHIP, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "rk2818-sdmmc.h" + +#define RK2818_MCI_DATA_ERROR_FLAGS (SDMMC_INT_DRTO | SDMMC_INT_DCRC | SDMMC_INT_HTO | SDMMC_INT_SBE | SDMMC_INT_EBE) +#define RK2818_MCI_CMD_ERROR_FLAGS (SDMMC_INT_RTO | SDMMC_INT_RCRC | SDMMC_INT_RE | SDMMC_INT_HLE) +#define RK2818_MCI_ERROR_FLAGS (RK2818_MCI_DATA_ERROR_FLAGS | RK2818_MCI_CMD_ERROR_FLAGS | SDMMC_INT_HLE) +#define RK2818_MCI_SEND_STATUS 1 +#define RK2818_MCI_RECV_STATUS 2 +#define RK2818_MCI_DMA_THRESHOLD 16 + +enum { + EVENT_CMD_COMPLETE = 0, + EVENT_XFER_COMPLETE, + EVENT_DATA_COMPLETE, + EVENT_DATA_ERROR, + EVENT_XFER_ERROR +}; + + +enum rk2818_sdmmc_state { + STATE_IDLE = 0, + STATE_SENDING_CMD, + STATE_SENDING_DATA, + STATE_DATA_BUSY, + STATE_SENDING_STOP, + STATE_DATA_ERROR, +}; + +struct rk2818_sdmmc_host { + struct device *dev; + struct clk *clk; + spinlock_t lock; + void __iomem *regs; + + struct scatterlist *sg; + unsigned int pio_offset; + struct resource *mem; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + + int dma_chn; + unsigned int use_dma:1; + u32 cmd_status; + u32 data_status; + u32 stop_cmdr; + u32 dir_status; + struct tasklet_struct tasklet; + unsigned long pending_events; + unsigned long completed_events; + enum rk2818_sdmmc_state state; + struct list_head queue; + + u32 bus_hz; + u32 current_speed; + struct platform_device *pdev; + struct rk2818_sdmmc_platform_data *pdata; + + struct mmc_host *mmc; + u32 ctype; + + struct list_head queue_node; + + unsigned int clock; + unsigned long flags; +#define RK2818_MMC_CARD_PRESENT 0 +#define RK2818_MMC_CARD_NEED_INIT 1 +#define RK2818_MMC_SHUTDOWN 2 + int irq; + + struct timer_list detect_timer; +}; + +#define rk2818_sdmmc_test_and_clear_pending(host, event) \ + test_and_clear_bit(event, &host->pending_events) +#define rk2818_sdmmc_set_completed(host, event) \ + set_bit(event, &host->completed_events) + +#define rk2818_sdmmc_set_pending(host, event) \ + set_bit(event, &host->pending_events) + +#if defined (CONFIG_DEBUG_FS) + +static int rk2818_sdmmc_req_show(struct seq_file *s, void *v) +{ + struct rk2818_sdmmc_host *host = s->private; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_command *stop; + struct mmc_data *data; + + spin_lock(&host->lock); + mrq = host->mrq; + + if (mrq) { + cmd = mrq->cmd; + data = mrq->data; + stop = mrq->stop; + + if (cmd) + seq_printf(s, + "CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n", + cmd->opcode, cmd->arg, cmd->flags, + cmd->resp[0], cmd->resp[1], cmd->resp[2], + cmd->resp[2], cmd->error); + if (data) + seq_printf(s, "DATA %u / %u * %u flg %x err %d\n", + data->bytes_xfered, data->blocks, + data->blksz, data->flags, data->error); + if (stop) + seq_printf(s, + "CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n", + stop->opcode, stop->arg, stop->flags, + stop->resp[0], stop->resp[1], stop->resp[2], + stop->resp[2], stop->error); + } + + spin_unlock(&host->lock); + + return 0; +} + +static int rk2818_sdmmc_req_open(struct inode *inode, struct file *file) +{ + return single_open(file, rk2818_sdmmc_req_show, inode->i_private); +} + +static const struct file_operations rk2818_sdmmc_req_fops = { + .owner = THIS_MODULE, + .open = rk2818_sdmmc_req_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int rk2818_sdmmc_regs_show(struct seq_file *s, void *v) +{ + struct rk2818_sdmmc_host *host = s->private; + + seq_printf(s, "STATUS: 0x%08x\n",readl(host->regs + SDMMC_STATUS)); + seq_printf(s, "RINTSTS: 0x%08x\n",readl(host->regs + SDMMC_RINTSTS)); + seq_printf(s, "CMD: 0x%08x\n", readl(host->regs + SDMMC_CMD)); + seq_printf(s, "CTRL: 0x%08x\n", readl(host->regs + SDMMC_CTRL)); + seq_printf(s, "INTMASK: 0x%08x\n", readl(host->regs + SDMMC_INTMASK)); + seq_printf(s, "CLKENA: 0x%08x\n", readl(host->regs + SDMMC_CLKENA)); + + return 0; +} + +static int rk2818_sdmmc_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, rk2818_sdmmc_regs_show, inode->i_private); +} + +static const struct file_operations rk2818_sdmmc_regs_fops = { + .owner = THIS_MODULE, + .open = rk2818_sdmmc_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void rk2818_sdmmc_init_debugfs(struct rk2818_sdmmc_host *host) +{ + struct mmc_host *mmc = host->mmc; + struct dentry *root; + struct dentry *node; + + root = mmc->debugfs_root; + if (!root) + return; + + node = debugfs_create_file("regs", S_IRUSR, root, host, + &rk2818_sdmmc_regs_fops); + if (IS_ERR(node)) + return; + if (!node) + goto err; + + node = debugfs_create_file("req", S_IRUSR, root, host, &rk2818_sdmmc_req_fops); + if (!node) + goto err; + + node = debugfs_create_u32("state", S_IRUSR, root, (u32 *)&host->state); + if (!node) + goto err; + + node = debugfs_create_x32("pending_events", S_IRUSR, root, + (u32 *)&host->pending_events); + if (!node) + goto err; + + node = debugfs_create_x32("completed_events", S_IRUSR, root, + (u32 *)&host->completed_events); + if (!node) + goto err; + + return; + +err: + dev_err(&mmc->class_dev, "failed to initialize debugfs for host\n"); +} +#endif +static void rk2818_sdmmc_set_power(struct rk2818_sdmmc_host *host, u32 ocr_avail) +{ + if(ocr_avail == 0) + writel(0, host->regs + SDMMC_PWREN); + else + writel(1, host->regs + SDMMC_PWREN); +} +static inline unsigned ns_to_clocks(unsigned clkrate, unsigned ns) +{ + u32 clks; + if (clkrate > 1000000) + clks = (ns * (clkrate / 1000000) + 999) / 1000; + else + clks = ((ns/1000) * (clkrate / 1000) + 999) / 1000; + + return clks; +} + +static void rk2818_sdmmc_set_timeout(struct rk2818_sdmmc_host *host, struct mmc_data *data) +{ + unsigned timeout; + + timeout = ns_to_clocks(host->clock, data->timeout_ns) + data->timeout_clks; + + dev_dbg(host->dev, "tmo req:%d + %d reg:%d clk:%d\n", + data->timeout_ns, data->timeout_clks, timeout, host->clock); + writel((timeout << 8) | (80), host->regs + SDMMC_TMOUT); +} + +static u32 rk2818_sdmmc_prepare_command(struct mmc_host *mmc, + struct mmc_command *cmd) +{ + struct mmc_data *data; + u32 cmdr; + + cmd->error = -EINPROGRESS; + cmdr = cmd->opcode; + + if(cmdr == 12) + cmdr |= SDMMC_CMD_STOP; + else + cmdr |= SDMMC_CMD_PRV_DAT_WAIT; + + if (cmd->flags & MMC_RSP_PRESENT) { + cmdr |= SDMMC_CMD_RESP_EXP; // expect the respond, need to set this bit + if (cmd->flags & MMC_RSP_136) + cmdr |= SDMMC_CMD_RESP_LONG; // expect long respond + + if(cmd->flags & MMC_RSP_CRC) + cmdr |= SDMMC_CMD_RESP_CRC; + } + + data = cmd->data; + if (data) { + cmdr |= SDMMC_CMD_DAT_EXP; + if (data->flags & MMC_DATA_STREAM) + cmdr |= SDMMC_CMD_STRM_MODE; // set stream mode + if (data->flags & MMC_DATA_WRITE) + cmdr |= SDMMC_CMD_DAT_WR; + + } + return cmdr; +} + + +static void rk2818_sdmmc_start_command(struct rk2818_sdmmc_host *host, + struct mmc_command *cmd, u32 cmd_flags) +{ + int tmo = 50; + host->cmd = cmd; + dev_dbg(host->dev, "start cmd:%d ARGR=0x%08x CMDR=0x%08x\n", + cmd->opcode, cmd->arg, cmd_flags); + writel(cmd->arg, host->regs + SDMMC_CMDARG); // write to CMDARG register + writel(cmd_flags | SDMMC_CMD_START, host->regs + SDMMC_CMD); // write to CMD register + + /* wait until CIU accepts the command */ + while (--tmo && (readl(host->regs + SDMMC_CMD) & SDMMC_CMD_START)) + cpu_relax(); +} + +static void send_stop_cmd(struct rk2818_sdmmc_host *host, struct mmc_data *data) +{ + rk2818_sdmmc_start_command(host, data->stop, host->stop_cmdr); +} + + +static void rk2818_sdmmc_dma_cleanup(struct rk2818_sdmmc_host *host) +{ + struct mmc_data *data = host->data; + if (data) + dma_unmap_sg(host->dev, data->sg, data->sg_len, + ((data->flags & MMC_DATA_WRITE)? DMA_TO_DEVICE : DMA_FROM_DEVICE)); +} + +static void rk2818_sdmmc_stop_dma(struct rk2818_sdmmc_host *host) +{ + if (host->dma_chn >= 0) { + writel(readl(host->regs + SDMMC_CTRL) & ~SDMMC_CTRL_DMA_ENABLE, + host->regs +SDMMC_CTRL); + disable_dma(host->dma_chn); + rk2818_sdmmc_dma_cleanup(host); + } else { + /* Data transfer was stopped by the interrupt handler */ + rk2818_sdmmc_set_pending(host, EVENT_XFER_COMPLETE); + } +} + +/* This function is called by the DMA driver from tasklet context. */ +static void rk2818_sdmmc_dma_complete(int chn, void *arg) +{ + struct rk2818_sdmmc_host *host = arg; + struct mmc_data *data = host->data; + + dev_dbg(host->dev, "DMA complete\n"); + + spin_lock(&host->lock); + rk2818_sdmmc_dma_cleanup(host); + disable_dma(host->dma_chn); + free_dma(host->dma_chn); + if (data) { + rk2818_sdmmc_set_pending(host, EVENT_XFER_COMPLETE); + tasklet_schedule(&host->tasklet); + } + spin_unlock(&host->lock); +} +static int rk2818_sdmmc_submit_data_dma(struct rk2818_sdmmc_host *host, struct mmc_data *data) +{ + struct scatterlist *sg; + unsigned int i; + + if(host->use_dma == 0) + return -ENOSYS; + if (data->blocks * data->blksz < RK2818_MCI_DMA_THRESHOLD) + return -EINVAL; + if (data->blksz & 3) + return -EINVAL; + for_each_sg(data->sg, sg, data->sg_len, i) { + if (sg->offset & 3 || sg->length & 3) + return -EINVAL; + } + for(i = 0; i < MAX_SG_CHN; i++) { + if(request_dma(i, "sd_mmc") == 0) { + host->dma_chn = i; + break; + } + } + if(i == MAX_SG_CHN) { + host->dma_chn = -1; + return -EINVAL; + } + dma_map_sg(host->dev, data->sg, data->sg_len, + (data->flags & MMC_DATA_READ)? DMA_FROM_DEVICE : DMA_TO_DEVICE); + + set_dma_sg(host->dma_chn, data->sg, data->sg_len); + + set_dma_mode(host->dma_chn, + (data->flags & MMC_DATA_READ)? DMA_MODE_READ : DMA_MODE_WRITE); + + set_dma_handler(host->dma_chn, rk2818_sdmmc_dma_complete, (void *)host); + + writel(readl(host->regs + SDMMC_CTRL) | SDMMC_CTRL_DMA_ENABLE, + host->regs +SDMMC_CTRL); + enable_dma(host->dma_chn); + dev_dbg(host->dev,"DMA enable, \n"); + return 0; +} + +static void rk2818_sdmmc_submit_data(struct rk2818_sdmmc_host *host, struct mmc_data *data) +{ + data->error = -EINPROGRESS; + + //WARN_ON(host->data); + host->sg = NULL; + host->data = data; + + if (rk2818_sdmmc_submit_data_dma(host, data)) { + host->sg = data->sg; + host->pio_offset = 0; + if (data->flags & MMC_DATA_READ) + host->dir_status = RK2818_MCI_RECV_STATUS; + else + host->dir_status = RK2818_MCI_SEND_STATUS; + writel(readl(host->regs + SDMMC_CTRL) | SDMMC_CTRL_DMA_ENABLE, + host->regs +SDMMC_CTRL); + } +} + +#define mci_send_cmd(host,cmd,arg) { \ + writel(arg, host->regs + SDMMC_CMDARG); \ + writel(SDMMC_CMD_START | cmd, host->regs + SDMMC_CMD); \ + while (readl(host->regs + SDMMC_CMD) & SDMMC_CMD_START); \ +} + +void rk2818_sdmmc_setup_bus(struct rk2818_sdmmc_host *host) +{ + u32 div; + + if (host->clock != host->current_speed) { + div = (((host->bus_hz + (host->bus_hz / 5)) / host->clock)) >> 1; + + dev_dbg(host->dev, "Bus speed = %dHz div:%d (actual %dHz)\n", + host->clock, div, (host->bus_hz / div) >> 1); + + /* store the actual clock for calculations */ + host->clock = (host->bus_hz / div) >> 1; + /* disable clock */ + writel(0, host->regs + SDMMC_CLKENA); + writel(0, host->regs + SDMMC_CLKSRC); + /* inform CIU */ + mci_send_cmd(host, SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0); + /* set clock to desired speed */ + writel(div, host->regs + SDMMC_CLKDIV); + /* inform CIU */ + mci_send_cmd(host, SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0); + /* enable clock */ + writel(SDMMC_CLKEN_ENABLE, host->regs + SDMMC_CLKENA); + /* inform CIU */ + mci_send_cmd(host, SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0); + + host->current_speed = host->clock; + } + + /* Set the current host bus width */ + writel(host->ctype, host->regs + SDMMC_CTYPE); +} + +static void rk2818_sdmmc_start_request(struct rk2818_sdmmc_host *host) +{ + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + u32 cmdflags; + + mrq = host->mrq; + + rk2818_sdmmc_setup_bus(host); + + host->mrq = mrq; + + host->pending_events = 0; + host->completed_events = 0; + host->data_status = 0; + + data = mrq->data; + if (data) { + rk2818_sdmmc_set_timeout(host, data); + writel(data->blksz * data->blocks, host->regs + SDMMC_BYTCNT); + writel(data->blksz, host->regs + SDMMC_BLKSIZ); + } + + cmd = mrq->cmd; + cmdflags = rk2818_sdmmc_prepare_command(host->mmc, cmd); + + if (unlikely(test_and_clear_bit(RK2818_MMC_CARD_NEED_INIT, &host->flags))) + cmdflags |= SDMMC_CMD_INIT; + + if (data) + rk2818_sdmmc_submit_data(host, data); + + rk2818_sdmmc_start_command(host, cmd, cmdflags); + if (mrq->stop) + { + host->stop_cmdr = rk2818_sdmmc_prepare_command(host->mmc, mrq->stop); + dev_dbg(host->dev, "mrq stop: stop_cmdr = %d", host->stop_cmdr); + } +} + + + +static void rk2818_sdmmc_queue_request(struct rk2818_sdmmc_host *host, struct mmc_request *mrq) +{ + dev_dbg(host->dev, "queue request: state=%d\n", + host->state); + + spin_lock(&host->lock); + host->mrq = mrq; + if (host->state == STATE_IDLE) { + host->state = STATE_SENDING_CMD; + rk2818_sdmmc_start_request(host); + } else { + list_add_tail(&host->queue_node, &host->queue); + } + spin_unlock(&host->lock); +} + + +static void rk2818_sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct rk2818_sdmmc_host *host = mmc_priv(mmc); + + WARN_ON(host->mrq); + + if (!test_bit(RK2818_MMC_CARD_PRESENT, &host->flags)) { + mrq->cmd->error = -ENOMEDIUM; + mmc_request_done(mmc, mrq); + return; + } + + rk2818_sdmmc_queue_request(host, mrq); +} + +static void rk2818_sdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct rk2818_sdmmc_host *host = mmc_priv(mmc); + + host->ctype = 0; // set default 1 bit mode + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + host->ctype = 0; + break; + case MMC_BUS_WIDTH_4: + host->ctype = SDMMC_CTYPE_4BIT; + break; + } + + + if (ios->clock) { + spin_lock(&host->lock); + /* + * Use mirror of ios->clock to prevent race with mmc + * core ios update when finding the minimum. + */ + host->clock = ios->clock; + + spin_unlock(&host->lock); + } else { + spin_lock(&host->lock); + host->clock = 0; + spin_unlock(&host->lock); + } + + switch (ios->power_mode) { + case MMC_POWER_UP: + set_bit(RK2818_MMC_CARD_NEED_INIT, &host->flags); + break; + default: + break; + } +} + + + +static int rk2818_sdmmc_get_ro(struct mmc_host *mmc) +{ + struct rk2818_sdmmc_host *host = mmc_priv(mmc); + u32 wrtprt = readl(host->regs + SDMMC_WRTPRT); + + return (wrtprt & SDMMC_WRITE_PROTECT)?1:0; +} + + +static int rk2818_sdmmc_get_cd(struct mmc_host *mmc) +{ + struct rk2818_sdmmc_host *host = mmc_priv(mmc); + u32 cdetect = readl(host->regs + SDMMC_CDETECT); + + return (cdetect & SDMMC_CARD_DETECT_N)?0:1; + +} + +static void rk2818_sdmmc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + u32 intmask; + unsigned long flags; + struct rk2818_sdmmc_host *host = mmc_priv(mmc); + spin_lock_irqsave(&host->lock, flags); + intmask = readl(host->regs + SDMMC_INTMASK); + if(enable) + writel(intmask | SDMMC_INT_SDIO, host->regs + SDMMC_INTMASK); + else + writel(intmask & ~SDMMC_INT_SDIO, host->regs + SDMMC_INTMASK); + spin_unlock_irqrestore(&host->lock, flags); +} + +static const struct mmc_host_ops rk2818_sdmmc_ops = { + .request = rk2818_sdmmc_request, + .set_ios = rk2818_sdmmc_set_ios, + .get_ro = rk2818_sdmmc_get_ro, + .get_cd = rk2818_sdmmc_get_cd, + .enable_sdio_irq = rk2818_sdmmc_enable_sdio_irq, +}; + +static void rk2818_sdmmc_request_end(struct rk2818_sdmmc_host *host, struct mmc_request *mrq) + __releases(&host->lock) + __acquires(&host->lock) +{ + struct mmc_host *prev_mmc = host->mmc; + + WARN_ON(host->cmd || host->data); + + host->mrq = NULL; + host->mrq = NULL; + if (!list_empty(&host->queue)) { + host = list_entry(host->queue.next, + struct rk2818_sdmmc_host, queue_node); + list_del(&host->queue_node); + dev_dbg(host->dev, "list not empty: %s is next\n", + mmc_hostname(host->mmc)); + host->state = STATE_SENDING_CMD; + rk2818_sdmmc_start_request(host); + } else { + dev_dbg(host->dev, "list empty\n"); + host->state = STATE_IDLE; + } + + spin_unlock(&host->lock); + mmc_request_done(prev_mmc, mrq); + + spin_lock(&host->lock); +} + +static void rk2818_sdmmc_command_complete(struct rk2818_sdmmc_host *host, + struct mmc_command *cmd) +{ + u32 status = host->cmd_status; + + host->cmd_status = 0; + + if(cmd->flags & MMC_RSP_PRESENT) { + + if(cmd->flags & MMC_RSP_136) { + + cmd->resp[3] = readl(host->regs + SDMMC_RESP0); + cmd->resp[2] = readl(host->regs + SDMMC_RESP1); + cmd->resp[1] = readl(host->regs + SDMMC_RESP2); + cmd->resp[0] = readl(host->regs + SDMMC_RESP3); + } else { + cmd->resp[0] = readl(host->regs + SDMMC_RESP0); + cmd->resp[1] = 0; + cmd->resp[2] = 0; + cmd->resp[3] = 0; + } + } + + if (status & SDMMC_INT_RTO) + cmd->error = -ETIMEDOUT; + else if ((cmd->flags & MMC_RSP_CRC) && (status & SDMMC_INT_RCRC)) + cmd->error = -EILSEQ; + else if (status & SDMMC_INT_RE) + cmd->error = -EIO; + else + cmd->error = 0; + + if (cmd->error) { + dev_dbg(host->dev, + "command error: status=0x%08x resp=0x%08x\n" + "cmd=0x%08x arg=0x%08x flg=0x%08x err=%d\n", + status, cmd->resp[0], + cmd->opcode, cmd->arg, cmd->flags, cmd->error); + + if (cmd->data) { + host->data = NULL; + rk2818_sdmmc_stop_dma(host); + } + } +} + +static void rk2818_sdmmc_tasklet_func(unsigned long priv) +{ + struct rk2818_sdmmc_host *host = (struct rk2818_sdmmc_host *)priv; + struct mmc_request *mrq = host->mrq; + struct mmc_data *data = host->data; + struct mmc_command *cmd = host->cmd; + enum rk2818_sdmmc_state state = host->state; + enum rk2818_sdmmc_state prev_state; + u32 status; + + spin_lock(&host->lock); + + state = host->state; + do { + prev_state = state; + + switch (state) { + case STATE_IDLE: + break; + + case STATE_SENDING_CMD: + if (!rk2818_sdmmc_test_and_clear_pending(host, + EVENT_CMD_COMPLETE)) + break; + + host->cmd = NULL; + rk2818_sdmmc_set_completed(host, EVENT_CMD_COMPLETE); + rk2818_sdmmc_command_complete(host, mrq->cmd); + if (!mrq->data || cmd->error) { + rk2818_sdmmc_request_end(host, host->mrq); + goto unlock; + } + + prev_state = state = STATE_SENDING_DATA; + /* fall through */ + + case STATE_SENDING_DATA: + if (rk2818_sdmmc_test_and_clear_pending(host, + EVENT_DATA_ERROR)) { + rk2818_sdmmc_stop_dma(host); + if (data->stop) + send_stop_cmd(host, data); + state = STATE_DATA_ERROR; + break; + } + + if (!rk2818_sdmmc_test_and_clear_pending(host, + EVENT_XFER_COMPLETE)) + break; + + rk2818_sdmmc_set_completed(host, EVENT_XFER_COMPLETE); + prev_state = state = STATE_DATA_BUSY; + /* fall through */ + + case STATE_DATA_BUSY: + if (!rk2818_sdmmc_test_and_clear_pending(host, + EVENT_DATA_COMPLETE)) + break; + + host->data = NULL; + rk2818_sdmmc_set_completed(host, EVENT_DATA_COMPLETE); + status = host->data_status; + + if (unlikely(status & RK2818_MCI_DATA_ERROR_FLAGS)) { + if (status & SDMMC_INT_DRTO) { + dev_err(&host->pdev->dev, + "data timeout error\n"); + data->error = -ETIMEDOUT; + } else if (status & SDMMC_INT_DCRC) { + dev_err(&host->pdev->dev, + "data CRC error\n"); + data->error = -EILSEQ; + } else { + dev_err(&host->pdev->dev, + "data FIFO error (status=%08x)\n", + status); + data->error = -EIO; + } + } + else { + data->bytes_xfered = data->blocks * data->blksz; + data->error = 0; + } + + if (!data->stop) { + rk2818_sdmmc_request_end(host, host->mrq); + goto unlock; + } + + prev_state = state = STATE_SENDING_STOP; + if (!data->error) + send_stop_cmd(host, data); + /* fall through */ + + case STATE_SENDING_STOP: + if (!rk2818_sdmmc_test_and_clear_pending(host, + EVENT_CMD_COMPLETE)) + break; + + host->cmd = NULL; + rk2818_sdmmc_command_complete(host, mrq->stop); + rk2818_sdmmc_request_end(host, host->mrq); + goto unlock; + case STATE_DATA_ERROR: + if (!rk2818_sdmmc_test_and_clear_pending(host, + EVENT_XFER_COMPLETE)) + break; + + state = STATE_DATA_BUSY; + break; + } + } while (state != prev_state); + + host->state = state; + +unlock: + spin_unlock(&host->lock); + +} + + + +inline static void rk2818_sdmmc_push_data(struct rk2818_sdmmc_host *host, void *buf, int cnt) +{ + u32* pData = (u32*)buf; + + dev_dbg(host->dev, "push data(cnt=%d)\n",cnt); + + if (cnt % 4 != 0) + printk("error not align 4\n"); + + cnt = cnt >> 2; + while (cnt > 0) { + writel(*pData++, host->regs + SDMMC_DATA); + cnt--; + } +} + +inline static void rk2818_sdmmc_pull_data(struct rk2818_sdmmc_host *host, void *buf,int cnt) +{ + u32* pData = (u32*)buf; + + dev_dbg(host->dev, "pull data(cnt=%d)\n",cnt); + + if (cnt % 4 != 0) + printk("error not align 4\n"); + cnt = cnt >> 2; + while (cnt > 0) { + *pData++ = readl(host->regs + SDMMC_DATA); + cnt--; + } +} + +static void rk2818_sdmmc_read_data_pio(struct rk2818_sdmmc_host *host) +{ + struct scatterlist *sg = host->sg; + void *buf = sg_virt(sg); + unsigned int offset = host->pio_offset; + struct mmc_data *data = host->data; + u32 status; + unsigned int nbytes = 0,len,old_len,count =0; + + do { + len = SDMMC_GET_FCNT(readl(host->regs + SDMMC_STATUS)) << 2; + if(count == 0) + old_len = len; + if (likely(offset + len <= sg->length)) { + rk2818_sdmmc_pull_data(host, (void *)(buf + offset),len); + + offset += len; + nbytes += len; + + if (offset == sg->length) { + flush_dcache_page(sg_page(sg)); + host->sg = sg = sg_next(sg); + if (!sg) + goto done; + offset = 0; + buf = sg_virt(sg); + } + } else { + unsigned int remaining = sg->length - offset; + rk2818_sdmmc_pull_data(host, (void *)(buf + offset),remaining); + nbytes += remaining; + + flush_dcache_page(sg_page(sg)); + host->sg = sg = sg_next(sg); + if (!sg) + goto done; + offset = len - remaining; + buf = sg_virt(sg); + rk2818_sdmmc_pull_data(host, buf, offset); + nbytes += offset; + } + + status = readl(host->regs + SDMMC_MINTSTS); + writel(SDMMC_INT_RXDR, host->regs + SDMMC_RINTSTS); // clear RXDR interrupt + if (status & RK2818_MCI_DATA_ERROR_FLAGS) { + host->data_status = status; + data->bytes_xfered += nbytes; + smp_wmb(); + rk2818_sdmmc_set_pending(host, EVENT_DATA_ERROR); + tasklet_schedule(&host->tasklet); + return; + } + count ++; + } while (status & SDMMC_INT_RXDR); // if the RXDR is ready let read again + len = SDMMC_GET_FCNT(readl(host->regs + SDMMC_STATUS)); + host->pio_offset = offset; + data->bytes_xfered += nbytes; + return; + +done: + data->bytes_xfered += nbytes; + smp_wmb(); + rk2818_sdmmc_set_pending(host, EVENT_XFER_COMPLETE); +} + +static void rk2818_sdmmc_write_data_pio(struct rk2818_sdmmc_host *host) +{ + struct scatterlist *sg = host->sg; + void *buf = sg_virt(sg); + unsigned int offset = host->pio_offset; + struct mmc_data *data = host->data; + u32 status; + unsigned int nbytes = 0,len; + + do { + + len = SDMMC_FIFO_SZ - (SDMMC_GET_FCNT(readl(host->regs + SDMMC_STATUS)) << 2); + if (likely(offset + len <= sg->length)) { + rk2818_sdmmc_push_data(host, (void *)(buf + offset),len); + + offset += len; + nbytes += len; + if (offset == sg->length) { + host->sg = sg = sg_next(sg); + if (!sg) + goto done; + + offset = 0; + buf = sg_virt(sg); + } + } else { + unsigned int remaining = sg->length - offset; + + rk2818_sdmmc_push_data(host, (void *)(buf + offset), remaining); + nbytes += remaining; + + host->sg = sg = sg_next(sg); + if (!sg) { + goto done; + } + + offset = len - remaining; + buf = sg_virt(sg); + rk2818_sdmmc_push_data(host, (void *)buf, offset); + nbytes += offset; + } + + status = readl(host->regs + SDMMC_MINTSTS); + writel(SDMMC_INT_TXDR, host->regs + SDMMC_RINTSTS); // clear RXDR interrupt + if (status & RK2818_MCI_DATA_ERROR_FLAGS) { + host->data_status = status; + data->bytes_xfered += nbytes; + smp_wmb(); + rk2818_sdmmc_set_pending(host, EVENT_DATA_ERROR); + tasklet_schedule(&host->tasklet); + return; + } + } while (status & SDMMC_INT_TXDR); // if TXDR, let write again + + host->pio_offset = offset; + data->bytes_xfered += nbytes; + + return; + +done: + data->bytes_xfered += nbytes; + smp_wmb(); + rk2818_sdmmc_set_pending(host, EVENT_XFER_COMPLETE); +} + +static void rk2818_sdmmc_cmd_interrupt(struct rk2818_sdmmc_host *host, u32 status) +{ + if(!host->cmd_status) + host->cmd_status = status; + + rk2818_sdmmc_set_pending(host, EVENT_CMD_COMPLETE); + tasklet_schedule(&host->tasklet); +} + +static irqreturn_t rk2818_sdmmc_interrupt(int irq, void *data) +{ + struct rk2818_sdmmc_host *host = data; + u32 status, pending; + unsigned int pass_count = 0; + + spin_lock(&host->lock); + do { + status = readl(host->regs + SDMMC_RINTSTS); + pending = readl(host->regs + SDMMC_MINTSTS);// read only mask reg + if (!pending) + break; + if(pending & SDMMC_INT_CD) { + disable_irq_nosync(irq); + writel(SDMMC_INT_CD, host->regs + SDMMC_RINTSTS); // clear interrupt + mod_timer(&host->detect_timer, jiffies + msecs_to_jiffies(20)); + } + if(pending & RK2818_MCI_CMD_ERROR_FLAGS) { + writel(RK2818_MCI_CMD_ERROR_FLAGS, host->regs + SDMMC_RINTSTS); // clear interrupt + host->cmd_status = status; + + rk2818_sdmmc_set_pending(host, EVENT_CMD_COMPLETE); + tasklet_schedule(&host->tasklet); + } + + if (pending & RK2818_MCI_DATA_ERROR_FLAGS) { // if there is an error, let report DATA_ERROR + writel(RK2818_MCI_DATA_ERROR_FLAGS, host->regs + SDMMC_RINTSTS); // clear interrupt + host->data_status = status; + + rk2818_sdmmc_set_pending(host, EVENT_DATA_ERROR); + tasklet_schedule(&host->tasklet); + } + + + if(pending & SDMMC_INT_DTO) { + writel(SDMMC_INT_DTO, host->regs + SDMMC_RINTSTS); // clear interrupt + if (!host->data_status) + host->data_status = status; + if(host->dir_status == RK2818_MCI_RECV_STATUS) { + if(host->sg != NULL) + rk2818_sdmmc_read_data_pio(host); + } + rk2818_sdmmc_set_pending(host, EVENT_DATA_COMPLETE); + tasklet_schedule(&host->tasklet); + } + + if (pending & SDMMC_INT_RXDR) { + writel(SDMMC_INT_RXDR, host->regs + SDMMC_RINTSTS); // clear interrupt + if(host->sg) + rk2818_sdmmc_read_data_pio(host); + } + + if (pending & SDMMC_INT_TXDR) { + writel(SDMMC_INT_TXDR, host->regs + SDMMC_RINTSTS); // clear interrupt + if(host->sg) { + rk2818_sdmmc_write_data_pio(host); + } + } + + if (pending & SDMMC_INT_CMD_DONE) { + writel(SDMMC_INT_CMD_DONE, host->regs + SDMMC_RINTSTS); // clear interrupt + rk2818_sdmmc_cmd_interrupt(host, status); + } + } while (pass_count++ < 5); + + spin_unlock(&host->lock); + + return IRQ_HANDLED; + //return pass_count ? IRQ_HANDLED : IRQ_NONE; +} + +static void rk2818_sdmmc_detect_change(unsigned long host_data) +{ + struct rk2818_sdmmc_host *host = (struct rk2818_sdmmc_host *) host_data; + struct mmc_request *mrq; + bool present; + bool present_old; + + enable_irq(host->irq); + present = rk2818_sdmmc_get_cd(host->mmc); + present_old = test_bit(RK2818_MMC_CARD_PRESENT, &host->flags); + dev_dbg(host->dev, "detect change: %d (was %d)\n", + present, present_old); + + if (present != present_old) { + + dev_info(host->dev, "card %s\n", present ? "inserted" : "removed"); + + spin_lock(&host->lock); + + /* Power up host */ + if (present != 0) { + rk2818_sdmmc_set_power(host, host->mmc->ocr_avail); + set_bit(RK2818_MMC_CARD_PRESENT, &host->flags); + } else { + rk2818_sdmmc_set_power(host, 0); + clear_bit(RK2818_MMC_CARD_PRESENT, &host->flags); + } + + + mrq = host->mrq; + if (mrq) { + if (mrq == host->mrq) { + writel((SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET | SDMMC_CTRL_DMA_RESET| + SDMMC_CTRL_INT_ENABLE), host->regs + SDMMC_CTRL); + /* wait till resets clear */ + while (readl(host->regs + SDMMC_CTRL) & + (SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET | SDMMC_CTRL_DMA_RESET)); + + host->data = NULL; + host->cmd = NULL; + + switch (host->state) { + case STATE_IDLE: + break; + case STATE_SENDING_CMD: + mrq->cmd->error = -ENOMEDIUM; + if (!mrq->data) + break; + /* fall through */ + case STATE_SENDING_DATA: + mrq->data->error = -ENOMEDIUM; + rk2818_sdmmc_stop_dma(host); + break; + case STATE_DATA_BUSY: + case STATE_DATA_ERROR: + if (mrq->data->error == -EINPROGRESS) + mrq->data->error = -ENOMEDIUM; + if (!mrq->stop) + break; + case STATE_SENDING_STOP: + mrq->stop->error = -ENOMEDIUM; + break; + } + + rk2818_sdmmc_request_end(host, mrq); + } else { + list_del(&host->queue_node); + mrq->cmd->error = -ENOMEDIUM; + if (mrq->data) + mrq->data->error = -ENOMEDIUM; + if (mrq->stop) + mrq->stop->error = -ENOMEDIUM; + + spin_unlock(&host->lock); + mmc_request_done(host->mmc, mrq); + spin_lock(&host->lock); + } + + } + + spin_unlock(&host->lock); + mmc_detect_change(host->mmc, 0); + } +} + + +static int rk2818_sdmmc_probe(struct platform_device *pdev) +{ + struct rk2818_sdmmc_host *host = NULL; + struct mmc_host *mmc; + struct resource *res; + struct rk2818_sdmmc_platform_data *pdata; + int ret = 0; + int tmo = 200; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } + if(pdata->cfg_gpio) + pdata->cfg_gpio(pdev); + + mmc = mmc_alloc_host(sizeof(struct rk2818_sdmmc_host), &pdev->dev); + if (!mmc) + { + dev_err(&pdev->dev, "Mmc alloc host failture\n"); + return -ENOMEM; + } + host = mmc_priv(mmc); + + host->mmc = mmc; + host->pdata = pdata; + host->pdev = pdev; + host->dev = &pdev->dev; + + host->use_dma = pdata->use_dma; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + { + dev_err(&pdev->dev, "Cannot find IO resource\n"); + ret = -ENOENT; + goto err_free_host; + } + host->mem = request_mem_region(res->start, resource_size(res), pdev->name); + if(host->mem == NULL) + { + dev_err(&pdev->dev, "Cannot request IO\n"); + ret = -ENXIO; + goto err_free_host; + } + host->regs = ioremap(res->start, res->end - res->start + 1); + if (!host->regs) + { + dev_err(&pdev->dev, "Cannot map IO\n"); + ret = -ENXIO; + goto err_release_resource; + } + + host->irq = ret = platform_get_irq(pdev, 0); + if (ret < 0) + { + dev_err(&pdev->dev, "Cannot find IRQ\n"); + goto err_free_map; + } + + spin_lock_init(&host->lock); + INIT_LIST_HEAD(&host->queue); + + host->clk = clk_get(&pdev->dev, "sdmmc"); + if (IS_ERR(host->clk)) { + dev_err(&pdev->dev, "failed to find clock source.\n"); + ret = PTR_ERR(host->clk); + host->clk = NULL; + goto err_free_map; + } + clk_enable(host->clk); + host->bus_hz = clk_get_rate(host->clk); + + writel((SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET | SDMMC_CTRL_DMA_RESET | SDMMC_CTRL_INT_ENABLE), host->regs + SDMMC_CTRL); + while (--tmo && readl(host->regs + SDMMC_CTRL) & + (SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET | SDMMC_CTRL_DMA_RESET)) + udelay(5); + if(--tmo < 0){ + dev_err(&pdev->dev, "failed to reset controller and fifo(timeout = 1ms).\n"); + goto err_free_clk; + } + + writel(0xFFFFFFFF, host->regs + SDMMC_RINTSTS); + writel(0, host->regs + SDMMC_INTMASK); // disable all mmc interrupt first + + /* Put in max timeout */ + writel(0xFFFFFFFF, host->regs + SDMMC_TMOUT); + + /* DMA Size = 8, RXMark = 15, TXMark = 16 */ + writel((3 << 28) | (15 << 16) | 16, host->regs + SDMMC_FIFOTH); + /* disable clock to CIU */ + writel(0, host->regs + SDMMC_CLKENA); + writel(0, host->regs + SDMMC_CLKSRC); + + tasklet_init(&host->tasklet, rk2818_sdmmc_tasklet_func, (unsigned long)host); + + ret = request_irq(host->irq, rk2818_sdmmc_interrupt, 0, dev_name(&pdev->dev), host); + if (ret){ + dev_err(&pdev->dev, "Cannot claim IRQ %d.\n", host->irq); + goto err_free_clk; + } + + + platform_set_drvdata(pdev, host); + + + mmc->ops = &rk2818_sdmmc_ops; + + mmc->f_min = host->bus_hz/510; + mmc->f_max = host->bus_hz/2; + dev_dbg(&pdev->dev, "bus_hz = %u\n", host->bus_hz); + mmc->ocr_avail = host->pdata->host_ocr_avail; + mmc->caps = host->pdata->host_caps; + + mmc->max_phys_segs = 4095; + mmc->max_hw_segs = 4095; + mmc->max_blk_size = 4095 * 512; /* BLKSIZ is 16 bits*/ + mmc->max_blk_count = 4095; + mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; + mmc->max_seg_size = mmc->max_req_size; + + rk2818_sdmmc_set_power(host, 0); + /* Assume card is present initially */ + if(rk2818_sdmmc_get_cd(host->mmc) != 0) + set_bit(RK2818_MMC_CARD_PRESENT, &host->flags); + else + clear_bit(RK2818_MMC_CARD_PRESENT, &host->flags); + ret = mmc_add_host(mmc); + if(ret) + { + dev_err(&pdev->dev, "failed to add mmc host.\n"); + goto err_free_irq; + } +#if defined (CONFIG_DEBUG_FS) + rk2818_sdmmc_init_debugfs(host); +#endif + /* Create card detect handler thread for the host */ + setup_timer(&host->detect_timer, rk2818_sdmmc_detect_change, + (unsigned long)host); + + writel(0xFFFFFFFF, host->regs + SDMMC_RINTSTS); + writel(SDMMC_INT_CMD_DONE | SDMMC_INT_DTO | SDMMC_INT_TXDR | + SDMMC_INT_RXDR | RK2818_MCI_ERROR_FLAGS | SDMMC_INT_CD, + host->regs + SDMMC_INTMASK); + writel(SDMMC_CTRL_INT_ENABLE, host->regs + SDMMC_CTRL); // enable mci interrupt + + dev_info(&pdev->dev, "RK2818 MMC controller at irq %d, %s\n", + host->irq, (host->use_dma == 1)?"use dma":"do not use dma"); + + return 0; + +err_free_irq: + free_irq(host->irq, host); +err_free_clk: + clk_disable(host->clk); + clk_put(host->clk); +err_free_map: + iounmap(host->regs); +err_release_resource: + release_resource(host->mem); +err_free_host: + kfree(host); + return ret; +} + +static int __exit rk2818_sdmmc_remove(struct platform_device *pdev) +{ + struct rk2818_sdmmc_host *host = platform_get_drvdata(pdev); + + writel(0xFFFFFFFF, host->regs + SDMMC_RINTSTS); + writel(0, host->regs + SDMMC_INTMASK); // disable all mmc interrupt first + + platform_set_drvdata(pdev, NULL); + + dev_dbg(&pdev->dev, "remove host\n"); + + writel(0, host->regs + SDMMC_CLKENA); + writel(0, host->regs + SDMMC_CLKSRC); + + del_timer_sync(&host->detect_timer); + set_bit(RK2818_MMC_SHUTDOWN, &host->flags); + mmc_remove_host(host->mmc); + mmc_free_host(host->mmc); + + clk_disable(host->clk); + clk_put(host->clk); + free_irq(host->irq, host); + iounmap(host->regs); + release_resource(host->mem); + kfree(host); + return 0; +} + +static int rk2818_sdmmc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct rk2818_sdmmc_host *host = platform_get_drvdata(pdev); +#ifdef CONFIG_PM + writel(0, host->regs + SDMMC_CLKENA); + clk_disable(host->clk); +#endif + return 0; +} + +static int rk2818_sdmmc_resume(struct platform_device *pdev) +{ + struct rk2818_sdmmc_host *host = platform_get_drvdata(pdev); +#ifdef CONFIG_PM + clk_enable(host->clk); + writel(SDMMC_CLKEN_ENABLE, host->regs + SDMMC_CLKENA); +#endif + return 0; +} + +static struct platform_driver rk2818_sdmmc_driver = { + .probe = rk2818_sdmmc_probe, + .suspend = rk2818_sdmmc_suspend, + .resume = rk2818_sdmmc_resume, + .remove = __exit_p(rk2818_sdmmc_remove), + .driver = { + .name = "rk2818_sdmmc", + }, +}; + +static int __init rk2818_sdmmc_init(void) +{ + return platform_driver_register(&rk2818_sdmmc_driver); +} + +static void __exit rk2818_sdmmc_exit(void) +{ + platform_driver_unregister(&rk2818_sdmmc_driver); +} + +module_init(rk2818_sdmmc_init); +module_exit(rk2818_sdmmc_exit); + +MODULE_DESCRIPTION("Driver for RK2818 SDMMC controller"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("kfx kfx@rock-chips.com"); + diff --git a/drivers/mmc/host/rk2818-sdmmc.h b/drivers/mmc/host/rk2818-sdmmc.h new file mode 100755 index 000000000000..444e1e88d9f3 --- /dev/null +++ b/drivers/mmc/host/rk2818-sdmmc.h @@ -0,0 +1,113 @@ +/* drivers/mmc/host/rk2818-sdmmc.h + * + * Copyright (C) 2010 ROCKCHIP, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __RK2818_SDMMC_H +#define __RK2818_SDMMC_H + +#define MAX_SG_CHN 2 + + +#define SDMMC_CTRL (0x000) +#define SDMMC_PWREN (0x004) +#define SDMMC_CLKDIV (0x008) +#define SDMMC_CLKSRC (0x00c) +#define SDMMC_CLKENA (0x010) +#define SDMMC_TMOUT (0x014) +#define SDMMC_CTYPE (0x018) +#define SDMMC_BLKSIZ (0x01c) +#define SDMMC_BYTCNT (0x020) +#define SDMMC_INTMASK (0x024) +#define SDMMC_CMDARG (0x028) +#define SDMMC_CMD (0x02c) +#define SDMMC_RESP0 (0x030) +#define SDMMC_RESP1 (0x034) +#define SDMMC_RESP2 (0x038) +#define SDMMC_RESP3 (0x03c) +#define SDMMC_MINTSTS (0x040) +#define SDMMC_RINTSTS (0x044) +#define SDMMC_STATUS (0x048) +#define SDMMC_FIFOTH (0x04c) +#define SDMMC_CDETECT (0x050) +#define SDMMC_WRTPRT (0x054) +#define SDMMC_TCBCNT (0x05c) +#define SDMMC_TBBCNT (0x060) +#define SDMMC_DEBNCE (0x064) + +#define SDMMC_DATA (0x100) + +#define _BIT(n) (1<<(n)) + +/* Control register defines */ +#define SDMMC_CTRL_ABRT_READ_DATA _BIT(8) +#define SDMMC_CTRL_SEND_IRQ_RESP _BIT(7) +#define SDMMC_CTRL_READ_WAIT _BIT(6) +#define SDMMC_CTRL_DMA_ENABLE _BIT(5) +#define SDMMC_CTRL_INT_ENABLE _BIT(4) +#define SDMMC_CTRL_DMA_RESET _BIT(2) +#define SDMMC_CTRL_FIFO_RESET _BIT(1) +#define SDMMC_CTRL_RESET _BIT(0) +/* Clock Enable register defines */ +#define SDMMC_CLKEN_LOW_PWR _BIT(16) +#define SDMMC_CLKEN_ENABLE _BIT(0) +/* time-out register defines */ +#define SDMMC_TMOUT_DATA(n) _SBF(8, (n)) +#define SDMMC_TMOUT_DATA_MSK 0xFFFFFF00 +#define SDMMC_TMOUT_RESP(n) ((n) & 0xFF) +#define SDMMC_TMOUT_RESP_MSK 0xFF +/* card-type register defines */ +#define SDMMC_CTYPE_8BIT _BIT(16) +#define SDMMC_CTYPE_4BIT _BIT(0) +/* Interrupt status & mask register defines */ +#define SDMMC_INT_SDIO _BIT(16) +#define SDMMC_INT_EBE _BIT(15) +#define SDMMC_INT_ACD _BIT(14) +#define SDMMC_INT_SBE _BIT(13) +#define SDMMC_INT_HLE _BIT(12) +#define SDMMC_INT_FRUN _BIT(11) +#define SDMMC_INT_HTO _BIT(10) +#define SDMMC_INT_DRTO _BIT(9) +#define SDMMC_INT_RTO _BIT(8) +#define SDMMC_INT_DCRC _BIT(7) +#define SDMMC_INT_RCRC _BIT(6) +#define SDMMC_INT_RXDR _BIT(5) +#define SDMMC_INT_TXDR _BIT(4) +#define SDMMC_INT_DTO _BIT(3) +#define SDMMC_INT_CMD_DONE _BIT(2) +#define SDMMC_INT_RE _BIT(1) +#define SDMMC_INT_CD _BIT(0) + +/* Command register defines */ +#define SDMMC_CMD_START _BIT(31) +#define SDMMC_CMD_CCS_EXP _BIT(23) +#define SDMMC_CMD_CEATA_RD _BIT(22) +#define SDMMC_CMD_UPD_CLK _BIT(21) +#define SDMMC_CMD_INIT _BIT(15) +#define SDMMC_CMD_STOP _BIT(14) +#define SDMMC_CMD_PRV_DAT_WAIT _BIT(13) +#define SDMMC_CMD_SEND_STOP _BIT(12) +#define SDMMC_CMD_STRM_MODE _BIT(11) +#define SDMMC_CMD_DAT_WR _BIT(10) +#define SDMMC_CMD_DAT_EXP _BIT(9) +#define SDMMC_CMD_RESP_CRC _BIT(8) +#define SDMMC_CMD_RESP_LONG _BIT(7) +#define SDMMC_CMD_RESP_EXP _BIT(6) +#define SDMMC_CMD_INDX(n) ((n) & 0x1F) +/* Status register defines */ +#define SDMMC_GET_FCNT(x) (((x)>>17) & 0x1FF) +#define SDMMC_FIFO_SZ 32 + +#define SDMMC_WRITE_PROTECT _BIT(0) +#define SDMMC_CARD_DETECT_N _BIT(0) +#endif -- 2.34.1