aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2008-04-21 17:23:30 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2008-04-21 17:23:30 -0700
commit135cedad7457be6a96d5e151dfd48f7888a75e94 (patch)
treed2ea838ef41ab7dcb85e655b0e6b6ed585c11fe8
parent8a3227268877b81096d7b7a841aaf51099ad2068 (diff)
parente70aa3fac1ac50c7a75ac676a1489dd1ea3b4be5 (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.c6
-rw-r--r--drivers/mmc/core/core.h4
-rw-r--r--drivers/mmc/core/host.c39
-rw-r--r--drivers/mmc/core/sdio_irq.c4
-rw-r--r--drivers/mmc/core/sdio_ops.c1
-rw-r--r--drivers/mmc/host/omap.c995
-rw-r--r--drivers/mmc/host/sdhci.c100
-rw-r--r--drivers/mmc/host/sdhci.h9
-rw-r--r--include/asm-arm/arch-omap/mmc.h2
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