summaryrefslogtreecommitdiffstats
path: root/drivers/mmc/host
diff options
context:
space:
mode:
authorPierre Ossman <drzeus@drzeus.cx>2007-02-11 19:57:36 +0100
committerPierre Ossman <drzeus@drzeus.cx>2007-05-01 13:04:17 +0200
commit1c6a0718f0bfdab0d9b7da5f7b74f38a0058c03a (patch)
tree5e7f2a26d5d1782d87c596b40f874c6c0b8b8e1a /drivers/mmc/host
parentmmc: Move queue functions to mmc_block (diff)
downloadlinux-1c6a0718f0bfdab0d9b7da5f7b74f38a0058c03a.tar.xz
linux-1c6a0718f0bfdab0d9b7da5f7b74f38a0058c03a.zip
mmc: Move host and card drivers to subdirs
Clean up the drivers/mmc directory by moving card and host drivers into subdirectories. Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
Diffstat (limited to 'drivers/mmc/host')
-rw-r--r--drivers/mmc/host/Kconfig103
-rw-r--r--drivers/mmc/host/Makefile18
-rw-r--r--drivers/mmc/host/at91_mci.c1001
-rw-r--r--drivers/mmc/host/au1xmmc.c1031
-rw-r--r--drivers/mmc/host/au1xmmc.h96
-rw-r--r--drivers/mmc/host/imxmmc.c1137
-rw-r--r--drivers/mmc/host/imxmmc.h67
-rw-r--r--drivers/mmc/host/mmci.c702
-rw-r--r--drivers/mmc/host/mmci.h179
-rw-r--r--drivers/mmc/host/omap.c1288
-rw-r--r--drivers/mmc/host/pxamci.c616
-rw-r--r--drivers/mmc/host/pxamci.h124
-rw-r--r--drivers/mmc/host/sdhci.c1539
-rw-r--r--drivers/mmc/host/sdhci.h210
-rw-r--r--drivers/mmc/host/tifm_sd.c1102
-rw-r--r--drivers/mmc/host/wbsd.c2062
-rw-r--r--drivers/mmc/host/wbsd.h185
17 files changed, 11460 insertions, 0 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
new file mode 100644
index 000000000000..ed4deab2203d
--- /dev/null
+++ b/drivers/mmc/host/Kconfig
@@ -0,0 +1,103 @@
+#
+# MMC/SD host controller drivers
+#
+
+comment "MMC/SD Host Controller Drivers"
+ depends on MMC
+
+config MMC_ARMMMCI
+ tristate "ARM AMBA Multimedia Card Interface support"
+ depends on ARM_AMBA && MMC
+ help
+ This selects the ARM(R) AMBA(R) PrimeCell Multimedia Card
+ Interface (PL180 and PL181) support. If you have an ARM(R)
+ platform with a Multimedia Card slot, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_PXA
+ tristate "Intel PXA25x/26x/27x Multimedia Card Interface support"
+ depends on ARCH_PXA && MMC
+ help
+ This selects the Intel(R) PXA(R) Multimedia card Interface.
+ If you have a PXA(R) platform with a Multimedia Card slot,
+ say Y or M here.
+
+ If unsure, say N.
+
+config MMC_SDHCI
+ tristate "Secure Digital Host Controller Interface support (EXPERIMENTAL)"
+ depends on PCI && MMC && EXPERIMENTAL
+ help
+ This select the generic Secure Digital Host Controller Interface.
+ It is used by manufacturers such as Texas Instruments(R), Ricoh(R)
+ and Toshiba(R). Most controllers found in laptops are of this type.
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_OMAP
+ tristate "TI OMAP Multimedia Card Interface support"
+ depends on ARCH_OMAP && MMC
+ select TPS65010 if MACH_OMAP_H2
+ help
+ This selects the TI OMAP Multimedia card Interface.
+ If you have an OMAP board with a Multimedia Card slot,
+ say Y or M here.
+
+ If unsure, say N.
+
+config MMC_WBSD
+ tristate "Winbond W83L51xD SD/MMC Card Interface support"
+ depends on MMC && ISA_DMA_API
+ help
+ This selects the Winbond(R) W83L51xD Secure digital and
+ Multimedia card Interface.
+ If you have a machine with a integrated W83L518D or W83L519D
+ SD/MMC card reader, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_AU1X
+ tristate "Alchemy AU1XX0 MMC Card Interface support"
+ depends on MMC && SOC_AU1200
+ help
+ This selects the AMD Alchemy(R) Multimedia card interface.
+ If you have a Alchemy platform with a MMC slot, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_AT91
+ tristate "AT91 SD/MMC Card Interface support"
+ depends on ARCH_AT91 && MMC
+ help
+ This selects the AT91 MCI controller.
+
+ If unsure, say N.
+
+config MMC_IMX
+ tristate "Motorola i.MX Multimedia Card Interface support"
+ depends on ARCH_IMX && MMC
+ help
+ This selects the Motorola i.MX Multimedia card Interface.
+ If you have a i.MX platform with a Multimedia Card slot,
+ say Y or M here.
+
+ If unsure, say N.
+
+config MMC_TIFM_SD
+ tristate "TI Flash Media MMC/SD Interface support (EXPERIMENTAL)"
+ depends on MMC && EXPERIMENTAL && PCI
+ select TIFM_CORE
+ help
+ Say Y here if you want to be able to access MMC/SD cards with
+ the Texas Instruments(R) Flash Media card reader, found in many
+ laptops.
+ This option 'selects' (turns on, enables) 'TIFM_CORE', but you
+ probably also need appropriate card reader host adapter, such as
+ 'Misc devices: TI Flash Media PCI74xx/PCI76xx host adapter support
+ (TIFM_7XX1)'.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tifm_sd.
+
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
new file mode 100644
index 000000000000..6685f64345b4
--- /dev/null
+++ b/drivers/mmc/host/Makefile
@@ -0,0 +1,18 @@
+#
+# Makefile for MMC/SD host controller drivers
+#
+
+ifeq ($(CONFIG_MMC_DEBUG),y)
+ EXTRA_CFLAGS += -DDEBUG
+endif
+
+obj-$(CONFIG_MMC_ARMMMCI) += mmci.o
+obj-$(CONFIG_MMC_PXA) += pxamci.o
+obj-$(CONFIG_MMC_IMX) += imxmmc.o
+obj-$(CONFIG_MMC_SDHCI) += sdhci.o
+obj-$(CONFIG_MMC_WBSD) += wbsd.o
+obj-$(CONFIG_MMC_AU1X) += au1xmmc.o
+obj-$(CONFIG_MMC_OMAP) += omap.o
+obj-$(CONFIG_MMC_AT91) += at91_mci.o
+obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o
+
diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c
new file mode 100644
index 000000000000..e37943c314cb
--- /dev/null
+++ b/drivers/mmc/host/at91_mci.c
@@ -0,0 +1,1001 @@
+/*
+ * linux/drivers/mmc/at91_mci.c - ATMEL AT91 MCI Driver
+ *
+ * Copyright (C) 2005 Cougar Creek Computing Devices Ltd, All Rights Reserved
+ *
+ * Copyright (C) 2006 Malcolm Noyes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/*
+ This is the AT91 MCI driver that has been tested with both MMC cards
+ and SD-cards. Boards that support write protect are now supported.
+ The CCAT91SBC001 board does not support SD cards.
+
+ The three entry points are at91_mci_request, at91_mci_set_ios
+ and at91_mci_get_ro.
+
+ SET IOS
+ This configures the device to put it into the correct mode and clock speed
+ required.
+
+ MCI REQUEST
+ MCI request processes the commands sent in the mmc_request structure. This
+ can consist of a processing command and a stop command in the case of
+ multiple block transfers.
+
+ There are three main types of request, commands, reads and writes.
+
+ Commands are straight forward. The command is submitted to the controller and
+ the request function returns. When the controller generates an interrupt to indicate
+ the command is finished, the response to the command are read and the mmc_request_done
+ function called to end the request.
+
+ Reads and writes work in a similar manner to normal commands but involve the PDC (DMA)
+ controller to manage the transfers.
+
+ A read is done from the controller directly to the scatterlist passed in from the request.
+ Due to a bug in the AT91RM9200 controller, when a read is completed, all the words are byte
+ swapped in the scatterlist buffers. AT91SAM926x are not affected by this bug.
+
+ The sequence of read interrupts is: ENDRX, RXBUFF, CMDRDY
+
+ A write is slightly different in that the bytes to write are read from the scatterlist
+ into a dma memory buffer (this is in case the source buffer should be read only). The
+ entire write buffer is then done from this single dma memory buffer.
+
+ The sequence of write interrupts is: ENDTX, TXBUFE, NOTBUSY, CMDRDY
+
+ GET RO
+ Gets the status of the write protect pin, if available.
+*/
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/atmel_pdc.h>
+
+#include <linux/mmc/host.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/mach/mmc.h>
+#include <asm/arch/board.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/at91_mci.h>
+
+#define DRIVER_NAME "at91_mci"
+
+#undef SUPPORT_4WIRE
+
+#define FL_SENT_COMMAND (1 << 0)
+#define FL_SENT_STOP (1 << 1)
+
+#define AT91_MCI_ERRORS (AT91_MCI_RINDE | AT91_MCI_RDIRE | AT91_MCI_RCRCE \
+ | AT91_MCI_RENDE | AT91_MCI_RTOE | AT91_MCI_DCRCE \
+ | AT91_MCI_DTOE | AT91_MCI_OVRE | AT91_MCI_UNRE)
+
+#define at91_mci_read(host, reg) __raw_readl((host)->baseaddr + (reg))
+#define at91_mci_write(host, reg, val) __raw_writel((val), (host)->baseaddr + (reg))
+
+
+/*
+ * Low level type for this driver
+ */
+struct at91mci_host
+{
+ struct mmc_host *mmc;
+ struct mmc_command *cmd;
+ struct mmc_request *request;
+
+ void __iomem *baseaddr;
+ int irq;
+
+ struct at91_mmc_data *board;
+ int present;
+
+ struct clk *mci_clk;
+
+ /*
+ * Flag indicating when the command has been sent. This is used to
+ * work out whether or not to send the stop
+ */
+ unsigned int flags;
+ /* flag for current bus settings */
+ u32 bus_mode;
+
+ /* DMA buffer used for transmitting */
+ unsigned int* buffer;
+ dma_addr_t physical_address;
+ unsigned int total_length;
+
+ /* Latest in the scatterlist that has been enabled for transfer, but not freed */
+ int in_use_index;
+
+ /* Latest in the scatterlist that has been enabled for transfer */
+ int transfer_index;
+};
+
+/*
+ * Copy from sg to a dma block - used for transfers
+ */
+static inline void at91mci_sg_to_dma(struct at91mci_host *host, struct mmc_data *data)
+{
+ unsigned int len, i, size;
+ unsigned *dmabuf = host->buffer;
+
+ size = host->total_length;
+ len = data->sg_len;
+
+ /*
+ * Just loop through all entries. Size might not
+ * be the entire list though so make sure that
+ * we do not transfer too much.
+ */
+ for (i = 0; i < len; i++) {
+ struct scatterlist *sg;
+ int amount;
+ unsigned int *sgbuffer;
+
+ sg = &data->sg[i];
+
+ sgbuffer = kmap_atomic(sg->page, KM_BIO_SRC_IRQ) + sg->offset;
+ amount = min(size, sg->length);
+ size -= amount;
+
+ if (cpu_is_at91rm9200()) { /* AT91RM9200 errata */
+ int index;
+
+ for (index = 0; index < (amount / 4); index++)
+ *dmabuf++ = swab32(sgbuffer[index]);
+ }
+ else
+ memcpy(dmabuf, sgbuffer, amount);
+
+ kunmap_atomic(sgbuffer, KM_BIO_SRC_IRQ);
+
+ if (size == 0)
+ break;
+ }
+
+ /*
+ * Check that we didn't get a request to transfer
+ * more data than can fit into the SG list.
+ */
+ BUG_ON(size != 0);
+}
+
+/*
+ * Prepare a dma read
+ */
+static void at91mci_pre_dma_read(struct at91mci_host *host)
+{
+ int i;
+ struct scatterlist *sg;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ pr_debug("pre dma read\n");
+
+ cmd = host->cmd;
+ if (!cmd) {
+ pr_debug("no command\n");
+ return;
+ }
+
+ data = cmd->data;
+ if (!data) {
+ pr_debug("no data\n");
+ return;
+ }
+
+ for (i = 0; i < 2; i++) {
+ /* nothing left to transfer */
+ if (host->transfer_index >= data->sg_len) {
+ pr_debug("Nothing left to transfer (index = %d)\n", host->transfer_index);
+ break;
+ }
+
+ /* Check to see if this needs filling */
+ if (i == 0) {
+ if (at91_mci_read(host, ATMEL_PDC_RCR) != 0) {
+ pr_debug("Transfer active in current\n");
+ continue;
+ }
+ }
+ else {
+ if (at91_mci_read(host, ATMEL_PDC_RNCR) != 0) {
+ pr_debug("Transfer active in next\n");
+ continue;
+ }
+ }
+
+ /* Setup the next transfer */
+ pr_debug("Using transfer index %d\n", host->transfer_index);
+
+ sg = &data->sg[host->transfer_index++];
+ pr_debug("sg = %p\n", sg);
+
+ sg->dma_address = dma_map_page(NULL, sg->page, sg->offset, sg->length, DMA_FROM_DEVICE);
+
+ pr_debug("dma address = %08X, length = %d\n", sg->dma_address, sg->length);
+
+ if (i == 0) {
+ at91_mci_write(host, ATMEL_PDC_RPR, sg->dma_address);
+ at91_mci_write(host, ATMEL_PDC_RCR, sg->length / 4);
+ }
+ else {
+ at91_mci_write(host, ATMEL_PDC_RNPR, sg->dma_address);
+ at91_mci_write(host, ATMEL_PDC_RNCR, sg->length / 4);
+ }
+ }
+
+ pr_debug("pre dma read done\n");
+}
+
+/*
+ * Handle after a dma read
+ */
+static void at91mci_post_dma_read(struct at91mci_host *host)
+{
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ pr_debug("post dma read\n");
+
+ cmd = host->cmd;
+ if (!cmd) {
+ pr_debug("no command\n");
+ return;
+ }
+
+ data = cmd->data;
+ if (!data) {
+ pr_debug("no data\n");
+ return;
+ }
+
+ while (host->in_use_index < host->transfer_index) {
+ unsigned int *buffer;
+
+ struct scatterlist *sg;
+
+ pr_debug("finishing index %d\n", host->in_use_index);
+
+ sg = &data->sg[host->in_use_index++];
+
+ pr_debug("Unmapping page %08X\n", sg->dma_address);
+
+ dma_unmap_page(NULL, sg->dma_address, sg->length, DMA_FROM_DEVICE);
+
+ /* Swap the contents of the buffer */
+ buffer = kmap_atomic(sg->page, KM_BIO_SRC_IRQ) + sg->offset;
+ pr_debug("buffer = %p, length = %d\n", buffer, sg->length);
+
+ data->bytes_xfered += sg->length;
+
+ if (cpu_is_at91rm9200()) { /* AT91RM9200 errata */
+ int index;
+
+ for (index = 0; index < (sg->length / 4); index++)
+ buffer[index] = swab32(buffer[index]);
+ }
+
+ kunmap_atomic(buffer, KM_BIO_SRC_IRQ);
+ flush_dcache_page(sg->page);
+ }
+
+ /* Is there another transfer to trigger? */
+ if (host->transfer_index < data->sg_len)
+ at91mci_pre_dma_read(host);
+ else {
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_RXBUFF);
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
+ }
+
+ pr_debug("post dma read done\n");
+}
+
+/*
+ * Handle transmitted data
+ */
+static void at91_mci_handle_transmitted(struct at91mci_host *host)
+{
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ pr_debug("Handling the transmit\n");
+
+ /* Disable the transfer */
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
+
+ /* Now wait for cmd ready */
+ at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_TXBUFE);
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
+
+ cmd = host->cmd;
+ if (!cmd) return;
+
+ data = cmd->data;
+ if (!data) return;
+
+ data->bytes_xfered = host->total_length;
+}
+
+/*
+ * Enable the controller
+ */
+static void at91_mci_enable(struct at91mci_host *host)
+{
+ at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN);
+ at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
+ at91_mci_write(host, AT91_MCI_DTOR, AT91_MCI_DTOMUL_1M | AT91_MCI_DTOCYC);
+ at91_mci_write(host, AT91_MCI_MR, AT91_MCI_PDCMODE | 0x34a);
+
+ /* use Slot A or B (only one at same time) */
+ at91_mci_write(host, AT91_MCI_SDCR, host->board->slot_b);
+}
+
+/*
+ * Disable the controller
+ */
+static void at91_mci_disable(struct at91mci_host *host)
+{
+ at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIDIS | AT91_MCI_SWRST);
+}
+
+/*
+ * Send a command
+ * return the interrupts to enable
+ */
+static unsigned int at91_mci_send_command(struct at91mci_host *host, struct mmc_command *cmd)
+{
+ unsigned int cmdr, mr;
+ unsigned int block_length;
+ struct mmc_data *data = cmd->data;
+
+ unsigned int blocks;
+ unsigned int ier = 0;
+
+ host->cmd = cmd;
+
+ /* Not sure if this is needed */
+#if 0
+ if ((at91_mci_read(host, AT91_MCI_SR) & AT91_MCI_RTOE) && (cmd->opcode == 1)) {
+ pr_debug("Clearing timeout\n");
+ at91_mci_write(host, AT91_MCI_ARGR, 0);
+ at91_mci_write(host, AT91_MCI_CMDR, AT91_MCI_OPDCMD);
+ while (!(at91_mci_read(host, AT91_MCI_SR) & AT91_MCI_CMDRDY)) {
+ /* spin */
+ pr_debug("Clearing: SR = %08X\n", at91_mci_read(host, AT91_MCI_SR));
+ }
+ }
+#endif
+ cmdr = cmd->opcode;
+
+ if (mmc_resp_type(cmd) == MMC_RSP_NONE)
+ cmdr |= AT91_MCI_RSPTYP_NONE;
+ else {
+ /* if a response is expected then allow maximum response latancy */
+ cmdr |= AT91_MCI_MAXLAT;
+ /* set 136 bit response for R2, 48 bit response otherwise */
+ if (mmc_resp_type(cmd) == MMC_RSP_R2)
+ cmdr |= AT91_MCI_RSPTYP_136;
+ else
+ cmdr |= AT91_MCI_RSPTYP_48;
+ }
+
+ if (data) {
+ block_length = data->blksz;
+ blocks = data->blocks;
+
+ /* always set data start - also set direction flag for read */
+ if (data->flags & MMC_DATA_READ)
+ cmdr |= (AT91_MCI_TRDIR | AT91_MCI_TRCMD_START);
+ else if (data->flags & MMC_DATA_WRITE)
+ cmdr |= AT91_MCI_TRCMD_START;
+
+ if (data->flags & MMC_DATA_STREAM)
+ cmdr |= AT91_MCI_TRTYP_STREAM;
+ if (data->flags & MMC_DATA_MULTI)
+ cmdr |= AT91_MCI_TRTYP_MULTIPLE;
+ }
+ else {
+ block_length = 0;
+ blocks = 0;
+ }
+
+ if (cmd->opcode == MMC_STOP_TRANSMISSION)
+ cmdr |= AT91_MCI_TRCMD_STOP;
+
+ if (host->bus_mode == MMC_BUSMODE_OPENDRAIN)
+ cmdr |= AT91_MCI_OPDCMD;
+
+ /*
+ * Set the arguments and send the command
+ */
+ pr_debug("Sending command %d as %08X, arg = %08X, blocks = %d, length = %d (MR = %08X)\n",
+ cmd->opcode, cmdr, cmd->arg, blocks, block_length, at91_mci_read(host, AT91_MCI_MR));
+
+ if (!data) {
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTDIS | ATMEL_PDC_RXTDIS);
+ at91_mci_write(host, ATMEL_PDC_RPR, 0);
+ at91_mci_write(host, ATMEL_PDC_RCR, 0);
+ at91_mci_write(host, ATMEL_PDC_RNPR, 0);
+ at91_mci_write(host, ATMEL_PDC_RNCR, 0);
+ at91_mci_write(host, ATMEL_PDC_TPR, 0);
+ at91_mci_write(host, ATMEL_PDC_TCR, 0);
+ at91_mci_write(host, ATMEL_PDC_TNPR, 0);
+ at91_mci_write(host, ATMEL_PDC_TNCR, 0);
+
+ at91_mci_write(host, AT91_MCI_ARGR, cmd->arg);
+ at91_mci_write(host, AT91_MCI_CMDR, cmdr);
+ return AT91_MCI_CMDRDY;
+ }
+
+ mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff; /* zero block length and PDC mode */
+ at91_mci_write(host, AT91_MCI_MR, mr | (block_length << 16) | AT91_MCI_PDCMODE);
+
+ /*
+ * Disable the PDC controller
+ */
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
+
+ if (cmdr & AT91_MCI_TRCMD_START) {
+ data->bytes_xfered = 0;
+ host->transfer_index = 0;
+ host->in_use_index = 0;
+ if (cmdr & AT91_MCI_TRDIR) {
+ /*
+ * Handle a read
+ */
+ host->buffer = NULL;
+ host->total_length = 0;
+
+ at91mci_pre_dma_read(host);
+ ier = AT91_MCI_ENDRX /* | AT91_MCI_RXBUFF */;
+ }
+ else {
+ /*
+ * Handle a write
+ */
+ host->total_length = block_length * blocks;
+ host->buffer = dma_alloc_coherent(NULL,
+ host->total_length,
+ &host->physical_address, GFP_KERNEL);
+
+ at91mci_sg_to_dma(host, data);
+
+ pr_debug("Transmitting %d bytes\n", host->total_length);
+
+ at91_mci_write(host, ATMEL_PDC_TPR, host->physical_address);
+ at91_mci_write(host, ATMEL_PDC_TCR, host->total_length / 4);
+ ier = AT91_MCI_TXBUFE;
+ }
+ }
+
+ /*
+ * Send the command and then enable the PDC - not the other way round as
+ * the data sheet says
+ */
+
+ at91_mci_write(host, AT91_MCI_ARGR, cmd->arg);
+ at91_mci_write(host, AT91_MCI_CMDR, cmdr);
+
+ if (cmdr & AT91_MCI_TRCMD_START) {
+ if (cmdr & AT91_MCI_TRDIR)
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTEN);
+ else
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
+ }
+ return ier;
+}
+
+/*
+ * Wait for a command to complete
+ */
+static void at91mci_process_command(struct at91mci_host *host, struct mmc_command *cmd)
+{
+ unsigned int ier;
+
+ ier = at91_mci_send_command(host, cmd);
+
+ pr_debug("setting ier to %08X\n", ier);
+
+ /* Stop on errors or the required value */
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_ERRORS | ier);
+}
+
+/*
+ * Process the next step in the request
+ */
+static void at91mci_process_next(struct at91mci_host *host)
+{
+ if (!(host->flags & FL_SENT_COMMAND)) {
+ host->flags |= FL_SENT_COMMAND;
+ at91mci_process_command(host, host->request->cmd);
+ }
+ else if ((!(host->flags & FL_SENT_STOP)) && host->request->stop) {
+ host->flags |= FL_SENT_STOP;
+ at91mci_process_command(host, host->request->stop);
+ }
+ else
+ mmc_request_done(host->mmc, host->request);
+}
+
+/*
+ * Handle a command that has been completed
+ */
+static void at91mci_completed_command(struct at91mci_host *host)
+{
+ struct mmc_command *cmd = host->cmd;
+ unsigned int status;
+
+ at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
+
+ cmd->resp[0] = at91_mci_read(host, AT91_MCI_RSPR(0));
+ cmd->resp[1] = at91_mci_read(host, AT91_MCI_RSPR(1));
+ cmd->resp[2] = at91_mci_read(host, AT91_MCI_RSPR(2));
+ cmd->resp[3] = at91_mci_read(host, AT91_MCI_RSPR(3));
+
+ if (host->buffer) {
+ dma_free_coherent(NULL, host->total_length, host->buffer, host->physical_address);
+ host->buffer = NULL;
+ }
+
+ status = at91_mci_read(host, AT91_MCI_SR);
+
+ pr_debug("Status = %08X [%08X %08X %08X %08X]\n",
+ status, cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
+
+ if (status & (AT91_MCI_RINDE | AT91_MCI_RDIRE | AT91_MCI_RCRCE |
+ AT91_MCI_RENDE | AT91_MCI_RTOE | AT91_MCI_DCRCE |
+ AT91_MCI_DTOE | AT91_MCI_OVRE | AT91_MCI_UNRE)) {
+ if ((status & AT91_MCI_RCRCE) &&
+ ((cmd->opcode == MMC_SEND_OP_COND) || (cmd->opcode == SD_APP_OP_COND))) {
+ cmd->error = MMC_ERR_NONE;
+ }
+ else {
+ if (status & (AT91_MCI_RTOE | AT91_MCI_DTOE))
+ cmd->error = MMC_ERR_TIMEOUT;
+ else if (status & (AT91_MCI_RCRCE | AT91_MCI_DCRCE))
+ cmd->error = MMC_ERR_BADCRC;
+ else if (status & (AT91_MCI_OVRE | AT91_MCI_UNRE))
+ cmd->error = MMC_ERR_FIFO;
+ else
+ cmd->error = MMC_ERR_FAILED;
+
+ pr_debug("Error detected and set to %d (cmd = %d, retries = %d)\n",
+ cmd->error, cmd->opcode, cmd->retries);
+ }
+ }
+ else
+ cmd->error = MMC_ERR_NONE;
+
+ at91mci_process_next(host);
+}
+
+/*
+ * Handle an MMC request
+ */
+static void at91_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct at91mci_host *host = mmc_priv(mmc);
+ host->request = mrq;
+ host->flags = 0;
+
+ at91mci_process_next(host);
+}
+
+/*
+ * Set the IOS
+ */
+static void at91_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ int clkdiv;
+ struct at91mci_host *host = mmc_priv(mmc);
+ unsigned long at91_master_clock = clk_get_rate(host->mci_clk);
+
+ host->bus_mode = ios->bus_mode;
+
+ if (ios->clock == 0) {
+ /* Disable the MCI controller */
+ at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIDIS);
+ clkdiv = 0;
+ }
+ else {
+ /* Enable the MCI controller */
+ at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN);
+
+ if ((at91_master_clock % (ios->clock * 2)) == 0)
+ clkdiv = ((at91_master_clock / ios->clock) / 2) - 1;
+ else
+ clkdiv = (at91_master_clock / ios->clock) / 2;
+
+ pr_debug("clkdiv = %d. mcck = %ld\n", clkdiv,
+ at91_master_clock / (2 * (clkdiv + 1)));
+ }
+ if (ios->bus_width == MMC_BUS_WIDTH_4 && host->board->wire4) {
+ pr_debug("MMC: Setting controller bus width to 4\n");
+ at91_mci_write(host, AT91_MCI_SDCR, at91_mci_read(host, AT91_MCI_SDCR) | AT91_MCI_SDCBUS);
+ }
+ else {
+ pr_debug("MMC: Setting controller bus width to 1\n");
+ at91_mci_write(host, AT91_MCI_SDCR, at91_mci_read(host, AT91_MCI_SDCR) & ~AT91_MCI_SDCBUS);
+ }
+
+ /* Set the clock divider */
+ at91_mci_write(host, AT91_MCI_MR, (at91_mci_read(host, AT91_MCI_MR) & ~AT91_MCI_CLKDIV) | clkdiv);
+
+ /* maybe switch power to the card */
+ if (host->board->vcc_pin) {
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ at91_set_gpio_value(host->board->vcc_pin, 0);
+ break;
+ case MMC_POWER_UP:
+ case MMC_POWER_ON:
+ at91_set_gpio_value(host->board->vcc_pin, 1);
+ break;
+ }
+ }
+}
+
+/*
+ * Handle an interrupt
+ */
+static irqreturn_t at91_mci_irq(int irq, void *devid)
+{
+ struct at91mci_host *host = devid;
+ int completed = 0;
+ unsigned int int_status, int_mask;
+
+ int_status = at91_mci_read(host, AT91_MCI_SR);
+ int_mask = at91_mci_read(host, AT91_MCI_IMR);
+
+ pr_debug("MCI irq: status = %08X, %08X, %08X\n", int_status, int_mask,
+ int_status & int_mask);
+
+ int_status = int_status & int_mask;
+
+ if (int_status & AT91_MCI_ERRORS) {
+ completed = 1;
+
+ if (int_status & AT91_MCI_UNRE)
+ pr_debug("MMC: Underrun error\n");
+ if (int_status & AT91_MCI_OVRE)
+ pr_debug("MMC: Overrun error\n");
+ if (int_status & AT91_MCI_DTOE)
+ pr_debug("MMC: Data timeout\n");
+ if (int_status & AT91_MCI_DCRCE)
+ pr_debug("MMC: CRC error in data\n");
+ if (int_status & AT91_MCI_RTOE)
+ pr_debug("MMC: Response timeout\n");
+ if (int_status & AT91_MCI_RENDE)
+ pr_debug("MMC: Response end bit error\n");
+ if (int_status & AT91_MCI_RCRCE)
+ pr_debug("MMC: Response CRC error\n");
+ if (int_status & AT91_MCI_RDIRE)
+ pr_debug("MMC: Response direction error\n");
+ if (int_status & AT91_MCI_RINDE)
+ pr_debug("MMC: Response index error\n");
+ } else {
+ /* Only continue processing if no errors */
+
+ if (int_status & AT91_MCI_TXBUFE) {
+ pr_debug("TX buffer empty\n");
+ at91_mci_handle_transmitted(host);
+ }
+
+ if (int_status & AT91_MCI_RXBUFF) {
+ pr_debug("RX buffer full\n");
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_CMDRDY);
+ }
+
+ if (int_status & AT91_MCI_ENDTX)
+ pr_debug("Transmit has ended\n");
+
+ if (int_status & AT91_MCI_ENDRX) {
+ pr_debug("Receive has ended\n");
+ at91mci_post_dma_read(host);
+ }
+
+ if (int_status & AT91_MCI_NOTBUSY) {
+ pr_debug("Card is ready\n");
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_CMDRDY);
+ }
+
+ if (int_status & AT91_MCI_DTIP)
+ pr_debug("Data transfer in progress\n");
+
+ if (int_status & AT91_MCI_BLKE)
+ pr_debug("Block transfer has ended\n");
+
+ if (int_status & AT91_MCI_TXRDY)
+ pr_debug("Ready to transmit\n");
+
+ if (int_status & AT91_MCI_RXRDY)
+ pr_debug("Ready to receive\n");
+
+ if (int_status & AT91_MCI_CMDRDY) {
+ pr_debug("Command ready\n");
+ completed = 1;
+ }
+ }
+
+ if (completed) {
+ pr_debug("Completed command\n");
+ at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
+ at91mci_completed_command(host);
+ } else
+ at91_mci_write(host, AT91_MCI_IDR, int_status);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t at91_mmc_det_irq(int irq, void *_host)
+{
+ struct at91mci_host *host = _host;
+ int present = !at91_get_gpio_value(irq);
+
+ /*
+ * we expect this irq on both insert and remove,
+ * and use a short delay to debounce.
+ */
+ if (present != host->present) {
+ host->present = present;
+ pr_debug("%s: card %s\n", mmc_hostname(host->mmc),
+ present ? "insert" : "remove");
+ if (!present) {
+ pr_debug("****** Resetting SD-card bus width ******\n");
+ at91_mci_write(host, AT91_MCI_SDCR, at91_mci_read(host, AT91_MCI_SDCR) & ~AT91_MCI_SDCBUS);
+ }
+ mmc_detect_change(host->mmc, msecs_to_jiffies(100));
+ }
+ return IRQ_HANDLED;
+}
+
+static int at91_mci_get_ro(struct mmc_host *mmc)
+{
+ int read_only = 0;
+ struct at91mci_host *host = mmc_priv(mmc);
+
+ if (host->board->wp_pin) {
+ read_only = at91_get_gpio_value(host->board->wp_pin);
+ printk(KERN_WARNING "%s: card is %s\n", mmc_hostname(mmc),
+ (read_only ? "read-only" : "read-write") );
+ }
+ else {
+ printk(KERN_WARNING "%s: host does not support reading read-only "
+ "switch. Assuming write-enable.\n", mmc_hostname(mmc));
+ }
+ return read_only;
+}
+
+static const struct mmc_host_ops at91_mci_ops = {
+ .request = at91_mci_request,
+ .set_ios = at91_mci_set_ios,
+ .get_ro = at91_mci_get_ro,
+};
+
+/*
+ * Probe for the device
+ */
+static int __init at91_mci_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct at91mci_host *host;
+ struct resource *res;
+ int ret;
+
+ pr_debug("Probe MCI devices\n");
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENXIO;
+
+ if (!request_mem_region(res->start, res->end - res->start + 1, DRIVER_NAME))
+ return -EBUSY;
+
+ mmc = mmc_alloc_host(sizeof(struct at91mci_host), &pdev->dev);
+ if (!mmc) {
+ pr_debug("Failed to allocate mmc host\n");
+ release_mem_region(res->start, res->end - res->start + 1);
+ return -ENOMEM;
+ }
+
+ mmc->ops = &at91_mci_ops;
+ mmc->f_min = 375000;
+ mmc->f_max = 25000000;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = MMC_CAP_BYTEBLOCK;
+
+ mmc->max_blk_size = 4095;
+ mmc->max_blk_count = mmc->max_req_size;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->buffer = NULL;
+ host->bus_mode = 0;
+ host->board = pdev->dev.platform_data;
+ if (host->board->wire4) {
+#ifdef SUPPORT_4WIRE
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+#else
+ printk("AT91 MMC: 4 wire bus mode not supported by this driver - using 1 wire\n");
+#endif
+ }
+
+ /*
+ * Get Clock
+ */
+ host->mci_clk = clk_get(&pdev->dev, "mci_clk");
+ if (IS_ERR(host->mci_clk)) {
+ printk(KERN_ERR "AT91 MMC: no clock defined.\n");
+ mmc_free_host(mmc);
+ release_mem_region(res->start, res->end - res->start + 1);
+ return -ENODEV;
+ }
+
+ /*
+ * Map I/O region
+ */
+ host->baseaddr = ioremap(res->start, res->end - res->start + 1);
+ if (!host->baseaddr) {
+ clk_put(host->mci_clk);
+ mmc_free_host(mmc);
+ release_mem_region(res->start, res->end - res->start + 1);
+ return -ENOMEM;
+ }
+
+ /*
+ * Reset hardware
+ */
+ clk_enable(host->mci_clk); /* Enable the peripheral clock */
+ at91_mci_disable(host);
+ at91_mci_enable(host);
+
+ /*
+ * Allocate the MCI interrupt
+ */
+ host->irq = platform_get_irq(pdev, 0);
+ ret = request_irq(host->irq, at91_mci_irq, IRQF_SHARED, DRIVER_NAME, host);
+ if (ret) {
+ printk(KERN_ERR "AT91 MMC: Failed to request MCI interrupt\n");
+ clk_disable(host->mci_clk);
+ clk_put(host->mci_clk);
+ mmc_free_host(mmc);
+ iounmap(host->baseaddr);
+ release_mem_region(res->start, res->end - res->start + 1);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, mmc);
+
+ /*
+ * Add host to MMC layer
+ */
+ if (host->board->det_pin)
+ host->present = !at91_get_gpio_value(host->board->det_pin);
+ else
+ host->present = -1;
+
+ mmc_add_host(mmc);
+
+ /*
+ * monitor card insertion/removal if we can
+ */
+ if (host->board->det_pin) {
+ ret = request_irq(host->board->det_pin, at91_mmc_det_irq,
+ 0, DRIVER_NAME, host);
+ if (ret)
+ printk(KERN_ERR "AT91 MMC: Couldn't allocate MMC detect irq\n");
+ }
+
+ pr_debug("Added MCI driver\n");
+
+ return 0;
+}
+
+/*
+ * Remove a device
+ */
+static int __exit at91_mci_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct at91mci_host *host;
+ struct resource *res;
+
+ if (!mmc)
+ return -1;
+
+ host = mmc_priv(mmc);
+
+ if (host->present != -1) {
+ free_irq(host->board->det_pin, host);
+ cancel_delayed_work(&host->mmc->detect);
+ }
+
+ at91_mci_disable(host);
+ mmc_remove_host(mmc);
+ free_irq(host->irq, host);
+
+ clk_disable(host->mci_clk); /* Disable the peripheral clock */
+ clk_put(host->mci_clk);
+
+ iounmap(host->baseaddr);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(res->start, res->end - res->start + 1);
+
+ mmc_free_host(mmc);
+ platform_set_drvdata(pdev, NULL);
+ pr_debug("MCI Removed\n");
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int at91_mci_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_suspend_host(mmc, state);
+
+ return ret;
+}
+
+static int at91_mci_resume(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_resume_host(mmc);
+
+ return ret;
+}
+#else
+#define at91_mci_suspend NULL
+#define at91_mci_resume NULL
+#endif
+
+static struct platform_driver at91_mci_driver = {
+ .remove = __exit_p(at91_mci_remove),
+ .suspend = at91_mci_suspend,
+ .resume = at91_mci_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init at91_mci_init(void)
+{
+ return platform_driver_probe(&at91_mci_driver, at91_mci_probe);
+}
+
+static void __exit at91_mci_exit(void)
+{
+ platform_driver_unregister(&at91_mci_driver);
+}
+
+module_init(at91_mci_init);
+module_exit(at91_mci_exit);
+
+MODULE_DESCRIPTION("AT91 Multimedia Card Interface driver");
+MODULE_AUTHOR("Nick Randell");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/au1xmmc.c b/drivers/mmc/host/au1xmmc.c
new file mode 100644
index 000000000000..b7156a4555b5
--- /dev/null
+++ b/drivers/mmc/host/au1xmmc.c
@@ -0,0 +1,1031 @@
+/*
+ * linux/drivers/mmc/au1xmmc.c - AU1XX0 MMC driver
+ *
+ * Copyright (c) 2005, Advanced Micro Devices, Inc.
+ *
+ * Developed with help from the 2.4.30 MMC AU1XXX controller including
+ * the following copyright notices:
+ * Copyright (c) 2003-2004 Embedded Edge, LLC.
+ * Portions Copyright (C) 2002 Embedix, Inc
+ * Copyright 2002 Hewlett-Packard Company
+
+ * 2.6 version of this driver inspired by:
+ * (drivers/mmc/wbsd.c) Copyright (C) 2004-2005 Pierre Ossman,
+ * All Rights Reserved.
+ * (drivers/mmc/pxa.c) Copyright (C) 2003 Russell King,
+ * All Rights Reserved.
+ *
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* Why is a timer used to detect insert events?
+ *
+ * From the AU1100 MMC application guide:
+ * If the Au1100-based design is intended to support both MultiMediaCards
+ * and 1- or 4-data bit SecureDigital cards, then the solution is to
+ * connect a weak (560KOhm) pull-up resistor to connector pin 1.
+ * In doing so, a MMC card never enters SPI-mode communications,
+ * but now the SecureDigital card-detect feature of CD/DAT3 is ineffective
+ * (the low to high transition will not occur).
+ *
+ * So we use the timer to check the status manually.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+
+#include <linux/mmc/host.h>
+#include <asm/io.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_dbdma.h>
+#include <asm/mach-au1x00/au1100_mmc.h>
+#include <asm/scatterlist.h>
+
+#include <au1xxx.h>
+#include "au1xmmc.h"
+
+#define DRIVER_NAME "au1xxx-mmc"
+
+/* Set this to enable special debugging macros */
+
+#ifdef DEBUG
+#define DBG(fmt, idx, args...) printk("au1xx(%d): DEBUG: " fmt, idx, ##args)
+#else
+#define DBG(fmt, idx, args...)
+#endif
+
+const struct {
+ u32 iobase;
+ u32 tx_devid, rx_devid;
+ u16 bcsrpwr;
+ u16 bcsrstatus;
+ u16 wpstatus;
+} au1xmmc_card_table[] = {
+ { SD0_BASE, DSCR_CMD0_SDMS_TX0, DSCR_CMD0_SDMS_RX0,
+ BCSR_BOARD_SD0PWR, BCSR_INT_SD0INSERT, BCSR_STATUS_SD0WP },
+#ifndef CONFIG_MIPS_DB1200
+ { SD1_BASE, DSCR_CMD0_SDMS_TX1, DSCR_CMD0_SDMS_RX1,
+ BCSR_BOARD_DS1PWR, BCSR_INT_SD1INSERT, BCSR_STATUS_SD1WP }
+#endif
+};
+
+#define AU1XMMC_CONTROLLER_COUNT \
+ (sizeof(au1xmmc_card_table) / sizeof(au1xmmc_card_table[0]))
+
+/* This array stores pointers for the hosts (used by the IRQ handler) */
+struct au1xmmc_host *au1xmmc_hosts[AU1XMMC_CONTROLLER_COUNT];
+static int dma = 1;
+
+#ifdef MODULE
+module_param(dma, bool, 0);
+MODULE_PARM_DESC(dma, "Use DMA engine for data transfers (0 = disabled)");
+#endif
+
+static inline void IRQ_ON(struct au1xmmc_host *host, u32 mask)
+{
+ u32 val = au_readl(HOST_CONFIG(host));
+ val |= mask;
+ au_writel(val, HOST_CONFIG(host));
+ au_sync();
+}
+
+static inline void FLUSH_FIFO(struct au1xmmc_host *host)
+{
+ u32 val = au_readl(HOST_CONFIG2(host));
+
+ au_writel(val | SD_CONFIG2_FF, HOST_CONFIG2(host));
+ au_sync_delay(1);
+
+ /* SEND_STOP will turn off clock control - this re-enables it */
+ val &= ~SD_CONFIG2_DF;
+
+ au_writel(val, HOST_CONFIG2(host));
+ au_sync();
+}
+
+static inline void IRQ_OFF(struct au1xmmc_host *host, u32 mask)
+{
+ u32 val = au_readl(HOST_CONFIG(host));
+ val &= ~mask;
+ au_writel(val, HOST_CONFIG(host));
+ au_sync();
+}
+
+static inline void SEND_STOP(struct au1xmmc_host *host)
+{
+
+ /* We know the value of CONFIG2, so avoid a read we don't need */
+ u32 mask = SD_CONFIG2_EN;
+
+ WARN_ON(host->status != HOST_S_DATA);
+ host->status = HOST_S_STOP;
+
+ au_writel(mask | SD_CONFIG2_DF, HOST_CONFIG2(host));
+ au_sync();
+
+ /* Send the stop commmand */
+ au_writel(STOP_CMD, HOST_CMD(host));
+}
+
+static void au1xmmc_set_power(struct au1xmmc_host *host, int state)
+{
+
+ u32 val = au1xmmc_card_table[host->id].bcsrpwr;
+
+ bcsr->board &= ~val;
+ if (state) bcsr->board |= val;
+
+ au_sync_delay(1);
+}
+
+static inline int au1xmmc_card_inserted(struct au1xmmc_host *host)
+{
+ return (bcsr->sig_status & au1xmmc_card_table[host->id].bcsrstatus)
+ ? 1 : 0;
+}
+
+static int au1xmmc_card_readonly(struct mmc_host *mmc)
+{
+ struct au1xmmc_host *host = mmc_priv(mmc);
+ return (bcsr->status & au1xmmc_card_table[host->id].wpstatus)
+ ? 1 : 0;
+}
+
+static void au1xmmc_finish_request(struct au1xmmc_host *host)
+{
+
+ struct mmc_request *mrq = host->mrq;
+
+ host->mrq = NULL;
+ host->flags &= HOST_F_ACTIVE;
+
+ host->dma.len = 0;
+ host->dma.dir = 0;
+
+ host->pio.index = 0;
+ host->pio.offset = 0;
+ host->pio.len = 0;
+
+ host->status = HOST_S_IDLE;
+
+ bcsr->disk_leds |= (1 << 8);
+
+ mmc_request_done(host->mmc, mrq);
+}
+
+static void au1xmmc_tasklet_finish(unsigned long param)
+{
+ struct au1xmmc_host *host = (struct au1xmmc_host *) param;
+ au1xmmc_finish_request(host);
+}
+
+static int au1xmmc_send_command(struct au1xmmc_host *host, int wait,
+ struct mmc_command *cmd)
+{
+
+ u32 mmccmd = (cmd->opcode << SD_CMD_CI_SHIFT);
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ break;
+ case MMC_RSP_R1:
+ mmccmd |= SD_CMD_RT_1;
+ break;
+ case MMC_RSP_R1B:
+ mmccmd |= SD_CMD_RT_1B;
+ break;
+ case MMC_RSP_R2:
+ mmccmd |= SD_CMD_RT_2;
+ break;
+ case MMC_RSP_R3:
+ mmccmd |= SD_CMD_RT_3;
+ break;
+ default:
+ printk(KERN_INFO "au1xmmc: unhandled response type %02x\n",
+ mmc_resp_type(cmd));
+ return MMC_ERR_INVALID;
+ }
+
+ switch(cmd->opcode) {
+ case MMC_READ_SINGLE_BLOCK:
+ case SD_APP_SEND_SCR:
+ mmccmd |= SD_CMD_CT_2;
+ break;
+ case MMC_READ_MULTIPLE_BLOCK:
+ mmccmd |= SD_CMD_CT_4;
+ break;
+ case MMC_WRITE_BLOCK:
+ mmccmd |= SD_CMD_CT_1;
+ break;
+
+ case MMC_WRITE_MULTIPLE_BLOCK:
+ mmccmd |= SD_CMD_CT_3;
+ break;
+ case MMC_STOP_TRANSMISSION:
+ mmccmd |= SD_CMD_CT_7;
+ break;
+ }
+
+ au_writel(cmd->arg, HOST_CMDARG(host));
+ au_sync();
+
+ if (wait)
+ IRQ_OFF(host, SD_CONFIG_CR);
+
+ au_writel((mmccmd | SD_CMD_GO), HOST_CMD(host));
+ au_sync();
+
+ /* Wait for the command to go on the line */
+
+ while(1) {
+ if (!(au_readl(HOST_CMD(host)) & SD_CMD_GO))
+ break;
+ }
+
+ /* Wait for the command to come back */
+
+ if (wait) {
+ u32 status = au_readl(HOST_STATUS(host));
+
+ while(!(status & SD_STATUS_CR))
+ status = au_readl(HOST_STATUS(host));
+
+ /* Clear the CR status */
+ au_writel(SD_STATUS_CR, HOST_STATUS(host));
+
+ IRQ_ON(host, SD_CONFIG_CR);
+ }
+
+ return MMC_ERR_NONE;
+}
+
+static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status)
+{
+
+ struct mmc_request *mrq = host->mrq;
+ struct mmc_data *data;
+ u32 crc;
+
+ WARN_ON(host->status != HOST_S_DATA && host->status != HOST_S_STOP);
+
+ if (host->mrq == NULL)
+ return;
+
+ data = mrq->cmd->data;
+
+ if (status == 0)
+ status = au_readl(HOST_STATUS(host));
+
+ /* The transaction is really over when the SD_STATUS_DB bit is clear */
+
+ while((host->flags & HOST_F_XMIT) && (status & SD_STATUS_DB))
+ status = au_readl(HOST_STATUS(host));
+
+ data->error = MMC_ERR_NONE;
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, host->dma.dir);
+
+ /* Process any errors */
+
+ crc = (status & (SD_STATUS_WC | SD_STATUS_RC));
+ if (host->flags & HOST_F_XMIT)
+ crc |= ((status & 0x07) == 0x02) ? 0 : 1;
+
+ if (crc)
+ data->error = MMC_ERR_BADCRC;
+
+ /* Clear the CRC bits */
+ au_writel(SD_STATUS_WC | SD_STATUS_RC, HOST_STATUS(host));
+
+ data->bytes_xfered = 0;
+
+ if (data->error == MMC_ERR_NONE) {
+ if (host->flags & HOST_F_DMA) {
+ u32 chan = DMA_CHANNEL(host);
+
+ chan_tab_t *c = *((chan_tab_t **) chan);
+ au1x_dma_chan_t *cp = c->chan_ptr;
+ data->bytes_xfered = cp->ddma_bytecnt;
+ }
+ else
+ data->bytes_xfered =
+ (data->blocks * data->blksz) -
+ host->pio.len;
+ }
+
+ au1xmmc_finish_request(host);
+}
+
+static void au1xmmc_tasklet_data(unsigned long param)
+{
+ struct au1xmmc_host *host = (struct au1xmmc_host *) param;
+
+ u32 status = au_readl(HOST_STATUS(host));
+ au1xmmc_data_complete(host, status);
+}
+
+#define AU1XMMC_MAX_TRANSFER 8
+
+static void au1xmmc_send_pio(struct au1xmmc_host *host)
+{
+
+ struct mmc_data *data = 0;
+ int sg_len, max, count = 0;
+ unsigned char *sg_ptr;
+ u32 status = 0;
+ struct scatterlist *sg;
+
+ data = host->mrq->data;
+
+ if (!(host->flags & HOST_F_XMIT))
+ return;
+
+ /* This is the pointer to the data buffer */
+ sg = &data->sg[host->pio.index];
+ sg_ptr = page_address(sg->page) + sg->offset + host->pio.offset;
+
+ /* This is the space left inside the buffer */
+ sg_len = data->sg[host->pio.index].length - host->pio.offset;
+
+ /* Check to if we need less then the size of the sg_buffer */
+
+ max = (sg_len > host->pio.len) ? host->pio.len : sg_len;
+ if (max > AU1XMMC_MAX_TRANSFER) max = AU1XMMC_MAX_TRANSFER;
+
+ for(count = 0; count < max; count++ ) {
+ unsigned char val;
+
+ status = au_readl(HOST_STATUS(host));
+
+ if (!(status & SD_STATUS_TH))
+ break;
+
+ val = *sg_ptr++;
+
+ au_writel((unsigned long) val, HOST_TXPORT(host));
+ au_sync();
+ }
+
+ host->pio.len -= count;
+ host->pio.offset += count;
+
+ if (count == sg_len) {
+ host->pio.index++;
+ host->pio.offset = 0;
+ }
+
+ if (host->pio.len == 0) {
+ IRQ_OFF(host, SD_CONFIG_TH);
+
+ if (host->flags & HOST_F_STOP)
+ SEND_STOP(host);
+
+ tasklet_schedule(&host->data_task);
+ }
+}
+
+static void au1xmmc_receive_pio(struct au1xmmc_host *host)
+{
+
+ struct mmc_data *data = 0;
+ int sg_len = 0, max = 0, count = 0;
+ unsigned char *sg_ptr = 0;
+ u32 status = 0;
+ struct scatterlist *sg;
+
+ data = host->mrq->data;
+
+ if (!(host->flags & HOST_F_RECV))
+ return;
+
+ max = host->pio.len;
+
+ if (host->pio.index < host->dma.len) {
+ sg = &data->sg[host->pio.index];
+ sg_ptr = page_address(sg->page) + sg->offset + host->pio.offset;
+
+ /* This is the space left inside the buffer */
+ sg_len = sg_dma_len(&data->sg[host->pio.index]) - host->pio.offset;
+
+ /* Check to if we need less then the size of the sg_buffer */
+ if (sg_len < max) max = sg_len;
+ }
+
+ if (max > AU1XMMC_MAX_TRANSFER)
+ max = AU1XMMC_MAX_TRANSFER;
+
+ for(count = 0; count < max; count++ ) {
+ u32 val;
+ status = au_readl(HOST_STATUS(host));
+
+ if (!(status & SD_STATUS_NE))
+ break;
+
+ if (status & SD_STATUS_RC) {
+ DBG("RX CRC Error [%d + %d].\n", host->id,
+ host->pio.len, count);
+ break;
+ }
+
+ if (status & SD_STATUS_RO) {
+ DBG("RX Overrun [%d + %d]\n", host->id,
+ host->pio.len, count);
+ break;
+ }
+ else if (status & SD_STATUS_RU) {
+ DBG("RX Underrun [%d + %d]\n", host->id,
+ host->pio.len, count);
+ break;
+ }
+
+ val = au_readl(HOST_RXPORT(host));
+
+ if (sg_ptr)
+ *sg_ptr++ = (unsigned char) (val & 0xFF);
+ }
+
+ host->pio.len -= count;
+ host->pio.offset += count;
+
+ if (sg_len && count == sg_len) {
+ host->pio.index++;
+ host->pio.offset = 0;
+ }
+
+ if (host->pio.len == 0) {
+ //IRQ_OFF(host, SD_CONFIG_RA | SD_CONFIG_RF);
+ IRQ_OFF(host, SD_CONFIG_NE);
+
+ if (host->flags & HOST_F_STOP)
+ SEND_STOP(host);
+
+ tasklet_schedule(&host->data_task);
+ }
+}
+
+/* static void au1xmmc_cmd_complete
+ This is called when a command has been completed - grab the response
+ and check for errors. Then start the data transfer if it is indicated.
+*/
+
+static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status)
+{
+
+ struct mmc_request *mrq = host->mrq;
+ struct mmc_command *cmd;
+ int trans;
+
+ if (!host->mrq)
+ return;
+
+ cmd = mrq->cmd;
+ cmd->error = MMC_ERR_NONE;
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ u32 r[4];
+ int i;
+
+ r[0] = au_readl(host->iobase + SD_RESP3);
+ r[1] = au_readl(host->iobase + SD_RESP2);
+ r[2] = au_readl(host->iobase + SD_RESP1);
+ r[3] = au_readl(host->iobase + SD_RESP0);
+
+ /* The CRC is omitted from the response, so really
+ * we only got 120 bytes, but the engine expects
+ * 128 bits, so we have to shift things up
+ */
+
+ for(i = 0; i < 4; i++) {
+ cmd->resp[i] = (r[i] & 0x00FFFFFF) << 8;
+ if (i != 3)
+ cmd->resp[i] |= (r[i + 1] & 0xFF000000) >> 24;
+ }
+ } else {
+ /* Techincally, we should be getting all 48 bits of
+ * the response (SD_RESP1 + SD_RESP2), but because
+ * our response omits the CRC, our data ends up
+ * being shifted 8 bits to the right. In this case,
+ * that means that the OSR data starts at bit 31,
+ * so we can just read RESP0 and return that
+ */
+ cmd->resp[0] = au_readl(host->iobase + SD_RESP0);
+ }
+ }
+
+ /* Figure out errors */
+
+ if (status & (SD_STATUS_SC | SD_STATUS_WC | SD_STATUS_RC))
+ cmd->error = MMC_ERR_BADCRC;
+
+ trans = host->flags & (HOST_F_XMIT | HOST_F_RECV);
+
+ if (!trans || cmd->error != MMC_ERR_NONE) {
+
+ IRQ_OFF(host, SD_CONFIG_TH | SD_CONFIG_RA|SD_CONFIG_RF);
+ tasklet_schedule(&host->finish_task);
+ return;
+ }
+
+ host->status = HOST_S_DATA;
+
+ if (host->flags & HOST_F_DMA) {
+ u32 channel = DMA_CHANNEL(host);
+
+ /* Start the DMA as soon as the buffer gets something in it */
+
+ if (host->flags & HOST_F_RECV) {
+ u32 mask = SD_STATUS_DB | SD_STATUS_NE;
+
+ while((status & mask) != mask)
+ status = au_readl(HOST_STATUS(host));
+ }
+
+ au1xxx_dbdma_start(channel);
+ }
+}
+
+static void au1xmmc_set_clock(struct au1xmmc_host *host, int rate)
+{
+
+ unsigned int pbus = get_au1x00_speed();
+ unsigned int divisor;
+ u32 config;
+
+ /* From databook:
+ divisor = ((((cpuclock / sbus_divisor) / 2) / mmcclock) / 2) - 1
+ */
+
+ pbus /= ((au_readl(SYS_POWERCTRL) & 0x3) + 2);
+ pbus /= 2;
+
+ divisor = ((pbus / rate) / 2) - 1;
+
+ config = au_readl(HOST_CONFIG(host));
+
+ config &= ~(SD_CONFIG_DIV);
+ config |= (divisor & SD_CONFIG_DIV) | SD_CONFIG_DE;
+
+ au_writel(config, HOST_CONFIG(host));
+ au_sync();
+}
+
+static int
+au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data)
+{
+
+ int datalen = data->blocks * data->blksz;
+
+ if (dma != 0)
+ host->flags |= HOST_F_DMA;
+
+ if (data->flags & MMC_DATA_READ)
+ host->flags |= HOST_F_RECV;
+ else
+ host->flags |= HOST_F_XMIT;
+
+ if (host->mrq->stop)
+ host->flags |= HOST_F_STOP;
+
+ host->dma.dir = DMA_BIDIRECTIONAL;
+
+ host->dma.len = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, host->dma.dir);
+
+ if (host->dma.len == 0)
+ return MMC_ERR_TIMEOUT;
+
+ au_writel(data->blksz - 1, HOST_BLKSIZE(host));
+
+ if (host->flags & HOST_F_DMA) {
+ int i;
+ u32 channel = DMA_CHANNEL(host);
+
+ au1xxx_dbdma_stop(channel);
+
+ for(i = 0; i < host->dma.len; i++) {
+ u32 ret = 0, flags = DDMA_FLAGS_NOIE;
+ struct scatterlist *sg = &data->sg[i];
+ int sg_len = sg->length;
+
+ int len = (datalen > sg_len) ? sg_len : datalen;
+
+ if (i == host->dma.len - 1)
+ flags = DDMA_FLAGS_IE;
+
+ if (host->flags & HOST_F_XMIT){
+ ret = au1xxx_dbdma_put_source_flags(channel,
+ (void *) (page_address(sg->page) +
+ sg->offset),
+ len, flags);
+ }
+ else {
+ ret = au1xxx_dbdma_put_dest_flags(channel,
+ (void *) (page_address(sg->page) +
+ sg->offset),
+ len, flags);
+ }
+
+ if (!ret)
+ goto dataerr;
+
+ datalen -= len;
+ }
+ }
+ else {
+ host->pio.index = 0;
+ host->pio.offset = 0;
+ host->pio.len = datalen;
+
+ if (host->flags & HOST_F_XMIT)
+ IRQ_ON(host, SD_CONFIG_TH);
+ else
+ IRQ_ON(host, SD_CONFIG_NE);
+ //IRQ_ON(host, SD_CONFIG_RA|SD_CONFIG_RF);
+ }
+
+ return MMC_ERR_NONE;
+
+ dataerr:
+ dma_unmap_sg(mmc_dev(host->mmc),data->sg,data->sg_len,host->dma.dir);
+ return MMC_ERR_TIMEOUT;
+}
+
+/* static void au1xmmc_request
+ This actually starts a command or data transaction
+*/
+
+static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq)
+{
+
+ struct au1xmmc_host *host = mmc_priv(mmc);
+ int ret = MMC_ERR_NONE;
+
+ WARN_ON(irqs_disabled());
+ WARN_ON(host->status != HOST_S_IDLE);
+
+ host->mrq = mrq;
+ host->status = HOST_S_CMD;
+
+ bcsr->disk_leds &= ~(1 << 8);
+
+ if (mrq->data) {
+ FLUSH_FIFO(host);
+ ret = au1xmmc_prepare_data(host, mrq->data);
+ }
+
+ if (ret == MMC_ERR_NONE)
+ ret = au1xmmc_send_command(host, 0, mrq->cmd);
+
+ if (ret != MMC_ERR_NONE) {
+ mrq->cmd->error = ret;
+ au1xmmc_finish_request(host);
+ }
+}
+
+static void au1xmmc_reset_controller(struct au1xmmc_host *host)
+{
+
+ /* Apply the clock */
+ au_writel(SD_ENABLE_CE, HOST_ENABLE(host));
+ au_sync_delay(1);
+
+ au_writel(SD_ENABLE_R | SD_ENABLE_CE, HOST_ENABLE(host));
+ au_sync_delay(5);
+
+ au_writel(~0, HOST_STATUS(host));
+ au_sync();
+
+ au_writel(0, HOST_BLKSIZE(host));
+ au_writel(0x001fffff, HOST_TIMEOUT(host));
+ au_sync();
+
+ au_writel(SD_CONFIG2_EN, HOST_CONFIG2(host));
+ au_sync();
+
+ au_writel(SD_CONFIG2_EN | SD_CONFIG2_FF, HOST_CONFIG2(host));
+ au_sync_delay(1);
+
+ au_writel(SD_CONFIG2_EN, HOST_CONFIG2(host));
+ au_sync();
+
+ /* Configure interrupts */
+ au_writel(AU1XMMC_INTERRUPTS, HOST_CONFIG(host));
+ au_sync();
+}
+
+
+static void au1xmmc_set_ios(struct mmc_host* mmc, struct mmc_ios* ios)
+{
+ struct au1xmmc_host *host = mmc_priv(mmc);
+
+ if (ios->power_mode == MMC_POWER_OFF)
+ au1xmmc_set_power(host, 0);
+ else if (ios->power_mode == MMC_POWER_ON) {
+ au1xmmc_set_power(host, 1);
+ }
+
+ if (ios->clock && ios->clock != host->clock) {
+ au1xmmc_set_clock(host, ios->clock);
+ host->clock = ios->clock;
+ }
+}
+
+static void au1xmmc_dma_callback(int irq, void *dev_id)
+{
+ struct au1xmmc_host *host = (struct au1xmmc_host *) dev_id;
+
+ /* Avoid spurious interrupts */
+
+ if (!host->mrq)
+ return;
+
+ if (host->flags & HOST_F_STOP)
+ SEND_STOP(host);
+
+ tasklet_schedule(&host->data_task);
+}
+
+#define STATUS_TIMEOUT (SD_STATUS_RAT | SD_STATUS_DT)
+#define STATUS_DATA_IN (SD_STATUS_NE)
+#define STATUS_DATA_OUT (SD_STATUS_TH)
+
+static irqreturn_t au1xmmc_irq(int irq, void *dev_id)
+{
+
+ u32 status;
+ int i, ret = 0;
+
+ disable_irq(AU1100_SD_IRQ);
+
+ for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) {
+ struct au1xmmc_host * host = au1xmmc_hosts[i];
+ u32 handled = 1;
+
+ status = au_readl(HOST_STATUS(host));
+
+ if (host->mrq && (status & STATUS_TIMEOUT)) {
+ if (status & SD_STATUS_RAT)
+ host->mrq->cmd->error = MMC_ERR_TIMEOUT;
+
+ else if (status & SD_STATUS_DT)
+ host->mrq->data->error = MMC_ERR_TIMEOUT;
+
+ /* In PIO mode, interrupts might still be enabled */
+ IRQ_OFF(host, SD_CONFIG_NE | SD_CONFIG_TH);
+
+ //IRQ_OFF(host, SD_CONFIG_TH|SD_CONFIG_RA|SD_CONFIG_RF);
+ tasklet_schedule(&host->finish_task);
+ }
+#if 0
+ else if (status & SD_STATUS_DD) {
+
+ /* Sometimes we get a DD before a NE in PIO mode */
+
+ if (!(host->flags & HOST_F_DMA) &&
+ (status & SD_STATUS_NE))
+ au1xmmc_receive_pio(host);
+ else {
+ au1xmmc_data_complete(host, status);
+ //tasklet_schedule(&host->data_task);
+ }
+ }
+#endif
+ else if (status & (SD_STATUS_CR)) {
+ if (host->status == HOST_S_CMD)
+ au1xmmc_cmd_complete(host,status);
+ }
+ else if (!(host->flags & HOST_F_DMA)) {
+ if ((host->flags & HOST_F_XMIT) &&
+ (status & STATUS_DATA_OUT))
+ au1xmmc_send_pio(host);
+ else if ((host->flags & HOST_F_RECV) &&
+ (status & STATUS_DATA_IN))
+ au1xmmc_receive_pio(host);
+ }
+ else if (status & 0x203FBC70) {
+ DBG("Unhandled status %8.8x\n", host->id, status);
+ handled = 0;
+ }
+
+ au_writel(status, HOST_STATUS(host));
+ au_sync();
+
+ ret |= handled;
+ }
+
+ enable_irq(AU1100_SD_IRQ);
+ return ret;
+}
+
+static void au1xmmc_poll_event(unsigned long arg)
+{
+ struct au1xmmc_host *host = (struct au1xmmc_host *) arg;
+
+ int card = au1xmmc_card_inserted(host);
+ int controller = (host->flags & HOST_F_ACTIVE) ? 1 : 0;
+
+ if (card != controller) {
+ host->flags &= ~HOST_F_ACTIVE;
+ if (card) host->flags |= HOST_F_ACTIVE;
+ mmc_detect_change(host->mmc, 0);
+ }
+
+ if (host->mrq != NULL) {
+ u32 status = au_readl(HOST_STATUS(host));
+ DBG("PENDING - %8.8x\n", host->id, status);
+ }
+
+ mod_timer(&host->timer, jiffies + AU1XMMC_DETECT_TIMEOUT);
+}
+
+static dbdev_tab_t au1xmmc_mem_dbdev =
+{
+ DSCR_CMD0_ALWAYS, DEV_FLAGS_ANYUSE, 0, 8, 0x00000000, 0, 0
+};
+
+static void au1xmmc_init_dma(struct au1xmmc_host *host)
+{
+
+ u32 rxchan, txchan;
+
+ int txid = au1xmmc_card_table[host->id].tx_devid;
+ int rxid = au1xmmc_card_table[host->id].rx_devid;
+
+ /* DSCR_CMD0_ALWAYS has a stride of 32 bits, we need a stride
+ of 8 bits. And since devices are shared, we need to create
+ our own to avoid freaking out other devices
+ */
+
+ int memid = au1xxx_ddma_add_device(&au1xmmc_mem_dbdev);
+
+ txchan = au1xxx_dbdma_chan_alloc(memid, txid,
+ au1xmmc_dma_callback, (void *) host);
+
+ rxchan = au1xxx_dbdma_chan_alloc(rxid, memid,
+ au1xmmc_dma_callback, (void *) host);
+
+ au1xxx_dbdma_set_devwidth(txchan, 8);
+ au1xxx_dbdma_set_devwidth(rxchan, 8);
+
+ au1xxx_dbdma_ring_alloc(txchan, AU1XMMC_DESCRIPTOR_COUNT);
+ au1xxx_dbdma_ring_alloc(rxchan, AU1XMMC_DESCRIPTOR_COUNT);
+
+ host->tx_chan = txchan;
+ host->rx_chan = rxchan;
+}
+
+static const struct mmc_host_ops au1xmmc_ops = {
+ .request = au1xmmc_request,
+ .set_ios = au1xmmc_set_ios,
+ .get_ro = au1xmmc_card_readonly,
+};
+
+static int __devinit au1xmmc_probe(struct platform_device *pdev)
+{
+
+ int i, ret = 0;
+
+ /* THe interrupt is shared among all controllers */
+ ret = request_irq(AU1100_SD_IRQ, au1xmmc_irq, IRQF_DISABLED, "MMC", 0);
+
+ if (ret) {
+ printk(DRIVER_NAME "ERROR: Couldn't get int %d: %d\n",
+ AU1100_SD_IRQ, ret);
+ return -ENXIO;
+ }
+
+ disable_irq(AU1100_SD_IRQ);
+
+ for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) {
+ struct mmc_host *mmc = mmc_alloc_host(sizeof(struct au1xmmc_host), &pdev->dev);
+ struct au1xmmc_host *host = 0;
+
+ if (!mmc) {
+ printk(DRIVER_NAME "ERROR: no mem for host %d\n", i);
+ au1xmmc_hosts[i] = 0;
+ continue;
+ }
+
+ mmc->ops = &au1xmmc_ops;
+
+ mmc->f_min = 450000;
+ mmc->f_max = 24000000;
+
+ mmc->max_seg_size = AU1XMMC_DESCRIPTOR_SIZE;
+ mmc->max_phys_segs = AU1XMMC_DESCRIPTOR_COUNT;
+
+ mmc->max_blk_size = 2048;
+ mmc->max_blk_count = 512;
+
+ mmc->ocr_avail = AU1XMMC_OCR;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+
+ host->id = i;
+ host->iobase = au1xmmc_card_table[host->id].iobase;
+ host->clock = 0;
+ host->power_mode = MMC_POWER_OFF;
+
+ host->flags = au1xmmc_card_inserted(host) ? HOST_F_ACTIVE : 0;
+ host->status = HOST_S_IDLE;
+
+ init_timer(&host->timer);
+
+ host->timer.function = au1xmmc_poll_event;
+ host->timer.data = (unsigned long) host;
+ host->timer.expires = jiffies + AU1XMMC_DETECT_TIMEOUT;
+
+ tasklet_init(&host->data_task, au1xmmc_tasklet_data,
+ (unsigned long) host);
+
+ tasklet_init(&host->finish_task, au1xmmc_tasklet_finish,
+ (unsigned long) host);
+
+ spin_lock_init(&host->lock);
+
+ if (dma != 0)
+ au1xmmc_init_dma(host);
+
+ au1xmmc_reset_controller(host);
+
+ mmc_add_host(mmc);
+ au1xmmc_hosts[i] = host;
+
+ add_timer(&host->timer);
+
+ printk(KERN_INFO DRIVER_NAME ": MMC Controller %d set up at %8.8X (mode=%s)\n",
+ host->id, host->iobase, dma ? "dma" : "pio");
+ }
+
+ enable_irq(AU1100_SD_IRQ);
+
+ return 0;
+}
+
+static int __devexit au1xmmc_remove(struct platform_device *pdev)
+{
+
+ int i;
+
+ disable_irq(AU1100_SD_IRQ);
+
+ for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) {
+ struct au1xmmc_host *host = au1xmmc_hosts[i];
+ if (!host) continue;
+
+ tasklet_kill(&host->data_task);
+ tasklet_kill(&host->finish_task);
+
+ del_timer_sync(&host->timer);
+ au1xmmc_set_power(host, 0);
+
+ mmc_remove_host(host->mmc);
+
+ au1xxx_dbdma_chan_free(host->tx_chan);
+ au1xxx_dbdma_chan_free(host->rx_chan);
+
+ au_writel(0x0, HOST_ENABLE(host));
+ au_sync();
+ }
+
+ free_irq(AU1100_SD_IRQ, 0);
+ return 0;
+}
+
+static struct platform_driver au1xmmc_driver = {
+ .probe = au1xmmc_probe,
+ .remove = au1xmmc_remove,
+ .suspend = NULL,
+ .resume = NULL,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+static int __init au1xmmc_init(void)
+{
+ return platform_driver_register(&au1xmmc_driver);
+}
+
+static void __exit au1xmmc_exit(void)
+{
+ platform_driver_unregister(&au1xmmc_driver);
+}
+
+module_init(au1xmmc_init);
+module_exit(au1xmmc_exit);
+
+#ifdef MODULE
+MODULE_AUTHOR("Advanced Micro Devices, Inc");
+MODULE_DESCRIPTION("MMC/SD driver for the Alchemy Au1XXX");
+MODULE_LICENSE("GPL");
+#endif
+
diff --git a/drivers/mmc/host/au1xmmc.h b/drivers/mmc/host/au1xmmc.h
new file mode 100644
index 000000000000..341cbdf0baca
--- /dev/null
+++ b/drivers/mmc/host/au1xmmc.h
@@ -0,0 +1,96 @@
+#ifndef _AU1XMMC_H_
+#define _AU1XMMC_H_
+
+/* Hardware definitions */
+
+#define AU1XMMC_DESCRIPTOR_COUNT 1
+#define AU1XMMC_DESCRIPTOR_SIZE 2048
+
+#define AU1XMMC_OCR ( MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 | \
+ MMC_VDD_30_31 | MMC_VDD_31_32 | MMC_VDD_32_33 | \
+ MMC_VDD_33_34 | MMC_VDD_34_35 | MMC_VDD_35_36)
+
+/* Easy access macros */
+
+#define HOST_STATUS(h) ((h)->iobase + SD_STATUS)
+#define HOST_CONFIG(h) ((h)->iobase + SD_CONFIG)
+#define HOST_ENABLE(h) ((h)->iobase + SD_ENABLE)
+#define HOST_TXPORT(h) ((h)->iobase + SD_TXPORT)
+#define HOST_RXPORT(h) ((h)->iobase + SD_RXPORT)
+#define HOST_CMDARG(h) ((h)->iobase + SD_CMDARG)
+#define HOST_BLKSIZE(h) ((h)->iobase + SD_BLKSIZE)
+#define HOST_CMD(h) ((h)->iobase + SD_CMD)
+#define HOST_CONFIG2(h) ((h)->iobase + SD_CONFIG2)
+#define HOST_TIMEOUT(h) ((h)->iobase + SD_TIMEOUT)
+#define HOST_DEBUG(h) ((h)->iobase + SD_DEBUG)
+
+#define DMA_CHANNEL(h) \
+ ( ((h)->flags & HOST_F_XMIT) ? (h)->tx_chan : (h)->rx_chan)
+
+/* This gives us a hard value for the stop command that we can write directly
+ * to the command register
+ */
+
+#define STOP_CMD (SD_CMD_RT_1B|SD_CMD_CT_7|(0xC << SD_CMD_CI_SHIFT)|SD_CMD_GO)
+
+/* This is the set of interrupts that we configure by default */
+
+#if 0
+#define AU1XMMC_INTERRUPTS (SD_CONFIG_SC | SD_CONFIG_DT | SD_CONFIG_DD | \
+ SD_CONFIG_RAT | SD_CONFIG_CR | SD_CONFIG_I)
+#endif
+
+#define AU1XMMC_INTERRUPTS (SD_CONFIG_SC | SD_CONFIG_DT | \
+ SD_CONFIG_RAT | SD_CONFIG_CR | SD_CONFIG_I)
+/* The poll event (looking for insert/remove events runs twice a second */
+#define AU1XMMC_DETECT_TIMEOUT (HZ/2)
+
+struct au1xmmc_host {
+ struct mmc_host *mmc;
+ struct mmc_request *mrq;
+
+ u32 id;
+
+ u32 flags;
+ u32 iobase;
+ u32 clock;
+ u32 bus_width;
+ u32 power_mode;
+
+ int status;
+
+ struct {
+ int len;
+ int dir;
+ } dma;
+
+ struct {
+ int index;
+ int offset;
+ int len;
+ } pio;
+
+ u32 tx_chan;
+ u32 rx_chan;
+
+ struct timer_list timer;
+ struct tasklet_struct finish_task;
+ struct tasklet_struct data_task;
+
+ spinlock_t lock;
+};
+
+/* Status flags used by the host structure */
+
+#define HOST_F_XMIT 0x0001
+#define HOST_F_RECV 0x0002
+#define HOST_F_DMA 0x0010
+#define HOST_F_ACTIVE 0x0100
+#define HOST_F_STOP 0x1000
+
+#define HOST_S_IDLE 0x0001
+#define HOST_S_CMD 0x0002
+#define HOST_S_DATA 0x0003
+#define HOST_S_STOP 0x0004
+
+#endif
diff --git a/drivers/mmc/host/imxmmc.c b/drivers/mmc/host/imxmmc.c
new file mode 100644
index 000000000000..7ee2045acbef
--- /dev/null
+++ b/drivers/mmc/host/imxmmc.c
@@ -0,0 +1,1137 @@
+/*
+ * linux/drivers/mmc/imxmmc.c - Motorola i.MX MMCI driver
+ *
+ * Copyright (C) 2004 Sascha Hauer, Pengutronix <sascha@saschahauer.de>
+ * Copyright (C) 2006 Pavel Pisa, PiKRON <ppisa@pikron.com>
+ *
+ * derived from pxamci.c by Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 2005-04-17 Pavel Pisa <pisa@cmp.felk.cvut.cz>
+ * Changed to conform redesigned i.MX scatter gather DMA interface
+ *
+ * 2005-11-04 Pavel Pisa <pisa@cmp.felk.cvut.cz>
+ * Updated for 2.6.14 kernel
+ *
+ * 2005-12-13 Jay Monkman <jtm@smoothsmoothie.com>
+ * Found and corrected problems in the write path
+ *
+ * 2005-12-30 Pavel Pisa <pisa@cmp.felk.cvut.cz>
+ * The event handling rewritten right way in softirq.
+ * Added many ugly hacks and delays to overcome SDHC
+ * deficiencies
+ *
+ */
+
+#ifdef CONFIG_MMC_DEBUG
+#define DEBUG
+#else
+#undef DEBUG
+#endif
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/delay.h>
+
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/sizes.h>
+#include <asm/arch/mmc.h>
+#include <asm/arch/imx-dma.h>
+
+#include "imxmmc.h"
+
+#define DRIVER_NAME "imx-mmc"
+
+#define IMXMCI_INT_MASK_DEFAULT (INT_MASK_BUF_READY | INT_MASK_DATA_TRAN | \
+ INT_MASK_WRITE_OP_DONE | INT_MASK_END_CMD_RES | \
+ INT_MASK_AUTO_CARD_DETECT | INT_MASK_DAT0_EN | INT_MASK_SDIO)
+
+struct imxmci_host {
+ struct mmc_host *mmc;
+ spinlock_t lock;
+ struct resource *res;
+ int irq;
+ imx_dmach_t dma;
+ unsigned int clkrt;
+ unsigned int cmdat;
+ volatile unsigned int imask;
+ unsigned int power_mode;
+ unsigned int present;
+ struct imxmmc_platform_data *pdata;
+
+ struct mmc_request *req;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ struct timer_list timer;
+ struct tasklet_struct tasklet;
+ unsigned int status_reg;
+ unsigned long pending_events;
+ /* Next to fields are there for CPU driven transfers to overcome SDHC deficiencies */
+ u16 *data_ptr;
+ unsigned int data_cnt;
+ atomic_t stuck_timeout;
+
+ unsigned int dma_nents;
+ unsigned int dma_size;
+ unsigned int dma_dir;
+ int dma_allocated;
+
+ unsigned char actual_bus_width;
+
+ int prev_cmd_code;
+};
+
+#define IMXMCI_PEND_IRQ_b 0
+#define IMXMCI_PEND_DMA_END_b 1
+#define IMXMCI_PEND_DMA_ERR_b 2
+#define IMXMCI_PEND_WAIT_RESP_b 3
+#define IMXMCI_PEND_DMA_DATA_b 4
+#define IMXMCI_PEND_CPU_DATA_b 5
+#define IMXMCI_PEND_CARD_XCHG_b 6
+#define IMXMCI_PEND_SET_INIT_b 7
+#define IMXMCI_PEND_STARTED_b 8
+
+#define IMXMCI_PEND_IRQ_m (1 << IMXMCI_PEND_IRQ_b)
+#define IMXMCI_PEND_DMA_END_m (1 << IMXMCI_PEND_DMA_END_b)
+#define IMXMCI_PEND_DMA_ERR_m (1 << IMXMCI_PEND_DMA_ERR_b)
+#define IMXMCI_PEND_WAIT_RESP_m (1 << IMXMCI_PEND_WAIT_RESP_b)
+#define IMXMCI_PEND_DMA_DATA_m (1 << IMXMCI_PEND_DMA_DATA_b)
+#define IMXMCI_PEND_CPU_DATA_m (1 << IMXMCI_PEND_CPU_DATA_b)
+#define IMXMCI_PEND_CARD_XCHG_m (1 << IMXMCI_PEND_CARD_XCHG_b)
+#define IMXMCI_PEND_SET_INIT_m (1 << IMXMCI_PEND_SET_INIT_b)
+#define IMXMCI_PEND_STARTED_m (1 << IMXMCI_PEND_STARTED_b)
+
+static void imxmci_stop_clock(struct imxmci_host *host)
+{
+ int i = 0;
+ MMC_STR_STP_CLK &= ~STR_STP_CLK_START_CLK;
+ while(i < 0x1000) {
+ if(!(i & 0x7f))
+ MMC_STR_STP_CLK |= STR_STP_CLK_STOP_CLK;
+
+ if(!(MMC_STATUS & STATUS_CARD_BUS_CLK_RUN)) {
+ /* Check twice before cut */
+ if(!(MMC_STATUS & STATUS_CARD_BUS_CLK_RUN))
+ return;
+ }
+
+ i++;
+ }
+ dev_dbg(mmc_dev(host->mmc), "imxmci_stop_clock blocked, no luck\n");
+}
+
+static int imxmci_start_clock(struct imxmci_host *host)
+{
+ unsigned int trials = 0;
+ unsigned int delay_limit = 128;
+ unsigned long flags;
+
+ MMC_STR_STP_CLK &= ~STR_STP_CLK_STOP_CLK;
+
+ clear_bit(IMXMCI_PEND_STARTED_b, &host->pending_events);
+
+ /*
+ * Command start of the clock, this usually succeeds in less
+ * then 6 delay loops, but during card detection (low clockrate)
+ * it takes up to 5000 delay loops and sometimes fails for the first time
+ */
+ MMC_STR_STP_CLK |= STR_STP_CLK_START_CLK;
+
+ do {
+ unsigned int delay = delay_limit;
+
+ while(delay--){
+ if(MMC_STATUS & STATUS_CARD_BUS_CLK_RUN)
+ /* Check twice before cut */
+ if(MMC_STATUS & STATUS_CARD_BUS_CLK_RUN)
+ return 0;
+
+ if(test_bit(IMXMCI_PEND_STARTED_b, &host->pending_events))
+ return 0;
+ }
+
+ local_irq_save(flags);
+ /*
+ * Ensure, that request is not doubled under all possible circumstances.
+ * It is possible, that cock running state is missed, because some other
+ * IRQ or schedule delays this function execution and the clocks has
+ * been already stopped by other means (response processing, SDHC HW)
+ */
+ if(!test_bit(IMXMCI_PEND_STARTED_b, &host->pending_events))
+ MMC_STR_STP_CLK |= STR_STP_CLK_START_CLK;
+ local_irq_restore(flags);
+
+ } while(++trials<256);
+
+ dev_err(mmc_dev(host->mmc), "imxmci_start_clock blocked, no luck\n");
+
+ return -1;
+}
+
+static void imxmci_softreset(void)
+{
+ /* reset sequence */
+ MMC_STR_STP_CLK = 0x8;
+ MMC_STR_STP_CLK = 0xD;
+ MMC_STR_STP_CLK = 0x5;
+ MMC_STR_STP_CLK = 0x5;
+ MMC_STR_STP_CLK = 0x5;
+ MMC_STR_STP_CLK = 0x5;
+ MMC_STR_STP_CLK = 0x5;
+ MMC_STR_STP_CLK = 0x5;
+ MMC_STR_STP_CLK = 0x5;
+ MMC_STR_STP_CLK = 0x5;
+
+ MMC_RES_TO = 0xff;
+ MMC_BLK_LEN = 512;
+ MMC_NOB = 1;
+}
+
+static int imxmci_busy_wait_for_status(struct imxmci_host *host,
+ unsigned int *pstat, unsigned int stat_mask,
+ int timeout, const char *where)
+{
+ int loops=0;
+ while(!(*pstat & stat_mask)) {
+ loops+=2;
+ if(loops >= timeout) {
+ dev_dbg(mmc_dev(host->mmc), "busy wait timeout in %s, STATUS = 0x%x (0x%x)\n",
+ where, *pstat, stat_mask);
+ return -1;
+ }
+ udelay(2);
+ *pstat |= MMC_STATUS;
+ }
+ if(!loops)
+ return 0;
+
+ /* The busy-wait is expected there for clock <8MHz due to SDHC hardware flaws */
+ if(!(stat_mask & STATUS_END_CMD_RESP) || (host->mmc->ios.clock>=8000000))
+ dev_info(mmc_dev(host->mmc), "busy wait for %d usec in %s, STATUS = 0x%x (0x%x)\n",
+ loops, where, *pstat, stat_mask);
+ return loops;
+}
+
+static void imxmci_setup_data(struct imxmci_host *host, struct mmc_data *data)
+{
+ unsigned int nob = data->blocks;
+ unsigned int blksz = data->blksz;
+ unsigned int datasz = nob * blksz;
+ int i;
+
+ if (data->flags & MMC_DATA_STREAM)
+ nob = 0xffff;
+
+ host->data = data;
+ data->bytes_xfered = 0;
+
+ MMC_NOB = nob;
+ MMC_BLK_LEN = blksz;
+
+ /*
+ * DMA cannot be used for small block sizes, we have to use CPU driven transfers otherwise.
+ * We are in big troubles for non-512 byte transfers according to note in the paragraph
+ * 20.6.7 of User Manual anyway, but we need to be able to transfer SCR at least.
+ * The situation is even more complex in reality. The SDHC in not able to handle wll
+ * partial FIFO fills and reads. The length has to be rounded up to burst size multiple.
+ * This is required for SCR read at least.
+ */
+ if (datasz < 512) {
+ host->dma_size = datasz;
+ if (data->flags & MMC_DATA_READ) {
+ host->dma_dir = DMA_FROM_DEVICE;
+
+ /* Hack to enable read SCR */
+ MMC_NOB = 1;
+ MMC_BLK_LEN = 512;
+ } else {
+ host->dma_dir = DMA_TO_DEVICE;
+ }
+
+ /* Convert back to virtual address */
+ host->data_ptr = (u16*)(page_address(data->sg->page) + data->sg->offset);
+ host->data_cnt = 0;
+
+ clear_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events);
+ set_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events);
+
+ return;
+ }
+
+ if (data->flags & MMC_DATA_READ) {
+ host->dma_dir = DMA_FROM_DEVICE;
+ host->dma_nents = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, host->dma_dir);
+
+ imx_dma_setup_sg(host->dma, data->sg, data->sg_len, datasz,
+ host->res->start + MMC_BUFFER_ACCESS_OFS, DMA_MODE_READ);
+
+ /*imx_dma_setup_mem2dev_ccr(host->dma, DMA_MODE_READ, IMX_DMA_WIDTH_16, CCR_REN);*/
+ CCR(host->dma) = CCR_DMOD_LINEAR | CCR_DSIZ_32 | CCR_SMOD_FIFO | CCR_SSIZ_16 | CCR_REN;
+ } else {
+ host->dma_dir = DMA_TO_DEVICE;
+
+ host->dma_nents = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, host->dma_dir);
+
+ imx_dma_setup_sg(host->dma, data->sg, data->sg_len, datasz,
+ host->res->start + MMC_BUFFER_ACCESS_OFS, DMA_MODE_WRITE);
+
+ /*imx_dma_setup_mem2dev_ccr(host->dma, DMA_MODE_WRITE, IMX_DMA_WIDTH_16, CCR_REN);*/
+ CCR(host->dma) = CCR_SMOD_LINEAR | CCR_SSIZ_32 | CCR_DMOD_FIFO | CCR_DSIZ_16 | CCR_REN;
+ }
+
+#if 1 /* This code is there only for consistency checking and can be disabled in future */
+ host->dma_size = 0;
+ for(i=0; i<host->dma_nents; i++)
+ host->dma_size+=data->sg[i].length;
+
+ if (datasz > host->dma_size) {
+ dev_err(mmc_dev(host->mmc), "imxmci_setup_data datasz 0x%x > 0x%x dm_size\n",
+ datasz, host->dma_size);
+ }
+#endif
+
+ host->dma_size = datasz;
+
+ wmb();
+
+ if(host->actual_bus_width == MMC_BUS_WIDTH_4)
+ BLR(host->dma) = 0; /* burst 64 byte read / 64 bytes write */
+ else
+ BLR(host->dma) = 16; /* burst 16 byte read / 16 bytes write */
+
+ RSSR(host->dma) = DMA_REQ_SDHC;
+
+ set_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events);
+ clear_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events);
+
+ /* start DMA engine for read, write is delayed after initial response */
+ if (host->dma_dir == DMA_FROM_DEVICE) {
+ imx_dma_enable(host->dma);
+ }
+}
+
+static void imxmci_start_cmd(struct imxmci_host *host, struct mmc_command *cmd, unsigned int cmdat)
+{
+ unsigned long flags;
+ u32 imask;
+
+ WARN_ON(host->cmd != NULL);
+ host->cmd = cmd;
+
+ /* Ensure, that clock are stopped else command programming and start fails */
+ imxmci_stop_clock(host);
+
+ if (cmd->flags & MMC_RSP_BUSY)
+ cmdat |= CMD_DAT_CONT_BUSY;
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_R1: /* short CRC, OPCODE */
+ case MMC_RSP_R1B:/* short CRC, OPCODE, BUSY */
+ cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R1;
+ break;
+ case MMC_RSP_R2: /* long 136 bit + CRC */
+ cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R2;
+ break;
+ case MMC_RSP_R3: /* short */
+ cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R3;
+ break;
+ default:
+ break;
+ }
+
+ if ( test_and_clear_bit(IMXMCI_PEND_SET_INIT_b, &host->pending_events) )
+ cmdat |= CMD_DAT_CONT_INIT; /* This command needs init */
+
+ if ( host->actual_bus_width == MMC_BUS_WIDTH_4 )
+ cmdat |= CMD_DAT_CONT_BUS_WIDTH_4;
+
+ MMC_CMD = cmd->opcode;
+ MMC_ARGH = cmd->arg >> 16;
+ MMC_ARGL = cmd->arg & 0xffff;
+ MMC_CMD_DAT_CONT = cmdat;
+
+ atomic_set(&host->stuck_timeout, 0);
+ set_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events);
+
+
+ imask = IMXMCI_INT_MASK_DEFAULT;
+ imask &= ~INT_MASK_END_CMD_RES;
+ if ( cmdat & CMD_DAT_CONT_DATA_ENABLE ) {
+ /*imask &= ~INT_MASK_BUF_READY;*/
+ imask &= ~INT_MASK_DATA_TRAN;
+ if ( cmdat & CMD_DAT_CONT_WRITE )
+ imask &= ~INT_MASK_WRITE_OP_DONE;
+ if(test_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events))
+ imask &= ~INT_MASK_BUF_READY;
+ }
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->imask = imask;
+ MMC_INT_MASK = host->imask;
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ dev_dbg(mmc_dev(host->mmc), "CMD%02d (0x%02x) mask set to 0x%04x\n",
+ cmd->opcode, cmd->opcode, imask);
+
+ imxmci_start_clock(host);
+}
+
+static void imxmci_finish_request(struct imxmci_host *host, struct mmc_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->pending_events &= ~(IMXMCI_PEND_WAIT_RESP_m | IMXMCI_PEND_DMA_END_m |
+ IMXMCI_PEND_DMA_DATA_m | IMXMCI_PEND_CPU_DATA_m);
+
+ host->imask = IMXMCI_INT_MASK_DEFAULT;
+ MMC_INT_MASK = host->imask;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if(req && req->cmd)
+ host->prev_cmd_code = req->cmd->opcode;
+
+ host->req = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+ mmc_request_done(host->mmc, req);
+}
+
+static int imxmci_finish_data(struct imxmci_host *host, unsigned int stat)
+{
+ struct mmc_data *data = host->data;
+ int data_error;
+
+ if(test_and_clear_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events)){
+ imx_dma_disable(host->dma);
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_nents,
+ host->dma_dir);
+ }
+
+ if ( stat & STATUS_ERR_MASK ) {
+ dev_dbg(mmc_dev(host->mmc), "request failed. status: 0x%08x\n",stat);
+ if(stat & (STATUS_CRC_READ_ERR | STATUS_CRC_WRITE_ERR))
+ data->error = MMC_ERR_BADCRC;
+ else if(stat & STATUS_TIME_OUT_READ)
+ data->error = MMC_ERR_TIMEOUT;
+ else
+ data->error = MMC_ERR_FAILED;
+ } else {
+ data->bytes_xfered = host->dma_size;
+ }
+
+ data_error = data->error;
+
+ host->data = NULL;
+
+ return data_error;
+}
+
+static int imxmci_cmd_done(struct imxmci_host *host, unsigned int stat)
+{
+ struct mmc_command *cmd = host->cmd;
+ int i;
+ u32 a,b,c;
+ struct mmc_data *data = host->data;
+
+ if (!cmd)
+ return 0;
+
+ host->cmd = NULL;
+
+ if (stat & STATUS_TIME_OUT_RESP) {
+ dev_dbg(mmc_dev(host->mmc), "CMD TIMEOUT\n");
+ cmd->error = MMC_ERR_TIMEOUT;
+ } else if (stat & STATUS_RESP_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
+ dev_dbg(mmc_dev(host->mmc), "cmd crc error\n");
+ cmd->error = MMC_ERR_BADCRC;
+ }
+
+ if(cmd->flags & MMC_RSP_PRESENT) {
+ if(cmd->flags & MMC_RSP_136) {
+ for (i = 0; i < 4; i++) {
+ u32 a = MMC_RES_FIFO & 0xffff;
+ u32 b = MMC_RES_FIFO & 0xffff;
+ cmd->resp[i] = a<<16 | b;
+ }
+ } else {
+ a = MMC_RES_FIFO & 0xffff;
+ b = MMC_RES_FIFO & 0xffff;
+ c = MMC_RES_FIFO & 0xffff;
+ cmd->resp[0] = a<<24 | b<<8 | c>>8;
+ }
+ }
+
+ dev_dbg(mmc_dev(host->mmc), "RESP 0x%08x, 0x%08x, 0x%08x, 0x%08x, error %d\n",
+ cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3], cmd->error);
+
+ if (data && (cmd->error == MMC_ERR_NONE) && !(stat & STATUS_ERR_MASK)) {
+ if (host->req->data->flags & MMC_DATA_WRITE) {
+
+ /* Wait for FIFO to be empty before starting DMA write */
+
+ stat = MMC_STATUS;
+ if(imxmci_busy_wait_for_status(host, &stat,
+ STATUS_APPL_BUFF_FE,
+ 40, "imxmci_cmd_done DMA WR") < 0) {
+ cmd->error = MMC_ERR_FIFO;
+ imxmci_finish_data(host, stat);
+ if(host->req)
+ imxmci_finish_request(host, host->req);
+ dev_warn(mmc_dev(host->mmc), "STATUS = 0x%04x\n",
+ stat);
+ return 0;
+ }
+
+ if(test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events)) {
+ imx_dma_enable(host->dma);
+ }
+ }
+ } else {
+ struct mmc_request *req;
+ imxmci_stop_clock(host);
+ req = host->req;
+
+ if(data)
+ imxmci_finish_data(host, stat);
+
+ if( req ) {
+ imxmci_finish_request(host, req);
+ } else {
+ dev_warn(mmc_dev(host->mmc), "imxmci_cmd_done: no request to finish\n");
+ }
+ }
+
+ return 1;
+}
+
+static int imxmci_data_done(struct imxmci_host *host, unsigned int stat)
+{
+ struct mmc_data *data = host->data;
+ int data_error;
+
+ if (!data)
+ return 0;
+
+ data_error = imxmci_finish_data(host, stat);
+
+ if (host->req->stop) {
+ imxmci_stop_clock(host);
+ imxmci_start_cmd(host, host->req->stop, 0);
+ } else {
+ struct mmc_request *req;
+ req = host->req;
+ if( req ) {
+ imxmci_finish_request(host, req);
+ } else {
+ dev_warn(mmc_dev(host->mmc), "imxmci_data_done: no request to finish\n");
+ }
+ }
+
+ return 1;
+}
+
+static int imxmci_cpu_driven_data(struct imxmci_host *host, unsigned int *pstat)
+{
+ int i;
+ int burst_len;
+ int trans_done = 0;
+ unsigned int stat = *pstat;
+
+ if(host->actual_bus_width != MMC_BUS_WIDTH_4)
+ burst_len = 16;
+ else
+ burst_len = 64;
+
+ /* This is unfortunately required */
+ dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data running STATUS = 0x%x\n",
+ stat);
+
+ udelay(20); /* required for clocks < 8MHz*/
+
+ if(host->dma_dir == DMA_FROM_DEVICE) {
+ imxmci_busy_wait_for_status(host, &stat,
+ STATUS_APPL_BUFF_FF | STATUS_DATA_TRANS_DONE |
+ STATUS_TIME_OUT_READ,
+ 50, "imxmci_cpu_driven_data read");
+
+ while((stat & (STATUS_APPL_BUFF_FF | STATUS_DATA_TRANS_DONE)) &&
+ !(stat & STATUS_TIME_OUT_READ) &&
+ (host->data_cnt < 512)) {
+
+ udelay(20); /* required for clocks < 8MHz*/
+
+ for(i = burst_len; i>=2 ; i-=2) {
+ u16 data;
+ data = MMC_BUFFER_ACCESS;
+ udelay(10); /* required for clocks < 8MHz*/
+ if(host->data_cnt+2 <= host->dma_size) {
+ *(host->data_ptr++) = data;
+ } else {
+ if(host->data_cnt < host->dma_size)
+ *(u8*)(host->data_ptr) = data;
+ }
+ host->data_cnt += 2;
+ }
+
+ stat = MMC_STATUS;
+
+ dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data read %d burst %d STATUS = 0x%x\n",
+ host->data_cnt, burst_len, stat);
+ }
+
+ if((stat & STATUS_DATA_TRANS_DONE) && (host->data_cnt >= 512))
+ trans_done = 1;
+
+ if(host->dma_size & 0x1ff)
+ stat &= ~STATUS_CRC_READ_ERR;
+
+ if(stat & STATUS_TIME_OUT_READ) {
+ dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data read timeout STATUS = 0x%x\n",
+ stat);
+ trans_done = -1;
+ }
+
+ } else {
+ imxmci_busy_wait_for_status(host, &stat,
+ STATUS_APPL_BUFF_FE,
+ 20, "imxmci_cpu_driven_data write");
+
+ while((stat & STATUS_APPL_BUFF_FE) &&
+ (host->data_cnt < host->dma_size)) {
+ if(burst_len >= host->dma_size - host->data_cnt) {
+ burst_len = host->dma_size - host->data_cnt;
+ host->data_cnt = host->dma_size;
+ trans_done = 1;
+ } else {
+ host->data_cnt += burst_len;
+ }
+
+ for(i = burst_len; i>0 ; i-=2)
+ MMC_BUFFER_ACCESS = *(host->data_ptr++);
+
+ stat = MMC_STATUS;
+
+ dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data write burst %d STATUS = 0x%x\n",
+ burst_len, stat);
+ }
+ }
+
+ *pstat = stat;
+
+ return trans_done;
+}
+
+static void imxmci_dma_irq(int dma, void *devid)
+{
+ struct imxmci_host *host = devid;
+ uint32_t stat = MMC_STATUS;
+
+ atomic_set(&host->stuck_timeout, 0);
+ host->status_reg = stat;
+ set_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+}
+
+static irqreturn_t imxmci_irq(int irq, void *devid)
+{
+ struct imxmci_host *host = devid;
+ uint32_t stat = MMC_STATUS;
+ int handled = 1;
+
+ MMC_INT_MASK = host->imask | INT_MASK_SDIO | INT_MASK_AUTO_CARD_DETECT;
+
+ atomic_set(&host->stuck_timeout, 0);
+ host->status_reg = stat;
+ set_bit(IMXMCI_PEND_IRQ_b, &host->pending_events);
+ set_bit(IMXMCI_PEND_STARTED_b, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+
+ return IRQ_RETVAL(handled);;
+}
+
+static void imxmci_tasklet_fnc(unsigned long data)
+{
+ struct imxmci_host *host = (struct imxmci_host *)data;
+ u32 stat;
+ unsigned int data_dir_mask = 0; /* STATUS_WR_CRC_ERROR_CODE_MASK */
+ int timeout = 0;
+
+ if(atomic_read(&host->stuck_timeout) > 4) {
+ char *what;
+ timeout = 1;
+ stat = MMC_STATUS;
+ host->status_reg = stat;
+ if (test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events))
+ if (test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events))
+ what = "RESP+DMA";
+ else
+ what = "RESP";
+ else
+ if (test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events))
+ if(test_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events))
+ what = "DATA";
+ else
+ what = "DMA";
+ else
+ what = "???";
+
+ dev_err(mmc_dev(host->mmc), "%s TIMEOUT, hardware stucked STATUS = 0x%04x IMASK = 0x%04x\n",
+ what, stat, MMC_INT_MASK);
+ dev_err(mmc_dev(host->mmc), "CMD_DAT_CONT = 0x%04x, MMC_BLK_LEN = 0x%04x, MMC_NOB = 0x%04x, DMA_CCR = 0x%08x\n",
+ MMC_CMD_DAT_CONT, MMC_BLK_LEN, MMC_NOB, CCR(host->dma));
+ dev_err(mmc_dev(host->mmc), "CMD%d, prevCMD%d, bus %d-bit, dma_size = 0x%x\n",
+ host->cmd?host->cmd->opcode:0, host->prev_cmd_code, 1<<host->actual_bus_width, host->dma_size);
+ }
+
+ if(!host->present || timeout)
+ host->status_reg = STATUS_TIME_OUT_RESP | STATUS_TIME_OUT_READ |
+ STATUS_CRC_READ_ERR | STATUS_CRC_WRITE_ERR;
+
+ if(test_bit(IMXMCI_PEND_IRQ_b, &host->pending_events) || timeout) {
+ clear_bit(IMXMCI_PEND_IRQ_b, &host->pending_events);
+
+ stat = MMC_STATUS;
+ /*
+ * This is not required in theory, but there is chance to miss some flag
+ * which clears automatically by mask write, FreeScale original code keeps
+ * stat from IRQ time so do I
+ */
+ stat |= host->status_reg;
+
+ if(test_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events))
+ stat &= ~STATUS_CRC_READ_ERR;
+
+ if(test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events)) {
+ imxmci_busy_wait_for_status(host, &stat,
+ STATUS_END_CMD_RESP | STATUS_ERR_MASK,
+ 20, "imxmci_tasklet_fnc resp (ERRATUM #4)");
+ }
+
+ if(stat & (STATUS_END_CMD_RESP | STATUS_ERR_MASK)) {
+ if(test_and_clear_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events))
+ imxmci_cmd_done(host, stat);
+ if(host->data && (stat & STATUS_ERR_MASK))
+ imxmci_data_done(host, stat);
+ }
+
+ if(test_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events)) {
+ stat |= MMC_STATUS;
+ if(imxmci_cpu_driven_data(host, &stat)){
+ if(test_and_clear_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events))
+ imxmci_cmd_done(host, stat);
+ atomic_clear_mask(IMXMCI_PEND_IRQ_m|IMXMCI_PEND_CPU_DATA_m,
+ &host->pending_events);
+ imxmci_data_done(host, stat);
+ }
+ }
+ }
+
+ if(test_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events) &&
+ !test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events)) {
+
+ stat = MMC_STATUS;
+ /* Same as above */
+ stat |= host->status_reg;
+
+ if(host->dma_dir == DMA_TO_DEVICE) {
+ data_dir_mask = STATUS_WRITE_OP_DONE;
+ } else {
+ data_dir_mask = STATUS_DATA_TRANS_DONE;
+ }
+
+ if(stat & data_dir_mask) {
+ clear_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events);
+ imxmci_data_done(host, stat);
+ }
+ }
+
+ if(test_and_clear_bit(IMXMCI_PEND_CARD_XCHG_b, &host->pending_events)) {
+
+ if(host->cmd)
+ imxmci_cmd_done(host, STATUS_TIME_OUT_RESP);
+
+ if(host->data)
+ imxmci_data_done(host, STATUS_TIME_OUT_READ |
+ STATUS_CRC_READ_ERR | STATUS_CRC_WRITE_ERR);
+
+ if(host->req)
+ imxmci_finish_request(host, host->req);
+
+ mmc_detect_change(host->mmc, msecs_to_jiffies(100));
+
+ }
+}
+
+static void imxmci_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+ struct imxmci_host *host = mmc_priv(mmc);
+ unsigned int cmdat;
+
+ WARN_ON(host->req != NULL);
+
+ host->req = req;
+
+ cmdat = 0;
+
+ if (req->data) {
+ imxmci_setup_data(host, req->data);
+
+ cmdat |= CMD_DAT_CONT_DATA_ENABLE;
+
+ if (req->data->flags & MMC_DATA_WRITE)
+ cmdat |= CMD_DAT_CONT_WRITE;
+
+ if (req->data->flags & MMC_DATA_STREAM) {
+ cmdat |= CMD_DAT_CONT_STREAM_BLOCK;
+ }
+ }
+
+ imxmci_start_cmd(host, req->cmd, cmdat);
+}
+
+#define CLK_RATE 19200000
+
+static void imxmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct imxmci_host *host = mmc_priv(mmc);
+ int prescaler;
+
+ if( ios->bus_width==MMC_BUS_WIDTH_4 ) {
+ host->actual_bus_width = MMC_BUS_WIDTH_4;
+ imx_gpio_mode(PB11_PF_SD_DAT3);
+ }else{
+ host->actual_bus_width = MMC_BUS_WIDTH_1;
+ imx_gpio_mode(GPIO_PORTB | GPIO_IN | GPIO_PUEN | 11);
+ }
+
+ if ( host->power_mode != ios->power_mode ) {
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ break;
+ case MMC_POWER_UP:
+ set_bit(IMXMCI_PEND_SET_INIT_b, &host->pending_events);
+ break;
+ case MMC_POWER_ON:
+ break;
+ }
+ host->power_mode = ios->power_mode;
+ }
+
+ if ( ios->clock ) {
+ unsigned int clk;
+
+ /* The prescaler is 5 for PERCLK2 equal to 96MHz
+ * then 96MHz / 5 = 19.2 MHz
+ */
+ clk=imx_get_perclk2();
+ prescaler=(clk+(CLK_RATE*7)/8)/CLK_RATE;
+ switch(prescaler) {
+ case 0:
+ case 1: prescaler = 0;
+ break;
+ case 2: prescaler = 1;
+ break;
+ case 3: prescaler = 2;
+ break;
+ case 4: prescaler = 4;
+ break;
+ default:
+ case 5: prescaler = 5;
+ break;
+ }
+
+ dev_dbg(mmc_dev(host->mmc), "PERCLK2 %d MHz -> prescaler %d\n",
+ clk, prescaler);
+
+ for(clk=0; clk<8; clk++) {
+ int x;
+ x = CLK_RATE / (1<<clk);
+ if( x <= ios->clock)
+ break;
+ }
+
+ MMC_STR_STP_CLK |= STR_STP_CLK_ENABLE; /* enable controller */
+
+ imxmci_stop_clock(host);
+ MMC_CLK_RATE = (prescaler<<3) | clk;
+ /*
+ * Under my understanding, clock should not be started there, because it would
+ * initiate SDHC sequencer and send last or random command into card
+ */
+ /*imxmci_start_clock(host);*/
+
+ dev_dbg(mmc_dev(host->mmc), "MMC_CLK_RATE: 0x%08x\n", MMC_CLK_RATE);
+ } else {
+ imxmci_stop_clock(host);
+ }
+}
+
+static const struct mmc_host_ops imxmci_ops = {
+ .request = imxmci_request,
+ .set_ios = imxmci_set_ios,
+};
+
+static struct resource *platform_device_resource(struct platform_device *dev, unsigned int mask, int nr)
+{
+ int i;
+
+ for (i = 0; i < dev->num_resources; i++)
+ if (dev->resource[i].flags == mask && nr-- == 0)
+ return &dev->resource[i];
+ return NULL;
+}
+
+static int platform_device_irq(struct platform_device *dev, int nr)
+{
+ int i;
+
+ for (i = 0; i < dev->num_resources; i++)
+ if (dev->resource[i].flags == IORESOURCE_IRQ && nr-- == 0)
+ return dev->resource[i].start;
+ return NO_IRQ;
+}
+
+static void imxmci_check_status(unsigned long data)
+{
+ struct imxmci_host *host = (struct imxmci_host *)data;
+
+ if( host->pdata->card_present() != host->present ) {
+ host->present ^= 1;
+ dev_info(mmc_dev(host->mmc), "card %s\n",
+ host->present ? "inserted" : "removed");
+
+ set_bit(IMXMCI_PEND_CARD_XCHG_b, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+ }
+
+ if(test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events) ||
+ test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events)) {
+ atomic_inc(&host->stuck_timeout);
+ if(atomic_read(&host->stuck_timeout) > 4)
+ tasklet_schedule(&host->tasklet);
+ } else {
+ atomic_set(&host->stuck_timeout, 0);
+
+ }
+
+ mod_timer(&host->timer, jiffies + (HZ>>1));
+}
+
+static int imxmci_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct imxmci_host *host = NULL;
+ struct resource *r;
+ int ret = 0, irq;
+
+ printk(KERN_INFO "i.MX mmc driver\n");
+
+ r = platform_device_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_device_irq(pdev, 0);
+ if (!r || irq == NO_IRQ)
+ return -ENXIO;
+
+ r = request_mem_region(r->start, 0x100, "IMXMCI");
+ if (!r)
+ return -EBUSY;
+
+ mmc = mmc_alloc_host(sizeof(struct imxmci_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ mmc->ops = &imxmci_ops;
+ mmc->f_min = 150000;
+ mmc->f_max = CLK_RATE/2;
+ mmc->ocr_avail = MMC_VDD_32_33;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_BYTEBLOCK;
+
+ /* MMC core transfer sizes tunable parameters */
+ mmc->max_hw_segs = 64;
+ mmc->max_phys_segs = 64;
+ mmc->max_seg_size = 64*512; /* default PAGE_CACHE_SIZE */
+ mmc->max_req_size = 64*512; /* default PAGE_CACHE_SIZE */
+ mmc->max_blk_size = 2048;
+ mmc->max_blk_count = 65535;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->dma_allocated = 0;
+ host->pdata = pdev->dev.platform_data;
+
+ spin_lock_init(&host->lock);
+ host->res = r;
+ host->irq = irq;
+
+ imx_gpio_mode(PB8_PF_SD_DAT0);
+ imx_gpio_mode(PB9_PF_SD_DAT1);
+ imx_gpio_mode(PB10_PF_SD_DAT2);
+ /* Configured as GPIO with pull-up to ensure right MCC card mode */
+ /* Switched to PB11_PF_SD_DAT3 if 4 bit bus is configured */
+ imx_gpio_mode(GPIO_PORTB | GPIO_IN | GPIO_PUEN | 11);
+ /* imx_gpio_mode(PB11_PF_SD_DAT3); */
+ imx_gpio_mode(PB12_PF_SD_CLK);
+ imx_gpio_mode(PB13_PF_SD_CMD);
+
+ imxmci_softreset();
+
+ if ( MMC_REV_NO != 0x390 ) {
+ dev_err(mmc_dev(host->mmc), "wrong rev.no. 0x%08x. aborting.\n",
+ MMC_REV_NO);
+ goto out;
+ }
+
+ MMC_READ_TO = 0x2db4; /* recommended in data sheet */
+
+ host->imask = IMXMCI_INT_MASK_DEFAULT;
+ MMC_INT_MASK = host->imask;
+
+
+ if(imx_dma_request_by_prio(&host->dma, DRIVER_NAME, DMA_PRIO_LOW)<0){
+ dev_err(mmc_dev(host->mmc), "imx_dma_request_by_prio failed\n");
+ ret = -EBUSY;
+ goto out;
+ }
+ host->dma_allocated=1;
+ imx_dma_setup_handlers(host->dma, imxmci_dma_irq, NULL, host);
+
+ tasklet_init(&host->tasklet, imxmci_tasklet_fnc, (unsigned long)host);
+ host->status_reg=0;
+ host->pending_events=0;
+
+ ret = request_irq(host->irq, imxmci_irq, 0, DRIVER_NAME, host);
+ if (ret)
+ goto out;
+
+ host->present = host->pdata->card_present();
+ init_timer(&host->timer);
+ host->timer.data = (unsigned long)host;
+ host->timer.function = imxmci_check_status;
+ add_timer(&host->timer);
+ mod_timer(&host->timer, jiffies + (HZ>>1));
+
+ platform_set_drvdata(pdev, mmc);
+
+ mmc_add_host(mmc);
+
+ return 0;
+
+out:
+ if (host) {
+ if(host->dma_allocated){
+ imx_dma_free(host->dma);
+ host->dma_allocated=0;
+ }
+ }
+ if (mmc)
+ mmc_free_host(mmc);
+ release_resource(r);
+ return ret;
+}
+
+static int imxmci_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (mmc) {
+ struct imxmci_host *host = mmc_priv(mmc);
+
+ tasklet_disable(&host->tasklet);
+
+ del_timer_sync(&host->timer);
+ mmc_remove_host(mmc);
+
+ free_irq(host->irq, host);
+ if(host->dma_allocated){
+ imx_dma_free(host->dma);
+ host->dma_allocated=0;
+ }
+
+ tasklet_kill(&host->tasklet);
+
+ release_resource(host->res);
+
+ mmc_free_host(mmc);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int imxmci_suspend(struct platform_device *dev, pm_message_t state)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_suspend_host(mmc, state);
+
+ return ret;
+}
+
+static int imxmci_resume(struct platform_device *dev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ struct imxmci_host *host;
+ int ret = 0;
+
+ if (mmc) {
+ host = mmc_priv(mmc);
+ if(host)
+ set_bit(IMXMCI_PEND_SET_INIT_b, &host->pending_events);
+ ret = mmc_resume_host(mmc);
+ }
+
+ return ret;
+}
+#else
+#define imxmci_suspend NULL
+#define imxmci_resume NULL
+#endif /* CONFIG_PM */
+
+static struct platform_driver imxmci_driver = {
+ .probe = imxmci_probe,
+ .remove = imxmci_remove,
+ .suspend = imxmci_suspend,
+ .resume = imxmci_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ }
+};
+
+static int __init imxmci_init(void)
+{
+ return platform_driver_register(&imxmci_driver);
+}
+
+static void __exit imxmci_exit(void)
+{
+ platform_driver_unregister(&imxmci_driver);
+}
+
+module_init(imxmci_init);
+module_exit(imxmci_exit);
+
+MODULE_DESCRIPTION("i.MX Multimedia Card Interface Driver");
+MODULE_AUTHOR("Sascha Hauer, Pengutronix");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/imxmmc.h b/drivers/mmc/host/imxmmc.h
new file mode 100644
index 000000000000..e5339e334dbb
--- /dev/null
+++ b/drivers/mmc/host/imxmmc.h
@@ -0,0 +1,67 @@
+
+# define __REG16(x) (*((volatile u16 *)IO_ADDRESS(x)))
+
+#define MMC_STR_STP_CLK __REG16(IMX_MMC_BASE + 0x00)
+#define MMC_STATUS __REG16(IMX_MMC_BASE + 0x04)
+#define MMC_CLK_RATE __REG16(IMX_MMC_BASE + 0x08)
+#define MMC_CMD_DAT_CONT __REG16(IMX_MMC_BASE + 0x0C)
+#define MMC_RES_TO __REG16(IMX_MMC_BASE + 0x10)
+#define MMC_READ_TO __REG16(IMX_MMC_BASE + 0x14)
+#define MMC_BLK_LEN __REG16(IMX_MMC_BASE + 0x18)
+#define MMC_NOB __REG16(IMX_MMC_BASE + 0x1C)
+#define MMC_REV_NO __REG16(IMX_MMC_BASE + 0x20)
+#define MMC_INT_MASK __REG16(IMX_MMC_BASE + 0x24)
+#define MMC_CMD __REG16(IMX_MMC_BASE + 0x28)
+#define MMC_ARGH __REG16(IMX_MMC_BASE + 0x2C)
+#define MMC_ARGL __REG16(IMX_MMC_BASE + 0x30)
+#define MMC_RES_FIFO __REG16(IMX_MMC_BASE + 0x34)
+#define MMC_BUFFER_ACCESS __REG16(IMX_MMC_BASE + 0x38)
+#define MMC_BUFFER_ACCESS_OFS 0x38
+
+
+#define STR_STP_CLK_ENDIAN (1<<5)
+#define STR_STP_CLK_RESET (1<<3)
+#define STR_STP_CLK_ENABLE (1<<2)
+#define STR_STP_CLK_START_CLK (1<<1)
+#define STR_STP_CLK_STOP_CLK (1<<0)
+#define STATUS_CARD_PRESENCE (1<<15)
+#define STATUS_SDIO_INT_ACTIVE (1<<14)
+#define STATUS_END_CMD_RESP (1<<13)
+#define STATUS_WRITE_OP_DONE (1<<12)
+#define STATUS_DATA_TRANS_DONE (1<<11)
+#define STATUS_WR_CRC_ERROR_CODE_MASK (3<<10)
+#define STATUS_CARD_BUS_CLK_RUN (1<<8)
+#define STATUS_APPL_BUFF_FF (1<<7)
+#define STATUS_APPL_BUFF_FE (1<<6)
+#define STATUS_RESP_CRC_ERR (1<<5)
+#define STATUS_CRC_READ_ERR (1<<3)
+#define STATUS_CRC_WRITE_ERR (1<<2)
+#define STATUS_TIME_OUT_RESP (1<<1)
+#define STATUS_TIME_OUT_READ (1<<0)
+#define STATUS_ERR_MASK 0x2f
+#define CLK_RATE_PRESCALER(x) ((x) & 0x7)
+#define CLK_RATE_CLK_RATE(x) (((x) & 0x7) << 3)
+#define CMD_DAT_CONT_CMD_RESP_LONG_OFF (1<<12)
+#define CMD_DAT_CONT_STOP_READWAIT (1<<11)
+#define CMD_DAT_CONT_START_READWAIT (1<<10)
+#define CMD_DAT_CONT_BUS_WIDTH_1 (0<<8)
+#define CMD_DAT_CONT_BUS_WIDTH_4 (2<<8)
+#define CMD_DAT_CONT_INIT (1<<7)
+#define CMD_DAT_CONT_BUSY (1<<6)
+#define CMD_DAT_CONT_STREAM_BLOCK (1<<5)
+#define CMD_DAT_CONT_WRITE (1<<4)
+#define CMD_DAT_CONT_DATA_ENABLE (1<<3)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R1 (1)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R2 (2)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R3 (3)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R4 (4)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R5 (5)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R6 (6)
+#define INT_MASK_AUTO_CARD_DETECT (1<<6)
+#define INT_MASK_DAT0_EN (1<<5)
+#define INT_MASK_SDIO (1<<4)
+#define INT_MASK_BUF_READY (1<<3)
+#define INT_MASK_END_CMD_RES (1<<2)
+#define INT_MASK_WRITE_OP_DONE (1<<1)
+#define INT_MASK_DATA_TRAN (1<<0)
+#define INT_ALL (0x7f)
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
new file mode 100644
index 000000000000..d11c2d23ceea
--- /dev/null
+++ b/drivers/mmc/host/mmci.c
@@ -0,0 +1,702 @@
+/*
+ * linux/drivers/mmc/mmci.c - ARM PrimeCell MMCI PL180/1 driver
+ *
+ * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/highmem.h>
+#include <linux/mmc/host.h>
+#include <linux/amba/bus.h>
+#include <linux/clk.h>
+
+#include <asm/cacheflush.h>
+#include <asm/div64.h>
+#include <asm/io.h>
+#include <asm/scatterlist.h>
+#include <asm/sizes.h>
+#include <asm/mach/mmc.h>
+
+#include "mmci.h"
+
+#define DRIVER_NAME "mmci-pl18x"
+
+#define DBG(host,fmt,args...) \
+ pr_debug("%s: %s: " fmt, mmc_hostname(host->mmc), __func__ , args)
+
+static unsigned int fmax = 515633;
+
+static void
+mmci_request_end(struct mmci_host *host, struct mmc_request *mrq)
+{
+ writel(0, host->base + MMCICOMMAND);
+
+ BUG_ON(host->data);
+
+ host->mrq = NULL;
+ host->cmd = NULL;
+
+ if (mrq->data)
+ mrq->data->bytes_xfered = host->data_xfered;
+
+ /*
+ * Need to drop the host lock here; mmc_request_done may call
+ * back into the driver...
+ */
+ spin_unlock(&host->lock);
+ mmc_request_done(host->mmc, mrq);
+ spin_lock(&host->lock);
+}
+
+static void mmci_stop_data(struct mmci_host *host)
+{
+ writel(0, host->base + MMCIDATACTRL);
+ writel(0, host->base + MMCIMASK1);
+ host->data = NULL;
+}
+
+static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
+{
+ unsigned int datactrl, timeout, irqmask;
+ unsigned long long clks;
+ void __iomem *base;
+ int blksz_bits;
+
+ DBG(host, "blksz %04x blks %04x flags %08x\n",
+ data->blksz, data->blocks, data->flags);
+
+ host->data = data;
+ host->size = data->blksz;
+ host->data_xfered = 0;
+
+ mmci_init_sg(host, data);
+
+ clks = (unsigned long long)data->timeout_ns * host->cclk;
+ do_div(clks, 1000000000UL);
+
+ timeout = data->timeout_clks + (unsigned int)clks;
+
+ base = host->base;
+ writel(timeout, base + MMCIDATATIMER);
+ writel(host->size, base + MMCIDATALENGTH);
+
+ blksz_bits = ffs(data->blksz) - 1;
+ BUG_ON(1 << blksz_bits != data->blksz);
+
+ datactrl = MCI_DPSM_ENABLE | blksz_bits << 4;
+ if (data->flags & MMC_DATA_READ) {
+ datactrl |= MCI_DPSM_DIRECTION;
+ irqmask = MCI_RXFIFOHALFFULLMASK;
+
+ /*
+ * If we have less than a FIFOSIZE of bytes to transfer,
+ * trigger a PIO interrupt as soon as any data is available.
+ */
+ if (host->size < MCI_FIFOSIZE)
+ irqmask |= MCI_RXDATAAVLBLMASK;
+ } else {
+ /*
+ * We don't actually need to include "FIFO empty" here
+ * since its implicit in "FIFO half empty".
+ */
+ irqmask = MCI_TXFIFOHALFEMPTYMASK;
+ }
+
+ writel(datactrl, base + MMCIDATACTRL);
+ writel(readl(base + MMCIMASK0) & ~MCI_DATAENDMASK, base + MMCIMASK0);
+ writel(irqmask, base + MMCIMASK1);
+}
+
+static void
+mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c)
+{
+ void __iomem *base = host->base;
+
+ DBG(host, "op %02x arg %08x flags %08x\n",
+ cmd->opcode, cmd->arg, cmd->flags);
+
+ if (readl(base + MMCICOMMAND) & MCI_CPSM_ENABLE) {
+ writel(0, base + MMCICOMMAND);
+ udelay(1);
+ }
+
+ c |= cmd->opcode | MCI_CPSM_ENABLE;
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136)
+ c |= MCI_CPSM_LONGRSP;
+ c |= MCI_CPSM_RESPONSE;
+ }
+ if (/*interrupt*/0)
+ c |= MCI_CPSM_INTERRUPT;
+
+ host->cmd = cmd;
+
+ writel(cmd->arg, base + MMCIARGUMENT);
+ writel(c, base + MMCICOMMAND);
+}
+
+static void
+mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
+ unsigned int status)
+{
+ if (status & MCI_DATABLOCKEND) {
+ host->data_xfered += data->blksz;
+ }
+ if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN|MCI_RXOVERRUN)) {
+ if (status & MCI_DATACRCFAIL)
+ data->error = MMC_ERR_BADCRC;
+ else if (status & MCI_DATATIMEOUT)
+ data->error = MMC_ERR_TIMEOUT;
+ else if (status & (MCI_TXUNDERRUN|MCI_RXOVERRUN))
+ data->error = MMC_ERR_FIFO;
+ status |= MCI_DATAEND;
+
+ /*
+ * We hit an error condition. Ensure that any data
+ * partially written to a page is properly coherent.
+ */
+ if (host->sg_len && data->flags & MMC_DATA_READ)
+ flush_dcache_page(host->sg_ptr->page);
+ }
+ if (status & MCI_DATAEND) {
+ mmci_stop_data(host);
+
+ if (!data->stop) {
+ mmci_request_end(host, data->mrq);
+ } else {
+ mmci_start_command(host, data->stop, 0);
+ }
+ }
+}
+
+static void
+mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd,
+ unsigned int status)
+{
+ void __iomem *base = host->base;
+
+ host->cmd = NULL;
+
+ cmd->resp[0] = readl(base + MMCIRESPONSE0);
+ cmd->resp[1] = readl(base + MMCIRESPONSE1);
+ cmd->resp[2] = readl(base + MMCIRESPONSE2);
+ cmd->resp[3] = readl(base + MMCIRESPONSE3);
+
+ if (status & MCI_CMDTIMEOUT) {
+ cmd->error = MMC_ERR_TIMEOUT;
+ } else if (status & MCI_CMDCRCFAIL && cmd->flags & MMC_RSP_CRC) {
+ cmd->error = MMC_ERR_BADCRC;
+ }
+
+ if (!cmd->data || cmd->error != MMC_ERR_NONE) {
+ if (host->data)
+ mmci_stop_data(host);
+ mmci_request_end(host, cmd->mrq);
+ } else if (!(cmd->data->flags & MMC_DATA_READ)) {
+ mmci_start_data(host, cmd->data);
+ }
+}
+
+static int mmci_pio_read(struct mmci_host *host, char *buffer, unsigned int remain)
+{
+ void __iomem *base = host->base;
+ char *ptr = buffer;
+ u32 status;
+
+ do {
+ int count = host->size - (readl(base + MMCIFIFOCNT) << 2);
+
+ if (count > remain)
+ count = remain;
+
+ if (count <= 0)
+ break;
+
+ readsl(base + MMCIFIFO, ptr, count >> 2);
+
+ ptr += count;
+ remain -= count;
+
+ if (remain == 0)
+ break;
+
+ status = readl(base + MMCISTATUS);
+ } while (status & MCI_RXDATAAVLBL);
+
+ return ptr - buffer;
+}
+
+static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int remain, u32 status)
+{
+ void __iomem *base = host->base;
+ char *ptr = buffer;
+
+ do {
+ unsigned int count, maxcnt;
+
+ maxcnt = status & MCI_TXFIFOEMPTY ? MCI_FIFOSIZE : MCI_FIFOHALFSIZE;
+ count = min(remain, maxcnt);
+
+ writesl(base + MMCIFIFO, ptr, count >> 2);
+
+ ptr += count;
+ remain -= count;
+
+ if (remain == 0)
+ break;
+
+ status = readl(base + MMCISTATUS);
+ } while (status & MCI_TXFIFOHALFEMPTY);
+
+ return ptr - buffer;
+}
+
+/*
+ * PIO data transfer IRQ handler.
+ */
+static irqreturn_t mmci_pio_irq(int irq, void *dev_id)
+{
+ struct mmci_host *host = dev_id;
+ void __iomem *base = host->base;
+ u32 status;
+
+ status = readl(base + MMCISTATUS);
+
+ DBG(host, "irq1 %08x\n", status);
+
+ do {
+ unsigned long flags;
+ unsigned int remain, len;
+ char *buffer;
+
+ /*
+ * For write, we only need to test the half-empty flag
+ * here - if the FIFO is completely empty, then by
+ * definition it is more than half empty.
+ *
+ * For read, check for data available.
+ */
+ if (!(status & (MCI_TXFIFOHALFEMPTY|MCI_RXDATAAVLBL)))
+ break;
+
+ /*
+ * Map the current scatter buffer.
+ */
+ buffer = mmci_kmap_atomic(host, &flags) + host->sg_off;
+ remain = host->sg_ptr->length - host->sg_off;
+
+ len = 0;
+ if (status & MCI_RXACTIVE)
+ len = mmci_pio_read(host, buffer, remain);
+ if (status & MCI_TXACTIVE)
+ len = mmci_pio_write(host, buffer, remain, status);
+
+ /*
+ * Unmap the buffer.
+ */
+ mmci_kunmap_atomic(host, buffer, &flags);
+
+ host->sg_off += len;
+ host->size -= len;
+ remain -= len;
+
+ if (remain)
+ break;
+
+ /*
+ * If we were reading, and we have completed this
+ * page, ensure that the data cache is coherent.
+ */
+ if (status & MCI_RXACTIVE)
+ flush_dcache_page(host->sg_ptr->page);
+
+ if (!mmci_next_sg(host))
+ break;
+
+ status = readl(base + MMCISTATUS);
+ } while (1);
+
+ /*
+ * If we're nearing the end of the read, switch to
+ * "any data available" mode.
+ */
+ if (status & MCI_RXACTIVE && host->size < MCI_FIFOSIZE)
+ writel(MCI_RXDATAAVLBLMASK, base + MMCIMASK1);
+
+ /*
+ * If we run out of data, disable the data IRQs; this
+ * prevents a race where the FIFO becomes empty before
+ * the chip itself has disabled the data path, and
+ * stops us racing with our data end IRQ.
+ */
+ if (host->size == 0) {
+ writel(0, base + MMCIMASK1);
+ writel(readl(base + MMCIMASK0) | MCI_DATAENDMASK, base + MMCIMASK0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Handle completion of command and data transfers.
+ */
+static irqreturn_t mmci_irq(int irq, void *dev_id)
+{
+ struct mmci_host *host = dev_id;
+ u32 status;
+ int ret = 0;
+
+ spin_lock(&host->lock);
+
+ do {
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ status = readl(host->base + MMCISTATUS);
+ status &= readl(host->base + MMCIMASK0);
+ writel(status, host->base + MMCICLEAR);
+
+ DBG(host, "irq0 %08x\n", status);
+
+ data = host->data;
+ if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN|
+ MCI_RXOVERRUN|MCI_DATAEND|MCI_DATABLOCKEND) && data)
+ mmci_data_irq(host, data, status);
+
+ cmd = host->cmd;
+ if (status & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT|MCI_CMDSENT|MCI_CMDRESPEND) && cmd)
+ mmci_cmd_irq(host, cmd, status);
+
+ ret = 1;
+ } while (status);
+
+ spin_unlock(&host->lock);
+
+ return IRQ_RETVAL(ret);
+}
+
+static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct mmci_host *host = mmc_priv(mmc);
+
+ WARN_ON(host->mrq != NULL);
+
+ spin_lock_irq(&host->lock);
+
+ host->mrq = mrq;
+
+ if (mrq->data && mrq->data->flags & MMC_DATA_READ)
+ mmci_start_data(host, mrq->data);
+
+ mmci_start_command(host, mrq->cmd, 0);
+
+ spin_unlock_irq(&host->lock);
+}
+
+static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mmci_host *host = mmc_priv(mmc);
+ u32 clk = 0, pwr = 0;
+
+ if (ios->clock) {
+ if (ios->clock >= host->mclk) {
+ clk = MCI_CLK_BYPASS;
+ host->cclk = host->mclk;
+ } else {
+ clk = host->mclk / (2 * ios->clock) - 1;
+ if (clk > 256)
+ clk = 255;
+ host->cclk = host->mclk / (2 * (clk + 1));
+ }
+ clk |= MCI_CLK_ENABLE;
+ }
+
+ if (host->plat->translate_vdd)
+ pwr |= host->plat->translate_vdd(mmc_dev(mmc), ios->vdd);
+
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ break;
+ case MMC_POWER_UP:
+ pwr |= MCI_PWR_UP;
+ break;
+ case MMC_POWER_ON:
+ pwr |= MCI_PWR_ON;
+ break;
+ }
+
+ if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
+ pwr |= MCI_ROD;
+
+ writel(clk, host->base + MMCICLOCK);
+
+ if (host->pwr != pwr) {
+ host->pwr = pwr;
+ writel(pwr, host->base + MMCIPOWER);
+ }
+}
+
+static const struct mmc_host_ops mmci_ops = {
+ .request = mmci_request,
+ .set_ios = mmci_set_ios,
+};
+
+static void mmci_check_status(unsigned long data)
+{
+ struct mmci_host *host = (struct mmci_host *)data;
+ unsigned int status;
+
+ status = host->plat->status(mmc_dev(host->mmc));
+ if (status ^ host->oldstat)
+ mmc_detect_change(host->mmc, 0);
+
+ host->oldstat = status;
+ mod_timer(&host->timer, jiffies + HZ);
+}
+
+static int mmci_probe(struct amba_device *dev, void *id)
+{
+ struct mmc_platform_data *plat = dev->dev.platform_data;
+ struct mmci_host *host;
+ struct mmc_host *mmc;
+ int ret;
+
+ /* must have platform data */
+ if (!plat) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = amba_request_regions(dev, DRIVER_NAME);
+ if (ret)
+ goto out;
+
+ mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto rel_regions;
+ }
+
+ host = mmc_priv(mmc);
+ host->clk = clk_get(&dev->dev, "MCLK");
+ if (IS_ERR(host->clk)) {
+ ret = PTR_ERR(host->clk);
+ host->clk = NULL;
+ goto host_free;
+ }
+
+ ret = clk_enable(host->clk);
+ if (ret)
+ goto clk_free;
+
+ host->plat = plat;
+ host->mclk = clk_get_rate(host->clk);
+ host->mmc = mmc;
+ host->base = ioremap(dev->res.start, SZ_4K);
+ if (!host->base) {
+ ret = -ENOMEM;
+ goto clk_disable;
+ }
+
+ mmc->ops = &mmci_ops;
+ mmc->f_min = (host->mclk + 511) / 512;
+ mmc->f_max = min(host->mclk, fmax);
+ mmc->ocr_avail = plat->ocr_mask;
+ mmc->caps = MMC_CAP_MULTIWRITE;
+
+ /*
+ * We can do SGIO
+ */
+ mmc->max_hw_segs = 16;
+ mmc->max_phys_segs = NR_SG;
+
+ /*
+ * Since we only have a 16-bit data length register, we must
+ * ensure that we don't exceed 2^16-1 bytes in a single request.
+ */
+ mmc->max_req_size = 65535;
+
+ /*
+ * Set the maximum segment size. Since we aren't doing DMA
+ * (yet) we are only limited by the data length register.
+ */
+ mmc->max_seg_size = mmc->max_req_size;
+
+ /*
+ * Block size can be up to 2048 bytes, but must be a power of two.
+ */
+ mmc->max_blk_size = 2048;
+
+ /*
+ * No limit on the number of blocks transferred.
+ */
+ mmc->max_blk_count = mmc->max_req_size;
+
+ spin_lock_init(&host->lock);
+
+ writel(0, host->base + MMCIMASK0);
+ writel(0, host->base + MMCIMASK1);
+ writel(0xfff, host->base + MMCICLEAR);
+
+ ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
+ if (ret)
+ goto unmap;
+
+ ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED, DRIVER_NAME " (pio)", host);
+ if (ret)
+ goto irq0_free;
+
+ writel(MCI_IRQENABLE, host->base + MMCIMASK0);
+
+ amba_set_drvdata(dev, mmc);
+
+ mmc_add_host(mmc);
+
+ printk(KERN_INFO "%s: MMCI rev %x cfg %02x at 0x%016llx irq %d,%d\n",
+ mmc_hostname(mmc), amba_rev(dev), amba_config(dev),
+ (unsigned long long)dev->res.start, dev->irq[0], dev->irq[1]);
+
+ init_timer(&host->timer);
+ host->timer.data = (unsigned long)host;
+ host->timer.function = mmci_check_status;
+ host->timer.expires = jiffies + HZ;
+ add_timer(&host->timer);
+
+ return 0;
+
+ irq0_free:
+ free_irq(dev->irq[0], host);
+ unmap:
+ iounmap(host->base);
+ clk_disable:
+ clk_disable(host->clk);
+ clk_free:
+ clk_put(host->clk);
+ host_free:
+ mmc_free_host(mmc);
+ rel_regions:
+ amba_release_regions(dev);
+ out:
+ return ret;
+}
+
+static int mmci_remove(struct amba_device *dev)
+{
+ struct mmc_host *mmc = amba_get_drvdata(dev);
+
+ amba_set_drvdata(dev, NULL);
+
+ if (mmc) {
+ struct mmci_host *host = mmc_priv(mmc);
+
+ del_timer_sync(&host->timer);
+
+ mmc_remove_host(mmc);
+
+ writel(0, host->base + MMCIMASK0);
+ writel(0, host->base + MMCIMASK1);
+
+ writel(0, host->base + MMCICOMMAND);
+ writel(0, host->base + MMCIDATACTRL);
+
+ free_irq(dev->irq[0], host);
+ free_irq(dev->irq[1], host);
+
+ iounmap(host->base);
+ clk_disable(host->clk);
+ clk_put(host->clk);
+
+ mmc_free_host(mmc);
+
+ amba_release_regions(dev);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mmci_suspend(struct amba_device *dev, pm_message_t state)
+{
+ struct mmc_host *mmc = amba_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc) {
+ struct mmci_host *host = mmc_priv(mmc);
+
+ ret = mmc_suspend_host(mmc, state);
+ if (ret == 0)
+ writel(0, host->base + MMCIMASK0);
+ }
+
+ return ret;
+}
+
+static int mmci_resume(struct amba_device *dev)
+{
+ struct mmc_host *mmc = amba_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc) {
+ struct mmci_host *host = mmc_priv(mmc);
+
+ writel(MCI_IRQENABLE, host->base + MMCIMASK0);
+
+ ret = mmc_resume_host(mmc);
+ }
+
+ return ret;
+}
+#else
+#define mmci_suspend NULL
+#define mmci_resume NULL
+#endif
+
+static struct amba_id mmci_ids[] = {
+ {
+ .id = 0x00041180,
+ .mask = 0x000fffff,
+ },
+ {
+ .id = 0x00041181,
+ .mask = 0x000fffff,
+ },
+ { 0, 0 },
+};
+
+static struct amba_driver mmci_driver = {
+ .drv = {
+ .name = DRIVER_NAME,
+ },
+ .probe = mmci_probe,
+ .remove = mmci_remove,
+ .suspend = mmci_suspend,
+ .resume = mmci_resume,
+ .id_table = mmci_ids,
+};
+
+static int __init mmci_init(void)
+{
+ return amba_driver_register(&mmci_driver);
+}
+
+static void __exit mmci_exit(void)
+{
+ amba_driver_unregister(&mmci_driver);
+}
+
+module_init(mmci_init);
+module_exit(mmci_exit);
+module_param(fmax, uint, 0444);
+
+MODULE_DESCRIPTION("ARM PrimeCell PL180/181 Multimedia Card Interface driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h
new file mode 100644
index 000000000000..6d7eadc9a678
--- /dev/null
+++ b/drivers/mmc/host/mmci.h
@@ -0,0 +1,179 @@
+/*
+ * linux/drivers/mmc/mmci.h - ARM PrimeCell MMCI PL180/1 driver
+ *
+ * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#define MMCIPOWER 0x000
+#define MCI_PWR_OFF 0x00
+#define MCI_PWR_UP 0x02
+#define MCI_PWR_ON 0x03
+#define MCI_OD (1 << 6)
+#define MCI_ROD (1 << 7)
+
+#define MMCICLOCK 0x004
+#define MCI_CLK_ENABLE (1 << 8)
+#define MCI_CLK_PWRSAVE (1 << 9)
+#define MCI_CLK_BYPASS (1 << 10)
+
+#define MMCIARGUMENT 0x008
+#define MMCICOMMAND 0x00c
+#define MCI_CPSM_RESPONSE (1 << 6)
+#define MCI_CPSM_LONGRSP (1 << 7)
+#define MCI_CPSM_INTERRUPT (1 << 8)
+#define MCI_CPSM_PENDING (1 << 9)
+#define MCI_CPSM_ENABLE (1 << 10)
+
+#define MMCIRESPCMD 0x010
+#define MMCIRESPONSE0 0x014
+#define MMCIRESPONSE1 0x018
+#define MMCIRESPONSE2 0x01c
+#define MMCIRESPONSE3 0x020
+#define MMCIDATATIMER 0x024
+#define MMCIDATALENGTH 0x028
+#define MMCIDATACTRL 0x02c
+#define MCI_DPSM_ENABLE (1 << 0)
+#define MCI_DPSM_DIRECTION (1 << 1)
+#define MCI_DPSM_MODE (1 << 2)
+#define MCI_DPSM_DMAENABLE (1 << 3)
+
+#define MMCIDATACNT 0x030
+#define MMCISTATUS 0x034
+#define MCI_CMDCRCFAIL (1 << 0)
+#define MCI_DATACRCFAIL (1 << 1)
+#define MCI_CMDTIMEOUT (1 << 2)
+#define MCI_DATATIMEOUT (1 << 3)
+#define MCI_TXUNDERRUN (1 << 4)
+#define MCI_RXOVERRUN (1 << 5)
+#define MCI_CMDRESPEND (1 << 6)
+#define MCI_CMDSENT (1 << 7)
+#define MCI_DATAEND (1 << 8)
+#define MCI_DATABLOCKEND (1 << 10)
+#define MCI_CMDACTIVE (1 << 11)
+#define MCI_TXACTIVE (1 << 12)
+#define MCI_RXACTIVE (1 << 13)
+#define MCI_TXFIFOHALFEMPTY (1 << 14)
+#define MCI_RXFIFOHALFFULL (1 << 15)
+#define MCI_TXFIFOFULL (1 << 16)
+#define MCI_RXFIFOFULL (1 << 17)
+#define MCI_TXFIFOEMPTY (1 << 18)
+#define MCI_RXFIFOEMPTY (1 << 19)
+#define MCI_TXDATAAVLBL (1 << 20)
+#define MCI_RXDATAAVLBL (1 << 21)
+
+#define MMCICLEAR 0x038
+#define MCI_CMDCRCFAILCLR (1 << 0)
+#define MCI_DATACRCFAILCLR (1 << 1)
+#define MCI_CMDTIMEOUTCLR (1 << 2)
+#define MCI_DATATIMEOUTCLR (1 << 3)
+#define MCI_TXUNDERRUNCLR (1 << 4)
+#define MCI_RXOVERRUNCLR (1 << 5)
+#define MCI_CMDRESPENDCLR (1 << 6)
+#define MCI_CMDSENTCLR (1 << 7)
+#define MCI_DATAENDCLR (1 << 8)
+#define MCI_DATABLOCKENDCLR (1 << 10)
+
+#define MMCIMASK0 0x03c
+#define MCI_CMDCRCFAILMASK (1 << 0)
+#define MCI_DATACRCFAILMASK (1 << 1)
+#define MCI_CMDTIMEOUTMASK (1 << 2)
+#define MCI_DATATIMEOUTMASK (1 << 3)
+#define MCI_TXUNDERRUNMASK (1 << 4)
+#define MCI_RXOVERRUNMASK (1 << 5)
+#define MCI_CMDRESPENDMASK (1 << 6)
+#define MCI_CMDSENTMASK (1 << 7)
+#define MCI_DATAENDMASK (1 << 8)
+#define MCI_DATABLOCKENDMASK (1 << 10)
+#define MCI_CMDACTIVEMASK (1 << 11)
+#define MCI_TXACTIVEMASK (1 << 12)
+#define MCI_RXACTIVEMASK (1 << 13)
+#define MCI_TXFIFOHALFEMPTYMASK (1 << 14)
+#define MCI_RXFIFOHALFFULLMASK (1 << 15)
+#define MCI_TXFIFOFULLMASK (1 << 16)
+#define MCI_RXFIFOFULLMASK (1 << 17)
+#define MCI_TXFIFOEMPTYMASK (1 << 18)
+#define MCI_RXFIFOEMPTYMASK (1 << 19)
+#define MCI_TXDATAAVLBLMASK (1 << 20)
+#define MCI_RXDATAAVLBLMASK (1 << 21)
+
+#define MMCIMASK1 0x040
+#define MMCIFIFOCNT 0x048
+#define MMCIFIFO 0x080 /* to 0x0bc */
+
+#define MCI_IRQENABLE \
+ (MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \
+ MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \
+ MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_DATABLOCKENDMASK)
+
+/*
+ * The size of the FIFO in bytes.
+ */
+#define MCI_FIFOSIZE (16*4)
+
+#define MCI_FIFOHALFSIZE (MCI_FIFOSIZE / 2)
+
+#define NR_SG 16
+
+struct clk;
+
+struct mmci_host {
+ void __iomem *base;
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+ struct mmc_host *mmc;
+ struct clk *clk;
+
+ unsigned int data_xfered;
+
+ spinlock_t lock;
+
+ unsigned int mclk;
+ unsigned int cclk;
+ u32 pwr;
+ struct mmc_platform_data *plat;
+
+ struct timer_list timer;
+ unsigned int oldstat;
+
+ unsigned int sg_len;
+
+ /* pio stuff */
+ struct scatterlist *sg_ptr;
+ unsigned int sg_off;
+ unsigned int size;
+};
+
+static inline void mmci_init_sg(struct mmci_host *host, struct mmc_data *data)
+{
+ /*
+ * Ideally, we want the higher levels to pass us a scatter list.
+ */
+ host->sg_len = data->sg_len;
+ host->sg_ptr = data->sg;
+ host->sg_off = 0;
+}
+
+static inline int mmci_next_sg(struct mmci_host *host)
+{
+ host->sg_ptr++;
+ host->sg_off = 0;
+ return --host->sg_len;
+}
+
+static inline char *mmci_kmap_atomic(struct mmci_host *host, unsigned long *flags)
+{
+ struct scatterlist *sg = host->sg_ptr;
+
+ local_irq_save(*flags);
+ return kmap_atomic(sg->page, KM_BIO_SRC_IRQ) + sg->offset;
+}
+
+static inline void mmci_kunmap_atomic(struct mmci_host *host, void *buffer, unsigned long *flags)
+{
+ kunmap_atomic(buffer, KM_BIO_SRC_IRQ);
+ local_irq_restore(*flags);
+}
diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c
new file mode 100644
index 000000000000..e851384e51f4
--- /dev/null
+++ b/drivers/mmc/host/omap.c
@@ -0,0 +1,1288 @@
+/*
+ * linux/drivers/media/mmc/omap.c
+ *
+ * Copyright (C) 2004 Nokia Corporation
+ * Written by Tuukka Tikkanen and Juha Yrjölä<juha.yrjola@nokia.com>
+ * Misc hacks here and there by Tony Lindgren <tony@atomide.com>
+ * Other hacks (DMA, SD, etc) by David Brownell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/clk.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/scatterlist.h>
+#include <asm/mach-types.h>
+
+#include <asm/arch/board.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/dma.h>
+#include <asm/arch/mux.h>
+#include <asm/arch/fpga.h>
+#include <asm/arch/tps65010.h>
+
+#define OMAP_MMC_REG_CMD 0x00
+#define OMAP_MMC_REG_ARGL 0x04
+#define OMAP_MMC_REG_ARGH 0x08
+#define OMAP_MMC_REG_CON 0x0c
+#define OMAP_MMC_REG_STAT 0x10
+#define OMAP_MMC_REG_IE 0x14
+#define OMAP_MMC_REG_CTO 0x18
+#define OMAP_MMC_REG_DTO 0x1c
+#define OMAP_MMC_REG_DATA 0x20
+#define OMAP_MMC_REG_BLEN 0x24
+#define OMAP_MMC_REG_NBLK 0x28
+#define OMAP_MMC_REG_BUF 0x2c
+#define OMAP_MMC_REG_SDIO 0x34
+#define OMAP_MMC_REG_REV 0x3c
+#define OMAP_MMC_REG_RSP0 0x40
+#define OMAP_MMC_REG_RSP1 0x44
+#define OMAP_MMC_REG_RSP2 0x48
+#define OMAP_MMC_REG_RSP3 0x4c
+#define OMAP_MMC_REG_RSP4 0x50
+#define OMAP_MMC_REG_RSP5 0x54
+#define OMAP_MMC_REG_RSP6 0x58
+#define OMAP_MMC_REG_RSP7 0x5c
+#define OMAP_MMC_REG_IOSR 0x60
+#define OMAP_MMC_REG_SYSC 0x64
+#define OMAP_MMC_REG_SYSS 0x68
+
+#define OMAP_MMC_STAT_CARD_ERR (1 << 14)
+#define OMAP_MMC_STAT_CARD_IRQ (1 << 13)
+#define OMAP_MMC_STAT_OCR_BUSY (1 << 12)
+#define OMAP_MMC_STAT_A_EMPTY (1 << 11)
+#define OMAP_MMC_STAT_A_FULL (1 << 10)
+#define OMAP_MMC_STAT_CMD_CRC (1 << 8)
+#define OMAP_MMC_STAT_CMD_TOUT (1 << 7)
+#define OMAP_MMC_STAT_DATA_CRC (1 << 6)
+#define OMAP_MMC_STAT_DATA_TOUT (1 << 5)
+#define OMAP_MMC_STAT_END_BUSY (1 << 4)
+#define OMAP_MMC_STAT_END_OF_DATA (1 << 3)
+#define OMAP_MMC_STAT_CARD_BUSY (1 << 2)
+#define OMAP_MMC_STAT_END_OF_CMD (1 << 0)
+
+#define OMAP_MMC_READ(host, reg) __raw_readw((host)->virt_base + OMAP_MMC_REG_##reg)
+#define OMAP_MMC_WRITE(host, reg, val) __raw_writew((val), (host)->virt_base + OMAP_MMC_REG_##reg)
+
+/*
+ * Command types
+ */
+#define OMAP_MMC_CMDTYPE_BC 0
+#define OMAP_MMC_CMDTYPE_BCR 1
+#define OMAP_MMC_CMDTYPE_AC 2
+#define OMAP_MMC_CMDTYPE_ADTC 3
+
+
+#define DRIVER_NAME "mmci-omap"
+
+/* Specifies how often in millisecs to poll for card status changes
+ * when the cover switch is open */
+#define OMAP_MMC_SWITCH_POLL_DELAY 500
+
+static int mmc_omap_enable_poll = 1;
+
+struct mmc_omap_host {
+ int initialized;
+ int suspended;
+ struct mmc_request * mrq;
+ struct mmc_command * cmd;
+ struct mmc_data * data;
+ struct mmc_host * mmc;
+ struct device * dev;
+ unsigned char id; /* 16xx chips have 2 MMC blocks */
+ struct clk * iclk;
+ struct clk * fclk;
+ struct resource *mem_res;
+ void __iomem *virt_base;
+ unsigned int phys_base;
+ int irq;
+ unsigned char bus_mode;
+ unsigned char hw_bus_mode;
+
+ unsigned int sg_len;
+ int sg_idx;
+ u16 * buffer;
+ u32 buffer_bytes_left;
+ u32 total_bytes_left;
+
+ unsigned use_dma:1;
+ unsigned brs_received:1, dma_done:1;
+ unsigned dma_is_read:1;
+ unsigned dma_in_use:1;
+ int dma_ch;
+ spinlock_t dma_lock;
+ struct timer_list dma_timer;
+ unsigned dma_len;
+
+ short power_pin;
+ short wp_pin;
+
+ int switch_pin;
+ struct work_struct switch_work;
+ struct timer_list switch_timer;
+ int switch_last_state;
+};
+
+static inline int
+mmc_omap_cover_is_open(struct mmc_omap_host *host)
+{
+ if (host->switch_pin < 0)
+ return 0;
+ return omap_get_gpio_datain(host->switch_pin);
+}
+
+static ssize_t
+mmc_omap_show_cover_switch(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_omap_host *host = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", mmc_omap_cover_is_open(host) ? "open" :
+ "closed");
+}
+
+static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL);
+
+static ssize_t
+mmc_omap_show_enable_poll(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", mmc_omap_enable_poll);
+}
+
+static ssize_t
+mmc_omap_store_enable_poll(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t size)
+{
+ int enable_poll;
+
+ if (sscanf(buf, "%10d", &enable_poll) != 1)
+ return -EINVAL;
+
+ if (enable_poll != mmc_omap_enable_poll) {
+ struct mmc_omap_host *host = dev_get_drvdata(dev);
+
+ mmc_omap_enable_poll = enable_poll;
+ if (enable_poll && host->switch_pin >= 0)
+ schedule_work(&host->switch_work);
+ }
+ return size;
+}
+
+static DEVICE_ATTR(enable_poll, 0664,
+ mmc_omap_show_enable_poll, mmc_omap_store_enable_poll);
+
+static void
+mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
+{
+ u32 cmdreg;
+ u32 resptype;
+ u32 cmdtype;
+
+ host->cmd = cmd;
+
+ resptype = 0;
+ cmdtype = 0;
+
+ /* Our hardware needs to know exact type */
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ break;
+ case MMC_RSP_R1:
+ case MMC_RSP_R1B:
+ /* resp 1, 1b, 6, 7 */
+ resptype = 1;
+ break;
+ case MMC_RSP_R2:
+ resptype = 2;
+ break;
+ case MMC_RSP_R3:
+ resptype = 3;
+ break;
+ default:
+ dev_err(mmc_dev(host->mmc), "Invalid response type: %04x\n", mmc_resp_type(cmd));
+ break;
+ }
+
+ if (mmc_cmd_type(cmd) == MMC_CMD_ADTC) {
+ cmdtype = OMAP_MMC_CMDTYPE_ADTC;
+ } else if (mmc_cmd_type(cmd) == MMC_CMD_BC) {
+ cmdtype = OMAP_MMC_CMDTYPE_BC;
+ } else if (mmc_cmd_type(cmd) == MMC_CMD_BCR) {
+ cmdtype = OMAP_MMC_CMDTYPE_BCR;
+ } else {
+ cmdtype = OMAP_MMC_CMDTYPE_AC;
+ }
+
+ cmdreg = cmd->opcode | (resptype << 8) | (cmdtype << 12);
+
+ if (host->bus_mode == MMC_BUSMODE_OPENDRAIN)
+ cmdreg |= 1 << 6;
+
+ if (cmd->flags & MMC_RSP_BUSY)
+ cmdreg |= 1 << 11;
+
+ if (host->data && !(host->data->flags & MMC_DATA_WRITE))
+ cmdreg |= 1 << 15;
+
+ clk_enable(host->fclk);
+
+ OMAP_MMC_WRITE(host, CTO, 200);
+ OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff);
+ OMAP_MMC_WRITE(host, ARGH, cmd->arg >> 16);
+ OMAP_MMC_WRITE(host, IE,
+ OMAP_MMC_STAT_A_EMPTY | OMAP_MMC_STAT_A_FULL |
+ OMAP_MMC_STAT_CMD_CRC | OMAP_MMC_STAT_CMD_TOUT |
+ OMAP_MMC_STAT_DATA_CRC | OMAP_MMC_STAT_DATA_TOUT |
+ OMAP_MMC_STAT_END_OF_CMD | OMAP_MMC_STAT_CARD_ERR |
+ OMAP_MMC_STAT_END_OF_DATA);
+ OMAP_MMC_WRITE(host, CMD, cmdreg);
+}
+
+static void
+mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ if (host->dma_in_use) {
+ enum dma_data_direction dma_data_dir;
+
+ BUG_ON(host->dma_ch < 0);
+ if (data->error != MMC_ERR_NONE)
+ omap_stop_dma(host->dma_ch);
+ /* Release DMA channel lazily */
+ mod_timer(&host->dma_timer, jiffies + HZ);
+ if (data->flags & MMC_DATA_WRITE)
+ dma_data_dir = DMA_TO_DEVICE;
+ else
+ dma_data_dir = DMA_FROM_DEVICE;
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len,
+ dma_data_dir);
+ }
+ host->data = NULL;
+ host->sg_len = 0;
+ clk_disable(host->fclk);
+
+ /* NOTE: MMC layer will sometimes poll-wait CMD13 next, issuing
+ * dozens of requests until the card finishes writing data.
+ * It'd be cheaper to just wait till an EOFB interrupt arrives...
+ */
+
+ if (!data->stop) {
+ host->mrq = NULL;
+ mmc_request_done(host->mmc, data->mrq);
+ return;
+ }
+
+ mmc_omap_start_command(host, data->stop);
+}
+
+static void
+mmc_omap_end_of_data(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ unsigned long flags;
+ int done;
+
+ if (!host->dma_in_use) {
+ mmc_omap_xfer_done(host, data);
+ return;
+ }
+ done = 0;
+ spin_lock_irqsave(&host->dma_lock, flags);
+ if (host->dma_done)
+ done = 1;
+ else
+ host->brs_received = 1;
+ spin_unlock_irqrestore(&host->dma_lock, flags);
+ if (done)
+ mmc_omap_xfer_done(host, data);
+}
+
+static void
+mmc_omap_dma_timer(unsigned long data)
+{
+ struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+
+ BUG_ON(host->dma_ch < 0);
+ omap_free_dma(host->dma_ch);
+ host->dma_ch = -1;
+}
+
+static void
+mmc_omap_dma_done(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ unsigned long flags;
+ int done;
+
+ done = 0;
+ spin_lock_irqsave(&host->dma_lock, flags);
+ if (host->brs_received)
+ done = 1;
+ else
+ host->dma_done = 1;
+ spin_unlock_irqrestore(&host->dma_lock, flags);
+ if (done)
+ mmc_omap_xfer_done(host, data);
+}
+
+static void
+mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
+{
+ host->cmd = NULL;
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ /* response type 2 */
+ cmd->resp[3] =
+ OMAP_MMC_READ(host, RSP0) |
+ (OMAP_MMC_READ(host, RSP1) << 16);
+ cmd->resp[2] =
+ OMAP_MMC_READ(host, RSP2) |
+ (OMAP_MMC_READ(host, RSP3) << 16);
+ cmd->resp[1] =
+ OMAP_MMC_READ(host, RSP4) |
+ (OMAP_MMC_READ(host, RSP5) << 16);
+ cmd->resp[0] =
+ OMAP_MMC_READ(host, RSP6) |
+ (OMAP_MMC_READ(host, RSP7) << 16);
+ } else {
+ /* response types 1, 1b, 3, 4, 5, 6 */
+ cmd->resp[0] =
+ OMAP_MMC_READ(host, RSP6) |
+ (OMAP_MMC_READ(host, RSP7) << 16);
+ }
+ }
+
+ if (host->data == NULL || cmd->error != MMC_ERR_NONE) {
+ host->mrq = NULL;
+ clk_disable(host->fclk);
+ mmc_request_done(host->mmc, cmd->mrq);
+ }
+}
+
+/* PIO only */
+static void
+mmc_omap_sg_to_buf(struct mmc_omap_host *host)
+{
+ struct scatterlist *sg;
+
+ sg = host->data->sg + host->sg_idx;
+ host->buffer_bytes_left = sg->length;
+ host->buffer = page_address(sg->page) + sg->offset;
+ if (host->buffer_bytes_left > host->total_bytes_left)
+ host->buffer_bytes_left = host->total_bytes_left;
+}
+
+/* PIO only */
+static void
+mmc_omap_xfer_data(struct mmc_omap_host *host, int write)
+{
+ int n;
+
+ if (host->buffer_bytes_left == 0) {
+ host->sg_idx++;
+ BUG_ON(host->sg_idx == host->sg_len);
+ mmc_omap_sg_to_buf(host);
+ }
+ n = 64;
+ if (n > host->buffer_bytes_left)
+ n = host->buffer_bytes_left;
+ host->buffer_bytes_left -= n;
+ host->total_bytes_left -= n;
+ host->data->bytes_xfered += n;
+
+ if (write) {
+ __raw_writesw(host->virt_base + OMAP_MMC_REG_DATA, host->buffer, n);
+ } else {
+ __raw_readsw(host->virt_base + OMAP_MMC_REG_DATA, host->buffer, n);
+ }
+}
+
+static inline void mmc_omap_report_irq(u16 status)
+{
+ static const char *mmc_omap_status_bits[] = {
+ "EOC", "CD", "CB", "BRS", "EOFB", "DTO", "DCRC", "CTO",
+ "CCRC", "CRW", "AF", "AE", "OCRB", "CIRQ", "CERR"
+ };
+ int i, c = 0;
+
+ for (i = 0; i < ARRAY_SIZE(mmc_omap_status_bits); i++)
+ if (status & (1 << i)) {
+ if (c)
+ printk(" ");
+ printk("%s", mmc_omap_status_bits[i]);
+ c++;
+ }
+}
+
+static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
+{
+ struct mmc_omap_host * host = (struct mmc_omap_host *)dev_id;
+ u16 status;
+ int end_command;
+ int end_transfer;
+ int transfer_error;
+
+ if (host->cmd == NULL && host->data == NULL) {
+ status = OMAP_MMC_READ(host, STAT);
+ dev_info(mmc_dev(host->mmc),"spurious irq 0x%04x\n", status);
+ if (status != 0) {
+ OMAP_MMC_WRITE(host, STAT, status);
+ OMAP_MMC_WRITE(host, IE, 0);
+ }
+ return IRQ_HANDLED;
+ }
+
+ end_command = 0;
+ end_transfer = 0;
+ transfer_error = 0;
+
+ while ((status = OMAP_MMC_READ(host, STAT)) != 0) {
+ OMAP_MMC_WRITE(host, STAT, status);
+#ifdef CONFIG_MMC_DEBUG
+ dev_dbg(mmc_dev(host->mmc), "MMC IRQ %04x (CMD %d): ",
+ status, host->cmd != NULL ? host->cmd->opcode : -1);
+ mmc_omap_report_irq(status);
+ printk("\n");
+#endif
+ if (host->total_bytes_left) {
+ if ((status & OMAP_MMC_STAT_A_FULL) ||
+ (status & OMAP_MMC_STAT_END_OF_DATA))
+ mmc_omap_xfer_data(host, 0);
+ if (status & OMAP_MMC_STAT_A_EMPTY)
+ mmc_omap_xfer_data(host, 1);
+ }
+
+ if (status & OMAP_MMC_STAT_END_OF_DATA) {
+ end_transfer = 1;
+ }
+
+ if (status & OMAP_MMC_STAT_DATA_TOUT) {
+ dev_dbg(mmc_dev(host->mmc), "data timeout\n");
+ if (host->data) {
+ host->data->error |= MMC_ERR_TIMEOUT;
+ transfer_error = 1;
+ }
+ }
+
+ if (status & OMAP_MMC_STAT_DATA_CRC) {
+ if (host->data) {
+ host->data->error |= MMC_ERR_BADCRC;
+ dev_dbg(mmc_dev(host->mmc),
+ "data CRC error, bytes left %d\n",
+ host->total_bytes_left);
+ transfer_error = 1;
+ } else {
+ dev_dbg(mmc_dev(host->mmc), "data CRC error\n");
+ }
+ }
+
+ if (status & OMAP_MMC_STAT_CMD_TOUT) {
+ /* Timeouts are routine with some commands */
+ if (host->cmd) {
+ if (host->cmd->opcode != MMC_ALL_SEND_CID &&
+ host->cmd->opcode !=
+ MMC_SEND_OP_COND &&
+ host->cmd->opcode !=
+ MMC_APP_CMD &&
+ !mmc_omap_cover_is_open(host))
+ dev_err(mmc_dev(host->mmc),
+ "command timeout, CMD %d\n",
+ host->cmd->opcode);
+ host->cmd->error = MMC_ERR_TIMEOUT;
+ end_command = 1;
+ }
+ }
+
+ if (status & OMAP_MMC_STAT_CMD_CRC) {
+ if (host->cmd) {
+ dev_err(mmc_dev(host->mmc),
+ "command CRC error (CMD%d, arg 0x%08x)\n",
+ host->cmd->opcode, host->cmd->arg);
+ host->cmd->error = MMC_ERR_BADCRC;
+ end_command = 1;
+ } else
+ dev_err(mmc_dev(host->mmc),
+ "command CRC error without cmd?\n");
+ }
+
+ if (status & OMAP_MMC_STAT_CARD_ERR) {
+ if (host->cmd && host->cmd->opcode == MMC_STOP_TRANSMISSION) {
+ u32 response = OMAP_MMC_READ(host, RSP6)
+ | (OMAP_MMC_READ(host, RSP7) << 16);
+ /* STOP sometimes sets must-ignore bits */
+ if (!(response & (R1_CC_ERROR
+ | R1_ILLEGAL_COMMAND
+ | R1_COM_CRC_ERROR))) {
+ end_command = 1;
+ continue;
+ }
+ }
+
+ dev_dbg(mmc_dev(host->mmc), "card status error (CMD%d)\n",
+ host->cmd->opcode);
+ if (host->cmd) {
+ host->cmd->error = MMC_ERR_FAILED;
+ end_command = 1;
+ }
+ if (host->data) {
+ host->data->error = MMC_ERR_FAILED;
+ transfer_error = 1;
+ }
+ }
+
+ /*
+ * NOTE: On 1610 the END_OF_CMD may come too early when
+ * starting a write
+ */
+ if ((status & OMAP_MMC_STAT_END_OF_CMD) &&
+ (!(status & OMAP_MMC_STAT_A_EMPTY))) {
+ end_command = 1;
+ }
+ }
+
+ if (end_command) {
+ mmc_omap_cmd_done(host, host->cmd);
+ }
+ if (transfer_error)
+ mmc_omap_xfer_done(host, host->data);
+ else if (end_transfer)
+ mmc_omap_end_of_data(host, host->data);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mmc_omap_switch_irq(int irq, void *dev_id)
+{
+ struct mmc_omap_host *host = (struct mmc_omap_host *) dev_id;
+
+ schedule_work(&host->switch_work);
+
+ return IRQ_HANDLED;
+}
+
+static void mmc_omap_switch_timer(unsigned long arg)
+{
+ struct mmc_omap_host *host = (struct mmc_omap_host *) arg;
+
+ schedule_work(&host->switch_work);
+}
+
+static void mmc_omap_switch_handler(struct work_struct *work)
+{
+ struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, switch_work);
+ struct mmc_card *card;
+ static int complained = 0;
+ int cards = 0, cover_open;
+
+ if (host->switch_pin == -1)
+ return;
+ cover_open = mmc_omap_cover_is_open(host);
+ if (cover_open != host->switch_last_state) {
+ kobject_uevent(&host->dev->kobj, KOBJ_CHANGE);
+ host->switch_last_state = cover_open;
+ }
+ mmc_detect_change(host->mmc, 0);
+ list_for_each_entry(card, &host->mmc->cards, node) {
+ if (mmc_card_present(card))
+ cards++;
+ }
+ if (mmc_omap_cover_is_open(host)) {
+ if (!complained) {
+ dev_info(mmc_dev(host->mmc), "cover is open");
+ complained = 1;
+ }
+ if (mmc_omap_enable_poll)
+ mod_timer(&host->switch_timer, jiffies +
+ msecs_to_jiffies(OMAP_MMC_SWITCH_POLL_DELAY));
+ } else {
+ complained = 0;
+ }
+}
+
+/* Prepare to transfer the next segment of a scatterlist */
+static void
+mmc_omap_prepare_dma(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ int dma_ch = host->dma_ch;
+ unsigned long data_addr;
+ u16 buf, frame;
+ u32 count;
+ struct scatterlist *sg = &data->sg[host->sg_idx];
+ int src_port = 0;
+ int dst_port = 0;
+ int sync_dev = 0;
+
+ data_addr = host->phys_base + OMAP_MMC_REG_DATA;
+ frame = data->blksz;
+ count = sg_dma_len(sg);
+
+ if ((data->blocks == 1) && (count > data->blksz))
+ count = frame;
+
+ host->dma_len = count;
+
+ /* FIFO is 16x2 bytes on 15xx, and 32x2 bytes on 16xx and 24xx.
+ * Use 16 or 32 word frames when the blocksize is at least that large.
+ * Blocksize is usually 512 bytes; but not for some SD reads.
+ */
+ if (cpu_is_omap15xx() && frame > 32)
+ frame = 32;
+ else if (frame > 64)
+ frame = 64;
+ count /= frame;
+ frame >>= 1;
+
+ if (!(data->flags & MMC_DATA_WRITE)) {
+ buf = 0x800f | ((frame - 1) << 8);
+
+ if (cpu_class_is_omap1()) {
+ src_port = OMAP_DMA_PORT_TIPB;
+ dst_port = OMAP_DMA_PORT_EMIFF;
+ }
+ if (cpu_is_omap24xx())
+ sync_dev = OMAP24XX_DMA_MMC1_RX;
+
+ omap_set_dma_src_params(dma_ch, src_port,
+ OMAP_DMA_AMODE_CONSTANT,
+ data_addr, 0, 0);
+ omap_set_dma_dest_params(dma_ch, dst_port,
+ OMAP_DMA_AMODE_POST_INC,
+ sg_dma_address(sg), 0, 0);
+ omap_set_dma_dest_data_pack(dma_ch, 1);
+ omap_set_dma_dest_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_4);
+ } else {
+ buf = 0x0f80 | ((frame - 1) << 0);
+
+ if (cpu_class_is_omap1()) {
+ src_port = OMAP_DMA_PORT_EMIFF;
+ dst_port = OMAP_DMA_PORT_TIPB;
+ }
+ if (cpu_is_omap24xx())
+ sync_dev = OMAP24XX_DMA_MMC1_TX;
+
+ omap_set_dma_dest_params(dma_ch, dst_port,
+ OMAP_DMA_AMODE_CONSTANT,
+ data_addr, 0, 0);
+ omap_set_dma_src_params(dma_ch, src_port,
+ OMAP_DMA_AMODE_POST_INC,
+ sg_dma_address(sg), 0, 0);
+ omap_set_dma_src_data_pack(dma_ch, 1);
+ omap_set_dma_src_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_4);
+ }
+
+ /* Max limit for DMA frame count is 0xffff */
+ BUG_ON(count > 0xffff);
+
+ OMAP_MMC_WRITE(host, BUF, buf);
+ omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S16,
+ frame, count, OMAP_DMA_SYNC_FRAME,
+ sync_dev, 0);
+}
+
+/* A scatterlist segment completed */
+static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)
+{
+ struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+ struct mmc_data *mmcdat = host->data;
+
+ if (unlikely(host->dma_ch < 0)) {
+ dev_err(mmc_dev(host->mmc),
+ "DMA callback while DMA not enabled\n");
+ return;
+ }
+ /* FIXME: We really should do something to _handle_ the errors */
+ if (ch_status & OMAP1_DMA_TOUT_IRQ) {
+ dev_err(mmc_dev(host->mmc),"DMA timeout\n");
+ return;
+ }
+ if (ch_status & OMAP_DMA_DROP_IRQ) {
+ dev_err(mmc_dev(host->mmc), "DMA sync error\n");
+ return;
+ }
+ if (!(ch_status & OMAP_DMA_BLOCK_IRQ)) {
+ return;
+ }
+ mmcdat->bytes_xfered += host->dma_len;
+ host->sg_idx++;
+ if (host->sg_idx < host->sg_len) {
+ mmc_omap_prepare_dma(host, host->data);
+ omap_start_dma(host->dma_ch);
+ } else
+ mmc_omap_dma_done(host, host->data);
+}
+
+static int mmc_omap_get_dma_channel(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ const char *dev_name;
+ int sync_dev, dma_ch, is_read, r;
+
+ is_read = !(data->flags & MMC_DATA_WRITE);
+ del_timer_sync(&host->dma_timer);
+ if (host->dma_ch >= 0) {
+ if (is_read == host->dma_is_read)
+ return 0;
+ omap_free_dma(host->dma_ch);
+ host->dma_ch = -1;
+ }
+
+ if (is_read) {
+ if (host->id == 1) {
+ sync_dev = OMAP_DMA_MMC_RX;
+ dev_name = "MMC1 read";
+ } else {
+ sync_dev = OMAP_DMA_MMC2_RX;
+ dev_name = "MMC2 read";
+ }
+ } else {
+ if (host->id == 1) {
+ sync_dev = OMAP_DMA_MMC_TX;
+ dev_name = "MMC1 write";
+ } else {
+ sync_dev = OMAP_DMA_MMC2_TX;
+ dev_name = "MMC2 write";
+ }
+ }
+ r = omap_request_dma(sync_dev, dev_name, mmc_omap_dma_cb,
+ host, &dma_ch);
+ if (r != 0) {
+ dev_dbg(mmc_dev(host->mmc), "omap_request_dma() failed with %d\n", r);
+ return r;
+ }
+ host->dma_ch = dma_ch;
+ host->dma_is_read = is_read;
+
+ return 0;
+}
+
+static inline void set_cmd_timeout(struct mmc_omap_host *host, struct mmc_request *req)
+{
+ u16 reg;
+
+ reg = OMAP_MMC_READ(host, SDIO);
+ reg &= ~(1 << 5);
+ OMAP_MMC_WRITE(host, SDIO, reg);
+ /* Set maximum timeout */
+ OMAP_MMC_WRITE(host, CTO, 0xff);
+}
+
+static inline void set_data_timeout(struct mmc_omap_host *host, struct mmc_request *req)
+{
+ int timeout;
+ u16 reg;
+
+ /* Convert ns to clock cycles by assuming 20MHz frequency
+ * 1 cycle at 20MHz = 500 ns
+ */
+ timeout = req->data->timeout_clks + req->data->timeout_ns / 500;
+
+ /* Check if we need to use timeout multiplier register */
+ reg = OMAP_MMC_READ(host, SDIO);
+ if (timeout > 0xffff) {
+ reg |= (1 << 5);
+ timeout /= 1024;
+ } else
+ reg &= ~(1 << 5);
+ OMAP_MMC_WRITE(host, SDIO, reg);
+ OMAP_MMC_WRITE(host, DTO, timeout);
+}
+
+static void
+mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
+{
+ struct mmc_data *data = req->data;
+ int i, use_dma, block_size;
+ unsigned sg_len;
+
+ host->data = data;
+ if (data == NULL) {
+ OMAP_MMC_WRITE(host, BLEN, 0);
+ OMAP_MMC_WRITE(host, NBLK, 0);
+ OMAP_MMC_WRITE(host, BUF, 0);
+ host->dma_in_use = 0;
+ set_cmd_timeout(host, req);
+ return;
+ }
+
+ block_size = data->blksz;
+
+ OMAP_MMC_WRITE(host, NBLK, data->blocks - 1);
+ OMAP_MMC_WRITE(host, BLEN, block_size - 1);
+ set_data_timeout(host, req);
+
+ /* cope with calling layer confusion; it issues "single
+ * block" writes using multi-block scatterlists.
+ */
+ sg_len = (data->blocks == 1) ? 1 : data->sg_len;
+
+ /* Only do DMA for entire blocks */
+ use_dma = host->use_dma;
+ if (use_dma) {
+ for (i = 0; i < sg_len; i++) {
+ if ((data->sg[i].length % block_size) != 0) {
+ use_dma = 0;
+ break;
+ }
+ }
+ }
+
+ host->sg_idx = 0;
+ if (use_dma) {
+ if (mmc_omap_get_dma_channel(host, data) == 0) {
+ enum dma_data_direction dma_data_dir;
+
+ if (data->flags & MMC_DATA_WRITE)
+ dma_data_dir = DMA_TO_DEVICE;
+ else
+ dma_data_dir = DMA_FROM_DEVICE;
+
+ host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ sg_len, dma_data_dir);
+ host->total_bytes_left = 0;
+ mmc_omap_prepare_dma(host, req->data);
+ host->brs_received = 0;
+ host->dma_done = 0;
+ host->dma_in_use = 1;
+ } else
+ use_dma = 0;
+ }
+
+ /* Revert to PIO? */
+ if (!use_dma) {
+ OMAP_MMC_WRITE(host, BUF, 0x1f1f);
+ host->total_bytes_left = data->blocks * block_size;
+ host->sg_len = sg_len;
+ mmc_omap_sg_to_buf(host);
+ host->dma_in_use = 0;
+ }
+}
+
+static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+ struct mmc_omap_host *host = mmc_priv(mmc);
+
+ WARN_ON(host->mrq != NULL);
+
+ host->mrq = req;
+
+ /* only touch fifo AFTER the controller readies it */
+ mmc_omap_prepare_data(host, req);
+ mmc_omap_start_command(host, req->cmd);
+ if (host->dma_in_use)
+ omap_start_dma(host->dma_ch);
+}
+
+static void innovator_fpga_socket_power(int on)
+{
+#if defined(CONFIG_MACH_OMAP_INNOVATOR) && defined(CONFIG_ARCH_OMAP15XX)
+ if (on) {
+ fpga_write(fpga_read(OMAP1510_FPGA_POWER) | (1 << 3),
+ OMAP1510_FPGA_POWER);
+ } else {
+ fpga_write(fpga_read(OMAP1510_FPGA_POWER) & ~(1 << 3),
+ OMAP1510_FPGA_POWER);
+ }
+#endif
+}
+
+/*
+ * Turn the socket power on/off. Innovator uses FPGA, most boards
+ * probably use GPIO.
+ */
+static void mmc_omap_power(struct mmc_omap_host *host, int on)
+{
+ if (on) {
+ if (machine_is_omap_innovator())
+ innovator_fpga_socket_power(1);
+ else if (machine_is_omap_h2())
+ tps65010_set_gpio_out_value(GPIO3, HIGH);
+ else if (machine_is_omap_h3())
+ /* GPIO 4 of TPS65010 sends SD_EN signal */
+ tps65010_set_gpio_out_value(GPIO4, HIGH);
+ else if (cpu_is_omap24xx()) {
+ u16 reg = OMAP_MMC_READ(host, CON);
+ OMAP_MMC_WRITE(host, CON, reg | (1 << 11));
+ } else
+ if (host->power_pin >= 0)
+ omap_set_gpio_dataout(host->power_pin, 1);
+ } else {
+ if (machine_is_omap_innovator())
+ innovator_fpga_socket_power(0);
+ else if (machine_is_omap_h2())
+ tps65010_set_gpio_out_value(GPIO3, LOW);
+ else if (machine_is_omap_h3())
+ tps65010_set_gpio_out_value(GPIO4, LOW);
+ else if (cpu_is_omap24xx()) {
+ u16 reg = OMAP_MMC_READ(host, CON);
+ OMAP_MMC_WRITE(host, CON, reg & ~(1 << 11));
+ } else
+ if (host->power_pin >= 0)
+ omap_set_gpio_dataout(host->power_pin, 0);
+ }
+}
+
+static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mmc_omap_host *host = mmc_priv(mmc);
+ int dsor;
+ int realclock, i;
+
+ realclock = ios->clock;
+
+ if (ios->clock == 0)
+ dsor = 0;
+ else {
+ int func_clk_rate = clk_get_rate(host->fclk);
+
+ dsor = func_clk_rate / realclock;
+ if (dsor < 1)
+ dsor = 1;
+
+ if (func_clk_rate / dsor > realclock)
+ dsor++;
+
+ if (dsor > 250)
+ dsor = 250;
+ dsor++;
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ dsor |= 1 << 15;
+ }
+
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ mmc_omap_power(host, 0);
+ break;
+ case MMC_POWER_UP:
+ case MMC_POWER_ON:
+ mmc_omap_power(host, 1);
+ dsor |= 1 << 11;
+ break;
+ }
+
+ host->bus_mode = ios->bus_mode;
+ host->hw_bus_mode = host->bus_mode;
+
+ clk_enable(host->fclk);
+
+ /* On insanely high arm_per frequencies something sometimes
+ * goes somehow out of sync, and the POW bit is not being set,
+ * which results in the while loop below getting stuck.
+ * Writing to the CON register twice seems to do the trick. */
+ for (i = 0; i < 2; i++)
+ OMAP_MMC_WRITE(host, CON, dsor);
+ if (ios->power_mode == MMC_POWER_UP) {
+ /* Send clock cycles, poll completion */
+ OMAP_MMC_WRITE(host, IE, 0);
+ OMAP_MMC_WRITE(host, STAT, 0xffff);
+ OMAP_MMC_WRITE(host, CMD, 1 << 7);
+ while ((OMAP_MMC_READ(host, STAT) & 1) == 0);
+ OMAP_MMC_WRITE(host, STAT, 1);
+ }
+ clk_disable(host->fclk);
+}
+
+static int mmc_omap_get_ro(struct mmc_host *mmc)
+{
+ struct mmc_omap_host *host = mmc_priv(mmc);
+
+ return host->wp_pin && omap_get_gpio_datain(host->wp_pin);
+}
+
+static const struct mmc_host_ops mmc_omap_ops = {
+ .request = mmc_omap_request,
+ .set_ios = mmc_omap_set_ios,
+ .get_ro = mmc_omap_get_ro,
+};
+
+static int __init mmc_omap_probe(struct platform_device *pdev)
+{
+ struct omap_mmc_conf *minfo = pdev->dev.platform_data;
+ struct mmc_host *mmc;
+ struct mmc_omap_host *host = NULL;
+ struct resource *res;
+ int ret = 0;
+ int irq;
+
+ if (minfo == NULL) {
+ dev_err(&pdev->dev, "platform data missing\n");
+ return -ENXIO;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (res == NULL || irq < 0)
+ return -ENXIO;
+
+ res = request_mem_region(res->start, res->end - res->start + 1,
+ pdev->name);
+ if (res == NULL)
+ return -EBUSY;
+
+ mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev);
+ if (mmc == NULL) {
+ ret = -ENOMEM;
+ goto err_free_mem_region;
+ }
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+
+ spin_lock_init(&host->dma_lock);
+ init_timer(&host->dma_timer);
+ host->dma_timer.function = mmc_omap_dma_timer;
+ host->dma_timer.data = (unsigned long) host;
+
+ host->id = pdev->id;
+ host->mem_res = res;
+ host->irq = irq;
+
+ if (cpu_is_omap24xx()) {
+ host->iclk = clk_get(&pdev->dev, "mmc_ick");
+ if (IS_ERR(host->iclk))
+ goto err_free_mmc_host;
+ clk_enable(host->iclk);
+ }
+
+ if (!cpu_is_omap24xx())
+ host->fclk = clk_get(&pdev->dev, "mmc_ck");
+ else
+ host->fclk = clk_get(&pdev->dev, "mmc_fck");
+
+ if (IS_ERR(host->fclk)) {
+ ret = PTR_ERR(host->fclk);
+ goto err_free_iclk;
+ }
+
+ /* REVISIT:
+ * Also, use minfo->cover to decide how to manage
+ * the card detect sensing.
+ */
+ host->power_pin = minfo->power_pin;
+ host->switch_pin = minfo->switch_pin;
+ host->wp_pin = minfo->wp_pin;
+ host->use_dma = 1;
+ host->dma_ch = -1;
+
+ host->irq = irq;
+ host->phys_base = host->mem_res->start;
+ host->virt_base = (void __iomem *) IO_ADDRESS(host->phys_base);
+
+ mmc->ops = &mmc_omap_ops;
+ mmc->f_min = 400000;
+ mmc->f_max = 24000000;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK;
+
+ if (minfo->wire4)
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+ /* Use scatterlist DMA to reduce per-transfer costs.
+ * NOTE max_seg_size assumption that small blocks aren't
+ * normally used (except e.g. for reading SD registers).
+ */
+ mmc->max_phys_segs = 32;
+ mmc->max_hw_segs = 32;
+ mmc->max_blk_size = 2048; /* BLEN is 11 bits (+1) */
+ mmc->max_blk_count = 2048; /* NBLK is 11 bits (+1) */
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+ mmc->max_seg_size = mmc->max_req_size;
+
+ if (host->power_pin >= 0) {
+ if ((ret = omap_request_gpio(host->power_pin)) != 0) {
+ dev_err(mmc_dev(host->mmc),
+ "Unable to get GPIO pin for MMC power\n");
+ goto err_free_fclk;
+ }
+ omap_set_gpio_direction(host->power_pin, 0);
+ }
+
+ ret = request_irq(host->irq, mmc_omap_irq, 0, DRIVER_NAME, host);
+ if (ret)
+ goto err_free_power_gpio;
+
+ host->dev = &pdev->dev;
+ platform_set_drvdata(pdev, host);
+
+ if (host->switch_pin >= 0) {
+ INIT_WORK(&host->switch_work, mmc_omap_switch_handler);
+ init_timer(&host->switch_timer);
+ host->switch_timer.function = mmc_omap_switch_timer;
+ host->switch_timer.data = (unsigned long) host;
+ if (omap_request_gpio(host->switch_pin) != 0) {
+ dev_warn(mmc_dev(host->mmc), "Unable to get GPIO pin for MMC cover switch\n");
+ host->switch_pin = -1;
+ goto no_switch;
+ }
+
+ omap_set_gpio_direction(host->switch_pin, 1);
+ ret = request_irq(OMAP_GPIO_IRQ(host->switch_pin),
+ mmc_omap_switch_irq, IRQF_TRIGGER_RISING, DRIVER_NAME, host);
+ if (ret) {
+ dev_warn(mmc_dev(host->mmc), "Unable to get IRQ for MMC cover switch\n");
+ omap_free_gpio(host->switch_pin);
+ host->switch_pin = -1;
+ goto no_switch;
+ }
+ ret = device_create_file(&pdev->dev, &dev_attr_cover_switch);
+ if (ret == 0) {
+ ret = device_create_file(&pdev->dev, &dev_attr_enable_poll);
+ if (ret != 0)
+ device_remove_file(&pdev->dev, &dev_attr_cover_switch);
+ }
+ if (ret) {
+ dev_warn(mmc_dev(host->mmc), "Unable to create sysfs attributes\n");
+ free_irq(OMAP_GPIO_IRQ(host->switch_pin), host);
+ omap_free_gpio(host->switch_pin);
+ host->switch_pin = -1;
+ goto no_switch;
+ }
+ if (mmc_omap_enable_poll && mmc_omap_cover_is_open(host))
+ schedule_work(&host->switch_work);
+ }
+
+ mmc_add_host(mmc);
+
+ return 0;
+
+no_switch:
+ /* FIXME: Free other resources too. */
+ if (host) {
+ if (host->iclk && !IS_ERR(host->iclk))
+ clk_put(host->iclk);
+ if (host->fclk && !IS_ERR(host->fclk))
+ clk_put(host->fclk);
+ mmc_free_host(host->mmc);
+ }
+err_free_power_gpio:
+ if (host->power_pin >= 0)
+ omap_free_gpio(host->power_pin);
+err_free_fclk:
+ clk_put(host->fclk);
+err_free_iclk:
+ if (host->iclk != NULL) {
+ clk_disable(host->iclk);
+ clk_put(host->iclk);
+ }
+err_free_mmc_host:
+ mmc_free_host(host->mmc);
+err_free_mem_region:
+ release_mem_region(res->start, res->end - res->start + 1);
+ return ret;
+}
+
+static int mmc_omap_remove(struct platform_device *pdev)
+{
+ struct mmc_omap_host *host = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ BUG_ON(host == NULL);
+
+ mmc_remove_host(host->mmc);
+ free_irq(host->irq, host);
+
+ if (host->power_pin >= 0)
+ omap_free_gpio(host->power_pin);
+ if (host->switch_pin >= 0) {
+ device_remove_file(&pdev->dev, &dev_attr_enable_poll);
+ device_remove_file(&pdev->dev, &dev_attr_cover_switch);
+ free_irq(OMAP_GPIO_IRQ(host->switch_pin), host);
+ omap_free_gpio(host->switch_pin);
+ host->switch_pin = -1;
+ del_timer_sync(&host->switch_timer);
+ flush_scheduled_work();
+ }
+ if (host->iclk && !IS_ERR(host->iclk))
+ clk_put(host->iclk);
+ if (host->fclk && !IS_ERR(host->fclk))
+ clk_put(host->fclk);
+
+ release_mem_region(pdev->resource[0].start,
+ pdev->resource[0].end - pdev->resource[0].start + 1);
+
+ mmc_free_host(host->mmc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mmc_omap_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+ int ret = 0;
+ struct mmc_omap_host *host = platform_get_drvdata(pdev);
+
+ if (host && host->suspended)
+ return 0;
+
+ if (host) {
+ ret = mmc_suspend_host(host->mmc, mesg);
+ if (ret == 0)
+ host->suspended = 1;
+ }
+ return ret;
+}
+
+static int mmc_omap_resume(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct mmc_omap_host *host = platform_get_drvdata(pdev);
+
+ if (host && !host->suspended)
+ return 0;
+
+ if (host) {
+ ret = mmc_resume_host(host->mmc);
+ if (ret == 0)
+ host->suspended = 0;
+ }
+
+ return ret;
+}
+#else
+#define mmc_omap_suspend NULL
+#define mmc_omap_resume NULL
+#endif
+
+static struct platform_driver mmc_omap_driver = {
+ .probe = mmc_omap_probe,
+ .remove = mmc_omap_remove,
+ .suspend = mmc_omap_suspend,
+ .resume = mmc_omap_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+static int __init mmc_omap_init(void)
+{
+ return platform_driver_register(&mmc_omap_driver);
+}
+
+static void __exit mmc_omap_exit(void)
+{
+ platform_driver_unregister(&mmc_omap_driver);
+}
+
+module_init(mmc_omap_init);
+module_exit(mmc_omap_exit);
+
+MODULE_DESCRIPTION("OMAP Multimedia Card driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS(DRIVER_NAME);
+MODULE_AUTHOR("Juha Yrjölä");
diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c
new file mode 100644
index 000000000000..a98ff98fa567
--- /dev/null
+++ b/drivers/mmc/host/pxamci.c
@@ -0,0 +1,616 @@
+/*
+ * linux/drivers/mmc/pxa.c - PXA MMCI driver
+ *
+ * Copyright (C) 2003 Russell King, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This hardware is really sick:
+ * - No way to clear interrupts.
+ * - Have to turn off the clock whenever we touch the device.
+ * - Doesn't tell you how many data blocks were transferred.
+ * Yuck!
+ *
+ * 1 and 3 byte data transfers not supported
+ * max block length up to 1023
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/mmc/host.h>
+
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/scatterlist.h>
+#include <asm/sizes.h>
+
+#include <asm/arch/pxa-regs.h>
+#include <asm/arch/mmc.h>
+
+#include "pxamci.h"
+
+#define DRIVER_NAME "pxa2xx-mci"
+
+#define NR_SG 1
+
+struct pxamci_host {
+ struct mmc_host *mmc;
+ spinlock_t lock;
+ struct resource *res;
+ void __iomem *base;
+ int irq;
+ int dma;
+ unsigned int clkrt;
+ unsigned int cmdat;
+ unsigned int imask;
+ unsigned int power_mode;
+ struct pxamci_platform_data *pdata;
+
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ dma_addr_t sg_dma;
+ struct pxa_dma_desc *sg_cpu;
+ unsigned int dma_len;
+
+ unsigned int dma_dir;
+};
+
+static void pxamci_stop_clock(struct pxamci_host *host)
+{
+ if (readl(host->base + MMC_STAT) & STAT_CLK_EN) {
+ unsigned long timeout = 10000;
+ unsigned int v;
+
+ writel(STOP_CLOCK, host->base + MMC_STRPCL);
+
+ do {
+ v = readl(host->base + MMC_STAT);
+ if (!(v & STAT_CLK_EN))
+ break;
+ udelay(1);
+ } while (timeout--);
+
+ if (v & STAT_CLK_EN)
+ dev_err(mmc_dev(host->mmc), "unable to stop clock\n");
+ }
+}
+
+static void pxamci_enable_irq(struct pxamci_host *host, unsigned int mask)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->imask &= ~mask;
+ writel(host->imask, host->base + MMC_I_MASK);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void pxamci_disable_irq(struct pxamci_host *host, unsigned int mask)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->imask |= mask;
+ writel(host->imask, host->base + MMC_I_MASK);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data)
+{
+ unsigned int nob = data->blocks;
+ unsigned long long clks;
+ unsigned int timeout;
+ u32 dcmd;
+ int i;
+
+ host->data = data;
+
+ if (data->flags & MMC_DATA_STREAM)
+ nob = 0xffff;
+
+ writel(nob, host->base + MMC_NOB);
+ writel(data->blksz, host->base + MMC_BLKLEN);
+
+ clks = (unsigned long long)data->timeout_ns * CLOCKRATE;
+ do_div(clks, 1000000000UL);
+ timeout = (unsigned int)clks + (data->timeout_clks << host->clkrt);
+ writel((timeout + 255) / 256, host->base + MMC_RDTO);
+
+ if (data->flags & MMC_DATA_READ) {
+ host->dma_dir = DMA_FROM_DEVICE;
+ dcmd = DCMD_INCTRGADDR | DCMD_FLOWTRG;
+ DRCMRTXMMC = 0;
+ DRCMRRXMMC = host->dma | DRCMR_MAPVLD;
+ } else {
+ host->dma_dir = DMA_TO_DEVICE;
+ dcmd = DCMD_INCSRCADDR | DCMD_FLOWSRC;
+ DRCMRRXMMC = 0;
+ DRCMRTXMMC = host->dma | DRCMR_MAPVLD;
+ }
+
+ dcmd |= DCMD_BURST32 | DCMD_WIDTH1;
+
+ host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ host->dma_dir);
+
+ for (i = 0; i < host->dma_len; i++) {
+ if (data->flags & MMC_DATA_READ) {
+ host->sg_cpu[i].dsadr = host->res->start + MMC_RXFIFO;
+ host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]);
+ } else {
+ host->sg_cpu[i].dsadr = sg_dma_address(&data->sg[i]);
+ host->sg_cpu[i].dtadr = host->res->start + MMC_TXFIFO;
+ }
+ host->sg_cpu[i].dcmd = dcmd | sg_dma_len(&data->sg[i]);
+ host->sg_cpu[i].ddadr = host->sg_dma + (i + 1) *
+ sizeof(struct pxa_dma_desc);
+ }
+ host->sg_cpu[host->dma_len - 1].ddadr = DDADR_STOP;
+ wmb();
+
+ DDADR(host->dma) = host->sg_dma;
+ DCSR(host->dma) = DCSR_RUN;
+}
+
+static void pxamci_start_cmd(struct pxamci_host *host, struct mmc_command *cmd, unsigned int cmdat)
+{
+ WARN_ON(host->cmd != NULL);
+ host->cmd = cmd;
+
+ if (cmd->flags & MMC_RSP_BUSY)
+ cmdat |= CMDAT_BUSY;
+
+#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE))
+ switch (RSP_TYPE(mmc_resp_type(cmd))) {
+ case RSP_TYPE(MMC_RSP_R1): /* r1, r1b, r6, r7 */
+ cmdat |= CMDAT_RESP_SHORT;
+ break;
+ case RSP_TYPE(MMC_RSP_R3):
+ cmdat |= CMDAT_RESP_R3;
+ break;
+ case RSP_TYPE(MMC_RSP_R2):
+ cmdat |= CMDAT_RESP_R2;
+ break;
+ default:
+ break;
+ }
+
+ writel(cmd->opcode, host->base + MMC_CMD);
+ writel(cmd->arg >> 16, host->base + MMC_ARGH);
+ writel(cmd->arg & 0xffff, host->base + MMC_ARGL);
+ writel(cmdat, host->base + MMC_CMDAT);
+ writel(host->clkrt, host->base + MMC_CLKRT);
+
+ writel(START_CLOCK, host->base + MMC_STRPCL);
+
+ pxamci_enable_irq(host, END_CMD_RES);
+}
+
+static void pxamci_finish_request(struct pxamci_host *host, struct mmc_request *mrq)
+{
+ host->mrq = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+ mmc_request_done(host->mmc, mrq);
+}
+
+static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat)
+{
+ struct mmc_command *cmd = host->cmd;
+ int i;
+ u32 v;
+
+ if (!cmd)
+ return 0;
+
+ host->cmd = NULL;
+
+ /*
+ * Did I mention this is Sick. We always need to
+ * discard the upper 8 bits of the first 16-bit word.
+ */
+ v = readl(host->base + MMC_RES) & 0xffff;
+ for (i = 0; i < 4; i++) {
+ u32 w1 = readl(host->base + MMC_RES) & 0xffff;
+ u32 w2 = readl(host->base + MMC_RES) & 0xffff;
+ cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8;
+ v = w2;
+ }
+
+ if (stat & STAT_TIME_OUT_RESPONSE) {
+ cmd->error = MMC_ERR_TIMEOUT;
+ } else if (stat & STAT_RES_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
+#ifdef CONFIG_PXA27x
+ /*
+ * workaround for erratum #42:
+ * Intel PXA27x Family Processor Specification Update Rev 001
+ */
+ if (cmd->opcode == MMC_ALL_SEND_CID ||
+ cmd->opcode == MMC_SEND_CSD ||
+ cmd->opcode == MMC_SEND_CID) {
+ /* a bogus CRC error can appear if the msb of
+ the 15 byte response is a one */
+ if ((cmd->resp[0] & 0x80000000) == 0)
+ cmd->error = MMC_ERR_BADCRC;
+ } else {
+ pr_debug("ignoring CRC from command %d - *risky*\n",cmd->opcode);
+ }
+#else
+ cmd->error = MMC_ERR_BADCRC;
+#endif
+ }
+
+ pxamci_disable_irq(host, END_CMD_RES);
+ if (host->data && cmd->error == MMC_ERR_NONE) {
+ pxamci_enable_irq(host, DATA_TRAN_DONE);
+ } else {
+ pxamci_finish_request(host, host->mrq);
+ }
+
+ return 1;
+}
+
+static int pxamci_data_done(struct pxamci_host *host, unsigned int stat)
+{
+ struct mmc_data *data = host->data;
+
+ if (!data)
+ return 0;
+
+ DCSR(host->dma) = 0;
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len,
+ host->dma_dir);
+
+ if (stat & STAT_READ_TIME_OUT)
+ data->error = MMC_ERR_TIMEOUT;
+ else if (stat & (STAT_CRC_READ_ERROR|STAT_CRC_WRITE_ERROR))
+ data->error = MMC_ERR_BADCRC;
+
+ /*
+ * There appears to be a hardware design bug here. There seems to
+ * be no way to find out how much data was transferred to the card.
+ * This means that if there was an error on any block, we mark all
+ * data blocks as being in error.
+ */
+ if (data->error == MMC_ERR_NONE)
+ data->bytes_xfered = data->blocks * data->blksz;
+ else
+ data->bytes_xfered = 0;
+
+ pxamci_disable_irq(host, DATA_TRAN_DONE);
+
+ host->data = NULL;
+ if (host->mrq->stop) {
+ pxamci_stop_clock(host);
+ pxamci_start_cmd(host, host->mrq->stop, 0);
+ } else {
+ pxamci_finish_request(host, host->mrq);
+ }
+
+ return 1;
+}
+
+static irqreturn_t pxamci_irq(int irq, void *devid)
+{
+ struct pxamci_host *host = devid;
+ unsigned int ireg;
+ int handled = 0;
+
+ ireg = readl(host->base + MMC_I_REG);
+
+ if (ireg) {
+ unsigned stat = readl(host->base + MMC_STAT);
+
+ pr_debug("PXAMCI: irq %08x stat %08x\n", ireg, stat);
+
+ if (ireg & END_CMD_RES)
+ handled |= pxamci_cmd_done(host, stat);
+ if (ireg & DATA_TRAN_DONE)
+ handled |= pxamci_data_done(host, stat);
+ }
+
+ return IRQ_RETVAL(handled);
+}
+
+static void pxamci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct pxamci_host *host = mmc_priv(mmc);
+ unsigned int cmdat;
+
+ WARN_ON(host->mrq != NULL);
+
+ host->mrq = mrq;
+
+ pxamci_stop_clock(host);
+
+ cmdat = host->cmdat;
+ host->cmdat &= ~CMDAT_INIT;
+
+ if (mrq->data) {
+ pxamci_setup_data(host, mrq->data);
+
+ cmdat &= ~CMDAT_BUSY;
+ cmdat |= CMDAT_DATAEN | CMDAT_DMAEN;
+ if (mrq->data->flags & MMC_DATA_WRITE)
+ cmdat |= CMDAT_WRITE;
+
+ if (mrq->data->flags & MMC_DATA_STREAM)
+ cmdat |= CMDAT_STREAM;
+ }
+
+ pxamci_start_cmd(host, mrq->cmd, cmdat);
+}
+
+static int pxamci_get_ro(struct mmc_host *mmc)
+{
+ struct pxamci_host *host = mmc_priv(mmc);
+
+ if (host->pdata && host->pdata->get_ro)
+ return host->pdata->get_ro(mmc_dev(mmc));
+ /* Host doesn't support read only detection so assume writeable */
+ return 0;
+}
+
+static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct pxamci_host *host = mmc_priv(mmc);
+
+ if (ios->clock) {
+ unsigned int clk = CLOCKRATE / ios->clock;
+ if (CLOCKRATE / clk > ios->clock)
+ clk <<= 1;
+ host->clkrt = fls(clk) - 1;
+ pxa_set_cken(CKEN12_MMC, 1);
+
+ /*
+ * we write clkrt on the next command
+ */
+ } else {
+ pxamci_stop_clock(host);
+ pxa_set_cken(CKEN12_MMC, 0);
+ }
+
+ if (host->power_mode != ios->power_mode) {
+ host->power_mode = ios->power_mode;
+
+ if (host->pdata && host->pdata->setpower)
+ host->pdata->setpower(mmc_dev(mmc), ios->vdd);
+
+ if (ios->power_mode == MMC_POWER_ON)
+ host->cmdat |= CMDAT_INIT;
+ }
+
+ pr_debug("PXAMCI: clkrt = %x cmdat = %x\n",
+ host->clkrt, host->cmdat);
+}
+
+static const struct mmc_host_ops pxamci_ops = {
+ .request = pxamci_request,
+ .get_ro = pxamci_get_ro,
+ .set_ios = pxamci_set_ios,
+};
+
+static void pxamci_dma_irq(int dma, void *devid)
+{
+ printk(KERN_ERR "DMA%d: IRQ???\n", dma);
+ DCSR(dma) = DCSR_STARTINTR|DCSR_ENDINTR|DCSR_BUSERR;
+}
+
+static irqreturn_t pxamci_detect_irq(int irq, void *devid)
+{
+ struct pxamci_host *host = mmc_priv(devid);
+
+ mmc_detect_change(devid, host->pdata->detect_delay);
+ return IRQ_HANDLED;
+}
+
+static int pxamci_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct pxamci_host *host = NULL;
+ struct resource *r;
+ int ret, irq;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (!r || irq < 0)
+ return -ENXIO;
+
+ r = request_mem_region(r->start, SZ_4K, DRIVER_NAME);
+ if (!r)
+ return -EBUSY;
+
+ mmc = mmc_alloc_host(sizeof(struct pxamci_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ mmc->ops = &pxamci_ops;
+ mmc->f_min = CLOCKRATE_MIN;
+ mmc->f_max = CLOCKRATE_MAX;
+
+ /*
+ * We can do SG-DMA, but we don't because we never know how much
+ * data we successfully wrote to the card.
+ */
+ mmc->max_phys_segs = NR_SG;
+
+ /*
+ * Our hardware DMA can handle a maximum of one page per SG entry.
+ */
+ mmc->max_seg_size = PAGE_SIZE;
+
+ /*
+ * Block length register is 10 bits.
+ */
+ mmc->max_blk_size = 1023;
+
+ /*
+ * Block count register is 16 bits.
+ */
+ mmc->max_blk_count = 65535;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->dma = -1;
+ host->pdata = pdev->dev.platform_data;
+ mmc->ocr_avail = host->pdata ?
+ host->pdata->ocr_mask :
+ MMC_VDD_32_33|MMC_VDD_33_34;
+
+ host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL);
+ if (!host->sg_cpu) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ spin_lock_init(&host->lock);
+ host->res = r;
+ host->irq = irq;
+ host->imask = MMC_I_MASK_ALL;
+
+ host->base = ioremap(r->start, SZ_4K);
+ if (!host->base) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /*
+ * Ensure that the host controller is shut down, and setup
+ * with our defaults.
+ */
+ pxamci_stop_clock(host);
+ writel(0, host->base + MMC_SPI);
+ writel(64, host->base + MMC_RESTO);
+ writel(host->imask, host->base + MMC_I_MASK);
+
+ host->dma = pxa_request_dma(DRIVER_NAME, DMA_PRIO_LOW,
+ pxamci_dma_irq, host);
+ if (host->dma < 0) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = request_irq(host->irq, pxamci_irq, 0, DRIVER_NAME, host);
+ if (ret)
+ goto out;
+
+ platform_set_drvdata(pdev, mmc);
+
+ if (host->pdata && host->pdata->init)
+ host->pdata->init(&pdev->dev, pxamci_detect_irq, mmc);
+
+ mmc_add_host(mmc);
+
+ return 0;
+
+ out:
+ if (host) {
+ if (host->dma >= 0)
+ pxa_free_dma(host->dma);
+ if (host->base)
+ iounmap(host->base);
+ if (host->sg_cpu)
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+ }
+ if (mmc)
+ mmc_free_host(mmc);
+ release_resource(r);
+ return ret;
+}
+
+static int pxamci_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (mmc) {
+ struct pxamci_host *host = mmc_priv(mmc);
+
+ if (host->pdata && host->pdata->exit)
+ host->pdata->exit(&pdev->dev, mmc);
+
+ mmc_remove_host(mmc);
+
+ pxamci_stop_clock(host);
+ writel(TXFIFO_WR_REQ|RXFIFO_RD_REQ|CLK_IS_OFF|STOP_CMD|
+ END_CMD_RES|PRG_DONE|DATA_TRAN_DONE,
+ host->base + MMC_I_MASK);
+
+ DRCMRRXMMC = 0;
+ DRCMRTXMMC = 0;
+
+ free_irq(host->irq, host);
+ pxa_free_dma(host->dma);
+ iounmap(host->base);
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+
+ release_resource(host->res);
+
+ mmc_free_host(mmc);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pxamci_suspend(struct platform_device *dev, pm_message_t state)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_suspend_host(mmc, state);
+
+ return ret;
+}
+
+static int pxamci_resume(struct platform_device *dev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_resume_host(mmc);
+
+ return ret;
+}
+#else
+#define pxamci_suspend NULL
+#define pxamci_resume NULL
+#endif
+
+static struct platform_driver pxamci_driver = {
+ .probe = pxamci_probe,
+ .remove = pxamci_remove,
+ .suspend = pxamci_suspend,
+ .resume = pxamci_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+static int __init pxamci_init(void)
+{
+ return platform_driver_register(&pxamci_driver);
+}
+
+static void __exit pxamci_exit(void)
+{
+ platform_driver_unregister(&pxamci_driver);
+}
+
+module_init(pxamci_init);
+module_exit(pxamci_exit);
+
+MODULE_DESCRIPTION("PXA Multimedia Card Interface Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/pxamci.h b/drivers/mmc/host/pxamci.h
new file mode 100644
index 000000000000..1b163220df2b
--- /dev/null
+++ b/drivers/mmc/host/pxamci.h
@@ -0,0 +1,124 @@
+#undef MMC_STRPCL
+#undef MMC_STAT
+#undef MMC_CLKRT
+#undef MMC_SPI
+#undef MMC_CMDAT
+#undef MMC_RESTO
+#undef MMC_RDTO
+#undef MMC_BLKLEN
+#undef MMC_NOB
+#undef MMC_PRTBUF
+#undef MMC_I_MASK
+#undef END_CMD_RES
+#undef PRG_DONE
+#undef DATA_TRAN_DONE
+#undef MMC_I_REG
+#undef MMC_CMD
+#undef MMC_ARGH
+#undef MMC_ARGL
+#undef MMC_RES
+#undef MMC_RXFIFO
+#undef MMC_TXFIFO
+
+#define MMC_STRPCL 0x0000
+#define STOP_CLOCK (1 << 0)
+#define START_CLOCK (2 << 0)
+
+#define MMC_STAT 0x0004
+#define STAT_END_CMD_RES (1 << 13)
+#define STAT_PRG_DONE (1 << 12)
+#define STAT_DATA_TRAN_DONE (1 << 11)
+#define STAT_CLK_EN (1 << 8)
+#define STAT_RECV_FIFO_FULL (1 << 7)
+#define STAT_XMIT_FIFO_EMPTY (1 << 6)
+#define STAT_RES_CRC_ERR (1 << 5)
+#define STAT_SPI_READ_ERROR_TOKEN (1 << 4)
+#define STAT_CRC_READ_ERROR (1 << 3)
+#define STAT_CRC_WRITE_ERROR (1 << 2)
+#define STAT_TIME_OUT_RESPONSE (1 << 1)
+#define STAT_READ_TIME_OUT (1 << 0)
+
+#define MMC_CLKRT 0x0008 /* 3 bit */
+
+#define MMC_SPI 0x000c
+#define SPI_CS_ADDRESS (1 << 3)
+#define SPI_CS_EN (1 << 2)
+#define CRC_ON (1 << 1)
+#define SPI_EN (1 << 0)
+
+#define MMC_CMDAT 0x0010
+#define CMDAT_DMAEN (1 << 7)
+#define CMDAT_INIT (1 << 6)
+#define CMDAT_BUSY (1 << 5)
+#define CMDAT_STREAM (1 << 4) /* 1 = stream */
+#define CMDAT_WRITE (1 << 3) /* 1 = write */
+#define CMDAT_DATAEN (1 << 2)
+#define CMDAT_RESP_NONE (0 << 0)
+#define CMDAT_RESP_SHORT (1 << 0)
+#define CMDAT_RESP_R2 (2 << 0)
+#define CMDAT_RESP_R3 (3 << 0)
+
+#define MMC_RESTO 0x0014 /* 7 bit */
+
+#define MMC_RDTO 0x0018 /* 16 bit */
+
+#define MMC_BLKLEN 0x001c /* 10 bit */
+
+#define MMC_NOB 0x0020 /* 16 bit */
+
+#define MMC_PRTBUF 0x0024
+#define BUF_PART_FULL (1 << 0)
+
+#define MMC_I_MASK 0x0028
+
+/*PXA27x MMC interrupts*/
+#define SDIO_SUSPEND_ACK (1 << 12)
+#define SDIO_INT (1 << 11)
+#define RD_STALLED (1 << 10)
+#define RES_ERR (1 << 9)
+#define DAT_ERR (1 << 8)
+#define TINT (1 << 7)
+
+/*PXA2xx MMC interrupts*/
+#define TXFIFO_WR_REQ (1 << 6)
+#define RXFIFO_RD_REQ (1 << 5)
+#define CLK_IS_OFF (1 << 4)
+#define STOP_CMD (1 << 3)
+#define END_CMD_RES (1 << 2)
+#define PRG_DONE (1 << 1)
+#define DATA_TRAN_DONE (1 << 0)
+
+#ifdef CONFIG_PXA27x
+#define MMC_I_MASK_ALL 0x00001fff
+#else
+#define MMC_I_MASK_ALL 0x0000007f
+#endif
+
+#define MMC_I_REG 0x002c
+/* same as MMC_I_MASK */
+
+#define MMC_CMD 0x0030
+
+#define MMC_ARGH 0x0034 /* 16 bit */
+
+#define MMC_ARGL 0x0038 /* 16 bit */
+
+#define MMC_RES 0x003c /* 16 bit */
+
+#define MMC_RXFIFO 0x0040 /* 8 bit */
+
+#define MMC_TXFIFO 0x0044 /* 8 bit */
+
+/*
+ * The base MMC clock rate
+ */
+#ifdef CONFIG_PXA27x
+#define CLOCKRATE_MIN 304688
+#define CLOCKRATE_MAX 19500000
+#else
+#define CLOCKRATE_MIN 312500
+#define CLOCKRATE_MAX 20000000
+#endif
+
+#define CLOCKRATE CLOCKRATE_MAX
+
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
new file mode 100644
index 000000000000..579142a7904b
--- /dev/null
+++ b/drivers/mmc/host/sdhci.c
@@ -0,0 +1,1539 @@
+/*
+ * linux/drivers/mmc/sdhci.c - Secure Digital Host Controller Interface driver
+ *
+ * Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved.
+ *
+ * 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/delay.h>
+#include <linux/highmem.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+
+#include <linux/mmc/host.h>
+
+#include <asm/scatterlist.h>
+
+#include "sdhci.h"
+
+#define DRIVER_NAME "sdhci"
+
+#define DBG(f, x...) \
+ pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x)
+
+static unsigned int debug_nodma = 0;
+static unsigned int debug_forcedma = 0;
+static unsigned int debug_quirks = 0;
+
+#define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0)
+#define SDHCI_QUIRK_FORCE_DMA (1<<1)
+/* Controller doesn't like some resets when there is no card inserted. */
+#define SDHCI_QUIRK_NO_CARD_NO_RESET (1<<2)
+#define SDHCI_QUIRK_SINGLE_POWER_WRITE (1<<3)
+
+static const struct pci_device_id pci_ids[] __devinitdata = {
+ {
+ .vendor = PCI_VENDOR_ID_RICOH,
+ .device = PCI_DEVICE_ID_RICOH_R5C822,
+ .subvendor = PCI_VENDOR_ID_IBM,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = SDHCI_QUIRK_CLOCK_BEFORE_RESET |
+ SDHCI_QUIRK_FORCE_DMA,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_RICOH,
+ .device = PCI_DEVICE_ID_RICOH_R5C822,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = SDHCI_QUIRK_FORCE_DMA |
+ SDHCI_QUIRK_NO_CARD_NO_RESET,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_TI,
+ .device = PCI_DEVICE_ID_TI_XX21_XX11_SD,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = SDHCI_QUIRK_FORCE_DMA,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_ENE,
+ .device = PCI_DEVICE_ID_ENE_CB712_SD,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE,
+ },
+
+ { /* Generic SD host controller */
+ PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00)
+ },
+
+ { /* end: all zeroes */ },
+};
+
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+static void sdhci_prepare_data(struct sdhci_host *, struct mmc_data *);
+static void sdhci_finish_data(struct sdhci_host *);
+
+static void sdhci_send_command(struct sdhci_host *, struct mmc_command *);
+static void sdhci_finish_command(struct sdhci_host *);
+
+static void sdhci_dumpregs(struct sdhci_host *host)
+{
+ printk(KERN_DEBUG DRIVER_NAME ": ============== REGISTER DUMP ==============\n");
+
+ printk(KERN_DEBUG DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n",
+ readl(host->ioaddr + SDHCI_DMA_ADDRESS),
+ readw(host->ioaddr + SDHCI_HOST_VERSION));
+ printk(KERN_DEBUG DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n",
+ readw(host->ioaddr + SDHCI_BLOCK_SIZE),
+ readw(host->ioaddr + SDHCI_BLOCK_COUNT));
+ printk(KERN_DEBUG DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n",
+ readl(host->ioaddr + SDHCI_ARGUMENT),
+ readw(host->ioaddr + SDHCI_TRANSFER_MODE));
+ printk(KERN_DEBUG DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n",
+ readl(host->ioaddr + SDHCI_PRESENT_STATE),
+ readb(host->ioaddr + SDHCI_HOST_CONTROL));
+ printk(KERN_DEBUG DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n",
+ readb(host->ioaddr + SDHCI_POWER_CONTROL),
+ readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL));
+ printk(KERN_DEBUG DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n",
+ readb(host->ioaddr + SDHCI_WALK_UP_CONTROL),
+ readw(host->ioaddr + SDHCI_CLOCK_CONTROL));
+ printk(KERN_DEBUG DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n",
+ readb(host->ioaddr + SDHCI_TIMEOUT_CONTROL),
+ readl(host->ioaddr + SDHCI_INT_STATUS));
+ printk(KERN_DEBUG DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n",
+ readl(host->ioaddr + SDHCI_INT_ENABLE),
+ readl(host->ioaddr + SDHCI_SIGNAL_ENABLE));
+ printk(KERN_DEBUG DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n",
+ readw(host->ioaddr + SDHCI_ACMD12_ERR),
+ readw(host->ioaddr + SDHCI_SLOT_INT_STATUS));
+ printk(KERN_DEBUG DRIVER_NAME ": Caps: 0x%08x | Max curr: 0x%08x\n",
+ readl(host->ioaddr + SDHCI_CAPABILITIES),
+ readl(host->ioaddr + SDHCI_MAX_CURRENT));
+
+ printk(KERN_DEBUG DRIVER_NAME ": ===========================================\n");
+}
+
+/*****************************************************************************\
+ * *
+ * Low level functions *
+ * *
+\*****************************************************************************/
+
+static void sdhci_reset(struct sdhci_host *host, u8 mask)
+{
+ unsigned long timeout;
+
+ if (host->chip->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
+ if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) &
+ SDHCI_CARD_PRESENT))
+ return;
+ }
+
+ writeb(mask, host->ioaddr + SDHCI_SOFTWARE_RESET);
+
+ if (mask & SDHCI_RESET_ALL)
+ host->clock = 0;
+
+ /* Wait max 100 ms */
+ timeout = 100;
+
+ /* hw clears the bit when it's done */
+ while (readb(host->ioaddr + SDHCI_SOFTWARE_RESET) & mask) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Reset 0x%x never completed.\n",
+ mmc_hostname(host->mmc), (int)mask);
+ sdhci_dumpregs(host);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+}
+
+static void sdhci_init(struct sdhci_host *host)
+{
+ u32 intmask;
+
+ sdhci_reset(host, SDHCI_RESET_ALL);
+
+ intmask = SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT |
+ SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX |
+ SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT |
+ SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT |
+ SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL |
+ SDHCI_INT_DMA_END | SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE;
+
+ writel(intmask, host->ioaddr + SDHCI_INT_ENABLE);
+ writel(intmask, host->ioaddr + SDHCI_SIGNAL_ENABLE);
+}
+
+static void sdhci_activate_led(struct sdhci_host *host)
+{
+ u8 ctrl;
+
+ ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL);
+ ctrl |= SDHCI_CTRL_LED;
+ writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL);
+}
+
+static void sdhci_deactivate_led(struct sdhci_host *host)
+{
+ u8 ctrl;
+
+ ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL);
+ ctrl &= ~SDHCI_CTRL_LED;
+ writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL);
+}
+
+/*****************************************************************************\
+ * *
+ * Core functions *
+ * *
+\*****************************************************************************/
+
+static inline char* sdhci_sg_to_buffer(struct sdhci_host* host)
+{
+ return page_address(host->cur_sg->page) + host->cur_sg->offset;
+}
+
+static inline int sdhci_next_sg(struct sdhci_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 sdhci_read_block_pio(struct sdhci_host *host)
+{
+ int blksize, chunk_remain;
+ u32 data;
+ char *buffer;
+ int size;
+
+ DBG("PIO reading\n");
+
+ blksize = host->data->blksz;
+ chunk_remain = 0;
+ data = 0;
+
+ buffer = sdhci_sg_to_buffer(host) + host->offset;
+
+ while (blksize) {
+ if (chunk_remain == 0) {
+ data = readl(host->ioaddr + SDHCI_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 (sdhci_next_sg(host) == 0) {
+ BUG_ON(blksize != 0);
+ return;
+ }
+ buffer = sdhci_sg_to_buffer(host);
+ }
+ }
+}
+
+static void sdhci_write_block_pio(struct sdhci_host *host)
+{
+ int blksize, 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 = sdhci_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 + SDHCI_BUFFER);
+ chunk_remain = min(blksize, 4);
+ }
+
+ if (host->remain == 0) {
+ if (sdhci_next_sg(host) == 0) {
+ BUG_ON(blksize != 0);
+ return;
+ }
+ buffer = sdhci_sg_to_buffer(host);
+ }
+ }
+}
+
+static void sdhci_transfer_pio(struct sdhci_host *host)
+{
+ u32 mask;
+
+ BUG_ON(!host->data);
+
+ if (host->num_sg == 0)
+ return;
+
+ if (host->data->flags & MMC_DATA_READ)
+ mask = SDHCI_DATA_AVAILABLE;
+ else
+ mask = SDHCI_SPACE_AVAILABLE;
+
+ while (readl(host->ioaddr + SDHCI_PRESENT_STATE) & mask) {
+ if (host->data->flags & MMC_DATA_READ)
+ sdhci_read_block_pio(host);
+ else
+ sdhci_write_block_pio(host);
+
+ if (host->num_sg == 0)
+ break;
+ }
+
+ DBG("PIO transfer complete.\n");
+}
+
+static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data)
+{
+ u8 count;
+ unsigned target_timeout, current_timeout;
+
+ 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);
+
+ /* timeout in us */
+ target_timeout = data->timeout_ns / 1000 +
+ data->timeout_clks / 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;
+ current_timeout = (1 << 13) * 1000 / host->timeout_clk;
+ while (current_timeout < target_timeout) {
+ count++;
+ current_timeout <<= 1;
+ if (count >= 0xF)
+ break;
+ }
+
+ if (count >= 0xF) {
+ printk(KERN_WARNING "%s: Too large timeout requested!\n",
+ mmc_hostname(host->mmc));
+ count = 0xE;
+ }
+
+ writeb(count, host->ioaddr + SDHCI_TIMEOUT_CONTROL);
+
+ if (host->flags & SDHCI_USE_DMA) {
+ int count;
+
+ count = pci_map_sg(host->chip->pdev, data->sg, data->sg_len,
+ (data->flags & MMC_DATA_READ)?PCI_DMA_FROMDEVICE:PCI_DMA_TODEVICE);
+ BUG_ON(count != 1);
+
+ writel(sg_dma_address(data->sg), host->ioaddr + SDHCI_DMA_ADDRESS);
+ } else {
+ host->cur_sg = data->sg;
+ host->num_sg = data->sg_len;
+
+ host->offset = 0;
+ host->remain = host->cur_sg->length;
+ }
+
+ /* We do not handle DMA boundaries, so set it to max (512 KiB) */
+ writew(SDHCI_MAKE_BLKSZ(7, data->blksz),
+ host->ioaddr + SDHCI_BLOCK_SIZE);
+ writew(data->blocks, host->ioaddr + SDHCI_BLOCK_COUNT);
+}
+
+static void sdhci_set_transfer_mode(struct sdhci_host *host,
+ struct mmc_data *data)
+{
+ u16 mode;
+
+ WARN_ON(host->data);
+
+ if (data == NULL)
+ return;
+
+ mode = SDHCI_TRNS_BLK_CNT_EN;
+ if (data->blocks > 1)
+ mode |= SDHCI_TRNS_MULTI;
+ if (data->flags & MMC_DATA_READ)
+ mode |= SDHCI_TRNS_READ;
+ if (host->flags & SDHCI_USE_DMA)
+ mode |= SDHCI_TRNS_DMA;
+
+ writew(mode, host->ioaddr + SDHCI_TRANSFER_MODE);
+}
+
+static void sdhci_finish_data(struct sdhci_host *host)
+{
+ struct mmc_data *data;
+ u16 blocks;
+
+ BUG_ON(!host->data);
+
+ data = host->data;
+ host->data = NULL;
+
+ if (host->flags & SDHCI_USE_DMA) {
+ pci_unmap_sg(host->chip->pdev, data->sg, data->sg_len,
+ (data->flags & MMC_DATA_READ)?PCI_DMA_FROMDEVICE:PCI_DMA_TODEVICE);
+ }
+
+ /*
+ * Controller doesn't count down when in single block mode.
+ */
+ if ((data->blocks == 1) && (data->error == MMC_ERR_NONE))
+ blocks = 0;
+ else
+ blocks = readw(host->ioaddr + SDHCI_BLOCK_COUNT);
+ data->bytes_xfered = data->blksz * (data->blocks - blocks);
+
+ if ((data->error == MMC_ERR_NONE) && blocks) {
+ printk(KERN_ERR "%s: Controller signalled completion even "
+ "though there were blocks left.\n",
+ mmc_hostname(host->mmc));
+ data->error = MMC_ERR_FAILED;
+ }
+
+ 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 != MMC_ERR_NONE) {
+ sdhci_reset(host, SDHCI_RESET_CMD);
+ sdhci_reset(host, SDHCI_RESET_DATA);
+ }
+
+ sdhci_send_command(host, data->stop);
+ } else
+ tasklet_schedule(&host->finish_tasklet);
+}
+
+static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
+{
+ int flags;
+ u32 mask;
+ unsigned long timeout;
+
+ WARN_ON(host->cmd);
+
+ DBG("Sending cmd (%x)\n", cmd->opcode);
+
+ /* Wait max 10 ms */
+ timeout = 10;
+
+ mask = SDHCI_CMD_INHIBIT;
+ if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY))
+ mask |= SDHCI_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 &= ~SDHCI_DATA_INHIBIT;
+
+ while (readl(host->ioaddr + SDHCI_PRESENT_STATE) & mask) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Controller never released "
+ "inhibit bit(s).\n", mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ cmd->error = MMC_ERR_FAILED;
+ tasklet_schedule(&host->finish_tasklet);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+ mod_timer(&host->timer, jiffies + 10 * HZ);
+
+ host->cmd = cmd;
+
+ sdhci_prepare_data(host, cmd->data);
+
+ writel(cmd->arg, host->ioaddr + SDHCI_ARGUMENT);
+
+ sdhci_set_transfer_mode(host, cmd->data);
+
+ if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
+ printk(KERN_ERR "%s: Unsupported response type!\n",
+ mmc_hostname(host->mmc));
+ cmd->error = MMC_ERR_INVALID;
+ tasklet_schedule(&host->finish_tasklet);
+ return;
+ }
+
+ if (!(cmd->flags & MMC_RSP_PRESENT))
+ flags = SDHCI_CMD_RESP_NONE;
+ else if (cmd->flags & MMC_RSP_136)
+ flags = SDHCI_CMD_RESP_LONG;
+ else if (cmd->flags & MMC_RSP_BUSY)
+ flags = SDHCI_CMD_RESP_SHORT_BUSY;
+ else
+ flags = SDHCI_CMD_RESP_SHORT;
+
+ if (cmd->flags & MMC_RSP_CRC)
+ flags |= SDHCI_CMD_CRC;
+ if (cmd->flags & MMC_RSP_OPCODE)
+ flags |= SDHCI_CMD_INDEX;
+ if (cmd->data)
+ flags |= SDHCI_CMD_DATA;
+
+ writew(SDHCI_MAKE_CMD(cmd->opcode, flags),
+ host->ioaddr + SDHCI_COMMAND);
+}
+
+static void sdhci_finish_command(struct sdhci_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] = readl(host->ioaddr +
+ SDHCI_RESPONSE + (3-i)*4) << 8;
+ if (i != 3)
+ host->cmd->resp[i] |=
+ readb(host->ioaddr +
+ SDHCI_RESPONSE + (3-i)*4-1);
+ }
+ } else {
+ host->cmd->resp[0] = readl(host->ioaddr + SDHCI_RESPONSE);
+ }
+ }
+
+ host->cmd->error = MMC_ERR_NONE;
+
+ DBG("Ending cmd (%x)\n", host->cmd->opcode);
+
+ if (host->cmd->data)
+ host->data = host->cmd->data;
+ else
+ tasklet_schedule(&host->finish_tasklet);
+
+ host->cmd = NULL;
+}
+
+static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ int div;
+ u16 clk;
+ unsigned long timeout;
+
+ if (clock == host->clock)
+ return;
+
+ writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL);
+
+ if (clock == 0)
+ goto out;
+
+ for (div = 1;div < 256;div *= 2) {
+ if ((host->max_clk / div) <= clock)
+ break;
+ }
+ div >>= 1;
+
+ clk = div << SDHCI_DIVIDER_SHIFT;
+ clk |= SDHCI_CLOCK_INT_EN;
+ writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL);
+
+ /* Wait max 10 ms */
+ timeout = 10;
+ while (!((clk = readw(host->ioaddr + SDHCI_CLOCK_CONTROL))
+ & SDHCI_CLOCK_INT_STABLE)) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Internal clock never "
+ "stabilised.\n", mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+ clk |= SDHCI_CLOCK_CARD_EN;
+ writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL);
+
+out:
+ host->clock = clock;
+}
+
+static void sdhci_set_power(struct sdhci_host *host, unsigned short power)
+{
+ u8 pwr;
+
+ if (host->power == power)
+ return;
+
+ if (power == (unsigned short)-1) {
+ writeb(0, host->ioaddr + SDHCI_POWER_CONTROL);
+ goto out;
+ }
+
+ /*
+ * Spec says that we should clear the power reg before setting
+ * a new value. Some controllers don't seem to like this though.
+ */
+ if (!(host->chip->quirks & SDHCI_QUIRK_SINGLE_POWER_WRITE))
+ writeb(0, host->ioaddr + SDHCI_POWER_CONTROL);
+
+ pwr = SDHCI_POWER_ON;
+
+ switch (power) {
+ case MMC_VDD_170:
+ case MMC_VDD_180:
+ case MMC_VDD_190:
+ pwr |= SDHCI_POWER_180;
+ break;
+ case MMC_VDD_290:
+ case MMC_VDD_300:
+ case MMC_VDD_310:
+ pwr |= SDHCI_POWER_300;
+ break;
+ case MMC_VDD_320:
+ case MMC_VDD_330:
+ case MMC_VDD_340:
+ pwr |= SDHCI_POWER_330;
+ break;
+ default:
+ BUG();
+ }
+
+ writeb(pwr, host->ioaddr + SDHCI_POWER_CONTROL);
+
+out:
+ host->power = power;
+}
+
+/*****************************************************************************\
+ * *
+ * MMC callbacks *
+ * *
+\*****************************************************************************/
+
+static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ WARN_ON(host->mrq != NULL);
+
+ sdhci_activate_led(host);
+
+ host->mrq = mrq;
+
+ if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
+ host->mrq->cmd->error = MMC_ERR_TIMEOUT;
+ tasklet_schedule(&host->finish_tasklet);
+ } else
+ sdhci_send_command(host, mrq->cmd);
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+ u8 ctrl;
+
+ host = mmc_priv(mmc);
+
+ 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) {
+ writel(0, host->ioaddr + SDHCI_SIGNAL_ENABLE);
+ sdhci_init(host);
+ }
+
+ sdhci_set_clock(host, ios->clock);
+
+ if (ios->power_mode == MMC_POWER_OFF)
+ sdhci_set_power(host, -1);
+ else
+ sdhci_set_power(host, ios->vdd);
+
+ ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL);
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ ctrl |= SDHCI_CTRL_4BITBUS;
+ else
+ ctrl &= ~SDHCI_CTRL_4BITBUS;
+
+ if (ios->timing == MMC_TIMING_SD_HS)
+ ctrl |= SDHCI_CTRL_HISPD;
+ else
+ ctrl &= ~SDHCI_CTRL_HISPD;
+
+ writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL);
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static int sdhci_get_ro(struct mmc_host *mmc)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+ int present;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ present = readl(host->ioaddr + SDHCI_PRESENT_STATE);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ return !(present & SDHCI_WRITE_PROTECT);
+}
+
+static const struct mmc_host_ops sdhci_ops = {
+ .request = sdhci_request,
+ .set_ios = sdhci_set_ios,
+ .get_ro = sdhci_get_ro,
+};
+
+/*****************************************************************************\
+ * *
+ * Tasklets *
+ * *
+\*****************************************************************************/
+
+static void sdhci_tasklet_card(unsigned long param)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+
+ host = (struct sdhci_host*)param;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Card removed during transfer!\n",
+ mmc_hostname(host->mmc));
+ printk(KERN_ERR "%s: Resetting controller.\n",
+ mmc_hostname(host->mmc));
+
+ sdhci_reset(host, SDHCI_RESET_CMD);
+ sdhci_reset(host, SDHCI_RESET_DATA);
+
+ host->mrq->cmd->error = MMC_ERR_FAILED;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ }
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ mmc_detect_change(host->mmc, msecs_to_jiffies(500));
+}
+
+static void sdhci_tasklet_finish(unsigned long param)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+ struct mmc_request *mrq;
+
+ host = (struct sdhci_host*)param;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ del_timer(&host->timer);
+
+ mrq = host->mrq;
+
+ DBG("Ending request, cmd (%x)\n", mrq->cmd->opcode);
+
+ /*
+ * The controller needs a reset of internal state machines
+ * upon error conditions.
+ */
+ if ((mrq->cmd->error != MMC_ERR_NONE) ||
+ (mrq->data && ((mrq->data->error != MMC_ERR_NONE) ||
+ (mrq->data->stop && (mrq->data->stop->error != MMC_ERR_NONE))))) {
+
+ /* Some controllers need this kick or reset won't work here */
+ if (host->chip->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) {
+ unsigned int clock;
+
+ /* This is to force an update */
+ clock = host->clock;
+ host->clock = 0;
+ sdhci_set_clock(host, clock);
+ }
+
+ /* Spec says we should do both at the same time, but Ricoh
+ controllers do not like that. */
+ sdhci_reset(host, SDHCI_RESET_CMD);
+ sdhci_reset(host, SDHCI_RESET_DATA);
+ }
+
+ host->mrq = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+
+ sdhci_deactivate_led(host);
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ mmc_request_done(host->mmc, mrq);
+}
+
+static void sdhci_timeout_timer(unsigned long data)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+
+ host = (struct sdhci_host*)data;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Timeout waiting for hardware "
+ "interrupt.\n", mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+
+ if (host->data) {
+ host->data->error = MMC_ERR_TIMEOUT;
+ sdhci_finish_data(host);
+ } else {
+ if (host->cmd)
+ host->cmd->error = MMC_ERR_TIMEOUT;
+ else
+ host->mrq->cmd->error = MMC_ERR_TIMEOUT;
+
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/*****************************************************************************\
+ * *
+ * Interrupt handling *
+ * *
+\*****************************************************************************/
+
+static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask)
+{
+ BUG_ON(intmask == 0);
+
+ if (!host->cmd) {
+ printk(KERN_ERR "%s: Got command interrupt even though no "
+ "command operation was in progress.\n",
+ mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ return;
+ }
+
+ if (intmask & SDHCI_INT_RESPONSE)
+ sdhci_finish_command(host);
+ else {
+ if (intmask & SDHCI_INT_TIMEOUT)
+ host->cmd->error = MMC_ERR_TIMEOUT;
+ else if (intmask & SDHCI_INT_CRC)
+ host->cmd->error = MMC_ERR_BADCRC;
+ else if (intmask & (SDHCI_INT_END_BIT | SDHCI_INT_INDEX))
+ host->cmd->error = MMC_ERR_FAILED;
+ else
+ host->cmd->error = MMC_ERR_INVALID;
+
+ tasklet_schedule(&host->finish_tasklet);
+ }
+}
+
+static void sdhci_data_irq(struct sdhci_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 & SDHCI_INT_DATA_END)
+ return;
+
+ printk(KERN_ERR "%s: Got data interrupt even though no "
+ "data operation was in progress.\n",
+ mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+
+ return;
+ }
+
+ if (intmask & SDHCI_INT_DATA_TIMEOUT)
+ host->data->error = MMC_ERR_TIMEOUT;
+ else if (intmask & SDHCI_INT_DATA_CRC)
+ host->data->error = MMC_ERR_BADCRC;
+ else if (intmask & SDHCI_INT_DATA_END_BIT)
+ host->data->error = MMC_ERR_FAILED;
+
+ if (host->data->error != MMC_ERR_NONE)
+ sdhci_finish_data(host);
+ else {
+ if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL))
+ sdhci_transfer_pio(host);
+
+ if (intmask & SDHCI_INT_DATA_END)
+ sdhci_finish_data(host);
+ }
+}
+
+static irqreturn_t sdhci_irq(int irq, void *dev_id)
+{
+ irqreturn_t result;
+ struct sdhci_host* host = dev_id;
+ u32 intmask;
+
+ spin_lock(&host->lock);
+
+ intmask = readl(host->ioaddr + SDHCI_INT_STATUS);
+
+ if (!intmask || intmask == 0xffffffff) {
+ result = IRQ_NONE;
+ goto out;
+ }
+
+ DBG("*** %s got interrupt: 0x%08x\n", host->slot_descr, intmask);
+
+ if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
+ writel(intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE),
+ host->ioaddr + SDHCI_INT_STATUS);
+ tasklet_schedule(&host->card_tasklet);
+ }
+
+ intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE);
+
+ if (intmask & SDHCI_INT_CMD_MASK) {
+ writel(intmask & SDHCI_INT_CMD_MASK,
+ host->ioaddr + SDHCI_INT_STATUS);
+ sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK);
+ }
+
+ if (intmask & SDHCI_INT_DATA_MASK) {
+ writel(intmask & SDHCI_INT_DATA_MASK,
+ host->ioaddr + SDHCI_INT_STATUS);
+ sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK);
+ }
+
+ intmask &= ~(SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK);
+
+ if (intmask & SDHCI_INT_BUS_POWER) {
+ printk(KERN_ERR "%s: Card is consuming too much power!\n",
+ mmc_hostname(host->mmc));
+ writel(SDHCI_INT_BUS_POWER, host->ioaddr + SDHCI_INT_STATUS);
+ }
+
+ intmask &= SDHCI_INT_BUS_POWER;
+
+ if (intmask) {
+ printk(KERN_ERR "%s: Unexpected interrupt 0x%08x.\n",
+ mmc_hostname(host->mmc), intmask);
+ sdhci_dumpregs(host);
+
+ writel(intmask, host->ioaddr + SDHCI_INT_STATUS);
+ }
+
+ result = IRQ_HANDLED;
+
+ mmiowb();
+out:
+ spin_unlock(&host->lock);
+
+ return result;
+}
+
+/*****************************************************************************\
+ * *
+ * Suspend/resume *
+ * *
+\*****************************************************************************/
+
+#ifdef CONFIG_PM
+
+static int sdhci_suspend (struct pci_dev *pdev, pm_message_t state)
+{
+ struct sdhci_chip *chip;
+ int i, ret;
+
+ chip = pci_get_drvdata(pdev);
+ if (!chip)
+ return 0;
+
+ DBG("Suspending...\n");
+
+ for (i = 0;i < chip->num_slots;i++) {
+ if (!chip->hosts[i])
+ continue;
+ ret = mmc_suspend_host(chip->hosts[i]->mmc, state);
+ if (ret) {
+ for (i--;i >= 0;i--)
+ mmc_resume_host(chip->hosts[i]->mmc);
+ return ret;
+ }
+ }
+
+ pci_save_state(pdev);
+ pci_enable_wake(pdev, pci_choose_state(pdev, state), 0);
+
+ for (i = 0;i < chip->num_slots;i++) {
+ if (!chip->hosts[i])
+ continue;
+ free_irq(chip->hosts[i]->irq, chip->hosts[i]);
+ }
+
+ pci_disable_device(pdev);
+ pci_set_power_state(pdev, pci_choose_state(pdev, state));
+
+ return 0;
+}
+
+static int sdhci_resume (struct pci_dev *pdev)
+{
+ struct sdhci_chip *chip;
+ int i, ret;
+
+ chip = pci_get_drvdata(pdev);
+ if (!chip)
+ return 0;
+
+ DBG("Resuming...\n");
+
+ pci_set_power_state(pdev, PCI_D0);
+ pci_restore_state(pdev);
+ ret = pci_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ for (i = 0;i < chip->num_slots;i++) {
+ if (!chip->hosts[i])
+ continue;
+ if (chip->hosts[i]->flags & SDHCI_USE_DMA)
+ pci_set_master(pdev);
+ ret = request_irq(chip->hosts[i]->irq, sdhci_irq,
+ IRQF_SHARED, chip->hosts[i]->slot_descr,
+ chip->hosts[i]);
+ if (ret)
+ return ret;
+ sdhci_init(chip->hosts[i]);
+ mmiowb();
+ ret = mmc_resume_host(chip->hosts[i]->mmc);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+#else /* CONFIG_PM */
+
+#define sdhci_suspend NULL
+#define sdhci_resume NULL
+
+#endif /* CONFIG_PM */
+
+/*****************************************************************************\
+ * *
+ * Device probing/removal *
+ * *
+\*****************************************************************************/
+
+static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot)
+{
+ int ret;
+ unsigned int version;
+ struct sdhci_chip *chip;
+ struct mmc_host *mmc;
+ struct sdhci_host *host;
+
+ u8 first_bar;
+ unsigned int caps;
+
+ chip = pci_get_drvdata(pdev);
+ BUG_ON(!chip);
+
+ ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &first_bar);
+ if (ret)
+ return ret;
+
+ first_bar &= PCI_SLOT_INFO_FIRST_BAR_MASK;
+
+ if (first_bar > 5) {
+ printk(KERN_ERR DRIVER_NAME ": Invalid first BAR. Aborting.\n");
+ return -ENODEV;
+ }
+
+ if (!(pci_resource_flags(pdev, first_bar + slot) & IORESOURCE_MEM)) {
+ printk(KERN_ERR DRIVER_NAME ": BAR is not iomem. Aborting.\n");
+ return -ENODEV;
+ }
+
+ if (pci_resource_len(pdev, first_bar + slot) != 0x100) {
+ printk(KERN_ERR DRIVER_NAME ": Invalid iomem size. "
+ "You may experience problems.\n");
+ }
+
+ if ((pdev->class & 0x0000FF) == PCI_SDHCI_IFVENDOR) {
+ printk(KERN_ERR DRIVER_NAME ": Vendor specific interface. Aborting.\n");
+ return -ENODEV;
+ }
+
+ if ((pdev->class & 0x0000FF) > PCI_SDHCI_IFVENDOR) {
+ printk(KERN_ERR DRIVER_NAME ": Unknown interface. Aborting.\n");
+ return -ENODEV;
+ }
+
+ mmc = mmc_alloc_host(sizeof(struct sdhci_host), &pdev->dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+
+ host->chip = chip;
+ chip->hosts[slot] = host;
+
+ host->bar = first_bar + slot;
+
+ host->addr = pci_resource_start(pdev, host->bar);
+ host->irq = pdev->irq;
+
+ DBG("slot %d at 0x%08lx, irq %d\n", slot, host->addr, host->irq);
+
+ snprintf(host->slot_descr, 20, "sdhci:slot%d", slot);
+
+ ret = pci_request_region(pdev, host->bar, host->slot_descr);
+ if (ret)
+ goto free;
+
+ host->ioaddr = ioremap_nocache(host->addr,
+ pci_resource_len(pdev, host->bar));
+ if (!host->ioaddr) {
+ ret = -ENOMEM;
+ goto release;
+ }
+
+ sdhci_reset(host, SDHCI_RESET_ALL);
+
+ version = readw(host->ioaddr + SDHCI_HOST_VERSION);
+ version = (version & SDHCI_SPEC_VER_MASK) >> SDHCI_SPEC_VER_SHIFT;
+ if (version != 0) {
+ printk(KERN_ERR "%s: Unknown controller version (%d). "
+ "You may experience problems.\n", host->slot_descr,
+ version);
+ }
+
+ caps = readl(host->ioaddr + SDHCI_CAPABILITIES);
+
+ if (debug_nodma)
+ DBG("DMA forced off\n");
+ else if (debug_forcedma) {
+ DBG("DMA forced on\n");
+ host->flags |= SDHCI_USE_DMA;
+ } else if (chip->quirks & SDHCI_QUIRK_FORCE_DMA)
+ host->flags |= SDHCI_USE_DMA;
+ else if ((pdev->class & 0x0000FF) != PCI_SDHCI_IFDMA)
+ DBG("Controller doesn't have DMA interface\n");
+ else if (!(caps & SDHCI_CAN_DO_DMA))
+ DBG("Controller doesn't have DMA capability\n");
+ else
+ host->flags |= SDHCI_USE_DMA;
+
+ if (host->flags & SDHCI_USE_DMA) {
+ if (pci_set_dma_mask(pdev, DMA_32BIT_MASK)) {
+ printk(KERN_WARNING "%s: No suitable DMA available. "
+ "Falling back to PIO.\n", host->slot_descr);
+ host->flags &= ~SDHCI_USE_DMA;
+ }
+ }
+
+ if (host->flags & SDHCI_USE_DMA)
+ pci_set_master(pdev);
+ else /* XXX: Hack to get MMC layer to avoid highmem */
+ pdev->dma_mask = 0;
+
+ host->max_clk =
+ (caps & SDHCI_CLOCK_BASE_MASK) >> SDHCI_CLOCK_BASE_SHIFT;
+ if (host->max_clk == 0) {
+ printk(KERN_ERR "%s: Hardware doesn't specify base clock "
+ "frequency.\n", host->slot_descr);
+ ret = -ENODEV;
+ goto unmap;
+ }
+ host->max_clk *= 1000000;
+
+ host->timeout_clk =
+ (caps & SDHCI_TIMEOUT_CLK_MASK) >> SDHCI_TIMEOUT_CLK_SHIFT;
+ if (host->timeout_clk == 0) {
+ printk(KERN_ERR "%s: Hardware doesn't specify timeout clock "
+ "frequency.\n", host->slot_descr);
+ ret = -ENODEV;
+ goto unmap;
+ }
+ if (caps & SDHCI_TIMEOUT_CLK_UNIT)
+ host->timeout_clk *= 1000;
+
+ /*
+ * Set host parameters.
+ */
+ mmc->ops = &sdhci_ops;
+ mmc->f_min = host->max_clk / 256;
+ mmc->f_max = host->max_clk;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK;
+
+ if (caps & SDHCI_CAN_DO_HISPD)
+ mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+
+ mmc->ocr_avail = 0;
+ if (caps & SDHCI_CAN_VDD_330)
+ mmc->ocr_avail |= MMC_VDD_32_33|MMC_VDD_33_34;
+ if (caps & SDHCI_CAN_VDD_300)
+ mmc->ocr_avail |= MMC_VDD_29_30|MMC_VDD_30_31;
+ if (caps & SDHCI_CAN_VDD_180)
+ mmc->ocr_avail |= MMC_VDD_17_18|MMC_VDD_18_19;
+
+ if (mmc->ocr_avail == 0) {
+ printk(KERN_ERR "%s: Hardware doesn't report any "
+ "support voltages.\n", host->slot_descr);
+ ret = -ENODEV;
+ goto unmap;
+ }
+
+ spin_lock_init(&host->lock);
+
+ /*
+ * Maximum number of segments. Hardware cannot do scatter lists.
+ */
+ if (host->flags & SDHCI_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 & SDHCI_MAX_BLOCK_MASK) >> SDHCI_MAX_BLOCK_SHIFT;
+ if (mmc->max_blk_size >= 3) {
+ printk(KERN_ERR "%s: Invalid maximum block size.\n",
+ host->slot_descr);
+ ret = -ENODEV;
+ goto unmap;
+ }
+ mmc->max_blk_size = 512 << mmc->max_blk_size;
+
+ /*
+ * Maximum block count.
+ */
+ mmc->max_blk_count = 65535;
+
+ /*
+ * Init tasklets.
+ */
+ tasklet_init(&host->card_tasklet,
+ sdhci_tasklet_card, (unsigned long)host);
+ tasklet_init(&host->finish_tasklet,
+ sdhci_tasklet_finish, (unsigned long)host);
+
+ setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host);
+
+ ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,
+ host->slot_descr, host);
+ if (ret)
+ goto untasklet;
+
+ sdhci_init(host);
+
+#ifdef CONFIG_MMC_DEBUG
+ sdhci_dumpregs(host);
+#endif
+
+ mmiowb();
+
+ mmc_add_host(mmc);
+
+ printk(KERN_INFO "%s: SDHCI at 0x%08lx irq %d %s\n", mmc_hostname(mmc),
+ host->addr, host->irq,
+ (host->flags & SDHCI_USE_DMA)?"DMA":"PIO");
+
+ return 0;
+
+untasklet:
+ tasklet_kill(&host->card_tasklet);
+ tasklet_kill(&host->finish_tasklet);
+unmap:
+ iounmap(host->ioaddr);
+release:
+ pci_release_region(pdev, host->bar);
+free:
+ mmc_free_host(mmc);
+
+ return ret;
+}
+
+static void sdhci_remove_slot(struct pci_dev *pdev, int slot)
+{
+ struct sdhci_chip *chip;
+ struct mmc_host *mmc;
+ struct sdhci_host *host;
+
+ chip = pci_get_drvdata(pdev);
+ host = chip->hosts[slot];
+ mmc = host->mmc;
+
+ chip->hosts[slot] = NULL;
+
+ mmc_remove_host(mmc);
+
+ sdhci_reset(host, SDHCI_RESET_ALL);
+
+ free_irq(host->irq, host);
+
+ del_timer_sync(&host->timer);
+
+ tasklet_kill(&host->card_tasklet);
+ tasklet_kill(&host->finish_tasklet);
+
+ iounmap(host->ioaddr);
+
+ pci_release_region(pdev, host->bar);
+
+ mmc_free_host(mmc);
+}
+
+static int __devinit sdhci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ int ret, i;
+ u8 slots, rev;
+ struct sdhci_chip *chip;
+
+ BUG_ON(pdev == NULL);
+ BUG_ON(ent == NULL);
+
+ pci_read_config_byte(pdev, PCI_CLASS_REVISION, &rev);
+
+ printk(KERN_INFO DRIVER_NAME
+ ": SDHCI controller found at %s [%04x:%04x] (rev %x)\n",
+ pci_name(pdev), (int)pdev->vendor, (int)pdev->device,
+ (int)rev);
+
+ ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &slots);
+ if (ret)
+ return ret;
+
+ slots = PCI_SLOT_INFO_SLOTS(slots) + 1;
+ DBG("found %d slot(s)\n", slots);
+ if (slots == 0)
+ return -ENODEV;
+
+ ret = pci_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ chip = kzalloc(sizeof(struct sdhci_chip) +
+ sizeof(struct sdhci_host*) * slots, GFP_KERNEL);
+ if (!chip) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ chip->pdev = pdev;
+ chip->quirks = ent->driver_data;
+
+ if (debug_quirks)
+ chip->quirks = debug_quirks;
+
+ chip->num_slots = slots;
+ pci_set_drvdata(pdev, chip);
+
+ for (i = 0;i < slots;i++) {
+ ret = sdhci_probe_slot(pdev, i);
+ if (ret) {
+ for (i--;i >= 0;i--)
+ sdhci_remove_slot(pdev, i);
+ goto free;
+ }
+ }
+
+ return 0;
+
+free:
+ pci_set_drvdata(pdev, NULL);
+ kfree(chip);
+
+err:
+ pci_disable_device(pdev);
+ return ret;
+}
+
+static void __devexit sdhci_remove(struct pci_dev *pdev)
+{
+ int i;
+ struct sdhci_chip *chip;
+
+ chip = pci_get_drvdata(pdev);
+
+ if (chip) {
+ for (i = 0;i < chip->num_slots;i++)
+ sdhci_remove_slot(pdev, i);
+
+ pci_set_drvdata(pdev, NULL);
+
+ kfree(chip);
+ }
+
+ pci_disable_device(pdev);
+}
+
+static struct pci_driver sdhci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pci_ids,
+ .probe = sdhci_probe,
+ .remove = __devexit_p(sdhci_remove),
+ .suspend = sdhci_suspend,
+ .resume = sdhci_resume,
+};
+
+/*****************************************************************************\
+ * *
+ * Driver init/exit *
+ * *
+\*****************************************************************************/
+
+static int __init sdhci_drv_init(void)
+{
+ printk(KERN_INFO DRIVER_NAME
+ ": Secure Digital Host Controller Interface driver\n");
+ printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
+
+ return pci_register_driver(&sdhci_driver);
+}
+
+static void __exit sdhci_drv_exit(void)
+{
+ DBG("Exiting\n");
+
+ pci_unregister_driver(&sdhci_driver);
+}
+
+module_init(sdhci_drv_init);
+module_exit(sdhci_drv_exit);
+
+module_param(debug_nodma, uint, 0444);
+module_param(debug_forcedma, uint, 0444);
+module_param(debug_quirks, uint, 0444);
+
+MODULE_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>");
+MODULE_DESCRIPTION("Secure Digital Host Controller Interface driver");
+MODULE_LICENSE("GPL");
+
+MODULE_PARM_DESC(debug_nodma, "Forcefully disable DMA transfers. (default 0)");
+MODULE_PARM_DESC(debug_forcedma, "Forcefully enable DMA transfers. (default 0)");
+MODULE_PARM_DESC(debug_quirks, "Force certain quirks.");
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
new file mode 100644
index 000000000000..7400f4bc114f
--- /dev/null
+++ b/drivers/mmc/host/sdhci.h
@@ -0,0 +1,210 @@
+/*
+ * linux/drivers/mmc/sdhci.h - Secure Digital Host Controller Interface driver
+ *
+ * Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved.
+ *
+ * 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.
+ */
+
+/*
+ * PCI registers
+ */
+
+#define PCI_SDHCI_IFPIO 0x00
+#define PCI_SDHCI_IFDMA 0x01
+#define PCI_SDHCI_IFVENDOR 0x02
+
+#define PCI_SLOT_INFO 0x40 /* 8 bits */
+#define PCI_SLOT_INFO_SLOTS(x) ((x >> 4) & 7)
+#define PCI_SLOT_INFO_FIRST_BAR_MASK 0x07
+
+/*
+ * Controller registers
+ */
+
+#define SDHCI_DMA_ADDRESS 0x00
+
+#define SDHCI_BLOCK_SIZE 0x04
+#define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF))
+
+#define SDHCI_BLOCK_COUNT 0x06
+
+#define SDHCI_ARGUMENT 0x08
+
+#define SDHCI_TRANSFER_MODE 0x0C
+#define SDHCI_TRNS_DMA 0x01
+#define SDHCI_TRNS_BLK_CNT_EN 0x02
+#define SDHCI_TRNS_ACMD12 0x04
+#define SDHCI_TRNS_READ 0x10
+#define SDHCI_TRNS_MULTI 0x20
+
+#define SDHCI_COMMAND 0x0E
+#define SDHCI_CMD_RESP_MASK 0x03
+#define SDHCI_CMD_CRC 0x08
+#define SDHCI_CMD_INDEX 0x10
+#define SDHCI_CMD_DATA 0x20
+
+#define SDHCI_CMD_RESP_NONE 0x00
+#define SDHCI_CMD_RESP_LONG 0x01
+#define SDHCI_CMD_RESP_SHORT 0x02
+#define SDHCI_CMD_RESP_SHORT_BUSY 0x03
+
+#define SDHCI_MAKE_CMD(c, f) (((c & 0xff) << 8) | (f & 0xff))
+
+#define SDHCI_RESPONSE 0x10
+
+#define SDHCI_BUFFER 0x20
+
+#define SDHCI_PRESENT_STATE 0x24
+#define SDHCI_CMD_INHIBIT 0x00000001
+#define SDHCI_DATA_INHIBIT 0x00000002
+#define SDHCI_DOING_WRITE 0x00000100
+#define SDHCI_DOING_READ 0x00000200
+#define SDHCI_SPACE_AVAILABLE 0x00000400
+#define SDHCI_DATA_AVAILABLE 0x00000800
+#define SDHCI_CARD_PRESENT 0x00010000
+#define SDHCI_WRITE_PROTECT 0x00080000
+
+#define SDHCI_HOST_CONTROL 0x28
+#define SDHCI_CTRL_LED 0x01
+#define SDHCI_CTRL_4BITBUS 0x02
+#define SDHCI_CTRL_HISPD 0x04
+
+#define SDHCI_POWER_CONTROL 0x29
+#define SDHCI_POWER_ON 0x01
+#define SDHCI_POWER_180 0x0A
+#define SDHCI_POWER_300 0x0C
+#define SDHCI_POWER_330 0x0E
+
+#define SDHCI_BLOCK_GAP_CONTROL 0x2A
+
+#define SDHCI_WALK_UP_CONTROL 0x2B
+
+#define SDHCI_CLOCK_CONTROL 0x2C
+#define SDHCI_DIVIDER_SHIFT 8
+#define SDHCI_CLOCK_CARD_EN 0x0004
+#define SDHCI_CLOCK_INT_STABLE 0x0002
+#define SDHCI_CLOCK_INT_EN 0x0001
+
+#define SDHCI_TIMEOUT_CONTROL 0x2E
+
+#define SDHCI_SOFTWARE_RESET 0x2F
+#define SDHCI_RESET_ALL 0x01
+#define SDHCI_RESET_CMD 0x02
+#define SDHCI_RESET_DATA 0x04
+
+#define SDHCI_INT_STATUS 0x30
+#define SDHCI_INT_ENABLE 0x34
+#define SDHCI_SIGNAL_ENABLE 0x38
+#define SDHCI_INT_RESPONSE 0x00000001
+#define SDHCI_INT_DATA_END 0x00000002
+#define SDHCI_INT_DMA_END 0x00000008
+#define SDHCI_INT_SPACE_AVAIL 0x00000010
+#define SDHCI_INT_DATA_AVAIL 0x00000020
+#define SDHCI_INT_CARD_INSERT 0x00000040
+#define SDHCI_INT_CARD_REMOVE 0x00000080
+#define SDHCI_INT_CARD_INT 0x00000100
+#define SDHCI_INT_TIMEOUT 0x00010000
+#define SDHCI_INT_CRC 0x00020000
+#define SDHCI_INT_END_BIT 0x00040000
+#define SDHCI_INT_INDEX 0x00080000
+#define SDHCI_INT_DATA_TIMEOUT 0x00100000
+#define SDHCI_INT_DATA_CRC 0x00200000
+#define SDHCI_INT_DATA_END_BIT 0x00400000
+#define SDHCI_INT_BUS_POWER 0x00800000
+#define SDHCI_INT_ACMD12ERR 0x01000000
+
+#define SDHCI_INT_NORMAL_MASK 0x00007FFF
+#define SDHCI_INT_ERROR_MASK 0xFFFF8000
+
+#define SDHCI_INT_CMD_MASK (SDHCI_INT_RESPONSE | SDHCI_INT_TIMEOUT | \
+ SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX)
+#define SDHCI_INT_DATA_MASK (SDHCI_INT_DATA_END | SDHCI_INT_DMA_END | \
+ SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | \
+ SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | \
+ SDHCI_INT_DATA_END_BIT)
+
+#define SDHCI_ACMD12_ERR 0x3C
+
+/* 3E-3F reserved */
+
+#define SDHCI_CAPABILITIES 0x40
+#define SDHCI_TIMEOUT_CLK_MASK 0x0000003F
+#define SDHCI_TIMEOUT_CLK_SHIFT 0
+#define SDHCI_TIMEOUT_CLK_UNIT 0x00000080
+#define SDHCI_CLOCK_BASE_MASK 0x00003F00
+#define SDHCI_CLOCK_BASE_SHIFT 8
+#define SDHCI_MAX_BLOCK_MASK 0x00030000
+#define SDHCI_MAX_BLOCK_SHIFT 16
+#define SDHCI_CAN_DO_HISPD 0x00200000
+#define SDHCI_CAN_DO_DMA 0x00400000
+#define SDHCI_CAN_VDD_330 0x01000000
+#define SDHCI_CAN_VDD_300 0x02000000
+#define SDHCI_CAN_VDD_180 0x04000000
+
+/* 44-47 reserved for more caps */
+
+#define SDHCI_MAX_CURRENT 0x48
+
+/* 4C-4F reserved for more max current */
+
+/* 50-FB reserved */
+
+#define SDHCI_SLOT_INT_STATUS 0xFC
+
+#define SDHCI_HOST_VERSION 0xFE
+#define SDHCI_VENDOR_VER_MASK 0xFF00
+#define SDHCI_VENDOR_VER_SHIFT 8
+#define SDHCI_SPEC_VER_MASK 0x00FF
+#define SDHCI_SPEC_VER_SHIFT 0
+
+struct sdhci_chip;
+
+struct sdhci_host {
+ struct sdhci_chip *chip;
+ struct mmc_host *mmc; /* MMC structure */
+
+ spinlock_t lock; /* Mutex */
+
+ int flags; /* Host attributes */
+#define SDHCI_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 */
+
+ 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 */
+
+ char slot_descr[20]; /* Name for reservations */
+
+ int irq; /* Device IRQ */
+ int bar; /* PCI BAR index */
+ unsigned long addr; /* Bus address */
+ void __iomem * ioaddr; /* Mapped address */
+
+ struct tasklet_struct card_tasklet; /* Tasklet structures */
+ struct tasklet_struct finish_tasklet;
+
+ struct timer_list timer; /* Timer for timeouts */
+};
+
+struct sdhci_chip {
+ struct pci_dev *pdev;
+
+ unsigned long quirks;
+
+ int num_slots; /* Slots on controller */
+ struct sdhci_host *hosts[0]; /* Pointers to hosts */
+};
diff --git a/drivers/mmc/host/tifm_sd.c b/drivers/mmc/host/tifm_sd.c
new file mode 100644
index 000000000000..b0d77d298412
--- /dev/null
+++ b/drivers/mmc/host/tifm_sd.c
@@ -0,0 +1,1102 @@
+/*
+ * tifm_sd.c - TI FlashMedia driver
+ *
+ * Copyright (C) 2006 Alex Dubov <oakad@yahoo.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Special thanks to Brad Campbell for extensive testing of this driver.
+ *
+ */
+
+
+#include <linux/tifm.h>
+#include <linux/mmc/host.h>
+#include <linux/highmem.h>
+#include <linux/scatterlist.h>
+#include <asm/io.h>
+
+#define DRIVER_NAME "tifm_sd"
+#define DRIVER_VERSION "0.8"
+
+static int no_dma = 0;
+static int fixed_timeout = 0;
+module_param(no_dma, bool, 0644);
+module_param(fixed_timeout, bool, 0644);
+
+/* Constants here are mostly from OMAP5912 datasheet */
+#define TIFM_MMCSD_RESET 0x0002
+#define TIFM_MMCSD_CLKMASK 0x03ff
+#define TIFM_MMCSD_POWER 0x0800
+#define TIFM_MMCSD_4BBUS 0x8000
+#define TIFM_MMCSD_RXDE 0x8000 /* rx dma enable */
+#define TIFM_MMCSD_TXDE 0x0080 /* tx dma enable */
+#define TIFM_MMCSD_BUFINT 0x0c00 /* set bits: AE, AF */
+#define TIFM_MMCSD_DPE 0x0020 /* data timeout counted in kilocycles */
+#define TIFM_MMCSD_INAB 0x0080 /* abort / initialize command */
+#define TIFM_MMCSD_READ 0x8000
+
+#define TIFM_MMCSD_ERRMASK 0x01e0 /* set bits: CCRC, CTO, DCRC, DTO */
+#define TIFM_MMCSD_EOC 0x0001 /* end of command phase */
+#define TIFM_MMCSD_CD 0x0002 /* card detect */
+#define TIFM_MMCSD_CB 0x0004 /* card enter busy state */
+#define TIFM_MMCSD_BRS 0x0008 /* block received/sent */
+#define TIFM_MMCSD_EOFB 0x0010 /* card exit busy state */
+#define TIFM_MMCSD_DTO 0x0020 /* data time-out */
+#define TIFM_MMCSD_DCRC 0x0040 /* data crc error */
+#define TIFM_MMCSD_CTO 0x0080 /* command time-out */
+#define TIFM_MMCSD_CCRC 0x0100 /* command crc error */
+#define TIFM_MMCSD_AF 0x0400 /* fifo almost full */
+#define TIFM_MMCSD_AE 0x0800 /* fifo almost empty */
+#define TIFM_MMCSD_OCRB 0x1000 /* OCR busy */
+#define TIFM_MMCSD_CIRQ 0x2000 /* card irq (cmd40/sdio) */
+#define TIFM_MMCSD_CERR 0x4000 /* card status error */
+
+#define TIFM_MMCSD_ODTO 0x0040 /* open drain / extended timeout */
+#define TIFM_MMCSD_CARD_RO 0x0200 /* card is read-only */
+
+#define TIFM_MMCSD_FIFO_SIZE 0x0020
+
+#define TIFM_MMCSD_RSP_R0 0x0000
+#define TIFM_MMCSD_RSP_R1 0x0100
+#define TIFM_MMCSD_RSP_R2 0x0200
+#define TIFM_MMCSD_RSP_R3 0x0300
+#define TIFM_MMCSD_RSP_R4 0x0400
+#define TIFM_MMCSD_RSP_R5 0x0500
+#define TIFM_MMCSD_RSP_R6 0x0600
+
+#define TIFM_MMCSD_RSP_BUSY 0x0800
+
+#define TIFM_MMCSD_CMD_BC 0x0000
+#define TIFM_MMCSD_CMD_BCR 0x1000
+#define TIFM_MMCSD_CMD_AC 0x2000
+#define TIFM_MMCSD_CMD_ADTC 0x3000
+
+#define TIFM_MMCSD_MAX_BLOCK_SIZE 0x0800UL
+
+enum {
+ CMD_READY = 0x0001,
+ FIFO_READY = 0x0002,
+ BRS_READY = 0x0004,
+ SCMD_ACTIVE = 0x0008,
+ SCMD_READY = 0x0010,
+ CARD_BUSY = 0x0020,
+ DATA_CARRY = 0x0040
+};
+
+struct tifm_sd {
+ struct tifm_dev *dev;
+
+ unsigned short eject:1,
+ open_drain:1,
+ no_dma:1;
+ unsigned short cmd_flags;
+
+ unsigned int clk_freq;
+ unsigned int clk_div;
+ unsigned long timeout_jiffies;
+
+ struct tasklet_struct finish_tasklet;
+ struct timer_list timer;
+ struct mmc_request *req;
+
+ int sg_len;
+ int sg_pos;
+ unsigned int block_pos;
+ struct scatterlist bounce_buf;
+ unsigned char bounce_buf_data[TIFM_MMCSD_MAX_BLOCK_SIZE];
+};
+
+/* for some reason, host won't respond correctly to readw/writew */
+static void tifm_sd_read_fifo(struct tifm_sd *host, struct page *pg,
+ unsigned int off, unsigned int cnt)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned char *buf;
+ unsigned int pos = 0, val;
+
+ buf = kmap_atomic(pg, KM_BIO_DST_IRQ) + off;
+ if (host->cmd_flags & DATA_CARRY) {
+ buf[pos++] = host->bounce_buf_data[0];
+ host->cmd_flags &= ~DATA_CARRY;
+ }
+
+ while (pos < cnt) {
+ val = readl(sock->addr + SOCK_MMCSD_DATA);
+ buf[pos++] = val & 0xff;
+ if (pos == cnt) {
+ host->bounce_buf_data[0] = (val >> 8) & 0xff;
+ host->cmd_flags |= DATA_CARRY;
+ break;
+ }
+ buf[pos++] = (val >> 8) & 0xff;
+ }
+ kunmap_atomic(buf - off, KM_BIO_DST_IRQ);
+}
+
+static void tifm_sd_write_fifo(struct tifm_sd *host, struct page *pg,
+ unsigned int off, unsigned int cnt)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned char *buf;
+ unsigned int pos = 0, val;
+
+ buf = kmap_atomic(pg, KM_BIO_SRC_IRQ) + off;
+ if (host->cmd_flags & DATA_CARRY) {
+ val = host->bounce_buf_data[0] | ((buf[pos++] << 8) & 0xff00);
+ writel(val, sock->addr + SOCK_MMCSD_DATA);
+ host->cmd_flags &= ~DATA_CARRY;
+ }
+
+ while (pos < cnt) {
+ val = buf[pos++];
+ if (pos == cnt) {
+ host->bounce_buf_data[0] = val & 0xff;
+ host->cmd_flags |= DATA_CARRY;
+ break;
+ }
+ val |= (buf[pos++] << 8) & 0xff00;
+ writel(val, sock->addr + SOCK_MMCSD_DATA);
+ }
+ kunmap_atomic(buf - off, KM_BIO_SRC_IRQ);
+}
+
+static void tifm_sd_transfer_data(struct tifm_sd *host)
+{
+ struct mmc_data *r_data = host->req->cmd->data;
+ struct scatterlist *sg = r_data->sg;
+ unsigned int off, cnt, t_size = TIFM_MMCSD_FIFO_SIZE * 2;
+ unsigned int p_off, p_cnt;
+ struct page *pg;
+
+ if (host->sg_pos == host->sg_len)
+ return;
+ while (t_size) {
+ cnt = sg[host->sg_pos].length - host->block_pos;
+ if (!cnt) {
+ host->block_pos = 0;
+ host->sg_pos++;
+ if (host->sg_pos == host->sg_len) {
+ if ((r_data->flags & MMC_DATA_WRITE)
+ && DATA_CARRY)
+ writel(host->bounce_buf_data[0],
+ host->dev->addr
+ + SOCK_MMCSD_DATA);
+
+ return;
+ }
+ cnt = sg[host->sg_pos].length;
+ }
+ off = sg[host->sg_pos].offset + host->block_pos;
+
+ pg = nth_page(sg[host->sg_pos].page, off >> PAGE_SHIFT);
+ p_off = offset_in_page(off);
+ p_cnt = PAGE_SIZE - p_off;
+ p_cnt = min(p_cnt, cnt);
+ p_cnt = min(p_cnt, t_size);
+
+ if (r_data->flags & MMC_DATA_READ)
+ tifm_sd_read_fifo(host, pg, p_off, p_cnt);
+ else if (r_data->flags & MMC_DATA_WRITE)
+ tifm_sd_write_fifo(host, pg, p_off, p_cnt);
+
+ t_size -= p_cnt;
+ host->block_pos += p_cnt;
+ }
+}
+
+static void tifm_sd_copy_page(struct page *dst, unsigned int dst_off,
+ struct page *src, unsigned int src_off,
+ unsigned int count)
+{
+ unsigned char *src_buf = kmap_atomic(src, KM_BIO_SRC_IRQ) + src_off;
+ unsigned char *dst_buf = kmap_atomic(dst, KM_BIO_DST_IRQ) + dst_off;
+
+ memcpy(dst_buf, src_buf, count);
+
+ kunmap_atomic(dst_buf - dst_off, KM_BIO_DST_IRQ);
+ kunmap_atomic(src_buf - src_off, KM_BIO_SRC_IRQ);
+}
+
+static void tifm_sd_bounce_block(struct tifm_sd *host, struct mmc_data *r_data)
+{
+ struct scatterlist *sg = r_data->sg;
+ unsigned int t_size = r_data->blksz;
+ unsigned int off, cnt;
+ unsigned int p_off, p_cnt;
+ struct page *pg;
+
+ dev_dbg(&host->dev->dev, "bouncing block\n");
+ while (t_size) {
+ cnt = sg[host->sg_pos].length - host->block_pos;
+ if (!cnt) {
+ host->block_pos = 0;
+ host->sg_pos++;
+ if (host->sg_pos == host->sg_len)
+ return;
+ cnt = sg[host->sg_pos].length;
+ }
+ off = sg[host->sg_pos].offset + host->block_pos;
+
+ pg = nth_page(sg[host->sg_pos].page, off >> PAGE_SHIFT);
+ p_off = offset_in_page(off);
+ p_cnt = PAGE_SIZE - p_off;
+ p_cnt = min(p_cnt, cnt);
+ p_cnt = min(p_cnt, t_size);
+
+ if (r_data->flags & MMC_DATA_WRITE)
+ tifm_sd_copy_page(host->bounce_buf.page,
+ r_data->blksz - t_size,
+ pg, p_off, p_cnt);
+ else if (r_data->flags & MMC_DATA_READ)
+ tifm_sd_copy_page(pg, p_off, host->bounce_buf.page,
+ r_data->blksz - t_size, p_cnt);
+
+ t_size -= p_cnt;
+ host->block_pos += p_cnt;
+ }
+}
+
+int tifm_sd_set_dma_data(struct tifm_sd *host, struct mmc_data *r_data)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned int t_size = TIFM_DMA_TSIZE * r_data->blksz;
+ unsigned int dma_len, dma_blk_cnt, dma_off;
+ struct scatterlist *sg = NULL;
+ unsigned long flags;
+
+ if (host->sg_pos == host->sg_len)
+ return 1;
+
+ if (host->cmd_flags & DATA_CARRY) {
+ host->cmd_flags &= ~DATA_CARRY;
+ local_irq_save(flags);
+ tifm_sd_bounce_block(host, r_data);
+ local_irq_restore(flags);
+ if (host->sg_pos == host->sg_len)
+ return 1;
+ }
+
+ dma_len = sg_dma_len(&r_data->sg[host->sg_pos]) - host->block_pos;
+ if (!dma_len) {
+ host->block_pos = 0;
+ host->sg_pos++;
+ if (host->sg_pos == host->sg_len)
+ return 1;
+ dma_len = sg_dma_len(&r_data->sg[host->sg_pos]);
+ }
+
+ if (dma_len < t_size) {
+ dma_blk_cnt = dma_len / r_data->blksz;
+ dma_off = host->block_pos;
+ host->block_pos += dma_blk_cnt * r_data->blksz;
+ } else {
+ dma_blk_cnt = TIFM_DMA_TSIZE;
+ dma_off = host->block_pos;
+ host->block_pos += t_size;
+ }
+
+ if (dma_blk_cnt)
+ sg = &r_data->sg[host->sg_pos];
+ else if (dma_len) {
+ if (r_data->flags & MMC_DATA_WRITE) {
+ local_irq_save(flags);
+ tifm_sd_bounce_block(host, r_data);
+ local_irq_restore(flags);
+ } else
+ host->cmd_flags |= DATA_CARRY;
+
+ sg = &host->bounce_buf;
+ dma_off = 0;
+ dma_blk_cnt = 1;
+ } else
+ return 1;
+
+ dev_dbg(&sock->dev, "setting dma for %d blocks\n", dma_blk_cnt);
+ writel(sg_dma_address(sg) + dma_off, sock->addr + SOCK_DMA_ADDRESS);
+ if (r_data->flags & MMC_DATA_WRITE)
+ writel((dma_blk_cnt << 8) | TIFM_DMA_TX | TIFM_DMA_EN,
+ sock->addr + SOCK_DMA_CONTROL);
+ else
+ writel((dma_blk_cnt << 8) | TIFM_DMA_EN,
+ sock->addr + SOCK_DMA_CONTROL);
+
+ return 0;
+}
+
+static unsigned int tifm_sd_op_flags(struct mmc_command *cmd)
+{
+ unsigned int rc = 0;
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ rc |= TIFM_MMCSD_RSP_R0;
+ break;
+ case MMC_RSP_R1B:
+ rc |= TIFM_MMCSD_RSP_BUSY; // deliberate fall-through
+ case MMC_RSP_R1:
+ rc |= TIFM_MMCSD_RSP_R1;
+ break;
+ case MMC_RSP_R2:
+ rc |= TIFM_MMCSD_RSP_R2;
+ break;
+ case MMC_RSP_R3:
+ rc |= TIFM_MMCSD_RSP_R3;
+ break;
+ default:
+ BUG();
+ }
+
+ switch (mmc_cmd_type(cmd)) {
+ case MMC_CMD_BC:
+ rc |= TIFM_MMCSD_CMD_BC;
+ break;
+ case MMC_CMD_BCR:
+ rc |= TIFM_MMCSD_CMD_BCR;
+ break;
+ case MMC_CMD_AC:
+ rc |= TIFM_MMCSD_CMD_AC;
+ break;
+ case MMC_CMD_ADTC:
+ rc |= TIFM_MMCSD_CMD_ADTC;
+ break;
+ default:
+ BUG();
+ }
+ return rc;
+}
+
+static void tifm_sd_exec(struct tifm_sd *host, struct mmc_command *cmd)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned int cmd_mask = tifm_sd_op_flags(cmd);
+
+ if (host->open_drain)
+ cmd_mask |= TIFM_MMCSD_ODTO;
+
+ if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+ cmd_mask |= TIFM_MMCSD_READ;
+
+ dev_dbg(&sock->dev, "executing opcode 0x%x, arg: 0x%x, mask: 0x%x\n",
+ cmd->opcode, cmd->arg, cmd_mask);
+
+ writel((cmd->arg >> 16) & 0xffff, sock->addr + SOCK_MMCSD_ARG_HIGH);
+ writel(cmd->arg & 0xffff, sock->addr + SOCK_MMCSD_ARG_LOW);
+ writel(cmd->opcode | cmd_mask, sock->addr + SOCK_MMCSD_COMMAND);
+}
+
+static void tifm_sd_fetch_resp(struct mmc_command *cmd, struct tifm_dev *sock)
+{
+ cmd->resp[0] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x1c) << 16)
+ | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x18);
+ cmd->resp[1] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x14) << 16)
+ | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x10);
+ cmd->resp[2] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x0c) << 16)
+ | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x08);
+ cmd->resp[3] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x04) << 16)
+ | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x00);
+}
+
+static void tifm_sd_check_status(struct tifm_sd *host)
+{
+ struct tifm_dev *sock = host->dev;
+ struct mmc_command *cmd = host->req->cmd;
+
+ if (cmd->error != MMC_ERR_NONE)
+ goto finish_request;
+
+ if (!(host->cmd_flags & CMD_READY))
+ return;
+
+ if (cmd->data) {
+ if (cmd->data->error != MMC_ERR_NONE) {
+ if ((host->cmd_flags & SCMD_ACTIVE)
+ && !(host->cmd_flags & SCMD_READY))
+ return;
+
+ goto finish_request;
+ }
+
+ if (!(host->cmd_flags & BRS_READY))
+ return;
+
+ if (!(host->no_dma || (host->cmd_flags & FIFO_READY)))
+ return;
+
+ if (cmd->data->flags & MMC_DATA_WRITE) {
+ if (host->req->stop) {
+ if (!(host->cmd_flags & SCMD_ACTIVE)) {
+ host->cmd_flags |= SCMD_ACTIVE;
+ writel(TIFM_MMCSD_EOFB
+ | readl(sock->addr
+ + SOCK_MMCSD_INT_ENABLE),
+ sock->addr
+ + SOCK_MMCSD_INT_ENABLE);
+ tifm_sd_exec(host, host->req->stop);
+ return;
+ } else {
+ if (!(host->cmd_flags & SCMD_READY)
+ || (host->cmd_flags & CARD_BUSY))
+ return;
+ writel((~TIFM_MMCSD_EOFB)
+ & readl(sock->addr
+ + SOCK_MMCSD_INT_ENABLE),
+ sock->addr
+ + SOCK_MMCSD_INT_ENABLE);
+ }
+ } else {
+ if (host->cmd_flags & CARD_BUSY)
+ return;
+ writel((~TIFM_MMCSD_EOFB)
+ & readl(sock->addr
+ + SOCK_MMCSD_INT_ENABLE),
+ sock->addr + SOCK_MMCSD_INT_ENABLE);
+ }
+ } else {
+ if (host->req->stop) {
+ if (!(host->cmd_flags & SCMD_ACTIVE)) {
+ host->cmd_flags |= SCMD_ACTIVE;
+ tifm_sd_exec(host, host->req->stop);
+ return;
+ } else {
+ if (!(host->cmd_flags & SCMD_READY))
+ return;
+ }
+ }
+ }
+ }
+finish_request:
+ tasklet_schedule(&host->finish_tasklet);
+}
+
+/* Called from interrupt handler */
+static void tifm_sd_data_event(struct tifm_dev *sock)
+{
+ struct tifm_sd *host;
+ unsigned int fifo_status = 0;
+ struct mmc_data *r_data = NULL;
+
+ spin_lock(&sock->lock);
+ host = mmc_priv((struct mmc_host*)tifm_get_drvdata(sock));
+ fifo_status = readl(sock->addr + SOCK_DMA_FIFO_STATUS);
+ dev_dbg(&sock->dev, "data event: fifo_status %x, flags %x\n",
+ fifo_status, host->cmd_flags);
+
+ if (host->req) {
+ r_data = host->req->cmd->data;
+
+ if (r_data && (fifo_status & TIFM_FIFO_READY)) {
+ if (tifm_sd_set_dma_data(host, r_data)) {
+ host->cmd_flags |= FIFO_READY;
+ tifm_sd_check_status(host);
+ }
+ }
+ }
+
+ writel(fifo_status, sock->addr + SOCK_DMA_FIFO_STATUS);
+ spin_unlock(&sock->lock);
+}
+
+/* Called from interrupt handler */
+static void tifm_sd_card_event(struct tifm_dev *sock)
+{
+ struct tifm_sd *host;
+ unsigned int host_status = 0;
+ int cmd_error = MMC_ERR_NONE;
+ struct mmc_command *cmd = NULL;
+ unsigned long flags;
+
+ spin_lock(&sock->lock);
+ host = mmc_priv((struct mmc_host*)tifm_get_drvdata(sock));
+ host_status = readl(sock->addr + SOCK_MMCSD_STATUS);
+ dev_dbg(&sock->dev, "host event: host_status %x, flags %x\n",
+ host_status, host->cmd_flags);
+
+ if (host->req) {
+ cmd = host->req->cmd;
+
+ if (host_status & TIFM_MMCSD_ERRMASK) {
+ writel(host_status & TIFM_MMCSD_ERRMASK,
+ sock->addr + SOCK_MMCSD_STATUS);
+ if (host_status & TIFM_MMCSD_CTO)
+ cmd_error = MMC_ERR_TIMEOUT;
+ else if (host_status & TIFM_MMCSD_CCRC)
+ cmd_error = MMC_ERR_BADCRC;
+
+ if (cmd->data) {
+ if (host_status & TIFM_MMCSD_DTO)
+ cmd->data->error = MMC_ERR_TIMEOUT;
+ else if (host_status & TIFM_MMCSD_DCRC)
+ cmd->data->error = MMC_ERR_BADCRC;
+ }
+
+ writel(TIFM_FIFO_INT_SETALL,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
+ writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL);
+
+ if (host->req->stop) {
+ if (host->cmd_flags & SCMD_ACTIVE) {
+ host->req->stop->error = cmd_error;
+ host->cmd_flags |= SCMD_READY;
+ } else {
+ cmd->error = cmd_error;
+ host->cmd_flags |= SCMD_ACTIVE;
+ tifm_sd_exec(host, host->req->stop);
+ goto done;
+ }
+ } else
+ cmd->error = cmd_error;
+ } else {
+ if (host_status & (TIFM_MMCSD_EOC | TIFM_MMCSD_CERR)) {
+ if (!(host->cmd_flags & CMD_READY)) {
+ host->cmd_flags |= CMD_READY;
+ tifm_sd_fetch_resp(cmd, sock);
+ } else if (host->cmd_flags & SCMD_ACTIVE) {
+ host->cmd_flags |= SCMD_READY;
+ tifm_sd_fetch_resp(host->req->stop,
+ sock);
+ }
+ }
+ if (host_status & TIFM_MMCSD_BRS)
+ host->cmd_flags |= BRS_READY;
+ }
+
+ if (host->no_dma && cmd->data) {
+ if (host_status & TIFM_MMCSD_AE)
+ writel(host_status & TIFM_MMCSD_AE,
+ sock->addr + SOCK_MMCSD_STATUS);
+
+ if (host_status & (TIFM_MMCSD_AE | TIFM_MMCSD_AF
+ | TIFM_MMCSD_BRS)) {
+ local_irq_save(flags);
+ tifm_sd_transfer_data(host);
+ local_irq_restore(flags);
+ host_status &= ~TIFM_MMCSD_AE;
+ }
+ }
+
+ if (host_status & TIFM_MMCSD_EOFB)
+ host->cmd_flags &= ~CARD_BUSY;
+ else if (host_status & TIFM_MMCSD_CB)
+ host->cmd_flags |= CARD_BUSY;
+
+ tifm_sd_check_status(host);
+ }
+done:
+ writel(host_status, sock->addr + SOCK_MMCSD_STATUS);
+ spin_unlock(&sock->lock);
+}
+
+static void tifm_sd_set_data_timeout(struct tifm_sd *host,
+ struct mmc_data *data)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned int data_timeout = data->timeout_clks;
+
+ if (fixed_timeout)
+ return;
+
+ data_timeout += data->timeout_ns /
+ ((1000000000UL / host->clk_freq) * host->clk_div);
+
+ if (data_timeout < 0xffff) {
+ writel(data_timeout, sock->addr + SOCK_MMCSD_DATA_TO);
+ writel((~TIFM_MMCSD_DPE)
+ & readl(sock->addr + SOCK_MMCSD_SDIO_MODE_CONFIG),
+ sock->addr + SOCK_MMCSD_SDIO_MODE_CONFIG);
+ } else {
+ data_timeout = (data_timeout >> 10) + 1;
+ if (data_timeout > 0xffff)
+ data_timeout = 0; /* set to unlimited */
+ writel(data_timeout, sock->addr + SOCK_MMCSD_DATA_TO);
+ writel(TIFM_MMCSD_DPE
+ | readl(sock->addr + SOCK_MMCSD_SDIO_MODE_CONFIG),
+ sock->addr + SOCK_MMCSD_SDIO_MODE_CONFIG);
+ }
+}
+
+static void tifm_sd_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct tifm_sd *host = mmc_priv(mmc);
+ struct tifm_dev *sock = host->dev;
+ unsigned long flags;
+ struct mmc_data *r_data = mrq->cmd->data;
+
+ spin_lock_irqsave(&sock->lock, flags);
+ if (host->eject) {
+ spin_unlock_irqrestore(&sock->lock, flags);
+ goto err_out;
+ }
+
+ if (host->req) {
+ printk(KERN_ERR "%s : unfinished request detected\n",
+ sock->dev.bus_id);
+ spin_unlock_irqrestore(&sock->lock, flags);
+ goto err_out;
+ }
+
+ host->cmd_flags = 0;
+ host->block_pos = 0;
+ host->sg_pos = 0;
+
+ if (r_data) {
+ tifm_sd_set_data_timeout(host, r_data);
+
+ if ((r_data->flags & MMC_DATA_WRITE) && !mrq->stop)
+ writel(TIFM_MMCSD_EOFB
+ | readl(sock->addr + SOCK_MMCSD_INT_ENABLE),
+ sock->addr + SOCK_MMCSD_INT_ENABLE);
+
+ if (host->no_dma) {
+ writel(TIFM_MMCSD_BUFINT
+ | readl(sock->addr + SOCK_MMCSD_INT_ENABLE),
+ sock->addr + SOCK_MMCSD_INT_ENABLE);
+ writel(((TIFM_MMCSD_FIFO_SIZE - 1) << 8)
+ | (TIFM_MMCSD_FIFO_SIZE - 1),
+ sock->addr + SOCK_MMCSD_BUFFER_CONFIG);
+
+ host->sg_len = r_data->sg_len;
+ } else {
+ sg_init_one(&host->bounce_buf, host->bounce_buf_data,
+ r_data->blksz);
+
+ if(1 != tifm_map_sg(sock, &host->bounce_buf, 1,
+ r_data->flags & MMC_DATA_WRITE
+ ? PCI_DMA_TODEVICE
+ : PCI_DMA_FROMDEVICE)) {
+ printk(KERN_ERR "%s : scatterlist map failed\n",
+ sock->dev.bus_id);
+ spin_unlock_irqrestore(&sock->lock, flags);
+ goto err_out;
+ }
+ host->sg_len = tifm_map_sg(sock, r_data->sg,
+ r_data->sg_len,
+ r_data->flags
+ & MMC_DATA_WRITE
+ ? PCI_DMA_TODEVICE
+ : PCI_DMA_FROMDEVICE);
+ if (host->sg_len < 1) {
+ printk(KERN_ERR "%s : scatterlist map failed\n",
+ sock->dev.bus_id);
+ tifm_unmap_sg(sock, &host->bounce_buf, 1,
+ r_data->flags & MMC_DATA_WRITE
+ ? PCI_DMA_TODEVICE
+ : PCI_DMA_FROMDEVICE);
+ spin_unlock_irqrestore(&sock->lock, flags);
+ goto err_out;
+ }
+
+ writel(TIFM_FIFO_INT_SETALL,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
+ writel(ilog2(r_data->blksz) - 2,
+ sock->addr + SOCK_FIFO_PAGE_SIZE);
+ writel(TIFM_FIFO_ENABLE,
+ sock->addr + SOCK_FIFO_CONTROL);
+ writel(TIFM_FIFO_INTMASK,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
+
+ if (r_data->flags & MMC_DATA_WRITE)
+ writel(TIFM_MMCSD_TXDE,
+ sock->addr + SOCK_MMCSD_BUFFER_CONFIG);
+ else
+ writel(TIFM_MMCSD_RXDE,
+ sock->addr + SOCK_MMCSD_BUFFER_CONFIG);
+
+ tifm_sd_set_dma_data(host, r_data);
+ }
+
+ writel(r_data->blocks - 1,
+ sock->addr + SOCK_MMCSD_NUM_BLOCKS);
+ writel(r_data->blksz - 1,
+ sock->addr + SOCK_MMCSD_BLOCK_LEN);
+ }
+
+ host->req = mrq;
+ mod_timer(&host->timer, jiffies + host->timeout_jiffies);
+ writel(TIFM_CTRL_LED | readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+ tifm_sd_exec(host, mrq->cmd);
+ spin_unlock_irqrestore(&sock->lock, flags);
+ return;
+
+err_out:
+ mrq->cmd->error = MMC_ERR_TIMEOUT;
+ mmc_request_done(mmc, mrq);
+}
+
+static void tifm_sd_end_cmd(unsigned long data)
+{
+ struct tifm_sd *host = (struct tifm_sd*)data;
+ struct tifm_dev *sock = host->dev;
+ struct mmc_host *mmc = tifm_get_drvdata(sock);
+ struct mmc_request *mrq;
+ struct mmc_data *r_data = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sock->lock, flags);
+
+ del_timer(&host->timer);
+ mrq = host->req;
+ host->req = NULL;
+
+ if (!mrq) {
+ printk(KERN_ERR " %s : no request to complete?\n",
+ sock->dev.bus_id);
+ spin_unlock_irqrestore(&sock->lock, flags);
+ return;
+ }
+
+ r_data = mrq->cmd->data;
+ if (r_data) {
+ if (host->no_dma) {
+ writel((~TIFM_MMCSD_BUFINT)
+ & readl(sock->addr + SOCK_MMCSD_INT_ENABLE),
+ sock->addr + SOCK_MMCSD_INT_ENABLE);
+ } else {
+ tifm_unmap_sg(sock, &host->bounce_buf, 1,
+ (r_data->flags & MMC_DATA_WRITE)
+ ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE);
+ tifm_unmap_sg(sock, r_data->sg, r_data->sg_len,
+ (r_data->flags & MMC_DATA_WRITE)
+ ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE);
+ }
+
+ r_data->bytes_xfered = r_data->blocks
+ - readl(sock->addr + SOCK_MMCSD_NUM_BLOCKS) - 1;
+ r_data->bytes_xfered *= r_data->blksz;
+ r_data->bytes_xfered += r_data->blksz
+ - readl(sock->addr + SOCK_MMCSD_BLOCK_LEN) + 1;
+ }
+
+ writel((~TIFM_CTRL_LED) & readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+
+ spin_unlock_irqrestore(&sock->lock, flags);
+ mmc_request_done(mmc, mrq);
+}
+
+static void tifm_sd_abort(unsigned long data)
+{
+ struct tifm_sd *host = (struct tifm_sd*)data;
+
+ printk(KERN_ERR
+ "%s : card failed to respond for a long period of time "
+ "(%x, %x)\n",
+ host->dev->dev.bus_id, host->req->cmd->opcode, host->cmd_flags);
+
+ tifm_eject(host->dev);
+}
+
+static void tifm_sd_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct tifm_sd *host = mmc_priv(mmc);
+ struct tifm_dev *sock = host->dev;
+ unsigned int clk_div1, clk_div2;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sock->lock, flags);
+
+ dev_dbg(&sock->dev, "ios: clock = %u, vdd = %x, bus_mode = %x, "
+ "chip_select = %x, power_mode = %x, bus_width = %x\n",
+ ios->clock, ios->vdd, ios->bus_mode, ios->chip_select,
+ ios->power_mode, ios->bus_width);
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4) {
+ writel(TIFM_MMCSD_4BBUS | readl(sock->addr + SOCK_MMCSD_CONFIG),
+ sock->addr + SOCK_MMCSD_CONFIG);
+ } else {
+ writel((~TIFM_MMCSD_4BBUS)
+ & readl(sock->addr + SOCK_MMCSD_CONFIG),
+ sock->addr + SOCK_MMCSD_CONFIG);
+ }
+
+ if (ios->clock) {
+ clk_div1 = 20000000 / ios->clock;
+ if (!clk_div1)
+ clk_div1 = 1;
+
+ clk_div2 = 24000000 / ios->clock;
+ if (!clk_div2)
+ clk_div2 = 1;
+
+ if ((20000000 / clk_div1) > ios->clock)
+ clk_div1++;
+ if ((24000000 / clk_div2) > ios->clock)
+ clk_div2++;
+ if ((20000000 / clk_div1) > (24000000 / clk_div2)) {
+ host->clk_freq = 20000000;
+ host->clk_div = clk_div1;
+ writel((~TIFM_CTRL_FAST_CLK)
+ & readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+ } else {
+ host->clk_freq = 24000000;
+ host->clk_div = clk_div2;
+ writel(TIFM_CTRL_FAST_CLK
+ | readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+ }
+ } else {
+ host->clk_div = 0;
+ }
+ host->clk_div &= TIFM_MMCSD_CLKMASK;
+ writel(host->clk_div
+ | ((~TIFM_MMCSD_CLKMASK)
+ & readl(sock->addr + SOCK_MMCSD_CONFIG)),
+ sock->addr + SOCK_MMCSD_CONFIG);
+
+ host->open_drain = (ios->bus_mode == MMC_BUSMODE_OPENDRAIN);
+
+ /* chip_select : maybe later */
+ //vdd
+ //power is set before probe / after remove
+
+ spin_unlock_irqrestore(&sock->lock, flags);
+}
+
+static int tifm_sd_ro(struct mmc_host *mmc)
+{
+ int rc = 0;
+ struct tifm_sd *host = mmc_priv(mmc);
+ struct tifm_dev *sock = host->dev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sock->lock, flags);
+ if (TIFM_MMCSD_CARD_RO & readl(sock->addr + SOCK_PRESENT_STATE))
+ rc = 1;
+ spin_unlock_irqrestore(&sock->lock, flags);
+ return rc;
+}
+
+static const struct mmc_host_ops tifm_sd_ops = {
+ .request = tifm_sd_request,
+ .set_ios = tifm_sd_ios,
+ .get_ro = tifm_sd_ro
+};
+
+static int tifm_sd_initialize_host(struct tifm_sd *host)
+{
+ int rc;
+ unsigned int host_status = 0;
+ struct tifm_dev *sock = host->dev;
+
+ writel(0, sock->addr + SOCK_MMCSD_INT_ENABLE);
+ mmiowb();
+ host->clk_div = 61;
+ host->clk_freq = 20000000;
+ writel(TIFM_MMCSD_RESET, sock->addr + SOCK_MMCSD_SYSTEM_CONTROL);
+ writel(host->clk_div | TIFM_MMCSD_POWER,
+ sock->addr + SOCK_MMCSD_CONFIG);
+
+ /* wait up to 0.51 sec for reset */
+ for (rc = 32; rc <= 256; rc <<= 1) {
+ if (1 & readl(sock->addr + SOCK_MMCSD_SYSTEM_STATUS)) {
+ rc = 0;
+ break;
+ }
+ msleep(rc);
+ }
+
+ if (rc) {
+ printk(KERN_ERR "%s : controller failed to reset\n",
+ sock->dev.bus_id);
+ return -ENODEV;
+ }
+
+ writel(0, sock->addr + SOCK_MMCSD_NUM_BLOCKS);
+ writel(host->clk_div | TIFM_MMCSD_POWER,
+ sock->addr + SOCK_MMCSD_CONFIG);
+ writel(TIFM_MMCSD_RXDE, sock->addr + SOCK_MMCSD_BUFFER_CONFIG);
+
+ // command timeout fixed to 64 clocks for now
+ writel(64, sock->addr + SOCK_MMCSD_COMMAND_TO);
+ writel(TIFM_MMCSD_INAB, sock->addr + SOCK_MMCSD_COMMAND);
+
+ for (rc = 16; rc <= 64; rc <<= 1) {
+ host_status = readl(sock->addr + SOCK_MMCSD_STATUS);
+ writel(host_status, sock->addr + SOCK_MMCSD_STATUS);
+ if (!(host_status & TIFM_MMCSD_ERRMASK)
+ && (host_status & TIFM_MMCSD_EOC)) {
+ rc = 0;
+ break;
+ }
+ msleep(rc);
+ }
+
+ if (rc) {
+ printk(KERN_ERR
+ "%s : card not ready - probe failed on initialization\n",
+ sock->dev.bus_id);
+ return -ENODEV;
+ }
+
+ writel(TIFM_MMCSD_CERR | TIFM_MMCSD_BRS | TIFM_MMCSD_EOC
+ | TIFM_MMCSD_ERRMASK,
+ sock->addr + SOCK_MMCSD_INT_ENABLE);
+ mmiowb();
+
+ return 0;
+}
+
+static int tifm_sd_probe(struct tifm_dev *sock)
+{
+ struct mmc_host *mmc;
+ struct tifm_sd *host;
+ int rc = -EIO;
+
+ if (!(TIFM_SOCK_STATE_OCCUPIED
+ & readl(sock->addr + SOCK_PRESENT_STATE))) {
+ printk(KERN_WARNING "%s : card gone, unexpectedly\n",
+ sock->dev.bus_id);
+ return rc;
+ }
+
+ mmc = mmc_alloc_host(sizeof(struct tifm_sd), &sock->dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ host = mmc_priv(mmc);
+ host->no_dma = no_dma;
+ tifm_set_drvdata(sock, mmc);
+ host->dev = sock;
+ host->timeout_jiffies = msecs_to_jiffies(1000);
+
+ tasklet_init(&host->finish_tasklet, tifm_sd_end_cmd,
+ (unsigned long)host);
+ setup_timer(&host->timer, tifm_sd_abort, (unsigned long)host);
+
+ mmc->ops = &tifm_sd_ops;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE;
+ mmc->f_min = 20000000 / 60;
+ mmc->f_max = 24000000;
+
+ mmc->max_blk_count = 2048;
+ mmc->max_hw_segs = mmc->max_blk_count;
+ mmc->max_blk_size = min(TIFM_MMCSD_MAX_BLOCK_SIZE, PAGE_SIZE);
+ mmc->max_seg_size = mmc->max_blk_count * mmc->max_blk_size;
+ mmc->max_req_size = mmc->max_seg_size;
+ mmc->max_phys_segs = mmc->max_hw_segs;
+
+ sock->card_event = tifm_sd_card_event;
+ sock->data_event = tifm_sd_data_event;
+ rc = tifm_sd_initialize_host(host);
+
+ if (!rc)
+ rc = mmc_add_host(mmc);
+ if (!rc)
+ return 0;
+
+ mmc_free_host(mmc);
+ return rc;
+}
+
+static void tifm_sd_remove(struct tifm_dev *sock)
+{
+ struct mmc_host *mmc = tifm_get_drvdata(sock);
+ struct tifm_sd *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sock->lock, flags);
+ host->eject = 1;
+ writel(0, sock->addr + SOCK_MMCSD_INT_ENABLE);
+ mmiowb();
+ spin_unlock_irqrestore(&sock->lock, flags);
+
+ tasklet_kill(&host->finish_tasklet);
+
+ spin_lock_irqsave(&sock->lock, flags);
+ if (host->req) {
+ writel(TIFM_FIFO_INT_SETALL,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
+ writel(0, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
+ host->req->cmd->error = MMC_ERR_TIMEOUT;
+ if (host->req->stop)
+ host->req->stop->error = MMC_ERR_TIMEOUT;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ spin_unlock_irqrestore(&sock->lock, flags);
+ mmc_remove_host(mmc);
+ dev_dbg(&sock->dev, "after remove\n");
+
+ /* The meaning of the bit majority in this constant is unknown. */
+ writel(0xfff8 & readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+
+ mmc_free_host(mmc);
+}
+
+#ifdef CONFIG_PM
+
+static int tifm_sd_suspend(struct tifm_dev *sock, pm_message_t state)
+{
+ struct mmc_host *mmc = tifm_get_drvdata(sock);
+ int rc;
+
+ rc = mmc_suspend_host(mmc, state);
+ /* The meaning of the bit majority in this constant is unknown. */
+ writel(0xfff8 & readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+ return rc;
+}
+
+static int tifm_sd_resume(struct tifm_dev *sock)
+{
+ struct mmc_host *mmc = tifm_get_drvdata(sock);
+ struct tifm_sd *host = mmc_priv(mmc);
+ int rc;
+
+ rc = tifm_sd_initialize_host(host);
+ dev_dbg(&sock->dev, "resume initialize %d\n", rc);
+
+ if (rc)
+ host->eject = 1;
+ else
+ rc = mmc_resume_host(mmc);
+
+ return rc;
+}
+
+#else
+
+#define tifm_sd_suspend NULL
+#define tifm_sd_resume NULL
+
+#endif /* CONFIG_PM */
+
+static struct tifm_device_id tifm_sd_id_tbl[] = {
+ { TIFM_TYPE_SD }, { }
+};
+
+static struct tifm_driver tifm_sd_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE
+ },
+ .id_table = tifm_sd_id_tbl,
+ .probe = tifm_sd_probe,
+ .remove = tifm_sd_remove,
+ .suspend = tifm_sd_suspend,
+ .resume = tifm_sd_resume
+};
+
+static int __init tifm_sd_init(void)
+{
+ return tifm_register_driver(&tifm_sd_driver);
+}
+
+static void __exit tifm_sd_exit(void)
+{
+ tifm_unregister_driver(&tifm_sd_driver);
+}
+
+MODULE_AUTHOR("Alex Dubov");
+MODULE_DESCRIPTION("TI FlashMedia SD driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(tifm, tifm_sd_id_tbl);
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(tifm_sd_init);
+module_exit(tifm_sd_exit);
diff --git a/drivers/mmc/host/wbsd.c b/drivers/mmc/host/wbsd.c
new file mode 100644
index 000000000000..9f7518b37c33
--- /dev/null
+++ b/drivers/mmc/host/wbsd.c
@@ -0,0 +1,2062 @@
+/*
+ * linux/drivers/mmc/wbsd.c - Winbond W83L51xD SD/MMC driver
+ *
+ * Copyright (C) 2004-2007 Pierre Ossman, All Rights Reserved.
+ *
+ * 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.
+ *
+ *
+ * Warning!
+ *
+ * Changes to the FIFO system should be done with extreme care since
+ * the hardware is full of bugs related to the FIFO. Known issues are:
+ *
+ * - FIFO size field in FSR is always zero.
+ *
+ * - FIFO interrupts tend not to work as they should. Interrupts are
+ * triggered only for full/empty events, not for threshold values.
+ *
+ * - On APIC systems the FIFO empty interrupt is sometimes lost.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/pnp.h>
+#include <linux/highmem.h>
+#include <linux/mmc/host.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/scatterlist.h>
+
+#include "wbsd.h"
+
+#define DRIVER_NAME "wbsd"
+
+#define DBG(x...) \
+ pr_debug(DRIVER_NAME ": " x)
+#define DBGF(f, x...) \
+ pr_debug(DRIVER_NAME " [%s()]: " f, __func__ , ##x)
+
+/*
+ * Device resources
+ */
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_device_id pnp_dev_table[] = {
+ { "WEC0517", 0 },
+ { "WEC0518", 0 },
+ { "", 0 },
+};
+
+MODULE_DEVICE_TABLE(pnp, pnp_dev_table);
+
+#endif /* CONFIG_PNP */
+
+static const int config_ports[] = { 0x2E, 0x4E };
+static const int unlock_codes[] = { 0x83, 0x87 };
+
+static const int valid_ids[] = {
+ 0x7112,
+ };
+
+#ifdef CONFIG_PNP
+static unsigned int nopnp = 0;
+#else
+static const unsigned int nopnp = 1;
+#endif
+static unsigned int io = 0x248;
+static unsigned int irq = 6;
+static int dma = 2;
+
+/*
+ * Basic functions
+ */
+
+static inline void wbsd_unlock_config(struct wbsd_host *host)
+{
+ BUG_ON(host->config == 0);
+
+ outb(host->unlock_code, host->config);
+ outb(host->unlock_code, host->config);
+}
+
+static inline void wbsd_lock_config(struct wbsd_host *host)
+{
+ BUG_ON(host->config == 0);
+
+ outb(LOCK_CODE, host->config);
+}
+
+static inline void wbsd_write_config(struct wbsd_host *host, u8 reg, u8 value)
+{
+ BUG_ON(host->config == 0);
+
+ outb(reg, host->config);
+ outb(value, host->config + 1);
+}
+
+static inline u8 wbsd_read_config(struct wbsd_host *host, u8 reg)
+{
+ BUG_ON(host->config == 0);
+
+ outb(reg, host->config);
+ return inb(host->config + 1);
+}
+
+static inline void wbsd_write_index(struct wbsd_host *host, u8 index, u8 value)
+{
+ outb(index, host->base + WBSD_IDXR);
+ outb(value, host->base + WBSD_DATAR);
+}
+
+static inline u8 wbsd_read_index(struct wbsd_host *host, u8 index)
+{
+ outb(index, host->base + WBSD_IDXR);
+ return inb(host->base + WBSD_DATAR);
+}
+
+/*
+ * Common routines
+ */
+
+static void wbsd_init_device(struct wbsd_host *host)
+{
+ u8 setup, ier;
+
+ /*
+ * Reset chip (SD/MMC part) and fifo.
+ */
+ setup = wbsd_read_index(host, WBSD_IDX_SETUP);
+ setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET;
+ wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+
+ /*
+ * Set DAT3 to input
+ */
+ setup &= ~WBSD_DAT3_H;
+ wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+ host->flags &= ~WBSD_FIGNORE_DETECT;
+
+ /*
+ * Read back default clock.
+ */
+ host->clk = wbsd_read_index(host, WBSD_IDX_CLK);
+
+ /*
+ * Power down port.
+ */
+ outb(WBSD_POWER_N, host->base + WBSD_CSR);
+
+ /*
+ * Set maximum timeout.
+ */
+ wbsd_write_index(host, WBSD_IDX_TAAC, 0x7F);
+
+ /*
+ * Test for card presence
+ */
+ if (inb(host->base + WBSD_CSR) & WBSD_CARDPRESENT)
+ host->flags |= WBSD_FCARD_PRESENT;
+ else
+ host->flags &= ~WBSD_FCARD_PRESENT;
+
+ /*
+ * Enable interesting interrupts.
+ */
+ ier = 0;
+ ier |= WBSD_EINT_CARD;
+ ier |= WBSD_EINT_FIFO_THRE;
+ ier |= WBSD_EINT_CRC;
+ ier |= WBSD_EINT_TIMEOUT;
+ ier |= WBSD_EINT_TC;
+
+ outb(ier, host->base + WBSD_EIR);
+
+ /*
+ * Clear interrupts.
+ */
+ inb(host->base + WBSD_ISR);
+}
+
+static void wbsd_reset(struct wbsd_host *host)
+{
+ u8 setup;
+
+ printk(KERN_ERR "%s: Resetting chip\n", mmc_hostname(host->mmc));
+
+ /*
+ * Soft reset of chip (SD/MMC part).
+ */
+ setup = wbsd_read_index(host, WBSD_IDX_SETUP);
+ setup |= WBSD_SOFT_RESET;
+ wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+}
+
+static void wbsd_request_end(struct wbsd_host *host, struct mmc_request *mrq)
+{
+ unsigned long dmaflags;
+
+ DBGF("Ending request, cmd (%x)\n", mrq->cmd->opcode);
+
+ if (host->dma >= 0) {
+ /*
+ * Release ISA DMA controller.
+ */
+ dmaflags = claim_dma_lock();
+ disable_dma(host->dma);
+ clear_dma_ff(host->dma);
+ release_dma_lock(dmaflags);
+
+ /*
+ * Disable DMA on host.
+ */
+ wbsd_write_index(host, WBSD_IDX_DMA, 0);
+ }
+
+ host->mrq = NULL;
+
+ /*
+ * MMC layer might call back into the driver so first unlock.
+ */
+ spin_unlock(&host->lock);
+ mmc_request_done(host->mmc, mrq);
+ spin_lock(&host->lock);
+}
+
+/*
+ * Scatter/gather functions
+ */
+
+static inline void wbsd_init_sg(struct wbsd_host *host, struct mmc_data *data)
+{
+ /*
+ * Get info. about SG list from data structure.
+ */
+ host->cur_sg = data->sg;
+ host->num_sg = data->sg_len;
+
+ host->offset = 0;
+ host->remain = host->cur_sg->length;
+}
+
+static inline int wbsd_next_sg(struct wbsd_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 inline char *wbsd_sg_to_buffer(struct wbsd_host *host)
+{
+ return page_address(host->cur_sg->page) + host->cur_sg->offset;
+}
+
+static inline void wbsd_sg_to_dma(struct wbsd_host *host, struct mmc_data *data)
+{
+ unsigned int len, i;
+ struct scatterlist *sg;
+ char *dmabuf = host->dma_buffer;
+ char *sgbuf;
+
+ sg = data->sg;
+ len = data->sg_len;
+
+ for (i = 0; i < len; i++) {
+ sgbuf = page_address(sg[i].page) + sg[i].offset;
+ memcpy(dmabuf, sgbuf, sg[i].length);
+ dmabuf += sg[i].length;
+ }
+}
+
+static inline void wbsd_dma_to_sg(struct wbsd_host *host, struct mmc_data *data)
+{
+ unsigned int len, i;
+ struct scatterlist *sg;
+ char *dmabuf = host->dma_buffer;
+ char *sgbuf;
+
+ sg = data->sg;
+ len = data->sg_len;
+
+ for (i = 0; i < len; i++) {
+ sgbuf = page_address(sg[i].page) + sg[i].offset;
+ memcpy(sgbuf, dmabuf, sg[i].length);
+ dmabuf += sg[i].length;
+ }
+}
+
+/*
+ * Command handling
+ */
+
+static inline void wbsd_get_short_reply(struct wbsd_host *host,
+ struct mmc_command *cmd)
+{
+ /*
+ * Correct response type?
+ */
+ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT) {
+ cmd->error = MMC_ERR_INVALID;
+ return;
+ }
+
+ cmd->resp[0] = wbsd_read_index(host, WBSD_IDX_RESP12) << 24;
+ cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP13) << 16;
+ cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP14) << 8;
+ cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP15) << 0;
+ cmd->resp[1] = wbsd_read_index(host, WBSD_IDX_RESP16) << 24;
+}
+
+static inline void wbsd_get_long_reply(struct wbsd_host *host,
+ struct mmc_command *cmd)
+{
+ int i;
+
+ /*
+ * Correct response type?
+ */
+ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_LONG) {
+ cmd->error = MMC_ERR_INVALID;
+ return;
+ }
+
+ for (i = 0; i < 4; i++) {
+ cmd->resp[i] =
+ wbsd_read_index(host, WBSD_IDX_RESP1 + i * 4) << 24;
+ cmd->resp[i] |=
+ wbsd_read_index(host, WBSD_IDX_RESP2 + i * 4) << 16;
+ cmd->resp[i] |=
+ wbsd_read_index(host, WBSD_IDX_RESP3 + i * 4) << 8;
+ cmd->resp[i] |=
+ wbsd_read_index(host, WBSD_IDX_RESP4 + i * 4) << 0;
+ }
+}
+
+static void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd)
+{
+ int i;
+ u8 status, isr;
+
+ DBGF("Sending cmd (%x)\n", cmd->opcode);
+
+ /*
+ * Clear accumulated ISR. The interrupt routine
+ * will fill this one with events that occur during
+ * transfer.
+ */
+ host->isr = 0;
+
+ /*
+ * Send the command (CRC calculated by host).
+ */
+ outb(cmd->opcode, host->base + WBSD_CMDR);
+ for (i = 3; i >= 0; i--)
+ outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR);
+
+ cmd->error = MMC_ERR_NONE;
+
+ /*
+ * Wait for the request to complete.
+ */
+ do {
+ status = wbsd_read_index(host, WBSD_IDX_STATUS);
+ } while (status & WBSD_CARDTRAFFIC);
+
+ /*
+ * Do we expect a reply?
+ */
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ /*
+ * Read back status.
+ */
+ isr = host->isr;
+
+ /* Card removed? */
+ if (isr & WBSD_INT_CARD)
+ cmd->error = MMC_ERR_TIMEOUT;
+ /* Timeout? */
+ else if (isr & WBSD_INT_TIMEOUT)
+ cmd->error = MMC_ERR_TIMEOUT;
+ /* CRC? */
+ else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC))
+ cmd->error = MMC_ERR_BADCRC;
+ /* All ok */
+ else {
+ if (cmd->flags & MMC_RSP_136)
+ wbsd_get_long_reply(host, cmd);
+ else
+ wbsd_get_short_reply(host, cmd);
+ }
+ }
+
+ DBGF("Sent cmd (%x), res %d\n", cmd->opcode, cmd->error);
+}
+
+/*
+ * Data functions
+ */
+
+static void wbsd_empty_fifo(struct wbsd_host *host)
+{
+ struct mmc_data *data = host->mrq->cmd->data;
+ char *buffer;
+ int i, fsr, fifo;
+
+ /*
+ * Handle excessive data.
+ */
+ if (host->num_sg == 0)
+ return;
+
+ buffer = wbsd_sg_to_buffer(host) + host->offset;
+
+ /*
+ * Drain the fifo. This has a tendency to loop longer
+ * than the FIFO length (usually one block).
+ */
+ while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_EMPTY)) {
+ /*
+ * The size field in the FSR is broken so we have to
+ * do some guessing.
+ */
+ if (fsr & WBSD_FIFO_FULL)
+ fifo = 16;
+ else if (fsr & WBSD_FIFO_FUTHRE)
+ fifo = 8;
+ else
+ fifo = 1;
+
+ for (i = 0; i < fifo; i++) {
+ *buffer = inb(host->base + WBSD_DFR);
+ buffer++;
+ host->offset++;
+ host->remain--;
+
+ data->bytes_xfered++;
+
+ /*
+ * End of scatter list entry?
+ */
+ if (host->remain == 0) {
+ /*
+ * Get next entry. Check if last.
+ */
+ if (!wbsd_next_sg(host))
+ return;
+
+ buffer = wbsd_sg_to_buffer(host);
+ }
+ }
+ }
+
+ /*
+ * This is a very dirty hack to solve a
+ * hardware problem. The chip doesn't trigger
+ * FIFO threshold interrupts properly.
+ */
+ if ((data->blocks * data->blksz - data->bytes_xfered) < 16)
+ tasklet_schedule(&host->fifo_tasklet);
+}
+
+static void wbsd_fill_fifo(struct wbsd_host *host)
+{
+ struct mmc_data *data = host->mrq->cmd->data;
+ char *buffer;
+ int i, fsr, fifo;
+
+ /*
+ * Check that we aren't being called after the
+ * entire buffer has been transfered.
+ */
+ if (host->num_sg == 0)
+ return;
+
+ buffer = wbsd_sg_to_buffer(host) + host->offset;
+
+ /*
+ * Fill the fifo. This has a tendency to loop longer
+ * than the FIFO length (usually one block).
+ */
+ while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_FULL)) {
+ /*
+ * The size field in the FSR is broken so we have to
+ * do some guessing.
+ */
+ if (fsr & WBSD_FIFO_EMPTY)
+ fifo = 0;
+ else if (fsr & WBSD_FIFO_EMTHRE)
+ fifo = 8;
+ else
+ fifo = 15;
+
+ for (i = 16; i > fifo; i--) {
+ outb(*buffer, host->base + WBSD_DFR);
+ buffer++;
+ host->offset++;
+ host->remain--;
+
+ data->bytes_xfered++;
+
+ /*
+ * End of scatter list entry?
+ */
+ if (host->remain == 0) {
+ /*
+ * Get next entry. Check if last.
+ */
+ if (!wbsd_next_sg(host))
+ return;
+
+ buffer = wbsd_sg_to_buffer(host);
+ }
+ }
+ }
+
+ /*
+ * The controller stops sending interrupts for
+ * 'FIFO empty' under certain conditions. So we
+ * need to be a bit more pro-active.
+ */
+ tasklet_schedule(&host->fifo_tasklet);
+}
+
+static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data)
+{
+ u16 blksize;
+ u8 setup;
+ unsigned long dmaflags;
+ unsigned int size;
+
+ DBGF("blksz %04x blks %04x flags %08x\n",
+ data->blksz, data->blocks, data->flags);
+ DBGF("tsac %d ms nsac %d clk\n",
+ data->timeout_ns / 1000000, data->timeout_clks);
+
+ /*
+ * Calculate size.
+ */
+ size = data->blocks * data->blksz;
+
+ /*
+ * Check timeout values for overflow.
+ * (Yes, some cards cause this value to overflow).
+ */
+ if (data->timeout_ns > 127000000)
+ wbsd_write_index(host, WBSD_IDX_TAAC, 127);
+ else {
+ wbsd_write_index(host, WBSD_IDX_TAAC,
+ data->timeout_ns / 1000000);
+ }
+
+ if (data->timeout_clks > 255)
+ wbsd_write_index(host, WBSD_IDX_NSAC, 255);
+ else
+ wbsd_write_index(host, WBSD_IDX_NSAC, data->timeout_clks);
+
+ /*
+ * Inform the chip of how large blocks will be
+ * sent. It needs this to determine when to
+ * calculate CRC.
+ *
+ * Space for CRC must be included in the size.
+ * Two bytes are needed for each data line.
+ */
+ if (host->bus_width == MMC_BUS_WIDTH_1) {
+ blksize = data->blksz + 2;
+
+ wbsd_write_index(host, WBSD_IDX_PBSMSB, (blksize >> 4) & 0xF0);
+ wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
+ } else if (host->bus_width == MMC_BUS_WIDTH_4) {
+ blksize = data->blksz + 2 * 4;
+
+ wbsd_write_index(host, WBSD_IDX_PBSMSB,
+ ((blksize >> 4) & 0xF0) | WBSD_DATA_WIDTH);
+ wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
+ } else {
+ data->error = MMC_ERR_INVALID;
+ return;
+ }
+
+ /*
+ * Clear the FIFO. This is needed even for DMA
+ * transfers since the chip still uses the FIFO
+ * internally.
+ */
+ setup = wbsd_read_index(host, WBSD_IDX_SETUP);
+ setup |= WBSD_FIFO_RESET;
+ wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+
+ /*
+ * DMA transfer?
+ */
+ if (host->dma >= 0) {
+ /*
+ * The buffer for DMA is only 64 kB.
+ */
+ BUG_ON(size > 0x10000);
+ if (size > 0x10000) {
+ data->error = MMC_ERR_INVALID;
+ return;
+ }
+
+ /*
+ * Transfer data from the SG list to
+ * the DMA buffer.
+ */
+ if (data->flags & MMC_DATA_WRITE)
+ wbsd_sg_to_dma(host, data);
+
+ /*
+ * Initialise the ISA DMA controller.
+ */
+ dmaflags = claim_dma_lock();
+ disable_dma(host->dma);
+ clear_dma_ff(host->dma);
+ if (data->flags & MMC_DATA_READ)
+ set_dma_mode(host->dma, DMA_MODE_READ & ~0x40);
+ else
+ set_dma_mode(host->dma, DMA_MODE_WRITE & ~0x40);
+ set_dma_addr(host->dma, host->dma_addr);
+ set_dma_count(host->dma, size);
+
+ enable_dma(host->dma);
+ release_dma_lock(dmaflags);
+
+ /*
+ * Enable DMA on the host.
+ */
+ wbsd_write_index(host, WBSD_IDX_DMA, WBSD_DMA_ENABLE);
+ } else {
+ /*
+ * This flag is used to keep printk
+ * output to a minimum.
+ */
+ host->firsterr = 1;
+
+ /*
+ * Initialise the SG list.
+ */
+ wbsd_init_sg(host, data);
+
+ /*
+ * Turn off DMA.
+ */
+ wbsd_write_index(host, WBSD_IDX_DMA, 0);
+
+ /*
+ * Set up FIFO threshold levels (and fill
+ * buffer if doing a write).
+ */
+ if (data->flags & MMC_DATA_READ) {
+ wbsd_write_index(host, WBSD_IDX_FIFOEN,
+ WBSD_FIFOEN_FULL | 8);
+ } else {
+ wbsd_write_index(host, WBSD_IDX_FIFOEN,
+ WBSD_FIFOEN_EMPTY | 8);
+ wbsd_fill_fifo(host);
+ }
+ }
+
+ data->error = MMC_ERR_NONE;
+}
+
+static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data)
+{
+ unsigned long dmaflags;
+ int count;
+ u8 status;
+
+ WARN_ON(host->mrq == NULL);
+
+ /*
+ * Send a stop command if needed.
+ */
+ if (data->stop)
+ wbsd_send_command(host, data->stop);
+
+ /*
+ * Wait for the controller to leave data
+ * transfer state.
+ */
+ do {
+ status = wbsd_read_index(host, WBSD_IDX_STATUS);
+ } while (status & (WBSD_BLOCK_READ | WBSD_BLOCK_WRITE));
+
+ /*
+ * DMA transfer?
+ */
+ if (host->dma >= 0) {
+ /*
+ * Disable DMA on the host.
+ */
+ wbsd_write_index(host, WBSD_IDX_DMA, 0);
+
+ /*
+ * Turn of ISA DMA controller.
+ */
+ dmaflags = claim_dma_lock();
+ disable_dma(host->dma);
+ clear_dma_ff(host->dma);
+ count = get_dma_residue(host->dma);
+ release_dma_lock(dmaflags);
+
+ data->bytes_xfered = host->mrq->data->blocks *
+ host->mrq->data->blksz - count;
+ data->bytes_xfered -= data->bytes_xfered % data->blksz;
+
+ /*
+ * Any leftover data?
+ */
+ if (count) {
+ printk(KERN_ERR "%s: Incomplete DMA transfer. "
+ "%d bytes left.\n",
+ mmc_hostname(host->mmc), count);
+
+ if (data->error == MMC_ERR_NONE)
+ data->error = MMC_ERR_FAILED;
+ } else {
+ /*
+ * Transfer data from DMA buffer to
+ * SG list.
+ */
+ if (data->flags & MMC_DATA_READ)
+ wbsd_dma_to_sg(host, data);
+ }
+
+ if (data->error != MMC_ERR_NONE) {
+ if (data->bytes_xfered)
+ data->bytes_xfered -= data->blksz;
+ }
+ }
+
+ DBGF("Ending data transfer (%d bytes)\n", data->bytes_xfered);
+
+ wbsd_request_end(host, host->mrq);
+}
+
+/*****************************************************************************\
+ * *
+ * MMC layer callbacks *
+ * *
+\*****************************************************************************/
+
+static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct wbsd_host *host = mmc_priv(mmc);
+ struct mmc_command *cmd;
+
+ /*
+ * Disable tasklets to avoid a deadlock.
+ */
+ spin_lock_bh(&host->lock);
+
+ BUG_ON(host->mrq != NULL);
+
+ cmd = mrq->cmd;
+
+ host->mrq = mrq;
+
+ /*
+ * If there is no card in the slot then
+ * timeout immediatly.
+ */
+ if (!(host->flags & WBSD_FCARD_PRESENT)) {
+ cmd->error = MMC_ERR_TIMEOUT;
+ goto done;
+ }
+
+ /*
+ * Does the request include data?
+ */
+ if (cmd->data) {
+ wbsd_prepare_data(host, cmd->data);
+
+ if (cmd->data->error != MMC_ERR_NONE)
+ goto done;
+ }
+
+ wbsd_send_command(host, cmd);
+
+ /*
+ * If this is a data transfer the request
+ * will be finished after the data has
+ * transfered.
+ */
+ if (cmd->data && (cmd->error == MMC_ERR_NONE)) {
+ /*
+ * The hardware is so delightfully stupid that it has a list
+ * of "data" commands. If a command isn't on this list, it'll
+ * just go back to the idle state and won't send any data
+ * interrupts.
+ */
+ switch (cmd->opcode) {
+ case 11:
+ case 17:
+ case 18:
+ case 20:
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ case 30:
+ case 42:
+ case 56:
+ break;
+
+ /* ACMDs. We don't keep track of state, so we just treat them
+ * like any other command. */
+ case 51:
+ break;
+
+ default:
+#ifdef CONFIG_MMC_DEBUG
+ printk(KERN_WARNING "%s: Data command %d is not "
+ "supported by this controller.\n",
+ mmc_hostname(host->mmc), cmd->opcode);
+#endif
+ cmd->data->error = MMC_ERR_INVALID;
+
+ if (cmd->data->stop)
+ wbsd_send_command(host, cmd->data->stop);
+
+ goto done;
+ };
+
+ /*
+ * Dirty fix for hardware bug.
+ */
+ if (host->dma == -1)
+ tasklet_schedule(&host->fifo_tasklet);
+
+ spin_unlock_bh(&host->lock);
+
+ return;
+ }
+
+done:
+ wbsd_request_end(host, mrq);
+
+ spin_unlock_bh(&host->lock);
+}
+
+static void wbsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct wbsd_host *host = mmc_priv(mmc);
+ u8 clk, setup, pwr;
+
+ spin_lock_bh(&host->lock);
+
+ /*
+ * Reset the chip on each power off.
+ * Should clear out any weird states.
+ */
+ if (ios->power_mode == MMC_POWER_OFF)
+ wbsd_init_device(host);
+
+ if (ios->clock >= 24000000)
+ clk = WBSD_CLK_24M;
+ else if (ios->clock >= 16000000)
+ clk = WBSD_CLK_16M;
+ else if (ios->clock >= 12000000)
+ clk = WBSD_CLK_12M;
+ else
+ clk = WBSD_CLK_375K;
+
+ /*
+ * Only write to the clock register when
+ * there is an actual change.
+ */
+ if (clk != host->clk) {
+ wbsd_write_index(host, WBSD_IDX_CLK, clk);
+ host->clk = clk;
+ }
+
+ /*
+ * Power up card.
+ */
+ if (ios->power_mode != MMC_POWER_OFF) {
+ pwr = inb(host->base + WBSD_CSR);
+ pwr &= ~WBSD_POWER_N;
+ outb(pwr, host->base + WBSD_CSR);
+ }
+
+ /*
+ * MMC cards need to have pin 1 high during init.
+ * It wreaks havoc with the card detection though so
+ * that needs to be disabled.
+ */
+ setup = wbsd_read_index(host, WBSD_IDX_SETUP);
+ if (ios->chip_select == MMC_CS_HIGH) {
+ BUG_ON(ios->bus_width != MMC_BUS_WIDTH_1);
+ setup |= WBSD_DAT3_H;
+ host->flags |= WBSD_FIGNORE_DETECT;
+ } else {
+ if (setup & WBSD_DAT3_H) {
+ setup &= ~WBSD_DAT3_H;
+
+ /*
+ * We cannot resume card detection immediatly
+ * because of capacitance and delays in the chip.
+ */
+ mod_timer(&host->ignore_timer, jiffies + HZ / 100);
+ }
+ }
+ wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+
+ /*
+ * Store bus width for later. Will be used when
+ * setting up the data transfer.
+ */
+ host->bus_width = ios->bus_width;
+
+ spin_unlock_bh(&host->lock);
+}
+
+static int wbsd_get_ro(struct mmc_host *mmc)
+{
+ struct wbsd_host *host = mmc_priv(mmc);
+ u8 csr;
+
+ spin_lock_bh(&host->lock);
+
+ csr = inb(host->base + WBSD_CSR);
+ csr |= WBSD_MSLED;
+ outb(csr, host->base + WBSD_CSR);
+
+ mdelay(1);
+
+ csr = inb(host->base + WBSD_CSR);
+ csr &= ~WBSD_MSLED;
+ outb(csr, host->base + WBSD_CSR);
+
+ spin_unlock_bh(&host->lock);
+
+ return csr & WBSD_WRPT;
+}
+
+static const struct mmc_host_ops wbsd_ops = {
+ .request = wbsd_request,
+ .set_ios = wbsd_set_ios,
+ .get_ro = wbsd_get_ro,
+};
+
+/*****************************************************************************\
+ * *
+ * Interrupt handling *
+ * *
+\*****************************************************************************/
+
+/*
+ * Helper function to reset detection ignore
+ */
+
+static void wbsd_reset_ignore(unsigned long data)
+{
+ struct wbsd_host *host = (struct wbsd_host *)data;
+
+ BUG_ON(host == NULL);
+
+ DBG("Resetting card detection ignore\n");
+
+ spin_lock_bh(&host->lock);
+
+ host->flags &= ~WBSD_FIGNORE_DETECT;
+
+ /*
+ * Card status might have changed during the
+ * blackout.
+ */
+ tasklet_schedule(&host->card_tasklet);
+
+ spin_unlock_bh(&host->lock);
+}
+
+/*
+ * Tasklets
+ */
+
+static inline struct mmc_data *wbsd_get_data(struct wbsd_host *host)
+{
+ WARN_ON(!host->mrq);
+ if (!host->mrq)
+ return NULL;
+
+ WARN_ON(!host->mrq->cmd);
+ if (!host->mrq->cmd)
+ return NULL;
+
+ WARN_ON(!host->mrq->cmd->data);
+ if (!host->mrq->cmd->data)
+ return NULL;
+
+ return host->mrq->cmd->data;
+}
+
+static void wbsd_tasklet_card(unsigned long param)
+{
+ struct wbsd_host *host = (struct wbsd_host *)param;
+ u8 csr;
+ int delay = -1;
+
+ spin_lock(&host->lock);
+
+ if (host->flags & WBSD_FIGNORE_DETECT) {
+ spin_unlock(&host->lock);
+ return;
+ }
+
+ csr = inb(host->base + WBSD_CSR);
+ WARN_ON(csr == 0xff);
+
+ if (csr & WBSD_CARDPRESENT) {
+ if (!(host->flags & WBSD_FCARD_PRESENT)) {
+ DBG("Card inserted\n");
+ host->flags |= WBSD_FCARD_PRESENT;
+
+ delay = 500;
+ }
+ } else if (host->flags & WBSD_FCARD_PRESENT) {
+ DBG("Card removed\n");
+ host->flags &= ~WBSD_FCARD_PRESENT;
+
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Card removed during transfer!\n",
+ mmc_hostname(host->mmc));
+ wbsd_reset(host);
+
+ host->mrq->cmd->error = MMC_ERR_FAILED;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+
+ delay = 0;
+ }
+
+ /*
+ * Unlock first since we might get a call back.
+ */
+
+ spin_unlock(&host->lock);
+
+ if (delay != -1)
+ mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
+}
+
+static void wbsd_tasklet_fifo(unsigned long param)
+{
+ struct wbsd_host *host = (struct wbsd_host *)param;
+ struct mmc_data *data;
+
+ spin_lock(&host->lock);
+
+ if (!host->mrq)
+ goto end;
+
+ data = wbsd_get_data(host);
+ if (!data)
+ goto end;
+
+ if (data->flags & MMC_DATA_WRITE)
+ wbsd_fill_fifo(host);
+ else
+ wbsd_empty_fifo(host);
+
+ /*
+ * Done?
+ */
+ if (host->num_sg == 0) {
+ wbsd_write_index(host, WBSD_IDX_FIFOEN, 0);
+ tasklet_schedule(&host->finish_tasklet);
+ }
+
+end:
+ spin_unlock(&host->lock);
+}
+
+static void wbsd_tasklet_crc(unsigned long param)
+{
+ struct wbsd_host *host = (struct wbsd_host *)param;
+ struct mmc_data *data;
+
+ spin_lock(&host->lock);
+
+ if (!host->mrq)
+ goto end;
+
+ data = wbsd_get_data(host);
+ if (!data)
+ goto end;
+
+ DBGF("CRC error\n");
+
+ data->error = MMC_ERR_BADCRC;
+
+ tasklet_schedule(&host->finish_tasklet);
+
+end:
+ spin_unlock(&host->lock);
+}
+
+static void wbsd_tasklet_timeout(unsigned long param)
+{
+ struct wbsd_host *host = (struct wbsd_host *)param;
+ struct mmc_data *data;
+
+ spin_lock(&host->lock);
+
+ if (!host->mrq)
+ goto end;
+
+ data = wbsd_get_data(host);
+ if (!data)
+ goto end;
+
+ DBGF("Timeout\n");
+
+ data->error = MMC_ERR_TIMEOUT;
+
+ tasklet_schedule(&host->finish_tasklet);
+
+end:
+ spin_unlock(&host->lock);
+}
+
+static void wbsd_tasklet_finish(unsigned long param)
+{
+ struct wbsd_host *host = (struct wbsd_host *)param;
+ struct mmc_data *data;
+
+ spin_lock(&host->lock);
+
+ WARN_ON(!host->mrq);
+ if (!host->mrq)
+ goto end;
+
+ data = wbsd_get_data(host);
+ if (!data)
+ goto end;
+
+ wbsd_finish_data(host, data);
+
+end:
+ spin_unlock(&host->lock);
+}
+
+/*
+ * Interrupt handling
+ */
+
+static irqreturn_t wbsd_irq(int irq, void *dev_id)
+{
+ struct wbsd_host *host = dev_id;
+ int isr;
+
+ isr = inb(host->base + WBSD_ISR);
+
+ /*
+ * Was it actually our hardware that caused the interrupt?
+ */
+ if (isr == 0xff || isr == 0x00)
+ return IRQ_NONE;
+
+ host->isr |= isr;
+
+ /*
+ * Schedule tasklets as needed.
+ */
+ if (isr & WBSD_INT_CARD)
+ tasklet_schedule(&host->card_tasklet);
+ if (isr & WBSD_INT_FIFO_THRE)
+ tasklet_schedule(&host->fifo_tasklet);
+ if (isr & WBSD_INT_CRC)
+ tasklet_hi_schedule(&host->crc_tasklet);
+ if (isr & WBSD_INT_TIMEOUT)
+ tasklet_hi_schedule(&host->timeout_tasklet);
+ if (isr & WBSD_INT_TC)
+ tasklet_schedule(&host->finish_tasklet);
+
+ return IRQ_HANDLED;
+}
+
+/*****************************************************************************\
+ * *
+ * Device initialisation and shutdown *
+ * *
+\*****************************************************************************/
+
+/*
+ * Allocate/free MMC structure.
+ */
+
+static int __devinit wbsd_alloc_mmc(struct device *dev)
+{
+ struct mmc_host *mmc;
+ struct wbsd_host *host;
+
+ /*
+ * Allocate MMC structure.
+ */
+ mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+
+ host->dma = -1;
+
+ /*
+ * Set host parameters.
+ */
+ mmc->ops = &wbsd_ops;
+ mmc->f_min = 375000;
+ mmc->f_max = 24000000;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK;
+
+ spin_lock_init(&host->lock);
+
+ /*
+ * Set up timers
+ */
+ init_timer(&host->ignore_timer);
+ host->ignore_timer.data = (unsigned long)host;
+ host->ignore_timer.function = wbsd_reset_ignore;
+
+ /*
+ * Maximum number of segments. Worst case is one sector per segment
+ * so this will be 64kB/512.
+ */
+ mmc->max_hw_segs = 128;
+ mmc->max_phys_segs = 128;
+
+ /*
+ * Maximum request size. Also limited by 64KiB buffer.
+ */
+ mmc->max_req_size = 65536;
+
+ /*
+ * Maximum segment size. Could be one segment with the maximum number
+ * of bytes.
+ */
+ mmc->max_seg_size = mmc->max_req_size;
+
+ /*
+ * Maximum block size. We have 12 bits (= 4095) but have to subtract
+ * space for CRC. So the maximum is 4095 - 4*2 = 4087.
+ */
+ mmc->max_blk_size = 4087;
+
+ /*
+ * Maximum block count. There is no real limit so the maximum
+ * request size will be the only restriction.
+ */
+ mmc->max_blk_count = mmc->max_req_size;
+
+ dev_set_drvdata(dev, mmc);
+
+ return 0;
+}
+
+static void __devexit wbsd_free_mmc(struct device *dev)
+{
+ struct mmc_host *mmc;
+ struct wbsd_host *host;
+
+ mmc = dev_get_drvdata(dev);
+ if (!mmc)
+ return;
+
+ host = mmc_priv(mmc);
+ BUG_ON(host == NULL);
+
+ del_timer_sync(&host->ignore_timer);
+
+ mmc_free_host(mmc);
+
+ dev_set_drvdata(dev, NULL);
+}
+
+/*
+ * Scan for known chip id:s
+ */
+
+static int __devinit wbsd_scan(struct wbsd_host *host)
+{
+ int i, j, k;
+ int id;
+
+ /*
+ * Iterate through all ports, all codes to
+ * find hardware that is in our known list.
+ */
+ for (i = 0; i < ARRAY_SIZE(config_ports); i++) {
+ if (!request_region(config_ports[i], 2, DRIVER_NAME))
+ continue;
+
+ for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) {
+ id = 0xFFFF;
+
+ host->config = config_ports[i];
+ host->unlock_code = unlock_codes[j];
+
+ wbsd_unlock_config(host);
+
+ outb(WBSD_CONF_ID_HI, config_ports[i]);
+ id = inb(config_ports[i] + 1) << 8;
+
+ outb(WBSD_CONF_ID_LO, config_ports[i]);
+ id |= inb(config_ports[i] + 1);
+
+ wbsd_lock_config(host);
+
+ for (k = 0; k < ARRAY_SIZE(valid_ids); k++) {
+ if (id == valid_ids[k]) {
+ host->chip_id = id;
+
+ return 0;
+ }
+ }
+
+ if (id != 0xFFFF) {
+ DBG("Unknown hardware (id %x) found at %x\n",
+ id, config_ports[i]);
+ }
+ }
+
+ release_region(config_ports[i], 2);
+ }
+
+ host->config = 0;
+ host->unlock_code = 0;
+
+ return -ENODEV;
+}
+
+/*
+ * Allocate/free io port ranges
+ */
+
+static int __devinit wbsd_request_region(struct wbsd_host *host, int base)
+{
+ if (base & 0x7)
+ return -EINVAL;
+
+ if (!request_region(base, 8, DRIVER_NAME))
+ return -EIO;
+
+ host->base = base;
+
+ return 0;
+}
+
+static void __devexit wbsd_release_regions(struct wbsd_host *host)
+{
+ if (host->base)
+ release_region(host->base, 8);
+
+ host->base = 0;
+
+ if (host->config)
+ release_region(host->config, 2);
+
+ host->config = 0;
+}
+
+/*
+ * Allocate/free DMA port and buffer
+ */
+
+static void __devinit wbsd_request_dma(struct wbsd_host *host, int dma)
+{
+ if (dma < 0)
+ return;
+
+ if (request_dma(dma, DRIVER_NAME))
+ goto err;
+
+ /*
+ * We need to allocate a special buffer in
+ * order for ISA to be able to DMA to it.
+ */
+ host->dma_buffer = kmalloc(WBSD_DMA_SIZE,
+ GFP_NOIO | GFP_DMA | __GFP_REPEAT | __GFP_NOWARN);
+ if (!host->dma_buffer)
+ goto free;
+
+ /*
+ * Translate the address to a physical address.
+ */
+ host->dma_addr = dma_map_single(mmc_dev(host->mmc), host->dma_buffer,
+ WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
+
+ /*
+ * ISA DMA must be aligned on a 64k basis.
+ */
+ if ((host->dma_addr & 0xffff) != 0)
+ goto kfree;
+ /*
+ * ISA cannot access memory above 16 MB.
+ */
+ else if (host->dma_addr >= 0x1000000)
+ goto kfree;
+
+ host->dma = dma;
+
+ return;
+
+kfree:
+ /*
+ * If we've gotten here then there is some kind of alignment bug
+ */
+ BUG_ON(1);
+
+ dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
+ WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
+ host->dma_addr = (dma_addr_t)NULL;
+
+ kfree(host->dma_buffer);
+ host->dma_buffer = NULL;
+
+free:
+ free_dma(dma);
+
+err:
+ printk(KERN_WARNING DRIVER_NAME ": Unable to allocate DMA %d. "
+ "Falling back on FIFO.\n", dma);
+}
+
+static void __devexit wbsd_release_dma(struct wbsd_host *host)
+{
+ if (host->dma_addr) {
+ dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
+ WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
+ }
+ kfree(host->dma_buffer);
+ if (host->dma >= 0)
+ free_dma(host->dma);
+
+ host->dma = -1;
+ host->dma_buffer = NULL;
+ host->dma_addr = (dma_addr_t)NULL;
+}
+
+/*
+ * Allocate/free IRQ.
+ */
+
+static int __devinit wbsd_request_irq(struct wbsd_host *host, int irq)
+{
+ int ret;
+
+ /*
+ * Allocate interrupt.
+ */
+
+ ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host);
+ if (ret)
+ return ret;
+
+ host->irq = irq;
+
+ /*
+ * Set up tasklets.
+ */
+ tasklet_init(&host->card_tasklet, wbsd_tasklet_card,
+ (unsigned long)host);
+ tasklet_init(&host->fifo_tasklet, wbsd_tasklet_fifo,
+ (unsigned long)host);
+ tasklet_init(&host->crc_tasklet, wbsd_tasklet_crc,
+ (unsigned long)host);
+ tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout,
+ (unsigned long)host);
+ tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish,
+ (unsigned long)host);
+
+ return 0;
+}
+
+static void __devexit wbsd_release_irq(struct wbsd_host *host)
+{
+ if (!host->irq)
+ return;
+
+ free_irq(host->irq, host);
+
+ host->irq = 0;
+
+ tasklet_kill(&host->card_tasklet);
+ tasklet_kill(&host->fifo_tasklet);
+ tasklet_kill(&host->crc_tasklet);
+ tasklet_kill(&host->timeout_tasklet);
+ tasklet_kill(&host->finish_tasklet);
+}
+
+/*
+ * Allocate all resources for the host.
+ */
+
+static int __devinit wbsd_request_resources(struct wbsd_host *host,
+ int base, int irq, int dma)
+{
+ int ret;
+
+ /*
+ * Allocate I/O ports.
+ */
+ ret = wbsd_request_region(host, base);
+ if (ret)
+ return ret;
+
+ /*
+ * Allocate interrupt.
+ */
+ ret = wbsd_request_irq(host, irq);
+ if (ret)
+ return ret;
+
+ /*
+ * Allocate DMA.
+ */
+ wbsd_request_dma(host, dma);
+
+ return 0;
+}
+
+/*
+ * Release all resources for the host.
+ */
+
+static void __devexit wbsd_release_resources(struct wbsd_host *host)
+{
+ wbsd_release_dma(host);
+ wbsd_release_irq(host);
+ wbsd_release_regions(host);
+}
+
+/*
+ * Configure the resources the chip should use.
+ */
+
+static void wbsd_chip_config(struct wbsd_host *host)
+{
+ wbsd_unlock_config(host);
+
+ /*
+ * Reset the chip.
+ */
+ wbsd_write_config(host, WBSD_CONF_SWRST, 1);
+ wbsd_write_config(host, WBSD_CONF_SWRST, 0);
+
+ /*
+ * Select SD/MMC function.
+ */
+ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
+
+ /*
+ * Set up card detection.
+ */
+ wbsd_write_config(host, WBSD_CONF_PINS, WBSD_PINS_DETECT_GP11);
+
+ /*
+ * Configure chip
+ */
+ wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8);
+ wbsd_write_config(host, WBSD_CONF_PORT_LO, host->base & 0xff);
+
+ wbsd_write_config(host, WBSD_CONF_IRQ, host->irq);
+
+ if (host->dma >= 0)
+ wbsd_write_config(host, WBSD_CONF_DRQ, host->dma);
+
+ /*
+ * Enable and power up chip.
+ */
+ wbsd_write_config(host, WBSD_CONF_ENABLE, 1);
+ wbsd_write_config(host, WBSD_CONF_POWER, 0x20);
+
+ wbsd_lock_config(host);
+}
+
+/*
+ * Check that configured resources are correct.
+ */
+
+static int wbsd_chip_validate(struct wbsd_host *host)
+{
+ int base, irq, dma;
+
+ wbsd_unlock_config(host);
+
+ /*
+ * Select SD/MMC function.
+ */
+ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
+
+ /*
+ * Read configuration.
+ */
+ base = wbsd_read_config(host, WBSD_CONF_PORT_HI) << 8;
+ base |= wbsd_read_config(host, WBSD_CONF_PORT_LO);
+
+ irq = wbsd_read_config(host, WBSD_CONF_IRQ);
+
+ dma = wbsd_read_config(host, WBSD_CONF_DRQ);
+
+ wbsd_lock_config(host);
+
+ /*
+ * Validate against given configuration.
+ */
+ if (base != host->base)
+ return 0;
+ if (irq != host->irq)
+ return 0;
+ if ((dma != host->dma) && (host->dma != -1))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Powers down the SD function
+ */
+
+static void wbsd_chip_poweroff(struct wbsd_host *host)
+{
+ wbsd_unlock_config(host);
+
+ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
+ wbsd_write_config(host, WBSD_CONF_ENABLE, 0);
+
+ wbsd_lock_config(host);
+}
+
+/*****************************************************************************\
+ * *
+ * Devices setup and shutdown *
+ * *
+\*****************************************************************************/
+
+static int __devinit wbsd_init(struct device *dev, int base, int irq, int dma,
+ int pnp)
+{
+ struct wbsd_host *host = NULL;
+ struct mmc_host *mmc = NULL;
+ int ret;
+
+ ret = wbsd_alloc_mmc(dev);
+ if (ret)
+ return ret;
+
+ mmc = dev_get_drvdata(dev);
+ host = mmc_priv(mmc);
+
+ /*
+ * Scan for hardware.
+ */
+ ret = wbsd_scan(host);
+ if (ret) {
+ if (pnp && (ret == -ENODEV)) {
+ printk(KERN_WARNING DRIVER_NAME
+ ": Unable to confirm device presence. You may "
+ "experience lock-ups.\n");
+ } else {
+ wbsd_free_mmc(dev);
+ return ret;
+ }
+ }
+
+ /*
+ * Request resources.
+ */
+ ret = wbsd_request_resources(host, base, irq, dma);
+ if (ret) {
+ wbsd_release_resources(host);
+ wbsd_free_mmc(dev);
+ return ret;
+ }
+
+ /*
+ * See if chip needs to be configured.
+ */
+ if (pnp) {
+ if ((host->config != 0) && !wbsd_chip_validate(host)) {
+ printk(KERN_WARNING DRIVER_NAME
+ ": PnP active but chip not configured! "
+ "You probably have a buggy BIOS. "
+ "Configuring chip manually.\n");
+ wbsd_chip_config(host);
+ }
+ } else
+ wbsd_chip_config(host);
+
+ /*
+ * Power Management stuff. No idea how this works.
+ * Not tested.
+ */
+#ifdef CONFIG_PM
+ if (host->config) {
+ wbsd_unlock_config(host);
+ wbsd_write_config(host, WBSD_CONF_PME, 0xA0);
+ wbsd_lock_config(host);
+ }
+#endif
+ /*
+ * Allow device to initialise itself properly.
+ */
+ mdelay(5);
+
+ /*
+ * Reset the chip into a known state.
+ */
+ wbsd_init_device(host);
+
+ mmc_add_host(mmc);
+
+ printk(KERN_INFO "%s: W83L51xD", mmc_hostname(mmc));
+ if (host->chip_id != 0)
+ printk(" id %x", (int)host->chip_id);
+ printk(" at 0x%x irq %d", (int)host->base, (int)host->irq);
+ if (host->dma >= 0)
+ printk(" dma %d", (int)host->dma);
+ else
+ printk(" FIFO");
+ if (pnp)
+ printk(" PnP");
+ printk("\n");
+
+ return 0;
+}
+
+static void __devexit wbsd_shutdown(struct device *dev, int pnp)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct wbsd_host *host;
+
+ if (!mmc)
+ return;
+
+ host = mmc_priv(mmc);
+
+ mmc_remove_host(mmc);
+
+ /*
+ * Power down the SD/MMC function.
+ */
+ if (!pnp)
+ wbsd_chip_poweroff(host);
+
+ wbsd_release_resources(host);
+
+ wbsd_free_mmc(dev);
+}
+
+/*
+ * Non-PnP
+ */
+
+static int __devinit wbsd_probe(struct platform_device *dev)
+{
+ /* Use the module parameters for resources */
+ return wbsd_init(&dev->dev, io, irq, dma, 0);
+}
+
+static int __devexit wbsd_remove(struct platform_device *dev)
+{
+ wbsd_shutdown(&dev->dev, 0);
+
+ return 0;
+}
+
+/*
+ * PnP
+ */
+
+#ifdef CONFIG_PNP
+
+static int __devinit
+wbsd_pnp_probe(struct pnp_dev *pnpdev, const struct pnp_device_id *dev_id)
+{
+ int io, irq, dma;
+
+ /*
+ * Get resources from PnP layer.
+ */
+ io = pnp_port_start(pnpdev, 0);
+ irq = pnp_irq(pnpdev, 0);
+ if (pnp_dma_valid(pnpdev, 0))
+ dma = pnp_dma(pnpdev, 0);
+ else
+ dma = -1;
+
+ DBGF("PnP resources: port %3x irq %d dma %d\n", io, irq, dma);
+
+ return wbsd_init(&pnpdev->dev, io, irq, dma, 1);
+}
+
+static void __devexit wbsd_pnp_remove(struct pnp_dev *dev)
+{
+ wbsd_shutdown(&dev->dev, 1);
+}
+
+#endif /* CONFIG_PNP */
+
+/*
+ * Power management
+ */
+
+#ifdef CONFIG_PM
+
+static int wbsd_suspend(struct wbsd_host *host, pm_message_t state)
+{
+ BUG_ON(host == NULL);
+
+ return mmc_suspend_host(host->mmc, state);
+}
+
+static int wbsd_resume(struct wbsd_host *host)
+{
+ BUG_ON(host == NULL);
+
+ wbsd_init_device(host);
+
+ return mmc_resume_host(host->mmc);
+}
+
+static int wbsd_platform_suspend(struct platform_device *dev,
+ pm_message_t state)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ struct wbsd_host *host;
+ int ret;
+
+ if (mmc == NULL)
+ return 0;
+
+ DBGF("Suspending...\n");
+
+ host = mmc_priv(mmc);
+
+ ret = wbsd_suspend(host, state);
+ if (ret)
+ return ret;
+
+ wbsd_chip_poweroff(host);
+
+ return 0;
+}
+
+static int wbsd_platform_resume(struct platform_device *dev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ struct wbsd_host *host;
+
+ if (mmc == NULL)
+ return 0;
+
+ DBGF("Resuming...\n");
+
+ host = mmc_priv(mmc);
+
+ wbsd_chip_config(host);
+
+ /*
+ * Allow device to initialise itself properly.
+ */
+ mdelay(5);
+
+ return wbsd_resume(host);
+}
+
+#ifdef CONFIG_PNP
+
+static int wbsd_pnp_suspend(struct pnp_dev *pnp_dev, pm_message_t state)
+{
+ struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
+ struct wbsd_host *host;
+
+ if (mmc == NULL)
+ return 0;
+
+ DBGF("Suspending...\n");
+
+ host = mmc_priv(mmc);
+
+ return wbsd_suspend(host, state);
+}
+
+static int wbsd_pnp_resume(struct pnp_dev *pnp_dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
+ struct wbsd_host *host;
+
+ if (mmc == NULL)
+ return 0;
+
+ DBGF("Resuming...\n");
+
+ host = mmc_priv(mmc);
+
+ /*
+ * See if chip needs to be configured.
+ */
+ if (host->config != 0) {
+ if (!wbsd_chip_validate(host)) {
+ printk(KERN_WARNING DRIVER_NAME
+ ": PnP active but chip not configured! "
+ "You probably have a buggy BIOS. "
+ "Configuring chip manually.\n");
+ wbsd_chip_config(host);
+ }
+ }
+
+ /*
+ * Allow device to initialise itself properly.
+ */
+ mdelay(5);
+
+ return wbsd_resume(host);
+}
+
+#endif /* CONFIG_PNP */
+
+#else /* CONFIG_PM */
+
+#define wbsd_platform_suspend NULL
+#define wbsd_platform_resume NULL
+
+#define wbsd_pnp_suspend NULL
+#define wbsd_pnp_resume NULL
+
+#endif /* CONFIG_PM */
+
+static struct platform_device *wbsd_device;
+
+static struct platform_driver wbsd_driver = {
+ .probe = wbsd_probe,
+ .remove = __devexit_p(wbsd_remove),
+
+ .suspend = wbsd_platform_suspend,
+ .resume = wbsd_platform_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+#ifdef CONFIG_PNP
+
+static struct pnp_driver wbsd_pnp_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pnp_dev_table,
+ .probe = wbsd_pnp_probe,
+ .remove = __devexit_p(wbsd_pnp_remove),
+
+ .suspend = wbsd_pnp_suspend,
+ .resume = wbsd_pnp_resume,
+};
+
+#endif /* CONFIG_PNP */
+
+/*
+ * Module loading/unloading
+ */
+
+static int __init wbsd_drv_init(void)
+{
+ int result;
+
+ printk(KERN_INFO DRIVER_NAME
+ ": Winbond W83L51xD SD/MMC card interface driver\n");
+ printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
+
+#ifdef CONFIG_PNP
+
+ if (!nopnp) {
+ result = pnp_register_driver(&wbsd_pnp_driver);
+ if (result < 0)
+ return result;
+ }
+#endif /* CONFIG_PNP */
+
+ if (nopnp) {
+ result = platform_driver_register(&wbsd_driver);
+ if (result < 0)
+ return result;
+
+ wbsd_device = platform_device_alloc(DRIVER_NAME, -1);
+ if (!wbsd_device) {
+ platform_driver_unregister(&wbsd_driver);
+ return -ENOMEM;
+ }
+
+ result = platform_device_add(wbsd_device);
+ if (result) {
+ platform_device_put(wbsd_device);
+ platform_driver_unregister(&wbsd_driver);
+ return result;
+ }
+ }
+
+ return 0;
+}
+
+static void __exit wbsd_drv_exit(void)
+{
+#ifdef CONFIG_PNP
+
+ if (!nopnp)
+ pnp_unregister_driver(&wbsd_pnp_driver);
+
+#endif /* CONFIG_PNP */
+
+ if (nopnp) {
+ platform_device_unregister(wbsd_device);
+
+ platform_driver_unregister(&wbsd_driver);
+ }
+
+ DBG("unloaded\n");
+}
+
+module_init(wbsd_drv_init);
+module_exit(wbsd_drv_exit);
+#ifdef CONFIG_PNP
+module_param(nopnp, uint, 0444);
+#endif
+module_param(io, uint, 0444);
+module_param(irq, uint, 0444);
+module_param(dma, int, 0444);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>");
+MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver");
+
+#ifdef CONFIG_PNP
+MODULE_PARM_DESC(nopnp, "Scan for device instead of relying on PNP. (default 0)");
+#endif
+MODULE_PARM_DESC(io, "I/O base to allocate. Must be 8 byte aligned. (default 0x248)");
+MODULE_PARM_DESC(irq, "IRQ to allocate. (default 6)");
+MODULE_PARM_DESC(dma, "DMA channel to allocate. -1 for no DMA. (default 2)");
diff --git a/drivers/mmc/host/wbsd.h b/drivers/mmc/host/wbsd.h
new file mode 100644
index 000000000000..873bda1e59b4
--- /dev/null
+++ b/drivers/mmc/host/wbsd.h
@@ -0,0 +1,185 @@
+/*
+ * linux/drivers/mmc/wbsd.h - Winbond W83L51xD SD/MMC driver
+ *
+ * Copyright (C) 2004-2007 Pierre Ossman, All Rights Reserved.
+ *
+ * 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.
+ */
+
+#define LOCK_CODE 0xAA
+
+#define WBSD_CONF_SWRST 0x02
+#define WBSD_CONF_DEVICE 0x07
+#define WBSD_CONF_ID_HI 0x20
+#define WBSD_CONF_ID_LO 0x21
+#define WBSD_CONF_POWER 0x22
+#define WBSD_CONF_PME 0x23
+#define WBSD_CONF_PMES 0x24
+
+#define WBSD_CONF_ENABLE 0x30
+#define WBSD_CONF_PORT_HI 0x60
+#define WBSD_CONF_PORT_LO 0x61
+#define WBSD_CONF_IRQ 0x70
+#define WBSD_CONF_DRQ 0x74
+
+#define WBSD_CONF_PINS 0xF0
+
+#define DEVICE_SD 0x03
+
+#define WBSD_PINS_DAT3_HI 0x20
+#define WBSD_PINS_DAT3_OUT 0x10
+#define WBSD_PINS_GP11_HI 0x04
+#define WBSD_PINS_DETECT_GP11 0x02
+#define WBSD_PINS_DETECT_DAT3 0x01
+
+#define WBSD_CMDR 0x00
+#define WBSD_DFR 0x01
+#define WBSD_EIR 0x02
+#define WBSD_ISR 0x03
+#define WBSD_FSR 0x04
+#define WBSD_IDXR 0x05
+#define WBSD_DATAR 0x06
+#define WBSD_CSR 0x07
+
+#define WBSD_EINT_CARD 0x40
+#define WBSD_EINT_FIFO_THRE 0x20
+#define WBSD_EINT_CRC 0x10
+#define WBSD_EINT_TIMEOUT 0x08
+#define WBSD_EINT_PROGEND 0x04
+#define WBSD_EINT_BUSYEND 0x02
+#define WBSD_EINT_TC 0x01
+
+#define WBSD_INT_PENDING 0x80
+#define WBSD_INT_CARD 0x40
+#define WBSD_INT_FIFO_THRE 0x20
+#define WBSD_INT_CRC 0x10
+#define WBSD_INT_TIMEOUT 0x08
+#define WBSD_INT_PROGEND 0x04
+#define WBSD_INT_BUSYEND 0x02
+#define WBSD_INT_TC 0x01
+
+#define WBSD_FIFO_EMPTY 0x80
+#define WBSD_FIFO_FULL 0x40
+#define WBSD_FIFO_EMTHRE 0x20
+#define WBSD_FIFO_FUTHRE 0x10
+#define WBSD_FIFO_SZMASK 0x0F
+
+#define WBSD_MSLED 0x20
+#define WBSD_POWER_N 0x10
+#define WBSD_WRPT 0x04
+#define WBSD_CARDPRESENT 0x01
+
+#define WBSD_IDX_CLK 0x01
+#define WBSD_IDX_PBSMSB 0x02
+#define WBSD_IDX_TAAC 0x03
+#define WBSD_IDX_NSAC 0x04
+#define WBSD_IDX_PBSLSB 0x05
+#define WBSD_IDX_SETUP 0x06
+#define WBSD_IDX_DMA 0x07
+#define WBSD_IDX_FIFOEN 0x08
+#define WBSD_IDX_STATUS 0x10
+#define WBSD_IDX_RSPLEN 0x1E
+#define WBSD_IDX_RESP0 0x1F
+#define WBSD_IDX_RESP1 0x20
+#define WBSD_IDX_RESP2 0x21
+#define WBSD_IDX_RESP3 0x22
+#define WBSD_IDX_RESP4 0x23
+#define WBSD_IDX_RESP5 0x24
+#define WBSD_IDX_RESP6 0x25
+#define WBSD_IDX_RESP7 0x26
+#define WBSD_IDX_RESP8 0x27
+#define WBSD_IDX_RESP9 0x28
+#define WBSD_IDX_RESP10 0x29
+#define WBSD_IDX_RESP11 0x2A
+#define WBSD_IDX_RESP12 0x2B
+#define WBSD_IDX_RESP13 0x2C
+#define WBSD_IDX_RESP14 0x2D
+#define WBSD_IDX_RESP15 0x2E
+#define WBSD_IDX_RESP16 0x2F
+#define WBSD_IDX_CRCSTATUS 0x30
+#define WBSD_IDX_ISR 0x3F
+
+#define WBSD_CLK_375K 0x00
+#define WBSD_CLK_12M 0x01
+#define WBSD_CLK_16M 0x02
+#define WBSD_CLK_24M 0x03
+
+#define WBSD_DATA_WIDTH 0x01
+
+#define WBSD_DAT3_H 0x08
+#define WBSD_FIFO_RESET 0x04
+#define WBSD_SOFT_RESET 0x02
+#define WBSD_INC_INDEX 0x01
+
+#define WBSD_DMA_SINGLE 0x02
+#define WBSD_DMA_ENABLE 0x01
+
+#define WBSD_FIFOEN_EMPTY 0x20
+#define WBSD_FIFOEN_FULL 0x10
+#define WBSD_FIFO_THREMASK 0x0F
+
+#define WBSD_BLOCK_READ 0x80
+#define WBSD_BLOCK_WRITE 0x40
+#define WBSD_BUSY 0x20
+#define WBSD_CARDTRAFFIC 0x04
+#define WBSD_SENDCMD 0x02
+#define WBSD_RECVRES 0x01
+
+#define WBSD_RSP_SHORT 0x00
+#define WBSD_RSP_LONG 0x01
+
+#define WBSD_CRC_MASK 0x1F
+#define WBSD_CRC_OK 0x05 /* S010E (00101) */
+#define WBSD_CRC_FAIL 0x0B /* S101E (01011) */
+
+#define WBSD_DMA_SIZE 65536
+
+struct wbsd_host
+{
+ struct mmc_host* mmc; /* MMC structure */
+
+ spinlock_t lock; /* Mutex */
+
+ int flags; /* Driver states */
+
+#define WBSD_FCARD_PRESENT (1<<0) /* Card is present */
+#define WBSD_FIGNORE_DETECT (1<<1) /* Ignore card detection */
+
+ struct mmc_request* mrq; /* Current request */
+
+ u8 isr; /* Accumulated ISR */
+
+ struct scatterlist* cur_sg; /* Current SG entry */
+ unsigned int num_sg; /* Number of entries left */
+
+ unsigned int offset; /* Offset into current entry */
+ unsigned int remain; /* Data left in curren entry */
+
+ char* dma_buffer; /* ISA DMA buffer */
+ dma_addr_t dma_addr; /* Physical address for same */
+
+ int firsterr; /* See fifo functions */
+
+ u8 clk; /* Current clock speed */
+ unsigned char bus_width; /* Current bus width */
+
+ int config; /* Config port */
+ u8 unlock_code; /* Code to unlock config */
+
+ int chip_id; /* ID of controller */
+
+ int base; /* I/O port base */
+ int irq; /* Interrupt */
+ int dma; /* DMA channel */
+
+ struct tasklet_struct card_tasklet; /* Tasklet structures */
+ struct tasklet_struct fifo_tasklet;
+ struct tasklet_struct crc_tasklet;
+ struct tasklet_struct timeout_tasklet;
+ struct tasklet_struct finish_tasklet;
+
+ struct timer_list ignore_timer; /* Ignore detection timer */
+};