diff options
Diffstat (limited to 'drivers/mmc')
-rw-r--r-- | drivers/mmc/Kconfig | 22 | ||||
-rw-r--r-- | drivers/mmc/Makefile | 6 | ||||
-rw-r--r-- | drivers/mmc/au1xmmc.c | 21 | ||||
-rw-r--r-- | drivers/mmc/mmc.c | 19 | ||||
-rw-r--r-- | drivers/mmc/mmci.c | 4 | ||||
-rw-r--r-- | drivers/mmc/omap.c | 1226 | ||||
-rw-r--r-- | drivers/mmc/omap.h | 55 | ||||
-rw-r--r-- | drivers/mmc/pxamci.c | 26 | ||||
-rw-r--r-- | drivers/mmc/sdhci.c | 1261 | ||||
-rw-r--r-- | drivers/mmc/sdhci.h | 185 | ||||
-rw-r--r-- | drivers/mmc/wbsd.c | 9 |
11 files changed, 2784 insertions, 50 deletions
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index 5d397b7a549..7cc162e8978 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -49,6 +49,28 @@ config MMC_PXA 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 diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index e351e71146e..c7c34aadfc9 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -17,7 +17,13 @@ obj-$(CONFIG_MMC_BLOCK) += mmc_block.o # obj-$(CONFIG_MMC_ARMMMCI) += mmci.o obj-$(CONFIG_MMC_PXA) += pxamci.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 mmc_core-y := mmc.o mmc_queue.o mmc_sysfs.o + +ifeq ($(CONFIG_MMC_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/mmc/au1xmmc.c b/drivers/mmc/au1xmmc.c index 8d84b045bc8..c0326bbc5f2 100644 --- a/drivers/mmc/au1xmmc.c +++ b/drivers/mmc/au1xmmc.c @@ -56,12 +56,11 @@ #define DRIVER_NAME "au1xxx-mmc" /* Set this to enable special debugging macros */ -/* #define MMC_DEBUG */ -#ifdef MMC_DEBUG -#define DEBUG(fmt, idx, args...) printk("au1xx(%d): DEBUG: " fmt, idx, ##args) +#ifdef DEBUG +#define DBG(fmt, idx, args...) printk("au1xx(%d): DEBUG: " fmt, idx, ##args) #else -#define DEBUG(fmt, idx, args...) +#define DBG(fmt, idx, args...) #endif const struct { @@ -87,7 +86,7 @@ struct au1xmmc_host *au1xmmc_hosts[AU1XMMC_CONTROLLER_COUNT]; static int dma = 1; #ifdef MODULE -MODULE_PARM(dma, "i"); +module_param(dma, bool, 0); MODULE_PARM_DESC(dma, "Use DMA engine for data transfers (0 = disabled)"); #endif @@ -424,18 +423,18 @@ static void au1xmmc_receive_pio(struct au1xmmc_host *host) break; if (status & SD_STATUS_RC) { - DEBUG("RX CRC Error [%d + %d].\n", host->id, + DBG("RX CRC Error [%d + %d].\n", host->id, host->pio.len, count); break; } if (status & SD_STATUS_RO) { - DEBUG("RX Overrun [%d + %d]\n", host->id, + DBG("RX Overrun [%d + %d]\n", host->id, host->pio.len, count); break; } else if (status & SD_STATUS_RU) { - DEBUG("RX Underrun [%d + %d]\n", host->id, + DBG("RX Underrun [%d + %d]\n", host->id, host->pio.len, count); break; } @@ -721,7 +720,7 @@ static void au1xmmc_set_ios(struct mmc_host* mmc, struct mmc_ios* ios) { struct au1xmmc_host *host = mmc_priv(mmc); - DEBUG("set_ios (power=%u, clock=%uHz, vdd=%u, mode=%u)\n", + DBG("set_ios (power=%u, clock=%uHz, vdd=%u, mode=%u)\n", host->id, ios->power_mode, ios->clock, ios->vdd, ios->bus_mode); @@ -810,7 +809,7 @@ static irqreturn_t au1xmmc_irq(int irq, void *dev_id, struct pt_regs *regs) au1xmmc_receive_pio(host); } else if (status & 0x203FBC70) { - DEBUG("Unhandled status %8.8x\n", host->id, status); + DBG("Unhandled status %8.8x\n", host->id, status); handled = 0; } @@ -839,7 +838,7 @@ static void au1xmmc_poll_event(unsigned long arg) if (host->mrq != NULL) { u32 status = au_readl(HOST_STATUS(host)); - DEBUG("PENDING - %8.8x\n", host->id, status); + DBG("PENDING - %8.8x\n", host->id, status); } mod_timer(&host->timer, jiffies + AU1XMMC_DETECT_TIMEOUT); diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 1888060c5e0..da6ddd910fc 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -27,12 +27,6 @@ #include "mmc.h" -#ifdef CONFIG_MMC_DEBUG -#define DBG(x...) printk(KERN_DEBUG x) -#else -#define DBG(x...) do { } while (0) -#endif - #define CMD_RETRIES 3 /* @@ -77,8 +71,9 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) { struct mmc_command *cmd = mrq->cmd; int err = mrq->cmd->error; - DBG("MMC: req done (%02x): %d: %08x %08x %08x %08x\n", cmd->opcode, - err, cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]); + pr_debug("MMC: req done (%02x): %d: %08x %08x %08x %08x\n", + cmd->opcode, err, cmd->resp[0], cmd->resp[1], + cmd->resp[2], cmd->resp[3]); if (err && cmd->retries) { cmd->retries--; @@ -102,8 +97,8 @@ EXPORT_SYMBOL(mmc_request_done); void mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) { - DBG("MMC: starting cmd %02x arg %08x flags %08x\n", - mrq->cmd->opcode, mrq->cmd->arg, mrq->cmd->flags); + pr_debug("MMC: starting cmd %02x arg %08x flags %08x\n", + mrq->cmd->opcode, mrq->cmd->arg, mrq->cmd->flags); WARN_ON(host->card_busy == NULL); @@ -976,8 +971,8 @@ static unsigned int mmc_calculate_clock(struct mmc_host *host) if (!mmc_card_dead(card) && max_dtr > card->csd.max_dtr) max_dtr = card->csd.max_dtr; - DBG("MMC: selected %d.%03dMHz transfer rate\n", - max_dtr / 1000000, (max_dtr / 1000) % 1000); + pr_debug("MMC: selected %d.%03dMHz transfer rate\n", + max_dtr / 1000000, (max_dtr / 1000) % 1000); return max_dtr; } diff --git a/drivers/mmc/mmci.c b/drivers/mmc/mmci.c index 9fef29d978b..df7e861e2fc 100644 --- a/drivers/mmc/mmci.c +++ b/drivers/mmc/mmci.c @@ -33,12 +33,8 @@ #define DRIVER_NAME "mmci-pl18x" -#ifdef CONFIG_MMC_DEBUG #define DBG(host,fmt,args...) \ pr_debug("%s: %s: " fmt, mmc_hostname(host->mmc), __func__ , args) -#else -#define DBG(host,fmt,args...) do { } while (0) -#endif static unsigned int fmax = 515633; diff --git a/drivers/mmc/omap.c b/drivers/mmc/omap.c new file mode 100644 index 00000000000..becb3c68c34 --- /dev/null +++ b/drivers/mmc/omap.c @@ -0,0 +1,1226 @@ +/* + * 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/config.h> +#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/protocol.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> + +#include "omap.h" + +#define DRIVER_NAME "mmci-omap" +#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE)) + +/* 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; + void __iomem *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 (RSP_TYPE(mmc_resp_type(cmd))) { + case RSP_TYPE(MMC_RSP_R1): + /* resp 1, resp 1b */ + resptype = 1; + break; + case RSP_TYPE(MMC_RSP_R2): + resptype = 2; + break; + case RSP_TYPE(MMC_RSP_R3): + resptype = 3; + break; + default: + 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->base, CTO, 200); + OMAP_MMC_WRITE(host->base, ARGL, cmd->arg & 0xffff); + OMAP_MMC_WRITE(host->base, ARGH, cmd->arg >> 16); + OMAP_MMC_WRITE(host->base, 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->base, 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->base, RSP0) | + (OMAP_MMC_READ(host->base, RSP1) << 16); + cmd->resp[2] = + OMAP_MMC_READ(host->base, RSP2) | + (OMAP_MMC_READ(host->base, RSP3) << 16); + cmd->resp[1] = + OMAP_MMC_READ(host->base, RSP4) | + (OMAP_MMC_READ(host->base, RSP5) << 16); + cmd->resp[0] = + OMAP_MMC_READ(host->base, RSP6) | + (OMAP_MMC_READ(host->base, RSP7) << 16); + } else { + /* response types 1, 1b, 3, 4, 5, 6 */ + cmd->resp[0] = + OMAP_MMC_READ(host->base, RSP6) | + (OMAP_MMC_READ(host->base, 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; + void __iomem *reg; + u16 *p; + + 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->base + OMAP_MMC_REG_DATA, host->buffer, n); + } else { + __raw_readsw(host->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 pt_regs *regs) +{ + 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->base, STAT); + dev_info(mmc_dev(host->mmc),"spurious irq 0x%04x\n", status); + if (status != 0) { + OMAP_MMC_WRITE(host->base, STAT, status); + OMAP_MMC_WRITE(host->base, IE, 0); + } + return IRQ_HANDLED; + } + + end_command = 0; + end_transfer = 0; + transfer_error = 0; + + while ((status = OMAP_MMC_READ(host->base, STAT)) != 0) { + OMAP_MMC_WRITE(host->base, 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->base, RSP6) + | (OMAP_MMC_READ(host->base, 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 pt_regs *regs) +{ + 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); +} + +/* FIXME: Handle card insertion and removal properly. Maybe use a mask + * for MMC state? */ +static void mmc_omap_switch_callback(unsigned long data, u8 mmc_mask) +{ +} + +static void mmc_omap_switch_handler(void *data) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *) data; + 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 = io_v2p((u32) host->base) + OMAP_MMC_REG_DATA; + frame = 1 << data->blksz_bits; + count = sg_dma_len(sg); + + if ((data->blocks == 1) && (count > (1 << data->blksz_bits))) + 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 */ + if (unlikely(count > 0xffff)) + BUG(); + + OMAP_MMC_WRITE(host->base, 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 & OMAP_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->base, SDIO); + reg &= ~(1 << 5); + OMAP_MMC_WRITE(host->base, SDIO, reg); + /* Set maximum timeout */ + OMAP_MMC_WRITE(host->base, 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->base, SDIO); + if (timeout > 0xffff) { + reg |= (1 << 5); + timeout /= 1024; + } else + reg &= ~(1 << 5); + OMAP_MMC_WRITE(host->base, SDIO, reg); + OMAP_MMC_WRITE(host->base, 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->base, BLEN, 0); + OMAP_MMC_WRITE(host->base, NBLK, 0); + OMAP_MMC_WRITE(host->base, BUF, 0); + host->dma_in_use = 0; + set_cmd_timeout(host, req); + return; + } + + + block_size = 1 << data->blksz_bits; + + OMAP_MMC_WRITE(host->base, NBLK, data->blocks - 1); + OMAP_MMC_WRITE(host->base, 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->base, 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->base, CON); + OMAP_MMC_WRITE(host->base, 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->base, CON); + OMAP_MMC_WRITE(host->base, 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->base, CON, dsor); + if (ios->power_mode == MMC_POWER_UP) { + /* Send clock cycles, poll completion */ + OMAP_MMC_WRITE(host->base, IE, 0); + OMAP_MMC_WRITE(host->base, STAT, 0xffff); + OMAP_MMC_WRITE(host->base, CMD, 1<<7); + while (0 == (OMAP_MMC_READ(host->base, STAT) & 1)); + OMAP_MMC_WRITE(host->base, 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 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; + int ret = 0; + + if (platform_get_resource(pdev, IORESOURCE_MEM, 0) || + platform_get_irq(pdev, IORESOURCE_IRQ, 0)) { + dev_err(&pdev->dev, "mmc_omap_probe: invalid resource type\n"); + return -ENODEV; + } + + if (!request_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1, + pdev->name)) { + dev_dbg(&pdev->dev, "request_mem_region failed\n"); + return -EBUSY; + } + + mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev); + if (!mmc) { + ret = -ENOMEM; + goto out; + } + + 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; + + if (cpu_is_omap24xx()) { + host->iclk = clk_get(&pdev->dev, "mmc_ick"); + if (IS_ERR(host->iclk)) + goto out; + 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 out; + } + + /* 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 = pdev->resource[1].start; + host->base = ioremap(pdev->res.start, SZ_4K); + if (!host->base) { + ret = -ENOMEM; + goto out; + } + + if (minfo->wire4) + mmc->caps |= MMC_CAP_4_BIT_DATA; + + mmc->ops = &mmc_omap_ops; + mmc->f_min = 400000; + mmc->f_max = 24000000; + mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34; + + /* 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_sectors = 256; /* NBLK max 11-bits, OMAP also limited by DMA */ + mmc->max_seg_size = mmc->max_sectors * 512; + + 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 out; + } + omap_set_gpio_direction(host->power_pin, 0); + } + + ret = request_irq(host->irq, mmc_omap_irq, 0, DRIVER_NAME, host); + if (ret) + goto out; + + host->dev = &pdev->dev; + platform_set_drvdata(pdev, host); + + mmc_add_host(mmc); + + if (host->switch_pin >= 0) { + INIT_WORK(&host->switch_work, mmc_omap_switch_handler, host); + 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, SA_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_wan(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); + } + +no_switch: + return 0; + +out: + /* 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); + } + 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); + + if (host) { + 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); + mmc_free_host(host->mmc); + } + + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1); + + 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/omap.h b/drivers/mmc/omap.h new file mode 100644 index 00000000000..c954d355a5e --- /dev/null +++ b/drivers/mmc/omap.h @@ -0,0 +1,55 @@ +#ifndef DRIVERS_MEDIA_MMC_OMAP_H +#define DRIVERS_MEDIA_MMC_OMAP_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(base, reg) __raw_readw((base) + OMAP_MMC_REG_##reg) +#define OMAP_MMC_WRITE(base, reg, val) __raw_writew((val), (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 + +#endif diff --git a/drivers/mmc/pxamci.c b/drivers/mmc/pxamci.c index 285d7d06809..eb9a8826e9b 100644 --- a/drivers/mmc/pxamci.c +++ b/drivers/mmc/pxamci.c @@ -37,12 +37,6 @@ #include "pxamci.h" -#ifdef CONFIG_MMC_DEBUG -#define DBG(x...) printk(KERN_DEBUG x) -#else -#define DBG(x...) do { } while (0) -#endif - #define DRIVER_NAME "pxa2xx-mci" #define NR_SG 1 @@ -206,7 +200,7 @@ static void pxamci_start_cmd(struct pxamci_host *host, struct mmc_command *cmd, static void pxamci_finish_request(struct pxamci_host *host, struct mmc_request *mrq) { - DBG("PXAMCI: request done\n"); + pr_debug("PXAMCI: request done\n"); host->mrq = NULL; host->cmd = NULL; host->data = NULL; @@ -252,7 +246,7 @@ static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat) if ((cmd->resp[0] & 0x80000000) == 0) cmd->error = MMC_ERR_BADCRC; } else { - DBG("ignoring CRC from command %d - *risky*\n",cmd->opcode); + pr_debug("ignoring CRC from command %d - *risky*\n",cmd->opcode); } #else cmd->error = MMC_ERR_BADCRC; @@ -317,12 +311,12 @@ static irqreturn_t pxamci_irq(int irq, void *devid, struct pt_regs *regs) ireg = readl(host->base + MMC_I_REG); - DBG("PXAMCI: irq %08x\n", ireg); + pr_debug("PXAMCI: irq %08x\n", ireg); if (ireg) { unsigned stat = readl(host->base + MMC_STAT); - DBG("PXAMCI: stat %08x\n", stat); + pr_debug("PXAMCI: stat %08x\n", stat); if (ireg & END_CMD_RES) handled |= pxamci_cmd_done(host, stat); @@ -376,9 +370,9 @@ static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct pxamci_host *host = mmc_priv(mmc); - DBG("pxamci_set_ios: clock %u power %u vdd %u.%02u\n", - ios->clock, ios->power_mode, ios->vdd / 100, - ios->vdd % 100); + pr_debug("pxamci_set_ios: clock %u power %u vdd %u.%02u\n", + ios->clock, ios->power_mode, ios->vdd / 100, + ios->vdd % 100); if (ios->clock) { unsigned int clk = CLOCKRATE / ios->clock; @@ -405,8 +399,8 @@ static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) host->cmdat |= CMDAT_INIT; } - DBG("pxamci_set_ios: clkrt = %x cmdat = %x\n", - host->clkrt, host->cmdat); + pr_debug("pxamci_set_ios: clkrt = %x cmdat = %x\n", + host->clkrt, host->cmdat); } static struct mmc_host_ops pxamci_ops = { @@ -438,7 +432,7 @@ static int pxamci_probe(struct platform_device *pdev) r = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); - if (!r || irq == NO_IRQ) + if (!r || irq < 0) return -ENXIO; r = request_mem_region(r->start, SZ_4K, DRIVER_NAME); diff --git a/drivers/mmc/sdhci.c b/drivers/mmc/sdhci.c new file mode 100644 index 00000000000..bdbfca05002 --- /dev/null +++ b/drivers/mmc/sdhci.c @@ -0,0 +1,1261 @@ +/* + * linux/drivers/mmc/sdhci.c - Secure Digital Host Controller Interface driver + * + * Copyright (C) 2005-2006 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 version 2 as + * published by the Free Software Foundation. + */ + + /* + * Note that PIO transfer is rather crappy atm. The buffer full/empty + * interrupts aren't reliable so we currently transfer the entire buffer + * directly. Patches to solve the problem are welcome. + */ + +#include <linux/delay.h> +#include <linux/highmem.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/protocol.h> + +#include <asm/scatterlist.h> + +#include "sdhci.h" + +#define DRIVER_NAME "sdhci" +#define DRIVER_VERSION "0.11" + +#define BUGMAIL "<sdhci-devel@list.drzeus.cx>" + +#define DBG(f, x...) \ + pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x) + +static const struct pci_device_id pci_ids[] __devinitdata = { + /* handle any 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) +{ + writeb(mask, host->ioaddr + SDHCI_SOFTWARE_RESET); + + if (mask & SDHCI_RESET_ALL) { + host->clock = 0; + + mdelay(50); + } +} + +static void sdhci_init(struct sdhci_host *host) +{ + u32 intmask; + + sdhci_reset(host, SDHCI_RESET_ALL); + + intmask = ~(SDHCI_INT_CARD_INT | SDHCI_INT_BUF_EMPTY | SDHCI_INT_BUF_FULL); + + writel(intmask, host->ioaddr + SDHCI_INT_ENABLE); + writel(intmask, host->ioaddr + SDHCI_SIGNAL_ENABLE); + + /* This is unknown magic. */ + writeb(0xE, host->ioaddr + SDHCI_TIMEOUT_CONTROL); +} + +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_kmap_sg(struct sdhci_host* host) +{ + host->mapped_sg = kmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ); + return host->mapped_sg + host->cur_sg->offset; +} + +static inline void sdhci_kunmap_sg(struct sdhci_host* host) +{ + kunmap_atomic(host->mapped_sg, KM_BIO_SRC_IRQ); +} + +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_transfer_pio(struct sdhci_host *host) +{ + char *buffer; + u32 mask; + int bytes, size; + unsigned long max_jiffies; + + BUG_ON(!host->data); + + if (host->num_sg == 0) + return; + + bytes = 0; + if (host->data->flags & MMC_DATA_READ) + mask = SDHCI_DATA_AVAILABLE; + else + mask = SDHCI_SPACE_AVAILABLE; + + buffer = sdhci_kmap_sg(host) + host->offset; + + /* Transfer shouldn't take more than 5 s */ + max_jiffies = jiffies + HZ * 5; + + while (host->size > 0) { + if (time_after(jiffies, max_jiffies)) { + printk(KERN_ERR "%s: PIO transfer stalled. " + "Please report this to " + BUGMAIL ".\n", mmc_hostname(host->mmc)); + sdhci_dumpregs(host); + + sdhci_kunmap_sg(host); + + host->data->error = MMC_ERR_FAILED; + sdhci_finish_data(host); + return; + } + + if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & mask)) + continue; + + size = min(host->size, host->remain); + + if (size >= 4) { + if (host->data->flags & MMC_DATA_READ) + *(u32*)buffer = readl(host->ioaddr + SDHCI_BUFFER); + else + writel(*(u32*)buffer, host->ioaddr + SDHCI_BUFFER); + size = 4; + } else if (size >= 2) { + if (host->data->flags & MMC_DATA_READ) + *(u16*)buffer = readw(host->ioaddr + SDHCI_BUFFER); + else + writew(*(u16*)buffer, host->ioaddr + SDHCI_BUFFER); + size = 2; + } else { + if (host->data->flags & MMC_DATA_READ) + *(u8*)buffer = readb(host->ioaddr + SDHCI_BUFFER); + else + writeb(*(u8*)buffer, host->ioaddr + SDHCI_BUFFER); + size = 1; + } + + buffer += size; + host->offset += size; + host->remain -= size; + + bytes += size; + host->size -= size; + + if (host->remain == 0) { + sdhci_kunmap_sg(host); + if (sdhci_next_sg(host) == 0) { + DBG("PIO transfer: %d bytes\n", bytes); + return; + } + buffer = sdhci_kmap_sg(host); + } + } + + sdhci_kunmap_sg(host); + + DBG("PIO transfer: %d bytes\n", bytes); +} + +static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data) +{ + u16 mode; + + WARN_ON(host->data); + + if (data == NULL) { + writew(0, host->ioaddr + SDHCI_TRANSFER_MODE); + return; + } + + DBG("blksz %04x blks %04x flags %08x\n", + 1 << data->blksz_bits, data->blocks, data->flags); + DBG("tsac %d ms nsac %d clk\n", + data->timeout_ns / 1000000, data->timeout_clks); + + 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); + + writew(1 << data->blksz_bits, host->ioaddr + SDHCI_BLOCK_SIZE); + writew(data->blocks, host->ioaddr + SDHCI_BLOCK_COUNT); + + 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->size = (1 << data->blksz_bits) * data->blocks; + + host->cur_sg = data->sg; + host->num_sg = data->sg_len; + + host->offset = 0; + host->remain = host->cur_sg->length; + } +} + +static void sdhci_finish_data(struct sdhci_host *host) +{ + struct mmc_data *data; + u32 intmask; + 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); + } else { + intmask = readl(host->ioaddr + SDHCI_SIGNAL_ENABLE); + intmask &= ~(SDHCI_INT_BUF_EMPTY | SDHCI_INT_BUF_FULL); + writel(intmask, host->ioaddr + SDHCI_SIGNAL_ENABLE); + + intmask = readl(host->ioaddr + SDHCI_INT_ENABLE); + intmask &= ~(SDHCI_INT_BUF_EMPTY | SDHCI_INT_BUF_FULL); + writel(intmask, host->ioaddr + SDHCI_INT_ENABLE); + } + + /* + * 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 = (1 << data->blksz_bits) * (data->blocks - blocks); + + if ((data->error == MMC_ERR_NONE) && blocks) { + printk(KERN_ERR "%s: Controller signalled completion even " + "though there were blocks left. Please report this " + "to " BUGMAIL ".\n", mmc_hostname(host->mmc)); + data->error = MMC_ERR_FAILED; + } + + if (host->size != 0) { + printk(KERN_ERR "%s: %d bytes were left untransferred. " + "Please report this to " BUGMAIL ".\n", + mmc_hostname(host->mmc), host->size); + 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 present; + unsigned long max_jiffies; + + WARN_ON(host->cmd); + + DBG("Sending cmd (%x)\n", cmd->opcode); + + /* Wait max 10 ms */ + max_jiffies = jiffies + (HZ + 99)/100; + do { + if (time_after(jiffies, max_jiffies)) { + printk(KERN_ERR "%s: Controller never released " + "inhibit bits. Please report this to " + BUGMAIL ".\n", mmc_hostname(host->mmc)); + sdhci_dumpregs(host); + cmd->error = MMC_ERR_FAILED; + tasklet_schedule(&host->finish_tasklet); + return; + } + present = readl(host->ioaddr + SDHCI_PRESENT_STATE); + } while (present & (SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT)); + + mod_timer(&host->timer, jiffies + 10 * HZ); + + host->cmd = cmd; + + sdhci_prepare_data(host, cmd->data); + + writel(cmd->arg, host->ioaddr + SDHCI_ARGUMENT); + + if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { + printk(KERN_ERR "%s: Unsupported response type! " + "Please report this to " BUGMAIL ".\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; + + writel(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) { + u32 intmask; + + host->data = host->cmd->data; + + if (!(host->flags & SDHCI_USE_DMA)) { + /* + * Don't enable the interrupts until now to make sure we + * get stable handling of the FIFO. + */ + intmask = readl(host->ioaddr + SDHCI_INT_ENABLE); + intmask |= SDHCI_INT_BUF_EMPTY | SDHCI_INT_BUF_FULL; + writel(intmask, host->ioaddr + SDHCI_INT_ENABLE); + + intmask = readl(host->ioaddr + SDHCI_SIGNAL_ENABLE); + intmask |= SDHCI_INT_BUF_EMPTY | SDHCI_INT_BUF_FULL; + writel(intmask, host->ioaddr + SDHCI_SIGNAL_ENABLE); + + /* + * The buffer interrupts are to unreliable so we + * start the transfer immediatly. + */ + sdhci_transfer_pio(host); + } + } 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 max_jiffies; + + 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 */ + max_jiffies = jiffies + (HZ + 99)/100; + do { + if (time_after(jiffies, max_jiffies)) { + printk(KERN_ERR "%s: Internal clock never stabilised. " + "Please report this to " BUGMAIL ".\n", + mmc_hostname(host->mmc)); + sdhci_dumpregs(host); + return; + } + clk = readw(host->ioaddr + SDHCI_CLOCK_CONTROL); + } while (!(clk & SDHCI_CLOCK_INT_STABLE)); + + clk |= SDHCI_CLOCK_CARD_EN; + writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); + +out: + host->clock = clock; +} + +/*****************************************************************************\ + * * + * 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); + + 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); + + DBG("clock %uHz busmode %u powermode %u cs %u Vdd %u width %u\n", + ios->clock, ios->bus_mode, ios->power_mode, ios->chip_select, + ios->vdd, ios->bus_width); + + /* + * 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); + spin_unlock_irqrestore(&host->lock, flags); + sdhci_init(host); + spin_lock_irqsave(&host->lock, flags); + } + + sdhci_set_clock(host, ios->clock); + + if (ios->power_mode == MMC_POWER_OFF) + writeb(0, host->ioaddr + SDHCI_POWER_CONTROL); + else + writeb(0xFF, host->ioaddr + SDHCI_POWER_CONTROL); + + ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL); + if (ios->bus_width == MMC_BUS_WIDTH_4) + ctrl |= SDHCI_CTRL_4BITBUS; + else + ctrl &= ~SDHCI_CTRL_4BITBUS; + writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); + + 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 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))))) { + 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); + + 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. " + "Please report this to " BUGMAIL ".\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); + } + } + + 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)); + printk(KERN_ERR "%s: Please report this to " BUGMAIL ".\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)); + printk(KERN_ERR "%s: Please report this to " BUGMAIL ".\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_BUF_FULL | SDHCI_INT_BUF_EMPTY)) + sdhci_transfer_pio(host); + + if (intmask & SDHCI_INT_DATA_END) + sdhci_finish_data(host); + } +} + +static irqreturn_t sdhci_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + irqreturn_t result; + struct sdhci_host* host = dev_id; + u32 intmask; + + spin_lock(&host->lock); + + intmask = readl(host->ioaddr + SDHCI_INT_STATUS); + + if (!intmask) { + 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)) + tasklet_schedule(&host->card_tasklet); + + if (intmask & SDHCI_INT_CMD_MASK) { + sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK); + + writel(intmask & SDHCI_INT_CMD_MASK, + host->ioaddr + SDHCI_INT_STATUS); + } + + if (intmask & SDHCI_INT_DATA_MASK) { + sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK); + + writel(intmask & SDHCI_INT_DATA_MASK, + host->ioaddr + SDHCI_INT_STATUS); + } + + intmask &= ~(SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK); + + if (intmask & SDHCI_INT_CARD_INT) { + printk(KERN_ERR "%s: Unexpected card interrupt. Please " + "report this to " BUGMAIL ".\n", + mmc_hostname(host->mmc)); + sdhci_dumpregs(host); + } + + if (intmask & SDHCI_INT_BUS_POWER) { + printk(KERN_ERR "%s: Unexpected bus power interrupt. Please " + "report this to " BUGMAIL ".\n", + mmc_hostname(host->mmc)); + sdhci_dumpregs(host); + } + + if (intmask & SDHCI_INT_ACMD12ERR) { + printk(KERN_ERR "%s: Unexpected auto CMD12 error. Please " + "report this to " BUGMAIL ".\n", + mmc_hostname(host->mmc)); + sdhci_dumpregs(host); + + writew(~0, host->ioaddr + SDHCI_ACMD12_ERR); + } + + if (intmask) + writel(intmask, host->ioaddr + SDHCI_INT_STATUS); + + result = IRQ_HANDLED; + +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); + 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); + pci_enable_device(pdev); + + 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); + sdhci_init(chip->hosts[i]); + 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; + 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. 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->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; + } + + caps = readl(host->ioaddr + SDHCI_CAPABILITIES); + + if ((caps & SDHCI_CAN_DO_DMA) && ((pdev->class & 0x0000FF) == 0x01)) + 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; + host->max_clk *= 1000000; + + /* + * Set host parameters. + */ + mmc->ops = &sdhci_ops; + mmc->f_min = host->max_clk / 256; + mmc->f_max = host->max_clk; + mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34; + mmc->caps = MMC_CAP_4_BIT_DATA; + + 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 sector + * count register. + */ + mmc->max_sectors = 0x3FFF; + + /* + * Maximum segment size. Could be one segment with the maximum number + * of sectors. + */ + mmc->max_seg_size = mmc->max_sectors * 512; + + /* + * 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, (int)host); + + ret = request_irq(host->irq, sdhci_irq, SA_SHIRQ, + host->slot_descr, host); + if (ret) + goto unmap; + + sdhci_init(host); + +#ifdef CONFIG_MMC_DEBUG + sdhci_dumpregs(host); +#endif + + host->chip = chip; + chip->hosts[slot] = host; + + 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; + +unmap: + tasklet_kill(&host->card_tasklet); + tasklet_kill(&host->finish_tasklet); + + 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; + struct sdhci_chip *chip; + + BUG_ON(pdev == NULL); + BUG_ON(ent == NULL); + + DBG("found at %s\n", pci_name(pdev)); + + 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->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, " + DRIVER_VERSION "\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_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>"); +MODULE_DESCRIPTION("Secure Digital Host Controller Interface driver"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/sdhci.h b/drivers/mmc/sdhci.h new file mode 100644 index 00000000000..3b270ef486b --- /dev/null +++ b/drivers/mmc/sdhci.h @@ -0,0 +1,185 @@ +/* + * linux/drivers/mmc/sdhci.h - Secure Digital Host Controller Interface driver + * + * Copyright (C) 2005 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 version 2 as + * published by the Free Software Foundation. + */ + +/* + * PCI registers + */ + +#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_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_POWER_CONTROL 0x29 + +#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_BUF_EMPTY 0x00000010 +#define SDHCI_INT_BUF_FULL 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_BUF_EMPTY | SDHCI_INT_BUF_FULL | \ + 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_CAN_DO_DMA 0x00400000 +#define SDHCI_CLOCK_BASE_MASK 0x00003F00 +#define SDHCI_CLOCK_BASE_SHIFT 8 + +/* 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 + +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 clock; /* Current clock (MHz) */ + + 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 */ + char *mapped_sg; /* This is where it's mapped */ + int num_sg; /* Entries left */ + int offset; /* Offset into current sg */ + int remain; /* Bytes left in current */ + + int size; /* Remaining bytes in transfer */ + + 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; + + int num_slots; /* Slots on controller */ + struct sdhci_host *hosts[0]; /* Pointers to hosts */ +}; diff --git a/drivers/mmc/wbsd.c b/drivers/mmc/wbsd.c index 3be397d436f..511f7b0b31d 100644 --- a/drivers/mmc/wbsd.c +++ b/drivers/mmc/wbsd.c @@ -44,15 +44,10 @@ #define DRIVER_NAME "wbsd" #define DRIVER_VERSION "1.5" -#ifdef CONFIG_MMC_DEBUG #define DBG(x...) \ - printk(KERN_DEBUG DRIVER_NAME ": " x) + pr_debug(DRIVER_NAME ": " x) #define DBGF(f, x...) \ - printk(KERN_DEBUG DRIVER_NAME " [%s()]: " f, __func__ , ##x) -#else -#define DBG(x...) do { } while (0) -#define DBGF(x...) do { } while (0) -#endif + pr_debug(DRIVER_NAME " [%s()]: " f, __func__ , ##x) /* * Device resources |