diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2008-04-21 17:23:30 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-04-21 17:23:30 -0700 |
commit | 135cedad7457be6a96d5e151dfd48f7888a75e94 (patch) | |
tree | d2ea838ef41ab7dcb85e655b0e6b6ed585c11fe8 | |
parent | 8a3227268877b81096d7b7a841aaf51099ad2068 (diff) | |
parent | e70aa3fac1ac50c7a75ac676a1489dd1ea3b4be5 (diff) |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc: (26 commits)
mmc: sdio_ops.c should #include "sdio_ops.h"
mmc: proper prototypes for mmc_attach_*()
mmc: make __mmc_release_bus() static
sdhci: improve no card, no reset quirk
MMC: OMAP: Do not busy wait for end of command for ever
MMC: OMAP: Start new commands from work queue instead of irq
MMC: OMAP: Lazy clock shutdown
MMC: OMAP: Move failing command abortion to workqueue
MMC: OMAP: Use tasklet instead of workqueue for cover switch notification
MMC: OMAP: Check the get_cover_state function pointer if not set
MMC: OMAP: Using setup_timer instead of init_timer
MMC: OMAP: Abort stuck commands
MMC: OMAP: General cleanup for MMC multislot support
MMC: OMAP: Power functions modified to MMC multislot support
MMC: OMAP: Fix timeout calculation for MMC multislot support
MMC: OMAP: New release dma and abort xfer functions
MMC: OMAP: Add back cover switch support
MMC: OMAP: Introduce new multislot structure and change driver to use it
MMC: OMAP: Remove cover switch handling to allow adding multislot support
MMC: OMAP: Fix the BYTEBLOCK capability removal
...
-rw-r--r-- | drivers/mmc/core/core.c | 6 | ||||
-rw-r--r-- | drivers/mmc/core/core.h | 4 | ||||
-rw-r--r-- | drivers/mmc/core/host.c | 39 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_irq.c | 4 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_ops.c | 1 | ||||
-rw-r--r-- | drivers/mmc/host/omap.c | 995 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 100 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.h | 9 | ||||
-rw-r--r-- | include/asm-arm/arch-omap/mmc.h | 2 |
9 files changed, 778 insertions, 382 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index b96667448eb..01ced4c5a61 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -35,10 +35,6 @@ #include "sd_ops.h" #include "sdio_ops.h" -extern int mmc_attach_mmc(struct mmc_host *host, u32 ocr); -extern int mmc_attach_sd(struct mmc_host *host, u32 ocr); -extern int mmc_attach_sdio(struct mmc_host *host, u32 ocr); - static struct workqueue_struct *workqueue; /* @@ -516,7 +512,7 @@ static void mmc_power_off(struct mmc_host *host) /* * Cleanup when the last reference to the bus operator is dropped. */ -void __mmc_release_bus(struct mmc_host *host) +static void __mmc_release_bus(struct mmc_host *host) { BUG_ON(!host); BUG_ON(host->bus_refs); diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index cfa8e15b592..cdb332b7ded 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -46,6 +46,10 @@ void mmc_rescan(struct work_struct *work); void mmc_start_host(struct mmc_host *host); void mmc_stop_host(struct mmc_host *host); +int mmc_attach_mmc(struct mmc_host *host, u32 ocr); +int mmc_attach_sd(struct mmc_host *host, u32 ocr); +int mmc_attach_sdio(struct mmc_host *host, u32 ocr); + extern int use_spi_crc; #endif diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index c65d203a846..1d795c5379b 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -2,7 +2,7 @@ * linux/drivers/mmc/core/host.c * * Copyright (C) 2003 Russell King, All Rights Reserved. - * Copyright (C) 2007 Pierre Ossman + * Copyright (C) 2007-2008 Pierre Ossman * * 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 @@ -57,12 +57,25 @@ static DEFINE_SPINLOCK(mmc_host_lock); */ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) { + int err; struct mmc_host *host; + if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL)) + return NULL; + host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL); if (!host) return NULL; + spin_lock(&mmc_host_lock); + err = idr_get_new(&mmc_host_idr, host, &host->index); + spin_unlock(&mmc_host_lock); + if (err) + goto free; + + snprintf(host->class_dev.bus_id, BUS_ID_SIZE, + "mmc%d", host->index); + host->parent = dev; host->class_dev.parent = dev; host->class_dev.class = &mmc_host_class; @@ -85,6 +98,10 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) host->max_blk_count = PAGE_CACHE_SIZE / 512; return host; + +free: + kfree(host); + return NULL; } EXPORT_SYMBOL(mmc_alloc_host); @@ -104,18 +121,6 @@ int mmc_add_host(struct mmc_host *host) WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) && !host->ops->enable_sdio_irq); - if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL)) - return -ENOMEM; - - spin_lock(&mmc_host_lock); - err = idr_get_new(&mmc_host_idr, host, &host->index); - spin_unlock(&mmc_host_lock); - if (err) - return err; - - snprintf(host->class_dev.bus_id, BUS_ID_SIZE, - "mmc%d", host->index); - led_trigger_register_simple(host->class_dev.bus_id, &host->led); err = device_add(&host->class_dev); @@ -144,10 +149,6 @@ void mmc_remove_host(struct mmc_host *host) device_del(&host->class_dev); led_trigger_unregister_simple(host->led); - - spin_lock(&mmc_host_lock); - idr_remove(&mmc_host_idr, host->index); - spin_unlock(&mmc_host_lock); } EXPORT_SYMBOL(mmc_remove_host); @@ -160,6 +161,10 @@ EXPORT_SYMBOL(mmc_remove_host); */ void mmc_free_host(struct mmc_host *host) { + spin_lock(&mmc_host_lock); + idr_remove(&mmc_host_idr, host->index); + spin_unlock(&mmc_host_lock); + put_device(&host->class_dev); } diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index 3bd3021f5e8..c292e124107 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -128,12 +128,12 @@ static int sdio_irq_thread(void *_host) } } - set_task_state(current, TASK_INTERRUPTIBLE); + set_current_state(TASK_INTERRUPTIBLE); if (host->caps & MMC_CAP_SDIO_IRQ) host->ops->enable_sdio_irq(host, 1); if (!kthread_should_stop()) schedule_timeout(period); - set_task_state(current, TASK_RUNNING); + set_current_state(TASK_RUNNING); } while (!kthread_should_stop()); if (host->caps & MMC_CAP_SDIO_IRQ) diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index e1fca588e38..c8fa095a448 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -17,6 +17,7 @@ #include <linux/mmc/sdio.h> #include "core.h" +#include "sdio_ops.h" int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) { diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 90c358b57d1..14759e9f42a 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -32,6 +32,7 @@ #include <asm/mach-types.h> #include <asm/arch/board.h> +#include <asm/arch/mmc.h> #include <asm/arch/gpio.h> #include <asm/arch/dma.h> #include <asm/arch/mux.h> @@ -93,9 +94,27 @@ /* 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; +#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; + unsigned powered:1; + + struct tasklet_struct cover_tasklet; + struct timer_list cover_timer; + unsigned cover_open; + + 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; @@ -115,6 +134,15 @@ struct mmc_omap_host { unsigned char bus_mode; unsigned char hw_bus_mode; + 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; u16 * buffer; @@ -131,63 +159,178 @@ struct mmc_omap_host { 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; + 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; + + struct timer_list clk_timer; + spinlock_t clk_lock; /* for changing enabled state */ + unsigned int fclk_enabled:1; + + struct omap_mmc_platform_data *pdata; }; -static inline int -mmc_omap_cover_is_open(struct mmc_omap_host *host) +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 = (1000000000 + slot->fclk_freq - 1) / slot->fclk_freq; + ndelay(8 * tick_ns); + } } -static ssize_t -mmc_omap_show_cover_switch(struct device *dev, - struct device_attribute *attr, char *buf) +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); + } - if (sscanf(buf, "%10d", &enable_poll) != 1) - return -EINVAL; + 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 (enable_poll != mmc_omap_enable_poll) { - struct mmc_omap_host *host = dev_get_drvdata(dev); + if (host->slots[i] == NULL || host->slots[i]->mrq == NULL) + continue; - mmc_omap_enable_poll = enable_poll; - if (enable_poll && host->switch_pin >= 0) - schedule_work(&host->switch_work); + 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); + + host->next_slot = new_slot; + host->mmc = new_slot->mmc; + spin_unlock_irqrestore(&host->slot_lock, flags); + schedule_work(&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 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(enable_poll, 0664, - mmc_omap_show_enable_poll, mmc_omap_store_enable_poll); +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) @@ -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,7 +385,7 @@ 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); @@ -257,26 +400,46 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) } static void +mmc_omap_release_dma(struct mmc_omap_host *host, struct mmc_data *data, + int abort) +{ + enum dma_data_direction dma_data_dir; + + 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); +} + +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 = (1000000000 + slot->fclk_freq - 1)/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) { - enum dma_data_direction dma_data_dir; - - 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 (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 +447,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; + schedule_work(&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 = (120*1000000 + slot->fclk_freq - 1)/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 @@ -345,6 +554,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,10 +580,66 @@ 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; + schedule_work(&host->cmd_abort_work); } + spin_unlock_irqrestore(&host->slot_lock, flags); } /* PIO only */ @@ -388,6 +655,14 @@ 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) @@ -436,11 +711,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,12 +727,19 @@ 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); + if (host->cmd != NULL) + cmd = host->cmd->opcode; + else + cmd = -1; #ifdef CONFIG_MMC_DEBUG dev_dbg(mmc_dev(host->mmc), "MMC IRQ %04x (CMD %d): ", - status, host->cmd != NULL ? host->cmd->opcode : -1); + status, cmd); mmc_omap_report_irq(status); printk("\n"); #endif @@ -468,12 +751,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 +778,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 +795,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 +807,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,63 +821,72 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) } } - if (end_command) { + if (cmd_error && host->data) { + del_timer(&host->cmd_abort_timer); + host->abort = 1; + OMAP_MMC_WRITE(host, IE, 0); + disable_irq(host->irq); + schedule_work(&host->cmd_abort_work); + return IRQ_HANDLED; + } + + if (end_command) 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); } - 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) +void omap_mmc_notify_cover_event(struct device *dev, int num, int is_closed) { - struct mmc_omap_host *host = (struct mmc_omap_host *) dev_id; + int cover_open; + struct mmc_omap_host *host = dev_get_drvdata(dev); + struct mmc_omap_slot *slot = host->slots[num]; - schedule_work(&host->switch_work); + BUG_ON(num >= host->nr_slots); - return IRQ_HANDLED; + /* 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(slot); + if (cover_open != slot->cover_open) { + slot->cover_open = cover_open; + sysfs_notify(&slot->mmc->class_dev.kobj, NULL, "cover_switch"); + } + + tasklet_hi_schedule(&slot->cover_tasklet); } -static void mmc_omap_switch_timer(unsigned long arg) +static void mmc_omap_cover_timer(unsigned long arg) { - struct mmc_omap_host *host = (struct mmc_omap_host *) arg; - - schedule_work(&host->switch_work); + struct mmc_omap_slot *slot = (struct mmc_omap_slot *) arg; + tasklet_schedule(&slot->cover_tasklet); } -static void mmc_omap_switch_handler(struct work_struct *work) +static void mmc_omap_cover_handler(unsigned long param) { - 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; + struct mmc_omap_slot *slot = (struct mmc_omap_slot *)param; + int cover_open = mmc_omap_cover_is_open(slot); - if (host->switch_pin == -1) + mmc_detect_change(slot->mmc, 0); + if (!cover_open) 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; - } + + /* + * 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; + + mod_timer(&slot->cover_timer, + jiffies + msecs_to_jiffies(OMAP_MMC_COVER_POLL_DELAY)); } /* Prepare to transfer the next segment of a scatterlist */ @@ -765,13 +1057,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); @@ -854,11 +1145,10 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) } } -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; @@ -867,60 +1157,56 @@ static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req) mmc_omap_start_command(host, req->cmd); if (host->dma_in_use) omap_start_dma(host->dma_ch); + BUG_ON(irqs_disabled()); } -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 (cpu_is_omap24xx()) { + 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 +1222,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 +1233,40 @@ static int mmc_omap_calc_divisor(struct mmc_h |