diff options
Diffstat (limited to 'drivers/mmc/host/omap.c')
| -rw-r--r-- | drivers/mmc/host/omap.c | 1537 |
1 files changed, 883 insertions, 654 deletions
diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index c9dfeb15b48..81974ecdfcb 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -2,7 +2,7 @@ * linux/drivers/mmc/host/omap.c * * Copyright (C) 2004 Nokia Corporation - * Written by Tuukka Tikkanen and Juha Yrjölä<juha.yrjola@nokia.com> + * 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 * @@ -17,51 +17,47 @@ #include <linux/ioport.h> #include <linux/platform_device.h> #include <linux/interrupt.h> +#include <linux/dmaengine.h> #include <linux/dma-mapping.h> #include <linux/delay.h> #include <linux/spinlock.h> #include <linux/timer.h> +#include <linux/of.h> +#include <linux/omap-dma.h> #include <linux/mmc/host.h> #include <linux/mmc/card.h> +#include <linux/mmc/mmc.h> #include <linux/clk.h> #include <linux/scatterlist.h> -#include <linux/i2c/tps65010.h> +#include <linux/slab.h> +#include <linux/platform_data/mmc-omap.h> -#include <asm/io.h> -#include <asm/irq.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> #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_REG_ARGL 0x01 +#define OMAP_MMC_REG_ARGH 0x02 +#define OMAP_MMC_REG_CON 0x03 +#define OMAP_MMC_REG_STAT 0x04 +#define OMAP_MMC_REG_IE 0x05 +#define OMAP_MMC_REG_CTO 0x06 +#define OMAP_MMC_REG_DTO 0x07 +#define OMAP_MMC_REG_DATA 0x08 +#define OMAP_MMC_REG_BLEN 0x09 +#define OMAP_MMC_REG_NBLK 0x0a +#define OMAP_MMC_REG_BUF 0x0b +#define OMAP_MMC_REG_SDIO 0x0d +#define OMAP_MMC_REG_REV 0x0f +#define OMAP_MMC_REG_RSP0 0x10 +#define OMAP_MMC_REG_RSP1 0x11 +#define OMAP_MMC_REG_RSP2 0x12 +#define OMAP_MMC_REG_RSP3 0x13 +#define OMAP_MMC_REG_RSP4 0x14 +#define OMAP_MMC_REG_RSP5 0x15 +#define OMAP_MMC_REG_RSP6 0x16 +#define OMAP_MMC_REG_RSP7 0x17 +#define OMAP_MMC_REG_IOSR 0x18 +#define OMAP_MMC_REG_SYSC 0x19 +#define OMAP_MMC_REG_SYSS 0x1a #define OMAP_MMC_STAT_CARD_ERR (1 << 14) #define OMAP_MMC_STAT_CARD_IRQ (1 << 13) @@ -77,8 +73,16 @@ #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) +#define mmc_omap7xx() (host->features & MMC_OMAP7XX) +#define mmc_omap15xx() (host->features & MMC_OMAP15XX) +#define mmc_omap16xx() (host->features & MMC_OMAP16XX) +#define MMC_OMAP1_MASK (MMC_OMAP7XX | MMC_OMAP15XX | MMC_OMAP16XX) +#define mmc_omap1() (host->features & MMC_OMAP1_MASK) +#define mmc_omap2() (!mmc_omap1()) + +#define OMAP_MMC_REG(host, reg) (OMAP_MMC_REG_##reg << (host)->reg_shift) +#define OMAP_MMC_READ(host, reg) __raw_readw((host)->virt_base + OMAP_MMC_REG(host, reg)) +#define OMAP_MMC_WRITE(host, reg, val) __raw_writew((val), (host)->virt_base + OMAP_MMC_REG(host, reg)) /* * Command types @@ -88,18 +92,33 @@ #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 +#define OMAP_MMC_COVER_POLL_DELAY 500 + +struct mmc_omap_host; + +struct mmc_omap_slot { + int id; + unsigned int vdd; + u16 saved_con; + u16 bus_mode; + unsigned int fclk_freq; + + struct tasklet_struct cover_tasklet; + struct timer_list cover_timer; + unsigned cover_open; -static int mmc_omap_enable_poll = 1; + struct mmc_request *mrq; + struct mmc_omap_host *host; + struct mmc_host *mmc; + struct omap_mmc_slot_data *pdata; +}; struct mmc_omap_host { int initialized; - int suspended; struct mmc_request * mrq; struct mmc_command * cmd; struct mmc_data * data; @@ -108,12 +127,24 @@ struct mmc_omap_host { unsigned char id; /* 16xx chips have 2 MMC blocks */ struct clk * iclk; struct clk * fclk; - struct resource *mem_res; + struct dma_chan *dma_rx; + u32 dma_rx_burst; + struct dma_chan *dma_tx; + u32 dma_tx_burst; void __iomem *virt_base; unsigned int phys_base; int irq; unsigned char bus_mode; - unsigned char hw_bus_mode; + unsigned int reg_shift; + + struct work_struct cmd_abort_work; + unsigned abort:1; + struct timer_list cmd_abort_timer; + + struct work_struct slot_release_work; + struct mmc_omap_slot *next_slot; + struct work_struct send_stop_work; + struct mmc_data *stop_data; unsigned int sg_len; int sg_idx; @@ -121,73 +152,184 @@ struct mmc_omap_host { u32 buffer_bytes_left; u32 total_bytes_left; - unsigned use_dma:1; + unsigned features; 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; + struct mmc_omap_slot *slots[OMAP_MMC_MAX_SLOTS]; + struct mmc_omap_slot *current_slot; + spinlock_t slot_lock; + wait_queue_head_t slot_wq; + int nr_slots; - int switch_pin; - struct work_struct switch_work; - struct timer_list switch_timer; - int switch_last_state; + struct timer_list clk_timer; + spinlock_t clk_lock; /* for changing enabled state */ + unsigned int fclk_enabled:1; + struct workqueue_struct *mmc_omap_wq; + + struct omap_mmc_platform_data *pdata; }; -static inline int -mmc_omap_cover_is_open(struct mmc_omap_host *host) + +static void mmc_omap_fclk_offdelay(struct mmc_omap_slot *slot) { - if (host->switch_pin < 0) - return 0; - return omap_get_gpio_datain(host->switch_pin); + unsigned long tick_ns; + + if (slot != NULL && slot->host->fclk_enabled && slot->fclk_freq > 0) { + tick_ns = DIV_ROUND_UP(NSEC_PER_SEC, slot->fclk_freq); + ndelay(8 * tick_ns); + } } -static ssize_t -mmc_omap_show_cover_switch(struct device *dev, - struct device_attribute *attr, char *buf) +static void mmc_omap_fclk_enable(struct mmc_omap_host *host, unsigned int enable) { - struct mmc_omap_host *host = dev_get_drvdata(dev); + unsigned long flags; - return sprintf(buf, "%s\n", mmc_omap_cover_is_open(host) ? "open" : - "closed"); + spin_lock_irqsave(&host->clk_lock, flags); + if (host->fclk_enabled != enable) { + host->fclk_enabled = enable; + if (enable) + clk_enable(host->fclk); + else + clk_disable(host->fclk); + } + spin_unlock_irqrestore(&host->clk_lock, flags); } -static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL); +static void mmc_omap_select_slot(struct mmc_omap_slot *slot, int claimed) +{ + struct mmc_omap_host *host = slot->host; + unsigned long flags; -static ssize_t -mmc_omap_show_enable_poll(struct device *dev, - struct device_attribute *attr, char *buf) + if (claimed) + goto no_claim; + spin_lock_irqsave(&host->slot_lock, flags); + while (host->mmc != NULL) { + spin_unlock_irqrestore(&host->slot_lock, flags); + wait_event(host->slot_wq, host->mmc == NULL); + spin_lock_irqsave(&host->slot_lock, flags); + } + host->mmc = slot->mmc; + spin_unlock_irqrestore(&host->slot_lock, flags); +no_claim: + del_timer(&host->clk_timer); + if (host->current_slot != slot || !claimed) + mmc_omap_fclk_offdelay(host->current_slot); + + if (host->current_slot != slot) { + OMAP_MMC_WRITE(host, CON, slot->saved_con & 0xFC00); + if (host->pdata->switch_slot != NULL) + host->pdata->switch_slot(mmc_dev(slot->mmc), slot->id); + host->current_slot = slot; + } + + if (claimed) { + mmc_omap_fclk_enable(host, 1); + + /* Doing the dummy read here seems to work around some bug + * at least in OMAP24xx silicon where the command would not + * start after writing the CMD register. Sigh. */ + OMAP_MMC_READ(host, CON); + + OMAP_MMC_WRITE(host, CON, slot->saved_con); + } else + mmc_omap_fclk_enable(host, 0); +} + +static void mmc_omap_start_request(struct mmc_omap_host *host, + struct mmc_request *req); + +static void mmc_omap_slot_release_work(struct work_struct *work) { - return snprintf(buf, PAGE_SIZE, "%d\n", mmc_omap_enable_poll); + struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, + slot_release_work); + struct mmc_omap_slot *next_slot = host->next_slot; + struct mmc_request *rq; + + host->next_slot = NULL; + mmc_omap_select_slot(next_slot, 1); + + rq = next_slot->mrq; + next_slot->mrq = NULL; + mmc_omap_start_request(host, rq); } -static ssize_t -mmc_omap_store_enable_poll(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t size) +static void mmc_omap_release_slot(struct mmc_omap_slot *slot, int clk_enabled) { - int enable_poll; + struct mmc_omap_host *host = slot->host; + unsigned long flags; + int i; + + BUG_ON(slot == NULL || host->mmc == NULL); + + if (clk_enabled) + /* Keeps clock running for at least 8 cycles on valid freq */ + mod_timer(&host->clk_timer, jiffies + HZ/10); + else { + del_timer(&host->clk_timer); + mmc_omap_fclk_offdelay(slot); + mmc_omap_fclk_enable(host, 0); + } + + spin_lock_irqsave(&host->slot_lock, flags); + /* Check for any pending requests */ + for (i = 0; i < host->nr_slots; i++) { + struct mmc_omap_slot *new_slot; - if (sscanf(buf, "%10d", &enable_poll) != 1) - return -EINVAL; + if (host->slots[i] == NULL || host->slots[i]->mrq == NULL) + continue; - if (enable_poll != mmc_omap_enable_poll) { - struct mmc_omap_host *host = dev_get_drvdata(dev); + BUG_ON(host->next_slot != NULL); + new_slot = host->slots[i]; + /* The current slot should not have a request in queue */ + BUG_ON(new_slot == host->current_slot); - mmc_omap_enable_poll = enable_poll; - if (enable_poll && host->switch_pin >= 0) - schedule_work(&host->switch_work); + host->next_slot = new_slot; + host->mmc = new_slot->mmc; + spin_unlock_irqrestore(&host->slot_lock, flags); + queue_work(host->mmc_omap_wq, &host->slot_release_work); + return; } - return size; + + host->mmc = NULL; + wake_up(&host->slot_wq); + spin_unlock_irqrestore(&host->slot_lock, flags); +} + +static inline +int mmc_omap_cover_is_open(struct mmc_omap_slot *slot) +{ + if (slot->pdata->get_cover_state) + return slot->pdata->get_cover_state(mmc_dev(slot->mmc), + slot->id); + return 0; } -static DEVICE_ATTR(enable_poll, 0664, - mmc_omap_show_enable_poll, mmc_omap_store_enable_poll); +static ssize_t +mmc_omap_show_cover_switch(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev); + struct mmc_omap_slot *slot = mmc_priv(mmc); + + return sprintf(buf, "%s\n", mmc_omap_cover_is_open(slot) ? "open" : + "closed"); +} + +static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL); + +static ssize_t +mmc_omap_show_slot_name(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev); + struct mmc_omap_slot *slot = mmc_priv(mmc); + + return sprintf(buf, "%s\n", slot->pdata->name); +} + +static DEVICE_ATTR(slot_name, S_IRUGO, mmc_omap_show_slot_name, NULL); static void mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) @@ -195,6 +337,7 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) u32 cmdreg; u32 resptype; u32 cmdtype; + u16 irq_mask; host->cmd = cmd; @@ -233,7 +376,7 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) cmdreg = cmd->opcode | (resptype << 8) | (cmdtype << 12); - if (host->bus_mode == MMC_BUSMODE_OPENDRAIN) + if (host->current_slot->bus_mode == MMC_BUSMODE_OPENDRAIN) cmdreg |= 1 << 6; if (cmd->flags & MMC_RSP_BUSY) @@ -242,41 +385,70 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) if (host->data && !(host->data->flags & MMC_DATA_WRITE)) cmdreg |= 1 << 15; - clk_enable(host->fclk); + mod_timer(&host->cmd_abort_timer, jiffies + HZ/2); 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); + irq_mask = 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; + if (cmd->opcode == MMC_ERASE) + irq_mask &= ~OMAP_MMC_STAT_DATA_TOUT; + OMAP_MMC_WRITE(host, IE, irq_mask); OMAP_MMC_WRITE(host, CMD, cmdreg); } static void -mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) +mmc_omap_release_dma(struct mmc_omap_host *host, struct mmc_data *data, + int abort) { - if (host->dma_in_use) { - enum dma_data_direction dma_data_dir; + enum dma_data_direction dma_data_dir; + struct device *dev = mmc_dev(host->mmc); + struct dma_chan *c; - BUG_ON(host->dma_ch < 0); - if (data->error) - 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); + if (data->flags & MMC_DATA_WRITE) { + dma_data_dir = DMA_TO_DEVICE; + c = host->dma_tx; + } else { + dma_data_dir = DMA_FROM_DEVICE; + c = host->dma_rx; + } + if (c) { + if (data->error) { + dmaengine_terminate_all(c); + /* Claim nothing transferred on error... */ + data->bytes_xfered = 0; + } + dev = c->device->dev; } + dma_unmap_sg(dev, data->sg, host->sg_len, dma_data_dir); +} + +static void mmc_omap_send_stop_work(struct work_struct *work) +{ + struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, + send_stop_work); + struct mmc_omap_slot *slot = host->current_slot; + struct mmc_data *data = host->stop_data; + unsigned long tick_ns; + + tick_ns = DIV_ROUND_UP(NSEC_PER_SEC, slot->fclk_freq); + ndelay(8*tick_ns); + + mmc_omap_start_command(host, data->stop); +} + +static void +mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) +{ + if (host->dma_in_use) + mmc_omap_release_dma(host, data, data->error); + 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. @@ -284,12 +456,58 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) */ if (!data->stop) { + struct mmc_host *mmc; + host->mrq = NULL; - mmc_request_done(host->mmc, data->mrq); + mmc = host->mmc; + mmc_omap_release_slot(host->current_slot, 1); + mmc_request_done(mmc, data->mrq); return; } - mmc_omap_start_command(host, data->stop); + host->stop_data = data; + queue_work(host->mmc_omap_wq, &host->send_stop_work); +} + +static void +mmc_omap_send_abort(struct mmc_omap_host *host, int maxloops) +{ + struct mmc_omap_slot *slot = host->current_slot; + unsigned int restarts, passes, timeout; + u16 stat = 0; + + /* Sending abort takes 80 clocks. Have some extra and round up */ + timeout = DIV_ROUND_UP(120 * USEC_PER_SEC, slot->fclk_freq); + restarts = 0; + while (restarts < maxloops) { + OMAP_MMC_WRITE(host, STAT, 0xFFFF); + OMAP_MMC_WRITE(host, CMD, (3 << 12) | (1 << 7)); + + passes = 0; + while (passes < timeout) { + stat = OMAP_MMC_READ(host, STAT); + if (stat & OMAP_MMC_STAT_END_OF_CMD) + goto out; + udelay(1); + passes++; + } + + restarts++; + } +out: + OMAP_MMC_WRITE(host, STAT, stat); +} + +static void +mmc_omap_abort_xfer(struct mmc_omap_host *host, struct mmc_data *data) +{ + if (host->dma_in_use) + mmc_omap_release_dma(host, data, 1); + + host->data = NULL; + host->sg_len = 0; + + mmc_omap_send_abort(host, 10000); } static void @@ -314,16 +532,6 @@ mmc_omap_end_of_data(struct mmc_omap_host *host, struct mmc_data *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; @@ -345,6 +553,8 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) { host->cmd = NULL; + del_timer(&host->cmd_abort_timer); + if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { /* response type 2 */ @@ -369,12 +579,68 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) } if (host->data == NULL || cmd->error) { + struct mmc_host *mmc; + + if (host->data != NULL) + mmc_omap_abort_xfer(host, host->data); host->mrq = NULL; - clk_disable(host->fclk); - mmc_request_done(host->mmc, cmd->mrq); + mmc = host->mmc; + mmc_omap_release_slot(host->current_slot, 1); + mmc_request_done(mmc, cmd->mrq); } } +/* + * Abort stuck command. Can occur when card is removed while it is being + * read. + */ +static void mmc_omap_abort_command(struct work_struct *work) +{ + struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, + cmd_abort_work); + BUG_ON(!host->cmd); + + dev_dbg(mmc_dev(host->mmc), "Aborting stuck command CMD%d\n", + host->cmd->opcode); + + if (host->cmd->error == 0) + host->cmd->error = -ETIMEDOUT; + + if (host->data == NULL) { + struct mmc_command *cmd; + struct mmc_host *mmc; + + cmd = host->cmd; + host->cmd = NULL; + mmc_omap_send_abort(host, 10000); + + host->mrq = NULL; + mmc = host->mmc; + mmc_omap_release_slot(host->current_slot, 1); + mmc_request_done(mmc, cmd->mrq); + } else + mmc_omap_cmd_done(host, host->cmd); + + host->abort = 0; + enable_irq(host->irq); +} + +static void +mmc_omap_cmd_timer(unsigned long data) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *) data; + unsigned long flags; + + spin_lock_irqsave(&host->slot_lock, flags); + if (host->cmd != NULL && !host->abort) { + OMAP_MMC_WRITE(host, IE, 0); + disable_irq(host->irq); + host->abort = 1; + queue_work(host->mmc_omap_wq, &host->cmd_abort_work); + } + spin_unlock_irqrestore(&host->slot_lock, flags); +} + /* PIO only */ static void mmc_omap_sg_to_buf(struct mmc_omap_host *host) @@ -388,11 +654,19 @@ mmc_omap_sg_to_buf(struct mmc_omap_host *host) host->buffer_bytes_left = host->total_bytes_left; } +static void +mmc_omap_clk_timer(unsigned long data) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *) data; + + mmc_omap_fclk_enable(host, 0); +} + /* PIO only */ static void mmc_omap_xfer_data(struct mmc_omap_host *host, int write) { - int n; + int n, nwords; if (host->buffer_bytes_left == 0) { host->sg_idx++; @@ -402,33 +676,48 @@ mmc_omap_xfer_data(struct mmc_omap_host *host, int write) n = 64; if (n > host->buffer_bytes_left) n = host->buffer_bytes_left; + + /* Round up to handle odd number of bytes to transfer */ + nwords = DIV_ROUND_UP(n, 2); + 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); + __raw_writesw(host->virt_base + OMAP_MMC_REG(host, DATA), + host->buffer, nwords); } else { - __raw_readsw(host->virt_base + OMAP_MMC_REG_DATA, host->buffer, n); + __raw_readsw(host->virt_base + OMAP_MMC_REG(host, DATA), + host->buffer, nwords); } + + host->buffer += nwords; } -static inline void mmc_omap_report_irq(u16 status) +#ifdef CONFIG_MMC_DEBUG +static void mmc_omap_report_irq(struct mmc_omap_host *host, 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; + int i; + char res[64], *buf = res; + + buf += sprintf(buf, "MMC IRQ 0x%x:", status); 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++; - } + if (status & (1 << i)) + buf += sprintf(buf, " %s", mmc_omap_status_bits[i]); + dev_vdbg(mmc_dev(host->mmc), "%s\n", res); +} +#else +static void mmc_omap_report_irq(struct mmc_omap_host *host, u16 status) +{ } +#endif + static irqreturn_t mmc_omap_irq(int irq, void *dev_id) { @@ -436,11 +725,12 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) u16 status; int end_command; int end_transfer; - int transfer_error; + int transfer_error, cmd_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); + dev_info(mmc_dev(host->slots[0]->mmc), + "Spurious IRQ 0x%04x\n", status); if (status != 0) { OMAP_MMC_WRITE(host, STAT, status); OMAP_MMC_WRITE(host, IE, 0); @@ -451,15 +741,20 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) end_command = 0; end_transfer = 0; transfer_error = 0; + cmd_error = 0; while ((status = OMAP_MMC_READ(host, STAT)) != 0) { + int cmd; + OMAP_MMC_WRITE(host, STAT, status); -#ifdef CONFIG_MMC_DEBUG + if (host->cmd != NULL) + cmd = host->cmd->opcode; + else + cmd = -1; 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 + status, cmd); + mmc_omap_report_irq(host, status); + if (host->total_bytes_left) { if ((status & OMAP_MMC_STAT_A_FULL) || (status & OMAP_MMC_STAT_END_OF_DATA)) @@ -468,12 +763,12 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) mmc_omap_xfer_data(host, 1); } - if (status & OMAP_MMC_STAT_END_OF_DATA) { + 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"); + dev_dbg(mmc_dev(host->mmc), "data timeout (CMD%d)\n", + cmd); if (host->data) { host->data->error = -ETIMEDOUT; transfer_error = 1; @@ -495,17 +790,16 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) 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)) + struct mmc_omap_slot *slot = + host->current_slot; + if (slot == NULL || + !mmc_omap_cover_is_open(slot)) dev_err(mmc_dev(host->mmc), - "command timeout, CMD %d\n", - host->cmd->opcode); + "command timeout (CMD%d)\n", + cmd); host->cmd->error = -ETIMEDOUT; end_command = 1; + cmd_error = 1; } } @@ -513,9 +807,10 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (host->cmd) { dev_err(mmc_dev(host->mmc), "command CRC error (CMD%d, arg 0x%08x)\n", - host->cmd->opcode, host->cmd->arg); + cmd, host->cmd->arg); host->cmd->error = -EILSEQ; end_command = 1; + cmd_error = 1; } else dev_err(mmc_dev(host->mmc), "command CRC error without cmd?\n"); @@ -524,13 +819,13 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (status & OMAP_MMC_STAT_CARD_ERR) { dev_dbg(mmc_dev(host->mmc), "ignoring card status error (CMD%d)\n", - host->cmd->opcode); + cmd); end_command = 1; } /* * NOTE: On 1610 the END_OF_CMD may come too early when - * starting a write + * starting a write */ if ((status & OMAP_MMC_STAT_END_OF_CMD) && (!(status & OMAP_MMC_STAT_A_EMPTY))) { @@ -538,218 +833,83 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) } } - if (end_command) { - mmc_omap_cmd_done(host, host->cmd); + if (cmd_error && host->data) { + del_timer(&host->cmd_abort_timer); + host->abort = 1; + OMAP_MMC_WRITE(host, IE, 0); + disable_irq_nosync(host->irq); + queue_work(host->mmc_omap_wq, &host->cmd_abort_work); + return IRQ_HANDLED; } - 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); + if (end_command && host->cmd) + mmc_omap_cmd_done(host, host->cmd); + if (host->data != NULL) { + 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 void mmc_omap_switch_timer(unsigned long arg) +void omap_mmc_notify_cover_event(struct device *dev, int num, int is_closed) { - struct mmc_omap_host *host = (struct mmc_omap_host *) arg; - - schedule_work(&host->switch_work); -} + int cover_open; + struct mmc_omap_host *host = dev_get_drvdata(dev); + struct mmc_omap_slot *slot = host->slots[num]; -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; + BUG_ON(num >= host->nr_slots); - if (host->switch_pin == -1) + /* Other subsystems can call in here before we're initialised. */ + if (host->nr_slots == 0 || !host->slots[num]) 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\n"); - 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); + cover_open = mmc_omap_cover_is_open(slot); + if (cover_open != slot->cover_open) { + slot->cover_open = cover_open; + sysfs_notify(&slot->mmc->class_dev.kobj, NULL, "cover_switch"); } - /* Max limit for DMA frame count is 0xffff */ - BUG_ON(count > 0xffff); + tasklet_hi_schedule(&slot->cover_tasklet); +} - 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); +static void mmc_omap_cover_timer(unsigned long arg) +{ + struct mmc_omap_slot *slot = (struct mmc_omap_slot *) arg; + tasklet_schedule(&slot->cover_tasklet); } -/* A scatterlist segment completed */ -static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data) +static void mmc_omap_cover_handler(unsigned long param) { - struct mmc_omap_host *host = (struct mmc_omap_host *) data; - struct mmc_data *mmcdat = host->data; + struct mmc_omap_slot *slot = (struct mmc_omap_slot *)param; + int cover_open = mmc_omap_cover_is_open(slot); - 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"); + mmc_detect_change(slot->mmc, 0); + if (!cover_open) return; - } - if (!(ch_status & OMAP_DMA_BLOCK_IRQ)) { + + /* + * If no card is inserted, we postpone polling until + * the cover has been closed. + */ + if (slot->mmc->card == NULL || !mmc_card_present(slot->mmc->card)) 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); + + mod_timer(&slot->cover_timer, + jiffies + msecs_to_jiffies(OMAP_MMC_COVER_POLL_DELAY)); } -static int mmc_omap_get_dma_channel(struct mmc_omap_host *host, struct mmc_data *data) +static void mmc_omap_dma_callback(void *priv) { - 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; - } + struct mmc_omap_host *host = priv; + struct mmc_data *data = host->data; - 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; + /* If we got to the end of DMA, assume everything went well */ + data->bytes_xfered += data->blocks * data->blksz; - return 0; + mmc_omap_dma_done(host, data); } static inline void set_cmd_timeout(struct mmc_omap_host *host, struct mmc_request *req) @@ -765,13 +925,12 @@ static inline void set_cmd_timeout(struct mmc_omap_host *host, struct mmc_reques static inline void set_data_timeout(struct mmc_omap_host *host, struct mmc_request *req) { - int timeout; + unsigned int timeout, cycle_ns; 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; + cycle_ns = 1000000000 / host->current_slot->fclk_freq; + timeout = req->data->timeout_ns / cycle_ns; + timeout += req->data->timeout_clks; /* Check if we need to use timeout multiplier register */ reg = OMAP_MMC_READ(host, SDIO); @@ -788,7 +947,7 @@ 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; + int i, use_dma = 1, block_size; unsigned sg_len; host->data = data; @@ -813,114 +972,160 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) 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; - } + 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; + enum dma_data_direction dma_data_dir; + struct dma_async_tx_descriptor *tx; + struct dma_chan *c; + u32 burst, *bp; + u16 buf; + + /* + * 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. + */ + burst = mmc_omap15xx() ? 32 : 64; + if (burst > data->blksz) + burst = data->blksz; + + burst >>= 1; + + if (data->flags & MMC_DATA_WRITE) { + c = host->dma_tx; + bp = &host->dma_tx_burst; + buf = 0x0f80 | (burst - 1) << 0; + dma_data_dir = DMA_TO_DEVICE; + } else { + c = host->dma_rx; + bp = &host->dma_rx_burst; + buf = 0x800f | (burst - 1) << 8; + dma_data_dir = DMA_FROM_DEVICE; + } + + if (!c) + goto use_pio; + + /* Only reconfigure if we have a different burst size */ + if (*bp != burst) { + struct dma_slave_config cfg; + + cfg.src_addr = host->phys_base + OMAP_MMC_REG(host, DATA); + cfg.dst_addr = host->phys_base + OMAP_MMC_REG(host, DATA); + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + cfg.src_maxburst = burst; + cfg.dst_maxburst = burst; + + if (dmaengine_slave_config(c, &cfg)) + goto use_pio; + + *bp = burst; + } + + host->sg_len = dma_map_sg(c->device->dev, data->sg, sg_len, + dma_data_dir); + if (host->sg_len == 0) + goto use_pio; + + tx = dmaengine_prep_slave_sg(c, data->sg, host->sg_len, + data->flags & MMC_DATA_WRITE ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!tx) + goto use_pio; + + OMAP_MMC_WRITE(host, BUF, buf); + + tx->callback = mmc_omap_dma_callback; + tx->callback_param = host; + dmaengine_submit(tx); + host->brs_received = 0; + host->dma_done = 0; + host->dma_in_use = 1; + return; } + use_pio: /* 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; - } + 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) +static void mmc_omap_start_request(struct mmc_omap_host *host, + struct mmc_request *req) { - struct mmc_omap_host *host = mmc_priv(mmc); - - WARN_ON(host->mrq != NULL); + BUG_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); + if (host->dma_in_use) { + struct dma_chan *c = host->data->flags & MMC_DATA_WRITE ? + host->dma_tx : host->dma_rx; + + dma_async_issue_pending(c); + } } -static void innovator_fpga_socket_power(int on) +static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req) { -#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 + struct mmc_omap_slot *slot = mmc_priv(mmc); + struct mmc_omap_host *host = slot->host; + unsigned long flags; + + spin_lock_irqsave(&host->slot_lock, flags); + if (host->mmc != NULL) { + BUG_ON(slot->mrq != NULL); + slot->mrq = req; + spin_unlock_irqrestore(&host->slot_lock, flags); + return; + } else + host->mmc = mmc; + spin_unlock_irqrestore(&host->slot_lock, flags); + mmc_omap_select_slot(slot, 1); + mmc_omap_start_request(host, req); } -/* - * 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) +static void mmc_omap_set_power(struct mmc_omap_slot *slot, int power_on, + int vdd) { - 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); + struct mmc_omap_host *host; + + host = slot->host; + + if (slot->pdata->set_power != NULL) + slot->pdata->set_power(mmc_dev(slot->mmc), slot->id, power_on, + vdd); + if (mmc_omap2()) { + u16 w; + + if (power_on) { + w = OMAP_MMC_READ(host, CON); + OMAP_MMC_WRITE(host, CON, w | (1 << 11)); + } else { + w = OMAP_MMC_READ(host, CON); + OMAP_MMC_WRITE(host, CON, w & ~(1 << 11)); + } } } static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios) { - struct mmc_omap_host *host = mmc_priv(mmc); + struct mmc_omap_slot *slot = mmc_priv(mmc); + struct mmc_omap_host *host = slot->host; int func_clk_rate = clk_get_rate(host->fclk); int dsor; @@ -936,7 +1141,8 @@ static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios) if (dsor > 250) dsor = 250; - dsor++; + + slot->fclk_freq = func_clk_rate / dsor; if (ios->bus_width == MMC_BUS_WIDTH_4) dsor |= 1 << 15; @@ -946,28 +1152,40 @@ static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios) 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 i; + struct mmc_omap_slot *slot = mmc_priv(mmc); + struct mmc_omap_host *host = slot->host; + int i, dsor; + int clk_enabled; + + mmc_omap_select_slot(slot, 0); dsor = mmc_omap_calc_divisor(mmc, ios); - host->bus_mode = ios->bus_mode; - host->hw_bus_mode = host->bus_mode; + if (ios->vdd != slot->vdd) + slot->vdd = ios->vdd; + + clk_enabled = 0; switch (ios->power_mode) { case MMC_POWER_OFF: - mmc_omap_power(host, 0); + mmc_omap_set_power(slot, 0, ios->vdd); break; case MMC_POWER_UP: /* Cannot touch dsor yet, just power up MMC */ - mmc_omap_power(host, 1); - return; + mmc_omap_set_power(slot, 1, ios->vdd); + goto exit; case MMC_POWER_ON: + mmc_omap_fclk_enable(host, 1); + clk_enabled = 1; dsor |= 1 << 11; break; } - clk_enable(host->fclk); + if (slot->bus_mode != ios->bus_mode) { + if (slot->pdata->set_bus_mode != NULL) + slot->pdata->set_bus_mode(mmc_dev(mmc), slot->id, + ios->bus_mode); + slot->bus_mode = ios->bus_mode; + } /* On insanely high arm_per frequencies something sometimes * goes somehow out of sync, and the POW bit is not being set, @@ -975,303 +1193,314 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) * Writing to the CON register twice seems to do the trick. */ for (i = 0; i < 2; i++) OMAP_MMC_WRITE(host, CON, dsor); + slot->saved_con = dsor; if (ios->power_mode == MMC_POWER_ON) { + /* worst case at 400kHz, 80 cycles makes 200 microsecs */ + int usecs = 250; + /* 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); + while (usecs > 0 && (OMAP_MMC_READ(host, STAT) & 1) == 0) { + udelay(1); + usecs--; + } 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); +exit: + mmc_omap_release_slot(slot, clk_enabled); } 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) +static int mmc_omap_new_slot(struct mmc_omap_host *host, int id) { - struct omap_mmc_conf *minfo = pdev->dev.platform_data; + struct mmc_omap_slot *slot = NULL; struct mmc_host *mmc; + int r; + + mmc = mmc_alloc_host(sizeof(struct mmc_omap_slot), host->dev); + if (mmc == NULL) + return -ENOMEM; + + slot = mmc_priv(mmc); + slot->host = host; + slot->mmc = mmc; + slot->id = id; + slot->pdata = &host->pdata->slots[id]; + + host->slots[id] = slot; + + mmc->caps = 0; + if (host->pdata->slots[id].wires >= 4) + mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_ERASE; + + mmc->ops = &mmc_omap_ops; + mmc->f_min = 400000; + + if (mmc_omap2()) + mmc->f_max = 48000000; + else + mmc->f_max = 24000000; + if (host->pdata->max_freq) + mmc->f_max = min(host->pdata->max_freq, mmc->f_max); + mmc->ocr_avail = slot->pdata->ocr_mask; + + /* 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_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 (slot->pdata->get_cover_state != NULL) { + setup_timer(&slot->cover_timer, mmc_omap_cover_timer, + (unsigned long)slot); + tasklet_init(&slot->cover_tasklet, mmc_omap_cover_handler, + (unsigned long)slot); + } + + r = mmc_add_host(mmc); + if (r < 0) + goto err_remove_host; + + if (slot->pdata->name != NULL) { + r = device_create_file(&mmc->class_dev, + &dev_attr_slot_name); + if (r < 0) + goto err_remove_host; + } + + if (slot->pdata->get_cover_state != NULL) { + r = device_create_file(&mmc->class_dev, + &dev_attr_cover_switch); + if (r < 0) + goto err_remove_slot_name; + tasklet_schedule(&slot->cover_tasklet); + } + + return 0; + +err_remove_slot_name: + if (slot->pdata->name != NULL) + device_remove_file(&mmc->class_dev, &dev_attr_slot_name); +err_remove_host: + mmc_remove_host(mmc); + mmc_free_host(mmc); + return r; +} + +static void mmc_omap_remove_slot(struct mmc_omap_slot *slot) +{ + struct mmc_host *mmc = slot->mmc; + + if (slot->pdata->name != NULL) + device_remove_file(&mmc->class_dev, &dev_attr_slot_name); + if (slot->pdata->get_cover_state != NULL) + device_remove_file(&mmc->class_dev, &dev_attr_cover_switch); + + tasklet_kill(&slot->cover_tasklet); + del_timer_sync(&slot->cover_timer); + flush_workqueue(slot->host->mmc_omap_wq); + + mmc_remove_host(mmc); + mmc_free_host(mmc); +} + +static int mmc_omap_probe(struct platform_device *pdev) +{ + struct omap_mmc_platform_data *pdata = pdev->dev.platform_data; struct mmc_omap_host *host = NULL; struct resource *res; - int ret = 0; + dma_cap_mask_t mask; + unsigned sig = 0; + int i, ret = 0; int irq; - if (minfo == NULL) { + if (pdata == NULL) { dev_err(&pdev->dev, "platform data missing\n"); return -ENXIO; } + if (pdata->nr_slots == 0) { + dev_err(&pdev->dev, "no slots\n"); + return -EPROBE_DEFER; + } + + host = devm_kzalloc(&pdev->dev, sizeof(struct mmc_omap_host), + GFP_KERNEL); + if (host == NULL) + return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); - if (res == NULL || irq < 0) + if (irq < 0) return -ENXIO; - res = request_mem_region(res->start, res->end - res->start + 1, - pdev->name); - if (res == NULL) - return -EBUSY; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->virt_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->virt_base)) + return PTR_ERR(host->virt_base); - mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev); - if (mmc == NULL) { - ret = -ENOMEM; - goto err_free_mem_region; - } + INIT_WORK(&host->slot_release_work, mmc_omap_slot_release_work); + INIT_WORK(&host->send_stop_work, mmc_omap_send_stop_work); + + INIT_WORK(&host->cmd_abort_work, mmc_omap_abort_command); + setup_timer(&host->cmd_abort_timer, mmc_omap_cmd_timer, + (unsigned long) host); - host = mmc_priv(mmc); - host->mmc = mmc; + spin_lock_init(&host->clk_lock); + setup_timer(&host->clk_timer, mmc_omap_clk_timer, (unsigned long) host); 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; + spin_lock_init(&host->slot_lock); + init_waitqueue_head(&host->slot_wq); + + host->pdata = pdata; + host->features = host->pdata->slots[0].features; + host->dev = &pdev->dev; + platform_set_drvdata(pdev, host); host->id = pdev->id; - host->mem_res = res; host->irq = irq; + host->phys_base = res->start; + host->iclk = clk_get(&pdev->dev, "ick"); + if (IS_ERR(host->iclk)) + return PTR_ERR(host->iclk); + clk_enable(host->iclk); - 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"); - + host->fclk = clk_get(&pdev->dev, "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); + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + host->dma_tx_burst = -1; + host->dma_rx_burst = -1; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx"); + if (res) + sig = res->start; + host->dma_tx = dma_request_slave_channel_compat(mask, + omap_dma_filter_fn, &sig, &pdev->dev, "tx"); + if (!host->dma_tx) + dev_warn(host->dev, "unable to obtain TX DMA engine channel %u\n", + sig); + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx"); + if (res) + sig = res->start; + host->dma_rx = dma_request_slave_channel_compat(mask, + omap_dma_filter_fn, &sig, &pdev->dev, "rx"); + if (!host->dma_rx) + dev_warn(host->dev, "unable to obtain RX DMA engine channel %u\n", + sig); - 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; + ret = request_irq(host->irq, mmc_omap_irq, 0, DRIVER_NAME, host); + if (ret) + goto err_free_dma; - 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); + if (pdata->init != NULL) { + ret = pdata->init(&pdev->dev); + if (ret < 0) + goto err_free_irq; } - ret = request_irq(host->irq, mmc_omap_irq, 0, DRIVER_NAME, host); - if (ret) - goto err_free_power_gpio; + host->nr_slots = pdata->nr_slots; + host->reg_shift = (mmc_omap7xx() ? 1 : 2); - host->dev = &pdev->dev; - platform_set_drvdata(pdev, host); + host->mmc_omap_wq = alloc_workqueue("mmc_omap", 0, 0); + if (!host->mmc_omap_wq) + goto err_plat_cleanup; - 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; - } + for (i = 0; i < pdata->nr_slots; i++) { + ret = mmc_omap_new_slot(host, i); + if (ret < 0) { + while (--i >= 0) + mmc_omap_remove_slot(host->slots[i]); - 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; + goto err_destroy_wq; } - 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: +err_destroy_wq: + destroy_workqueue(host->mmc_omap_wq); +err_plat_cleanup: + if (pdata->cleanup) + pdata->cleanup(&pdev->dev); +err_free_irq: + free_irq(host->irq, host); +err_free_dma: + if (host->dma_tx) + dma_release_channel(host->dma_tx); + if (host->dma_rx) + dma_release_channel(host->dma_rx); 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); + clk_disable(host->iclk); + clk_put(host->iclk); 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); + int i; 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); + for (i = 0; i < host->nr_slots; i++) + mmc_omap_remove_slot(host->slots[i]); - release_mem_region(pdev->resource[0].start, - pdev->resource[0].end - pdev->resource[0].start + 1); + if (host->pdata->cleanup) + host->pdata->cleanup(&pdev->dev); - mmc_free_host(host->mmc); + mmc_omap_fclk_enable(host, 0); + free_irq(host->irq, host); + clk_put(host->fclk); + clk_disable(host->iclk); + clk_put(host->iclk); - return 0; -} + if (host->dma_tx) + dma_release_channel(host->dma_tx); + if (host->dma_rx) + dma_release_channel(host->dma_rx); -#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); + destroy_workqueue(host->mmc_omap_wq); - if (host && host->suspended) - return 0; - - if (host) { - ret = mmc_suspend_host(host->mmc, mesg); - if (ret == 0) - host->suspended = 1; - } - return ret; + return 0; } -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 +#if IS_BUILTIN(CONFIG_OF) +static const struct of_device_id mmc_omap_match[] = { + { .compatible = "ti,omap2420-mmc", }, + { }, +}; #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, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(mmc_omap_match), }, }; -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_platform_driver(mmc_omap_driver); MODULE_DESCRIPTION("OMAP Multimedia Card driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS(DRIVER_NAME); -MODULE_AUTHOR("Juha Yrjölä"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_AUTHOR("Juha Yrjölä"); |
