2009-01-14 19:46:50

by Anton Vorontsov

[permalink] [raw]
Subject: [PATCH] mmc: Add driver for Freescale eSDHC controllers

From: Xie Xiaobo <[email protected]>

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 <[email protected]>
Signed-off-by: Konjin Lai <[email protected]>
Signed-off-by: Joe D'Abbraccio <Joe.D'[email protected]>
Signed-off-by: Anton Vorontsov <[email protected]>
---
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 <[email protected]>
+ *
+ * Derived from sdhci driver by Pierre Ossman <[email protected]>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/uaccess.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/mmc/host.h>
+#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 <[email protected]>");
+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 <[email protected]>
+ *
+ * Derived from sdhci driver by Pierre Ossman <[email protected]>
+ *
+ * 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 <linux/io.h>
+
+/*
+ * 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 */
--
1.5.6.5


2009-01-14 23:37:29

by Liu Dave-R63238

[permalink] [raw]
Subject: RE: [PATCH] mmc: Add driver for Freescale eSDHC controllers

> This patch adds support for the Freescale Enhanced Secure Digital
> Host Controller Interface as found in some Freescale PowerPC
> microprocessors (e.g. MPC837x SOCs).

The Freescale ESDHC controller is basically compatible with the
standard SDHC controller. but the eshdc expand some bits.

The esdhc.c is much like the orignal sdhci.c.
it is possible to merge them.

Thanks, Dave

2009-01-14 23:56:23

by Anton Vorontsov

[permalink] [raw]
Subject: Re: [PATCH] mmc: Add driver for Freescale eSDHC controllers

On Thu, Jan 15, 2009 at 07:37:00AM +0800, Liu Dave wrote:
> > This patch adds support for the Freescale Enhanced Secure Digital
> > Host Controller Interface as found in some Freescale PowerPC
> > microprocessors (e.g. MPC837x SOCs).
>
> The Freescale ESDHC controller is basically compatible with the
> standard SDHC controller. but the eshdc expand some bits.
>
> The esdhc.c is much like the orignal sdhci.c.
> it is possible to merge them.

Ah. I wonder why Freescale just didn't write some patches for sdhci,
but copied the code instead...

Thanks for the info, I'll look into merging the driver into sdhci.

--
Anton Vorontsov
email: [email protected]
irc://irc.freenode.net/bd2

2009-01-15 03:46:38

by Kumar Gala

[permalink] [raw]
Subject: Re: [PATCH] mmc: Add driver for Freescale eSDHC controllers


On Jan 14, 2009, at 1:46 PM, Anton Vorontsov wrote:

> From: Xie Xiaobo <[email protected]>
>
> 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 <[email protected]>
> Signed-off-by: Konjin Lai <[email protected]>
> Signed-off-by: Joe D'Abbraccio <Joe.D'[email protected]>
> Signed-off-by: Anton Vorontsov <[email protected]>
> ---
> 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

Want to work some device tree docs for this.

- k

2009-01-15 05:55:23

by Pierre Ossman

[permalink] [raw]
Subject: Re: [PATCH] mmc: Add driver for Freescale eSDHC controllers

On Thu, 15 Jan 2009 02:56:05 +0300
Anton Vorontsov <[email protected]> wrote:

>
> Ah. I wonder why Freescale just didn't write some patches for sdhci,
> but copied the code instead...
>

Probably because it was quicker. Samsung did the same thing, but now
Ben Dooks has patches to properly support it via sdhci.

Rgds
--
-- Pierre Ossman

Linux kernel, MMC maintainer http://www.kernel.org
rdesktop, core developer http://www.rdesktop.org

WARNING: This correspondence is being monitored by the
Swedish government. Make sure your server uses encryption
for SMTP traffic and consider using PGP for end-to-end
encryption.

2009-01-15 06:11:28

by Liu Dave-R63238

[permalink] [raw]
Subject: RE: [PATCH] mmc: Add driver for Freescale eSDHC controllers

> Probably because it was quicker. Samsung did the same thing, but now
> Ben Dooks has patches to properly support it via sdhci.

