diff options
author | Pierre Ossman <drzeus@drzeus.cx> | 2007-02-11 19:57:36 +0100 |
---|---|---|
committer | Pierre Ossman <drzeus@drzeus.cx> | 2007-05-01 13:04:17 +0200 |
commit | 1c6a0718f0bfdab0d9b7da5f7b74f38a0058c03a (patch) | |
tree | 5e7f2a26d5d1782d87c596b40f874c6c0b8b8e1a /drivers/mmc/host | |
parent | 98ac2162699f7e9880683cb954891817f20b607c (diff) | |
download | op-kernel-dev-1c6a0718f0bfdab0d9b7da5f7b74f38a0058c03a.zip op-kernel-dev-1c6a0718f0bfdab0d9b7da5f7b74f38a0058c03a.tar.gz |
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/Kconfig | 103 | ||||
-rw-r--r-- | drivers/mmc/host/Makefile | 18 | ||||
-rw-r--r-- | drivers/mmc/host/at91_mci.c | 1001 | ||||
-rw-r--r-- | drivers/mmc/host/au1xmmc.c | 1031 | ||||
-rw-r--r-- | drivers/mmc/host/au1xmmc.h | 96 | ||||
-rw-r--r-- | drivers/mmc/host/imxmmc.c | 1137 | ||||
-rw-r--r-- | drivers/mmc/host/imxmmc.h | 67 | ||||
-rw-r--r-- | drivers/mmc/host/mmci.c | 702 | ||||
-rw-r--r-- | drivers/mmc/host/mmci.h | 179 | ||||
-rw-r--r-- | drivers/mmc/host/omap.c | 1288 | ||||
-rw-r--r-- | drivers/mmc/host/pxamci.c | 616 | ||||
-rw-r--r-- | drivers/mmc/host/pxamci.h | 124 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 1539 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.h | 210 | ||||
-rw-r--r-- | drivers/mmc/host/tifm_sd.c | 1102 | ||||
-rw-r--r-- | drivers/mmc/host/wbsd.c | 2062 | ||||
-rw-r--r-- | drivers/mmc/host/wbsd.h | 185 |
17 files changed, 11460 insertions, 0 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig new file mode 100644 index 0000000..ed4deab --- /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 0000000..6685f64 --- /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 0000000..e37943c --- /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 0000000..b7156a4 --- /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 0000000..341cbdf --- /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 0000000..7ee2045 --- /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 0000000..e5339e3 --- /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 0000000..d11c2d2 --- /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 0000000..6d7eadc --- /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 0000000..e851384 --- /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 0000000..a98ff98 --- /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 0000000..1b16322 --- /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 0000000..579142a --- /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 0000000..7400f4b --- /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 0000000..b0d77d2 --- /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 0000000..9f7518b --- /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 0000000..873bda1 --- /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 */ +}; |