From patchwork Wed Jan 14 19:46:31 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anton Vorontsov X-Patchwork-Id: 18524 X-Patchwork-Delegate: galak@kernel.crashing.org Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from ozlabs.org (localhost [127.0.0.1]) by ozlabs.org (Postfix) with ESMTP id 51E00DE013 for ; Thu, 15 Jan 2009 06:50:04 +1100 (EST) X-Original-To: linuxppc-dev@ozlabs.org Delivered-To: linuxppc-dev@ozlabs.org Received: from buildserver.ru.mvista.com (unknown [85.21.88.6]) by ozlabs.org (Postfix) with ESMTP id 103B0DE125 for ; Thu, 15 Jan 2009 06:46:33 +1100 (EST) Received: from localhost (unknown [10.150.0.9]) by buildserver.ru.mvista.com (Postfix) with ESMTP id 4F7368816; Thu, 15 Jan 2009 00:46:34 +0400 (SAMT) Date: Wed, 14 Jan 2009 22:46:31 +0300 From: Anton Vorontsov To: Pierre Ossman Subject: [PATCH] mmc: Add driver for Freescale eSDHC controllers Message-ID: <20090114194631.GA25978@oksana.dev.rtsoft.ru> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.18 (2008-05-17) Cc: linuxppc-dev@ozlabs.org, Konjin Lai , linux-kernel@vger.kernel.org, Xie Xiaobo , Joe D'Abbraccio X-BeenThere: linuxppc-dev@ozlabs.org X-Mailman-Version: 2.1.11 Precedence: list List-Id: Linux on PowerPC Developers Mail List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@ozlabs.org Errors-To: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@ozlabs.org From: Xie Xiaobo This patch adds support for the Freescale Enhanced Secure Digital Host Controller Interface as found in some Freescale PowerPC microprocessors (e.g. MPC837x SOCs). Signed-off-by: Xie Xiaobo Signed-off-by: Konjin Lai Signed-off-by: Joe D'Abbraccio Signed-off-by: Anton Vorontsov --- drivers/mmc/host/Kconfig | 9 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/esdhc.c | 1321 +++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/esdhc.h | 255 +++++++++ 4 files changed, 1586 insertions(+), 0 deletions(-) create mode 100644 drivers/mmc/host/esdhc.c create mode 100644 drivers/mmc/host/esdhc.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index dfa585f..941975c 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -65,6 +65,15 @@ config MMC_RICOH_MMC If unsure, say Y. +config MMC_ESDHC + tristate "Freescale Enhanced SD Host Controller Interface support" + depends on FSL_SOC + help + This selects the Freescale Enhanced SD Host Controller Interface + as found in some Freescale PowerPC microprocessors. + + If unsure, say N. + config MMC_OMAP tristate "TI OMAP Multimedia Card Interface support" depends on ARCH_OMAP diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index f485328..031489f 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_MMC_IMX) += imxmmc.o obj-$(CONFIG_MMC_SDHCI) += sdhci.o obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o +obj-$(CONFIG_MMC_ESDHC) += esdhc.o obj-$(CONFIG_MMC_WBSD) += wbsd.o obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_OMAP) += omap.o diff --git a/drivers/mmc/host/esdhc.c b/drivers/mmc/host/esdhc.c new file mode 100644 index 0000000..c97689d --- /dev/null +++ b/drivers/mmc/host/esdhc.c @@ -0,0 +1,1321 @@ +/* + * Freescale Enhanced Secure Digital Host Controller driver. + * + * Copyright (C) 2005-2008 Pierre Ossman, All Rights Reserved. + * Copyright (C) 2007-2009 Freescale Semiconductor, Inc. All rights reserved. + * Copyright (C) 2008-2009 MontaVista Software, Inc. All rights reserved. + * + * Author: Xiaobo Xie + * + * Derived from sdhci driver by Pierre Ossman + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "esdhc.h" + +#define DBG(fmt, args...) pr_debug("[%s] " fmt "\n", __func__, ## args) + +static unsigned int debug_nodma; +module_param(debug_nodma, uint, 0444); +MODULE_PARM_DESC(debug_nodma, "Forcefully disable DMA transfers."); + +static unsigned int debug_forcedma; +module_param(debug_forcedma, uint, 0444); +MODULE_PARM_DESC(debug_forcedma, "Forcefully enable DMA transfers."); + +#define ESDHC_QUIRK_CLOCK_BEFORE_RESET (1 << 0) +#define ESDHC_QUIRK_FORCE_DMA (1 << 1) +#define ESDHC_QUIRK_NO_CARD_NO_RESET (1 << 2) +#define ESDHC_QUIRK_SINGLE_POWER_WRITE (1 << 3) + +static unsigned int debug_quirks; +module_param(debug_quirks, uint, 0444); +MODULE_PARM_DESC(debug_quirks, "Force certain quirks."); + +static void esdhc_dumpregs(struct esdhc_host *host) +{ + DBG("========= REGISTER DUMP =========="); + DBG("Sysaddr: 0x%08x | Blkattr: 0x%08x", + fsl_readl(host->ioaddr + ESDHC_DMA_ADDRESS), + fsl_readl(host->ioaddr + ESDHC_BLOCK_ATTR)); + DBG("Argument: 0x%08x | COMMAND: 0x%08x", + fsl_readl(host->ioaddr + ESDHC_ARGUMENT), + fsl_readl(host->ioaddr + ESDHC_COMMAND)); + DBG("Present: 0x%08x | DMA ctl: 0x%08x", + fsl_readl(host->ioaddr + ESDHC_PRESENT_STATE), + fsl_readl(host->ioaddr + ESDHC_DMA_SYSCTL)); + DBG("PROCTL: 0x%08x | SYSCTL: 0x%08x", + fsl_readl(host->ioaddr + ESDHC_PROTOCOL_CONTROL), + fsl_readl(host->ioaddr + ESDHC_SYSTEM_CONTROL)); + DBG("Int stat: 0x%08x", + fsl_readl(host->ioaddr + ESDHC_INT_STATUS)); + DBG("Intenab: 0x%08x | Sigenab: 0x%08x", + fsl_readl(host->ioaddr + ESDHC_INT_ENABLE), + fsl_readl(host->ioaddr + ESDHC_SIGNAL_ENABLE)); + DBG("AC12 err: 0x%08x | Version: 0x%08x", + fsl_readl(host->ioaddr + ESDHC_ACMD12_ERR), + fsl_readl(host->ioaddr + ESDHC_HOST_VERSION)); + DBG("Caps: 0x%08x | Watermark: 0x%08x", + fsl_readl(host->ioaddr + ESDHC_CAPABILITIES), + fsl_readl(host->ioaddr + ESDHC_WML)); + DBG("=================================="); +} + +static void esdhc_reset(struct esdhc_host *host, u8 mask) +{ + unsigned long timeout; + unsigned int sysctl; + + if (host->quirks & ESDHC_QUIRK_NO_CARD_NO_RESET) { + if (!(fsl_readl(host->ioaddr + ESDHC_PRESENT_STATE) & + ESDHC_CARD_PRESENT)) + return; + } + + setbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, + mask << ESDHC_RESET_SHIFT); + + if (mask & ESDHC_RESET_ALL) { + host->clock = 0; + host->bus_width = 0; + } + + /* Wait max 100 ms */ + timeout = 100; + + /* hw clears the bit when it's done */ + sysctl = (mask << ESDHC_RESET_SHIFT); + while (fsl_readl(host->ioaddr + ESDHC_SYSTEM_CONTROL) & sysctl) { + if (timeout == 0) { + dev_err(host->mmc->parent, "Reset 0x%x never " + "completed.\n", (int)mask); + esdhc_dumpregs(host); + return; + } + timeout--; + mdelay(1); + } +} + +static void esdhc_init(struct esdhc_host *host) +{ + u32 intmask; + + esdhc_reset(host, ESDHC_RESET_ALL); + + setbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, + (ESDHC_CLOCK_INT_EN | ESDHC_CLOCK_INT_STABLE)); + + intmask = fsl_readl(host->ioaddr + ESDHC_INT_STATUS); + fsl_writel(host->ioaddr + ESDHC_INT_STATUS, intmask); + + intmask = ESDHC_INT_DATA_END_BIT | ESDHC_INT_DATA_CRC | + ESDHC_INT_DATA_TIMEOUT | ESDHC_INT_INDEX | + ESDHC_INT_END_BIT | ESDHC_INT_CRC | ESDHC_INT_TIMEOUT | + ESDHC_INT_DATA_AVAIL | ESDHC_INT_SPACE_AVAIL | + ESDHC_INT_DMA_END | ESDHC_INT_DATA_END | ESDHC_INT_RESPONSE; + + if (host->card_insert) + intmask |= ESDHC_INT_CARD_REMOVE; + else + intmask |= ESDHC_INT_CARD_INSERT; + + fsl_writel(host->ioaddr + ESDHC_INT_ENABLE, intmask); + fsl_writel(host->ioaddr + ESDHC_SIGNAL_ENABLE, intmask); + + setbits32(host->ioaddr + ESDHC_DMA_SYSCTL, ESDHC_DMA_SNOOP); +} + +static void reset_regs(struct esdhc_host *host) +{ + u32 intmask; + + intmask = fsl_readl(host->ioaddr + ESDHC_INT_STATUS); + fsl_writel(host->ioaddr + ESDHC_INT_STATUS, intmask); + + intmask = ESDHC_INT_DATA_END_BIT | ESDHC_INT_DATA_CRC | + ESDHC_INT_DATA_TIMEOUT | ESDHC_INT_INDEX | + ESDHC_INT_END_BIT | ESDHC_INT_CRC | ESDHC_INT_TIMEOUT | + ESDHC_INT_DATA_AVAIL | ESDHC_INT_SPACE_AVAIL | + ESDHC_INT_DMA_END | ESDHC_INT_DATA_END | ESDHC_INT_RESPONSE; + + if (host->card_insert) + intmask |= ESDHC_INT_CARD_REMOVE; + else + intmask |= ESDHC_INT_CARD_INSERT; + + fsl_writel(host->ioaddr + ESDHC_INT_ENABLE, intmask); + fsl_writel(host->ioaddr + ESDHC_SIGNAL_ENABLE, intmask); + + if (host->bus_width == MMC_BUS_WIDTH_4) { + intmask = fsl_readl(host->ioaddr + ESDHC_PROTOCOL_CONTROL); + intmask |= ESDHC_CTRL_4BITBUS; + fsl_writel(host->ioaddr + ESDHC_PROTOCOL_CONTROL, intmask); + } +} + +/* Return the SG's virtual address */ +static inline char *esdhc_sg_to_buffer(struct esdhc_host *host) +{ + return sg_virt(host->cur_sg); +} + +static inline int esdhc_next_sg(struct esdhc_host *host) +{ + /* Skip to next SG entry. */ + host->cur_sg++; + host->num_sg--; + + /* Any entries left? */ + if (host->num_sg > 0) { + host->offset = 0; + host->remain = host->cur_sg->length; + } + + return host->num_sg; +} + +static void esdhc_read_block_pio(struct esdhc_host *host) +{ + int blksize; + int chunk_remain; + u32 data; + char *buffer; + int size; + + DBG("PIO reading\n"); + + blksize = host->data->blksz; + chunk_remain = 0; + data = 0; + + buffer = esdhc_sg_to_buffer(host) + host->offset; + + while (blksize) { + if (chunk_remain == 0) { + data = fsl_readl(host->ioaddr + ESDHC_BUFFER); + chunk_remain = min(blksize, 4); + } + + size = min(host->remain, chunk_remain); + + chunk_remain -= size; + blksize -= size; + host->offset += size; + host->remain -= size; + + while (size) { + *buffer = data & 0xFF; + buffer++; + data >>= 8; + size--; + } + + if (host->remain == 0) { + if (esdhc_next_sg(host) == 0) { + BUG_ON(blksize != 0); + return; + } + buffer = esdhc_sg_to_buffer(host); + } + } +} + +static void esdhc_write_block_pio(struct esdhc_host *host) +{ + int blksize; + int chunk_remain; + u32 data; + char *buffer; + int bytes, size; + + DBG("PIO writing\n"); + + blksize = host->data->blksz; + chunk_remain = 4; + data = 0; + + bytes = 0; + buffer = esdhc_sg_to_buffer(host) + host->offset; + + while (blksize) { + size = min(host->remain, chunk_remain); + + chunk_remain -= size; + blksize -= size; + host->offset += size; + host->remain -= size; + + while (size) { + data >>= 8; + data |= (u32)*buffer << 24; + buffer++; + size--; + } + + if (chunk_remain == 0) { + writel(data, host->ioaddr + ESDHC_BUFFER); + chunk_remain = min(blksize, 4); + } + + if (host->remain == 0) { + if (esdhc_next_sg(host) == 0) { + BUG_ON(blksize != 0); + return; + } + buffer = esdhc_sg_to_buffer(host); + } + } +} + +static void esdhc_transfer_pio(struct esdhc_host *host) +{ + u32 mask; + + BUG_ON(!host->data); + + if (host->num_sg == 0) + return; + + if (host->data->flags & MMC_DATA_READ) + mask = ESDHC_DATA_AVAILABLE; + else + mask = ESDHC_SPACE_AVAILABLE; + + while (fsl_readl(host->ioaddr + ESDHC_PRESENT_STATE) & mask) { + if (host->data->flags & MMC_DATA_READ) + esdhc_read_block_pio(host); + else + esdhc_write_block_pio(host); + + if (host->num_sg == 0) + break; + } + + DBG("PIO transfer complete.\n"); +} + +static void esdhc_prepare_data(struct esdhc_host *host, struct mmc_data *data) +{ + u8 count; + unsigned blkattr = 0; + unsigned target_timeout, current_timeout; + unsigned int sysctl; + + WARN_ON(host->data); + + if (data == NULL) + return; + + DBG("blksz %04x blks %04x flags %08x\n", + data->blksz, data->blocks, data->flags); + DBG("tsac %d ms nsac %d clk\n", + data->timeout_ns / 1000000, data->timeout_clks); + + /* Sanity checks */ + BUG_ON(data->blksz * data->blocks > 524288); + BUG_ON(data->blksz > host->mmc->max_blk_size); + BUG_ON(data->blocks > 65535); + + if (host->clock == 0) { + dev_err(host->mmc->parent, "The SD_CLK isn't set\n"); + return; + } + /* timeout in us */ + target_timeout = data->timeout_ns / 1000 + + (data->timeout_clks * 1000000) / host->clock; + + /* + * Figure out needed cycles. + * We do this in steps in order to fit inside a 32 bit int. + * The first step is the minimum timeout, which will have a + * minimum resolution of 6 bits: + * (1) 2^13*1000 > 2^22, + * (2) host->timeout_clk < 2^16 + * => + * (1) / (2) > 2^6 + */ + count = 0; + host->timeout_clk = host->clock/1000; + + current_timeout = (1 << 13) * 1000 / host->timeout_clk; + while (current_timeout < target_timeout) { + count++; + current_timeout <<= 1; + if (count >= 0xF) + break; + } + + if (count >= 0xF) { + dev_warn(host->mmc->parent, "Too large timeout requested!\n"); + count = 0xE; + } + if (data->blocks >= 0x50) + count = 0xE; + + sysctl = fsl_readl(host->ioaddr + ESDHC_SYSTEM_CONTROL); + sysctl &= (~ESDHC_TIMEOUT_MASK); + fsl_writel(host->ioaddr + ESDHC_SYSTEM_CONTROL, + sysctl | (count << ESDHC_TIMEOUT_SHIFT)); + + if (host->flags & ESDHC_USE_DMA) { + int sg_count; + unsigned int wml; + unsigned int wml_value; + + sg_count = dma_map_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, + (data->flags & MMC_DATA_READ) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); + BUG_ON(sg_count != 1); + + fsl_writel(host->ioaddr + ESDHC_DMA_ADDRESS, + sg_dma_address(data->sg)); + + /* Disable the BRR and BWR interrupt */ + clrbits32(host->ioaddr + ESDHC_INT_ENABLE, + (ESDHC_INT_DATA_AVAIL | ESDHC_INT_SPACE_AVAIL)); + clrbits32(host->ioaddr + ESDHC_SIGNAL_ENABLE, + (ESDHC_INT_DATA_AVAIL | ESDHC_INT_SPACE_AVAIL)); + + wml_value = data->blksz/4; + if (data->flags & MMC_DATA_READ) { + if (wml_value > 0x10) + wml_value = 0x10; + wml = (wml_value & ESDHC_WML_MASK) | + ((0x10 & ESDHC_WML_MASK) + << ESDHC_WML_WRITE_SHIFT); + } else { + if (wml_value > 0x80) + wml_value = 0x80; + wml = (0x10 & ESDHC_WML_MASK) | + (((wml_value) & ESDHC_WML_MASK) + << ESDHC_WML_WRITE_SHIFT); + } + + fsl_writel(host->ioaddr + ESDHC_WML, wml); + } else { + host->cur_sg = data->sg; + host->num_sg = data->sg_len; + + host->offset = 0; + host->remain = host->cur_sg->length; + + setbits32(host->ioaddr + ESDHC_INT_ENABLE, + (ESDHC_INT_DATA_AVAIL | ESDHC_INT_SPACE_AVAIL)); + setbits32(host->ioaddr + ESDHC_SIGNAL_ENABLE, + (ESDHC_INT_DATA_AVAIL | ESDHC_INT_SPACE_AVAIL)); + } + + /* We do not handle DMA boundaries */ + blkattr = data->blksz; + blkattr |= data->blocks << 16; + fsl_writel(host->ioaddr + ESDHC_BLOCK_ATTR, blkattr); +} + +static unsigned int esdhc_set_transfer_mode(struct esdhc_host *host, + struct mmc_data *data) +{ + u32 mode = 0; + + WARN_ON(host->data); + + if (data == NULL) + return 0; + + mode = ESDHC_TRNS_BLK_CNT_EN; + if (data->blocks > 1) + mode |= ESDHC_TRNS_MULTI; + if (data->flags & MMC_DATA_READ) + mode |= ESDHC_TRNS_READ; + if (host->flags & ESDHC_USE_DMA) + mode |= ESDHC_TRNS_DMA; + + return mode; +} + +static void esdhc_send_command(struct esdhc_host *host, struct mmc_command *cmd) +{ + unsigned int flags; + u32 mask; + unsigned long timeout; + + WARN_ON(host->cmd); + + DBG("Sending cmd (%d)", cmd->opcode); + + /* Wait max 10 ms. */ + timeout = 10; + + mask = ESDHC_CMD_INHIBIT; + if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY)) + mask |= ESDHC_DATA_INHIBIT; + + /* + * We shouldn't wait for data inihibit for stop commands, even + * though they might use busy signaling. + */ + if (host->mrq->data && cmd == host->mrq->data->stop) + mask &= ~ESDHC_DATA_INHIBIT; + + while (fsl_readl(host->ioaddr + ESDHC_PRESENT_STATE) & mask) { + if (timeout == 0) { + dev_err(host->mmc->parent, "Controller never " + "released inhibit bit(s).\n"); + esdhc_dumpregs(host); + cmd->error = -EIO; + tasklet_schedule(&host->finish_tasklet); + return; + } + timeout--; + mdelay(1); + } + + mod_timer(&host->timer, jiffies + 10 * HZ); + + host->cmd = cmd; + + esdhc_prepare_data(host, cmd->data); + + fsl_writel(host->ioaddr + ESDHC_ARGUMENT, cmd->arg); + + flags = esdhc_set_transfer_mode(host, cmd->data); + + if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { + dev_err(host->mmc->parent, "Unsupported response type!\n"); + cmd->error = -EINVAL; + tasklet_schedule(&host->finish_tasklet); + return; + } + + if (!(cmd->flags & MMC_RSP_PRESENT)) + flags |= ESDHC_CMD_RESP_NONE; + else if (cmd->flags & MMC_RSP_136) + flags |= ESDHC_CMD_RESP_LONG; + else if (cmd->flags & MMC_RSP_BUSY) + flags |= ESDHC_CMD_RESP_SHORT_BUSY; + else + flags |= ESDHC_CMD_RESP_SHORT; + + if (cmd->flags & MMC_RSP_CRC) + flags |= ESDHC_CMD_CRC_EN; + if (cmd->flags & MMC_RSP_OPCODE) + flags |= ESDHC_CMD_INDEX_EN; + if (cmd->data) + flags |= ESDHC_CMD_DATA; + + fsl_writel(host->ioaddr + ESDHC_COMMAND, + ESDHC_MAKE_CMD(cmd->opcode, flags)); +} + +static void esdhc_finish_data(struct esdhc_host *host) +{ + struct mmc_data *data; + u16 blocks; + + BUG_ON(!host->data); + + data = host->data; + host->data = NULL; + + if (host->flags & ESDHC_USE_DMA) { + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + (data->flags & MMC_DATA_READ) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); + } + + /* + * Controller doesn't count down when in single block mode. + */ + if (data->blocks == 1 && data->error == 0) + blocks = 0; + else + blocks = fsl_readl(host->ioaddr + ESDHC_BLOCK_ATTR) >> 16; + + data->bytes_xfered = data->blksz * (data->blocks - blocks); + + if (data->error == 0 && blocks) { + dev_err(host->mmc->parent, "Controller signalled completion " + "even though there were blocks left.\n"); + data->error = -EIO; + } + + if (blocks == 0 && data->error == -ETIMEDOUT) { + dev_err(host->mmc->parent, "Controller transmitted completion " + "even though there were timeout error.\n"); + data->error = 0; + } + + DBG("Ending data transfer (%d bytes)\n", data->bytes_xfered); + + if (data->stop) { + /* + * The controller needs a reset of internal state machines + * upon error conditions. + */ + if (data->error != 0) { + esdhc_reset(host, ESDHC_RESET_CMD); + esdhc_reset(host, ESDHC_RESET_DATA); + reset_regs(host); + } + + esdhc_send_command(host, data->stop); + } else { + tasklet_schedule(&host->finish_tasklet); + } +} + +static void esdhc_finish_command(struct esdhc_host *host) +{ + int i; + + BUG_ON(host->cmd == NULL); + + if (host->cmd->flags & MMC_RSP_PRESENT) { + if (host->cmd->flags & MMC_RSP_136) { + /* CRC is stripped so we need to do some shifting. */ + for (i = 0; i < 4; i++) { + host->cmd->resp[i] = fsl_readl(host->ioaddr + + ESDHC_RESPONSE + (3-i)*4) << 8; + if (i != 3) { + host->cmd->resp[i] |= + fsl_readl(host->ioaddr + + ESDHC_RESPONSE + + (2 - i) * 4) >> 24; + } + } + } else { + host->cmd->resp[0] = fsl_readl(host->ioaddr + + ESDHC_RESPONSE); + } + } + + host->cmd->error = 0; + + DBG("Ending cmd (%d)", host->cmd->opcode); + + if (host->cmd->data) + host->data = host->cmd->data; + else + tasklet_schedule(&host->finish_tasklet); + + host->cmd = NULL; +} + +static void esdhc_set_clock(struct esdhc_host *host, unsigned int clock) +{ + int pre_div; + int div; + u16 clk; + unsigned long timeout; + + if (clock == host->clock) + return; + + clrbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, ESDHC_CLOCK_MASK); + + if (clock == 0) + goto out; + + if (host->max_clk / 16 > clock) { + for (pre_div = 1; pre_div < 256; pre_div *= 2) { + if ((host->max_clk / pre_div) < (clock*16)) + break; + } + } else { + pre_div = 1; + } + + for (div = 1; div <= 16; div++) { + if ((host->max_clk / (div * pre_div)) <= clock) + break; + } + + pre_div >>= 1; + div -= 1; + + clk = (div << ESDHC_DIVIDER_SHIFT) | (pre_div << ESDHC_PREDIV_SHIFT); + setbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, clk); + + /* Wait max 10 ms */ + timeout = 10; + while (timeout) { + timeout--; + mdelay(1); + } + + setbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, ESDHC_CLOCK_CARD_EN); + esdhc_dumpregs(host); + +out: + host->clock = clock; + if (host->clock == 0) + setbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, + ESDHC_CLOCK_DEFAULT); +} + +static void esdhc_set_power(struct esdhc_host *host, unsigned short power) +{ + if (host->power == power) + return; + if (power == (unsigned short)-1) + host->power = power; +} + +static void esdhc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct esdhc_host *host = mmc_priv(mmc); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + WARN_ON(host->mrq != NULL); + + host->mrq = mrq; + + if (!(fsl_readl(host->ioaddr + ESDHC_PRESENT_STATE) & + ESDHC_CARD_PRESENT)) { + host->mrq->cmd->error = -ETIMEDOUT; + tasklet_schedule(&host->finish_tasklet); + } else { + esdhc_send_command(host, mrq->cmd); + } + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void esdhc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct esdhc_host *host = mmc_priv(mmc); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&host->lock, flags); + + /* + * Reset the chip on each power off. + * Should clear out any weird states. + */ + + if (ios->power_mode == MMC_POWER_OFF) { + fsl_writel(host->ioaddr + ESDHC_SIGNAL_ENABLE, 0); + esdhc_init(host); + } + + esdhc_set_clock(host, ios->clock); + + if (ios->power_mode == MMC_POWER_OFF) + esdhc_set_power(host, -1); + else + esdhc_set_power(host, ios->vdd); + + ctrl = fsl_readl(host->ioaddr + ESDHC_PROTOCOL_CONTROL); + + if (ios->bus_width == MMC_BUS_WIDTH_4) { + ctrl |= ESDHC_CTRL_4BITBUS; + host->bus_width = MMC_BUS_WIDTH_4; + } else { + ctrl &= ~ESDHC_CTRL_4BITBUS; + host->bus_width = MMC_BUS_WIDTH_1; + } + + fsl_writel(host->ioaddr + ESDHC_PROTOCOL_CONTROL, ctrl); + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); +} + +static int esdhc_get_ro(struct mmc_host *mmc) +{ + struct esdhc_host *host = mmc_priv(mmc); + unsigned long flags; + int present; + + spin_lock_irqsave(&host->lock, flags); + + present = fsl_readl(host->ioaddr + ESDHC_PRESENT_STATE); + + spin_unlock_irqrestore(&host->lock, flags); + + /* esdhc is different form sdhc */ + return present & ESDHC_WRITE_PROTECT; +} + +static const struct mmc_host_ops esdhc_ops = { + .request = esdhc_request, + .set_ios = esdhc_set_ios, + .get_ro = esdhc_get_ro, +}; + +static void esdhc_tasklet_card(unsigned long param) +{ + struct esdhc_host *host = (struct esdhc_host *)param; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if (!(fsl_readl(host->ioaddr + ESDHC_PRESENT_STATE) & + ESDHC_CARD_PRESENT)) { + if (host->mrq) { + dev_err(host->mmc->parent, + "Card removed during transfer! " + "Resetting the controller.\n"); + + esdhc_reset(host, ESDHC_RESET_CMD); + esdhc_reset(host, ESDHC_RESET_DATA); + + host->mrq->cmd->error = -EIO; + tasklet_schedule(&host->finish_tasklet); + } + host->card_insert = 0; + } else { + esdhc_reset(host, ESDHC_INIT_CARD); + host->card_insert = 1; + } + + spin_unlock_irqrestore(&host->lock, flags); + + mmc_detect_change(host->mmc, msecs_to_jiffies(500)); +} + +static void esdhc_tasklet_finish(unsigned long param) +{ + struct esdhc_host *host = (struct esdhc_host *)param; + unsigned long flags; + struct mmc_request *mrq; + + spin_lock_irqsave(&host->lock, flags); + + del_timer(&host->timer); + + mrq = host->mrq; + + DBG("Ending request, cmd (%d)", mrq->cmd->opcode); + + /* + * The controller needs a reset of internal state machines + * upon error conditions. + */ + if ((mrq->cmd->error != 0) || + (mrq->data && ((mrq->data->error != 0) || + (mrq->data->stop && + (mrq->data->stop->error != 0))))) { + + /* Some controllers need this kick or reset won't work here */ + if (host->quirks & ESDHC_QUIRK_CLOCK_BEFORE_RESET) { + unsigned int clock; + + /* This is to force an update */ + clock = host->clock; + host->clock = 0; + esdhc_set_clock(host, clock); + } + + /* Spec says we should do both at the same time, but Ricoh + controllers do not like that. */ + if (mrq->cmd->error != -ETIMEDOUT) { + esdhc_reset(host, ESDHC_RESET_CMD); + esdhc_reset(host, ESDHC_RESET_DATA); + reset_regs(host); + esdhc_dumpregs(host); + } + } + + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); + + mmc_request_done(host->mmc, mrq); +} + +static void esdhc_timeout_timer(unsigned long data) +{ + struct esdhc_host *host = (struct esdhc_host *)data; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if (host->mrq) { + dev_err(host->mmc->parent, "Timeout waiting for hardware " + "interrupt.\n"); + esdhc_dumpregs(host); + + if (host->data) { + host->data->error = -ETIMEDOUT; + esdhc_finish_data(host); + } else { + if (host->cmd) + host->cmd->error = -ETIMEDOUT; + else + host->mrq->cmd->error = -ETIMEDOUT; + + tasklet_schedule(&host->finish_tasklet); + } + } + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void esdhc_cmd_irq(struct esdhc_host *host, u32 intmask) +{ + BUG_ON(intmask == 0); + + if (!host->cmd) { + dev_err(host->mmc->parent, "Got command interrupt even " + "though no command operation was in progress.\n"); + esdhc_dumpregs(host); + return; + } + + if (intmask & ESDHC_INT_TIMEOUT) { + host->cmd->error = -ETIMEDOUT; + tasklet_schedule(&host->finish_tasklet); + } else if (intmask & ESDHC_INT_RESPONSE) { + esdhc_finish_command(host); + } else { + if (intmask & ESDHC_INT_CRC) + host->cmd->error = -EILSEQ; + else if (intmask & (ESDHC_INT_END_BIT | ESDHC_INT_INDEX)) + host->cmd->error = -EIO; + else + host->cmd->error = -EINVAL; + + tasklet_schedule(&host->finish_tasklet); + } +} + +static void esdhc_data_irq(struct esdhc_host *host, u32 intmask) +{ + BUG_ON(intmask == 0); + + if (!host->data) { + /* + * A data end interrupt is sent together with the response + * for the stop command. + */ + if (intmask & ESDHC_INT_DATA_END || + intmask & ESDHC_INT_DMA_END) + return; + + dev_err(host->mmc->parent, "Got data interrupt even though " + "no data operation was in progress.\n"); + esdhc_dumpregs(host); + return; + } + + if (intmask & ESDHC_INT_DATA_TIMEOUT) + host->data->error = -ETIMEDOUT; + else if (intmask & ESDHC_INT_DATA_CRC) + host->data->error = -EILSEQ; + else if (intmask & ESDHC_INT_DATA_END_BIT) + host->data->error = -EIO; + + if (host->data->error != 0) { + esdhc_finish_data(host); + } else { + if (intmask & (ESDHC_INT_DATA_AVAIL | ESDHC_INT_SPACE_AVAIL)) + esdhc_transfer_pio(host); + + /* + * We currently don't do anything fancy with DMA + * boundaries, but as we can't disable the feature + * we need to at least restart the transfer. + */ + if (intmask & ESDHC_INT_DMA_END) + fsl_writel(host->ioaddr + ESDHC_DMA_ADDRESS, + fsl_readl(host->ioaddr + ESDHC_DMA_ADDRESS)); + + if (intmask & ESDHC_INT_DATA_END) + esdhc_finish_data(host); + } +} + +static irqreturn_t esdhc_irq(int irq, void *dev_id) +{ + struct esdhc_host *host = dev_id; + irqreturn_t result; + u32 status; + + spin_lock_irq(&host->lock); + + status = fsl_readl(host->ioaddr + ESDHC_INT_STATUS); + + if (!status || status == 0xffffffff) { + result = IRQ_NONE; + goto out; + } + + if (status & (ESDHC_INT_CARD_INSERT | ESDHC_INT_CARD_REMOVE)) { + if (status & ESDHC_INT_CARD_INSERT) { + if (fsl_readl(host->ioaddr + ESDHC_PRESENT_STATE) & + ESDHC_CARD_PRESENT) { + DBG("*** got card-insert interrupt"); + fsl_writel(host->ioaddr + ESDHC_INT_ENABLE, + ESDHC_INT_INSERT_MASK); + fsl_writel(host->ioaddr + ESDHC_SIGNAL_ENABLE, + ESDHC_INT_INSERT_MASK); + } + } + if (status & ESDHC_INT_CARD_REMOVE) { + if (!(fsl_readl(host->ioaddr + ESDHC_PRESENT_STATE) & + ESDHC_CARD_PRESENT)) { + DBG("*** got card-remove interrupt"); + fsl_writel(host->ioaddr + ESDHC_INT_ENABLE, + ESDHC_INT_REMOVE_MASK); + fsl_writel(host->ioaddr + ESDHC_SIGNAL_ENABLE, + ESDHC_INT_REMOVE_MASK); + } + } + + tasklet_schedule(&host->card_tasklet); + } + + status &= ~(ESDHC_INT_CARD_INSERT | ESDHC_INT_CARD_REMOVE); + + if (status & ESDHC_INT_CMD_MASK) { + fsl_writel(host->ioaddr + ESDHC_INT_STATUS, + status & ESDHC_INT_CMD_MASK); + esdhc_cmd_irq(host, status & ESDHC_INT_CMD_MASK); + } + + if (status & ESDHC_INT_DATA_MASK) { + fsl_writel(host->ioaddr + ESDHC_INT_STATUS, + status & ESDHC_INT_DATA_MASK); + esdhc_data_irq(host, status & ESDHC_INT_DATA_MASK); + } + + status &= ~(ESDHC_INT_CMD_MASK | ESDHC_INT_DATA_MASK); + + if (status) { + dev_err(host->mmc->parent, "Unexpected interrupt 0x%08x.\n", + status); + esdhc_dumpregs(host); + fsl_writel(host->ioaddr + ESDHC_INT_STATUS, status); + } + + result = IRQ_HANDLED; + + mmiowb(); +out: + spin_unlock_irq(&host->lock); + + return result; +} + +#ifdef CONFIG_PM + +static int esdhc_suspend(struct of_device *ofdev, pm_message_t state) +{ + struct esdhc_host *host = dev_get_drvdata(&ofdev->dev); + int ret; + + DBG("Suspending..."); + + ret = mmc_suspend_host(host->mmc, state); + if (ret) + return ret; + + disable_irq(host->irq); + return 0; +} + +static int esdhc_resume(struct of_device *ofdev) +{ + struct esdhc_host *host = dev_get_drvdata(&ofdev->dev); + int ret; + + DBG("Resuming..."); + + esdhc_init(host); + enable_irq(host->irq); + mmiowb(); + + ret = mmc_resume_host(host->mmc); + if (ret) + return ret; + return 0; +} + +#else + +#define esdhc_suspend NULL +#define esdhc_resume NULL + +#endif /* CONFIG_PM */ + +static int __devinit esdhc_probe(struct of_device *ofdev, + const struct of_device_id *match) +{ + struct device_node *np = ofdev->node; + struct esdhc_host *host; + struct mmc_host *mmc; + struct resource res; + unsigned int version; + unsigned int caps; + const u32 *clk; + int size; + int ret; + + mmc = mmc_alloc_host(sizeof(struct esdhc_host), &ofdev->dev); + if (!mmc) + return -ENOMEM; + + host = mmc_priv(mmc); + host->mmc = mmc; + dev_set_drvdata(&ofdev->dev, host); + + ret = of_address_to_resource(np, 0, &res); + if (ret) + goto err_no_addr; + + host->addr = res.start; + host->size = res.end - res.start + 1; + + if (!request_mem_region(host->addr, host->size, + dev_name(&ofdev->dev))) { + ret = -EBUSY; + goto err_addr_request; + } + + host->ioaddr = ioremap_nocache(host->addr, host->size); + if (!host->ioaddr) { + ret = -ENOMEM; + goto err_addr_map; + } + + host->irq = irq_of_parse_and_map(np, 0); + if (!host->irq) { + ret = -EINVAL; + goto err_no_irq; + } + + esdhc_reset(host, ESDHC_RESET_ALL); + + version = fsl_readl(host->ioaddr + ESDHC_HOST_VERSION); + if (version != 0x01) { + dev_err(host->mmc->parent, "Unknown controller version " + "(%d). You may experience problems.\n", version); + } + + + host->quirks = ESDHC_QUIRK_NO_CARD_NO_RESET; + if (debug_quirks) + host->quirks = debug_quirks; + + caps = fsl_readl(host->ioaddr + ESDHC_CAPABILITIES); + + if (debug_nodma) { + DBG("DMA forced off\n"); + } else if (debug_forcedma) { + DBG("DMA forced on\n"); + host->flags |= ESDHC_USE_DMA; + } else if (host->quirks & ESDHC_QUIRK_FORCE_DMA) { + host->flags |= ESDHC_USE_DMA; + } else if (!(caps & ESDHC_CAN_DO_DMA)) { + DBG("Controller doesn't have DMA capability\n"); + } else { + host->flags |= ESDHC_USE_DMA; + } + + clk = of_get_property(np, "clock-frequency", &size); + if (!clk || size != sizeof(*clk) || !*clk) { + ret = -EINVAL; + goto err_no_clk; + } + host->max_clk = *clk; + + /* + * Set host parameters. + */ + mmc->ops = &esdhc_ops; + mmc->f_min = 400000; + mmc->f_max = min((int)*clk, 50000000); + mmc->caps = MMC_CAP_4_BIT_DATA; + + if (caps & ESDHC_CAN_DO_HISPD) + mmc->caps |= MMC_CAP_SD_HIGHSPEED; + + mmc->ocr_avail = 0; + if (caps & ESDHC_CAN_VDD_330) + mmc->ocr_avail |= MMC_VDD_32_33|MMC_VDD_33_34; + if (caps & ESDHC_CAN_VDD_300) + mmc->ocr_avail |= MMC_VDD_29_30|MMC_VDD_30_31; + if (caps & ESDHC_CAN_VDD_180) + mmc->ocr_avail |= MMC_VDD_165_195; + + if (mmc->ocr_avail == 0) { + dev_err(host->mmc->parent, "Hardware doesn't report any " + "support voltages.\n"); + ret = -ENODEV; + goto err_no_ocr; + } + + spin_lock_init(&host->lock); + + /* + * Maximum number of segments. Hardware cannot do scatter lists. + */ + if (host->flags & ESDHC_USE_DMA) + mmc->max_hw_segs = 1; + else + mmc->max_hw_segs = 16; + mmc->max_phys_segs = 16; + + /* + * Maximum number of sectors in one transfer. Limited by DMA boundary + * size (512KiB). + */ + mmc->max_req_size = 524288; + + /* + * Maximum segment size. Could be one segment with the maximum number + * of bytes. + */ + mmc->max_seg_size = mmc->max_req_size; + + /* + * Maximum block size. This varies from controller to controller and + * is specified in the capabilities register. + */ + mmc->max_blk_size = (caps & ESDHC_MAX_BLOCK_MASK) >> + ESDHC_MAX_BLOCK_SHIFT; + if (mmc->max_blk_size > 3) { + dev_err(host->mmc->parent, "Invalid maximum block size.\n"); + ret = -ENODEV; + goto err_blk_size; + } + mmc->max_blk_size = 512 << mmc->max_blk_size; + + /* + * Maximum block count. + */ + mmc->max_blk_count = 65535; + + /* + * Init tasklets. + */ + tasklet_init(&host->card_tasklet, + esdhc_tasklet_card, (unsigned long)host); + tasklet_init(&host->finish_tasklet, + esdhc_tasklet_finish, (unsigned long)host); + + setup_timer(&host->timer, esdhc_timeout_timer, (unsigned long)host); + + esdhc_init(host); + +#ifdef CONFIG_MMC_DEBUG + esdhc_dumpregs(host); +#endif + + ret = request_irq(host->irq, esdhc_irq, IRQF_SHARED, + mmc_hostname(mmc), host); + if (ret) + goto err_request_irq; + + mmiowb(); + + mmc_add_host(mmc); + + dev_info(host->mmc->parent, "probed at 0x%08lx, irq %d, %s\n", + host->addr, host->irq, + (host->flags & ESDHC_USE_DMA) ? "DMA" : "PIO"); + + return 0; + +err_request_irq: + tasklet_kill(&host->card_tasklet); + tasklet_kill(&host->finish_tasklet); +err_blk_size: +err_no_ocr: +err_no_clk: + irq_dispose_mapping(host->irq); +err_no_irq: + iounmap(host->ioaddr); +err_addr_map: + release_mem_region(host->addr, host->size); +err_addr_request: +err_no_addr: + mmc_free_host(mmc); + return ret; +} + +static int __devexit esdhc_remove(struct of_device *ofdev) +{ + + struct esdhc_host *host = dev_get_drvdata(&ofdev->dev); + struct mmc_host *mmc = host->mmc; + + mmc_remove_host(mmc); + + esdhc_reset(host, ESDHC_RESET_ALL); + + free_irq(host->irq, host); + irq_dispose_mapping(host->irq); + + del_timer_sync(&host->timer); + + tasklet_kill(&host->card_tasklet); + tasklet_kill(&host->finish_tasklet); + + iounmap(host->ioaddr); + + release_mem_region(host->addr, host->size); + + mmc_free_host(mmc); + return 0; +} + +static const struct of_device_id fsl_esdhc_match[] = { + { .compatible = "fsl,esdhc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, fsl_esdhc_match); + +static struct of_platform_driver esdhc_driver = { + .driver.name = "esdhc", + .match_table = fsl_esdhc_match, + .probe = esdhc_probe, + .remove = __devexit_p(esdhc_remove), + .suspend = esdhc_suspend, + .resume = esdhc_resume, +}; + +static int __init esdhc_drv_init(void) +{ + pr_info("%s: Freescale Enhanced Secure Digital Host Controller\n", + esdhc_driver.driver.name); + return of_register_platform_driver(&esdhc_driver); +} +module_init(esdhc_drv_init); + +static void __exit esdhc_drv_exit(void) +{ + of_unregister_platform_driver(&esdhc_driver); +} +module_exit(esdhc_drv_exit); + +MODULE_DESCRIPTION("Enhanced Secure Digital Host Controller driver"); +MODULE_AUTHOR("Xiaobo Xie "); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/esdhc.h b/drivers/mmc/host/esdhc.h new file mode 100644 index 0000000..0e259c6 --- /dev/null +++ b/drivers/mmc/host/esdhc.h @@ -0,0 +1,255 @@ +/* + * Freescale Enhanced Secure Digital Host Controller driver. + * + * Copyright (C) 2005-2008 Pierre Ossman, All Rights Reserved. + * Copyright (C) 2007-2009 Freescale Semiconductor, Inc. All rights reserved. + * Copyright (C) 2008-2009 MontaVista Software, Inc. All rights reserved. + * + * Author: Xiaobo Xie + * + * Derived from sdhci driver by Pierre Ossman + * + * 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. + */ + +#ifndef __MMC_HOST_ESDHC_H +#define __MMC_HOST_ESDHC_H + +#include + +/* + * Controller registers (Big Endian) + */ +/* DMA System Address Register */ +#define ESDHC_DMA_ADDRESS 0x00 + +/* Block Attributes Register */ +#define ESDHC_BLOCK_ATTR 0x04 +#define ESDHC_BLOCK_SIZE_MASK 0x00000fff +#define ESDHC_BLCOK_CNT_MASK 0xffff0000 +#define ESDHC_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF)) + +/* Command Argument */ +#define ESDHC_ARGUMENT 0x08 + +/* Transfer Type Register */ +#define ESDHC_COMMAND 0x0C + +#define ESDHC_TRNS_DMA 0x00000001 +#define ESDHC_TRNS_BLK_CNT_EN 0x00000002 +#define ESDHC_TRNS_ACMD12 0x00000004 +#define ESDHC_TRNS_READ 0x00000010 +#define ESDHC_TRNS_MULTI 0x00000020 + +#define ESDHC_CMD_RESP_MASK 0x00030000 +#define ESDHC_CMD_CRC_EN 0x00080000 +#define ESDHC_CMD_INDEX_EN 0x00100000 +#define ESDHC_CMD_DATA 0x00200000 +#define ESDHC_CMD_TYPE_MASK 0x00c00000 +#define ESDHC_CMD_INDEX 0x3f000000 + +#define ESDHC_CMD_RESP_NONE 0x00000000 +#define ESDHC_CMD_RESP_LONG 0x00010000 +#define ESDHC_CMD_RESP_SHORT 0x00020000 +#define ESDHC_CMD_RESP_SHORT_BUSY 0x00030000 + +#define ESDHC_MAKE_CMD(c, f) (((c & 0xff) << 24) | (f & 0xfb0037)) + +/* Response Register */ +#define ESDHC_RESPONSE 0x10 + +/* Buffer Data Port Register */ +#define ESDHC_BUFFER 0x20 + +/* Present State Register */ +#define ESDHC_PRESENT_STATE 0x24 +#define ESDHC_CMD_INHIBIT 0x00000001 +#define ESDHC_DATA_INHIBIT 0x00000002 +#define ESDHC_DOING_WRITE 0x00000100 +#define ESDHC_DOING_READ 0x00000200 +#define ESDHC_SPACE_AVAILABLE 0x00000400 +#define ESDHC_DATA_AVAILABLE 0x00000800 +#define ESDHC_CARD_PRESENT 0x00010000 +#define ESDHC_WRITE_PROTECT 0x00080000 + +/* Protocol control Register */ +#define ESDHC_PROTOCOL_CONTROL 0x28 + +#define ESDHC_CTRL_BUS_MASK 0x00000006 +#define ESDHC_CTRL_4BITBUS 0x00000002 +#define ESDHC_CTRL_D3_DETEC 0x00000008 +#define ESDHC_CTRL_DTCT_EN 0x00000080 +#define ESDHC_CTRL_DTCT_STATUS 0x00000040 +#define ESDHC_CTRL_WU_CRM 0x04000000 +#define ESDHC_CTRL_WU_CINS 0x02000000 +#define ESDHC_CTRL_WU_CINT 0x01000000 + +/* System Control Register */ +#define ESDHC_SYSTEM_CONTROL 0x2C + +#define ESDHC_CLOCK_MASK 0x0000fff0 +#define ESDHC_CLOCK_DEFAULT 0x00008000 +#define ESDHC_PREDIV_SHIFT 8 +#define ESDHC_DIVIDER_SHIFT 4 +#define ESDHC_CLOCK_CARD_EN 0x00000004 +#define ESDHC_CLOCK_INT_STABLE 0x00000002 +#define ESDHC_CLOCK_INT_EN 0x00000001 + +#define ESDHC_TIMEOUT_MASK 0x000f0000 +#define ESDHC_TIMEOUT_SHIFT 16 + +#define ESDHC_RESET_SHIFT 24 +#define ESDHC_RESET_ALL 0x01 +#define ESDHC_RESET_CMD 0x02 +#define ESDHC_RESET_DATA 0x04 +#define ESDHC_INIT_CARD 0x08 + +/* Interrupt Register */ +#define ESDHC_INT_STATUS 0x30 +#define ESDHC_INT_ENABLE 0x34 +#define ESDHC_SIGNAL_ENABLE 0x38 + +#define ESDHC_INT_RESPONSE 0x00000001 +#define ESDHC_INT_DATA_END 0x00000002 +#define ESDHC_INT_DMA_END 0x00000008 +#define ESDHC_INT_SPACE_AVAIL 0x00000010 +#define ESDHC_INT_DATA_AVAIL 0x00000020 +#define ESDHC_INT_CARD_INSERT 0x00000040 +#define ESDHC_INT_CARD_REMOVE 0x00000080 +#define ESDHC_INT_CARD_INT 0x00000100 + +#define ESDHC_INT_TIMEOUT 0x00010000 +#define ESDHC_INT_CRC 0x00020000 +#define ESDHC_INT_END_BIT 0x00040000 +#define ESDHC_INT_INDEX 0x00080000 +#define ESDHC_INT_DATA_TIMEOUT 0x00100000 +#define ESDHC_INT_DATA_CRC 0x00200000 +#define ESDHC_INT_DATA_END_BIT 0x00400000 +#define ESDHC_INT_ACMD12ERR 0x01000000 +#define ESDHC_INT_DMAERR 0x10000000 + +#define ESDHC_INT_NORMAL_MASK 0x00007FFF +#define ESDHC_INT_ERROR_MASK 0xFFFF8000 + +#define ESDHC_INT_CMD_MASK (ESDHC_INT_RESPONSE | ESDHC_INT_TIMEOUT | \ + ESDHC_INT_CRC | ESDHC_INT_END_BIT | ESDHC_INT_INDEX) +#define ESDHC_INT_DATA_MASK (ESDHC_INT_DATA_END | ESDHC_INT_DMA_END | \ + ESDHC_INT_DATA_AVAIL | ESDHC_INT_SPACE_AVAIL | \ + ESDHC_INT_DATA_TIMEOUT | ESDHC_INT_DATA_CRC | \ + ESDHC_INT_DATA_END_BIT) + +#define ESDHC_INT_INSERT_MASK (ESDHC_INT_DATA_END_BIT | ESDHC_INT_DATA_CRC | \ + ESDHC_INT_DATA_TIMEOUT | ESDHC_INT_INDEX | \ + ESDHC_INT_END_BIT | ESDHC_INT_CRC | ESDHC_INT_TIMEOUT | \ + ESDHC_INT_DATA_AVAIL | ESDHC_INT_SPACE_AVAIL | \ + ESDHC_INT_DMA_END | ESDHC_INT_DATA_END | \ + ESDHC_INT_RESPONSE | ESDHC_INT_CARD_REMOVE) + +#define ESDHC_INT_REMOVE_MASK (ESDHC_INT_DATA_END_BIT | ESDHC_INT_DATA_CRC | \ + ESDHC_INT_DATA_TIMEOUT | ESDHC_INT_INDEX | \ + ESDHC_INT_END_BIT | ESDHC_INT_CRC | ESDHC_INT_TIMEOUT | \ + ESDHC_INT_DATA_AVAIL | ESDHC_INT_SPACE_AVAIL | \ + ESDHC_INT_DMA_END | ESDHC_INT_DATA_END | \ + ESDHC_INT_RESPONSE | ESDHC_INT_CARD_INSERT) + +/* Auto CMD12 Error Status Register */ +#define ESDHC_ACMD12_ERR 0x3C + +/* 3E-3F reserved */ +/* Host Controller Capabilities */ +#define ESDHC_CAPABILITIES 0x40 + +#define ESDHC_MAX_BLOCK_MASK 0x00070000 +#define ESDHC_MAX_BLOCK_SHIFT 16 +#define ESDHC_CAN_DO_HISPD 0x00200000 +#define ESDHC_CAN_DO_DMA 0x00400000 +#define ESDHC_CAN_DO_SUSPEND 0x00800000 +#define ESDHC_CAN_VDD_330 0x01000000 +#define ESDHC_CAN_VDD_300 0x02000000 +#define ESDHC_CAN_VDD_180 0x04000000 + +/* Watermark Level Register */ +#define ESDHC_WML 0x44 +#define ESDHC_WML_MASK 0xff +#define ESDHC_WML_READ_SHIFT 0 +#define ESDHC_WML_WRITE_SHIFT 16 + +/* 45-4F reserved for more caps and max curren*/ + +/* Force Event Register */ +#define ESDHC_FORCE_EVENT 0x50 + +/* 54-FB reserved */ + +/* Host Controller Version Register */ +#define ESDHC_HOST_VERSION 0xFC + +#define ESDHC_VENDOR_VER_MASK 0xFF00 +#define ESDHC_VENDOR_VER_SHIFT 8 +#define ESDHC_SPEC_VER_MASK 0x00FF +#define ESDHC_SPEC_VER_SHIFT 0 + +#define ESDHC_DMA_SYSCTL 0x40C +#define ESDHC_DMA_SNOOP 0x00000040 + +#define ESDHC_SLOTS_NUMBER 1 + +/* The SCCR[SDHCCM] Register */ +#define MPC837X_SCCR_OFFS 0xA08 +#define MPC837X_SDHCCM_MASK 0x0c000000 +#define MPC837X_SDHCCM_SHIFT 26 + +static inline u32 fsl_readl(void __iomem *addr) +{ + return in_be32(addr); +} + +static inline void fsl_writel(void __iomem *addr, u32 val) +{ + out_be32(addr, val); +} + +struct mmc_host; + +struct esdhc_host { + struct mmc_host *mmc; /* MMC structure */ + + spinlock_t lock; /* Mutex */ + + unsigned long quirks; /* Host quirks */ + int flags; /* Host attributes */ +#define ESDHC_USE_DMA (1<<0) + + unsigned int max_clk; /* Max possible freq (MHz) */ + unsigned int timeout_clk; /* Timeout freq (KHz) */ + + unsigned int clock; /* Current clock (MHz) */ + unsigned short power; /* Current voltage */ + unsigned short bus_width; /* current bus width */ + + struct mmc_request *mrq; /* Current request */ + struct mmc_command *cmd; /* Current command */ + struct mmc_data *data; /* Current data request */ + + struct scatterlist *cur_sg; /* We're working on this */ + int num_sg; /* Entries left */ + int offset; /* Offset into current sg */ + int remain; /* Bytes left in current */ + + int card_insert; + + int irq; /* Device IRQ */ + unsigned long addr; /* Bus address */ + unsigned int size; /* IO size */ + void __iomem *ioaddr; /* Mapped address */ + + struct tasklet_struct card_tasklet; /* Tasklet structures */ + struct tasklet_struct finish_tasklet; + + struct timer_list timer; /* Timer for timeouts */ +}; + +#endif /* __MMC_HOST_ESDHC_H */