Pierre,
What samsung driver are you talking? Could you point it to me?

Thanks, Dave

2009-01-15 06:40:12

by Pierre Ossman

[permalink] [raw]
Subject: Re: [PATCH] mmc: Add driver for Freescale eSDHC controllers

On Thu, 15 Jan 2009 14:11:09 +0800
"Liu Dave" <[email protected]> wrote:

> > Probably because it was quicker. Samsung did the same thing, but now
> > Ben Dooks has patches to properly support it via sdhci.
>
> Pierre,
> What samsung driver are you talking? Could you point it to me?
>

I'm afraid I haven't seen any readily available sources for their
driver. If you meant Ben's version, you can find the latest revision
here:

http://marc.info/?t=122823266100005

Unfortunately marc.info didn't sort out the threading, but you should
be able to find all the patches there or some other LKML archive.

Rgds
--
-- Pierre Ossman

Linux kernel, MMC maintainer http://www.kernel.org
rdesktop, core developer http://www.rdesktop.org

WARNING: This correspondence is being monitored by the
Swedish government. Make sure your server uses encryption
for SMTP traffic and consider using PGP for end-to-end
encryption.

2009-01-15 06:56:52

by Liu Dave-R63238

[permalink] [raw]
Subject: RE: [PATCH] mmc: Add driver for Freescale eSDHC controllers

> I'm afraid I haven't seen any readily available sources for their
> driver. If you meant Ben's version, you can find the latest revision
> here:
>
> http://marc.info/?t=122823266100005
>
> Unfortunately marc.info didn't sort out the threading, but you should
> be able to find all the patches there or some other LKML archive.

Thanks Pierre, That is my needs.

2009-01-15 14:36:25

by Ben Dooks

[permalink] [raw]
Subject: Re: [PATCH] mmc: Add driver for Freescale eSDHC controllers

On Thu, Jan 15, 2009 at 02:56:05AM +0300, Anton Vorontsov wrote:
> On Thu, Jan 15, 2009 at 07:37:00AM +0800, Liu Dave wrote:
> > > This patch adds support for the Freescale Enhanced Secure Digital
> > > Host Controller Interface as found in some Freescale PowerPC
> > > microprocessors (e.g. MPC837x SOCs).
> >
> > The Freescale ESDHC controller is basically compatible with the
> > standard SDHC controller. but the eshdc expand some bits.
> >
> > The esdhc.c is much like the orignal sdhci.c.
> > it is possible to merge them.
>
> Ah. I wonder why Freescale just didn't write some patches for sdhci,
> but copied the code instead...

For the same reasons earthmen like tea.

--
Ben ([email protected], http://www.fluff.org/)

'a smiley only costs 4 bytes'

2009-01-15 21:52:58

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCH] mmc: Add driver for Freescale eSDHC controllers

On Wednesday 14 January 2009, Anton Vorontsov wrote:
>
> +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.


The naming could be improved, calling it ESDHC instead of ESDHCI is very
confusing: SDHCI is the controller chip while SDHC is a type of card (high
capacity SD). This driver clearly does not refer to the card type but
rather to the controller.

Arnd <><

2009-01-22 01:35:49

by Anton Vorontsov

[permalink] [raw]
Subject: Re: [PATCH] mmc: Add driver for Freescale eSDHC controllers

On Wed, Jan 14, 2009 at 09:43:50PM -0600, Kumar Gala wrote:
>
> On Jan 14, 2009, at 1:46 PM, Anton Vorontsov wrote:
>
>> From: Xie Xiaobo <[email protected]>
>>
>> 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 <[email protected]>
>> Signed-off-by: Konjin Lai <[email protected]>
>> Signed-off-by: Joe D'Abbraccio <Joe.D'[email protected]>
>> Signed-off-by: Anton Vorontsov <[email protected]>
>> ---
>> 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
>
> Want to work some device tree docs for this.

Will do. Though all the needed nodes already in the .dts files. ;-)

Thanks,

--
Anton Vorontsov
email: [email protected]
irc://irc.freenode.net/bd2