Merge git://git.kernel.org/pub/scm/linux/kernel/git/rusty/linux-2.6-for-linus
[pandora-kernel.git] / drivers / mmc / host / dw_mmc.c
index 66dcddb..0c839d3 100644 (file)
@@ -33,6 +33,7 @@
 #include <linux/mmc/dw_mmc.h>
 #include <linux/bitops.h>
 #include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
 
 #include "dw_mmc.h"
 
@@ -100,6 +101,8 @@ struct dw_mci_slot {
        int                     last_detect_state;
 };
 
+static struct workqueue_struct *dw_mci_card_workqueue;
+
 #if defined(CONFIG_DEBUG_FS)
 static int dw_mci_req_show(struct seq_file *s, void *v)
 {
@@ -284,7 +287,7 @@ static void send_stop_cmd(struct dw_mci *host, struct mmc_data *data)
 /* DMA interface functions */
 static void dw_mci_stop_dma(struct dw_mci *host)
 {
-       if (host->use_dma) {
+       if (host->using_dma) {
                host->dma_ops->stop(host);
                host->dma_ops->cleanup(host);
        } else {
@@ -432,6 +435,8 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
        unsigned int i, direction, sg_len;
        u32 temp;
 
+       host->using_dma = 0;
+
        /* If we don't have a channel, we can't do DMA */
        if (!host->use_dma)
                return -ENODEV;
@@ -451,6 +456,8 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
                        return -EINVAL;
        }
 
+       host->using_dma = 1;
+
        if (data->flags & MMC_DATA_READ)
                direction = DMA_FROM_DEVICE;
        else
@@ -489,14 +496,18 @@ static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data)
        host->sg = NULL;
        host->data = data;
 
+       if (data->flags & MMC_DATA_READ)
+               host->dir_status = DW_MCI_RECV_STATUS;
+       else
+               host->dir_status = DW_MCI_SEND_STATUS;
+
        if (dw_mci_submit_data_dma(host, data)) {
                host->sg = data->sg;
                host->pio_offset = 0;
-               if (data->flags & MMC_DATA_READ)
-                       host->dir_status = DW_MCI_RECV_STATUS;
-               else
-                       host->dir_status = DW_MCI_SEND_STATUS;
+               host->part_buf_start = 0;
+               host->part_buf_count = 0;
 
+               mci_writel(host, RINTSTS, SDMMC_INT_TXDR | SDMMC_INT_RXDR);
                temp = mci_readl(host, INTMASK);
                temp |= SDMMC_INT_TXDR | SDMMC_INT_RXDR;
                mci_writel(host, INTMASK, temp);
@@ -574,7 +585,7 @@ static void dw_mci_setup_bus(struct dw_mci_slot *slot)
        }
 
        /* Set the current slot bus width */
-       mci_writel(host, CTYPE, slot->ctype);
+       mci_writel(host, CTYPE, (slot->ctype << slot->id));
 }
 
 static void dw_mci_start_request(struct dw_mci *host,
@@ -624,13 +635,13 @@ static void dw_mci_start_request(struct dw_mci *host,
                host->stop_cmdr = dw_mci_prepare_command(slot->mmc, mrq->stop);
 }
 
+/* must be called with host->lock held */
 static void dw_mci_queue_request(struct dw_mci *host, struct dw_mci_slot *slot,
                                 struct mmc_request *mrq)
 {
        dev_vdbg(&slot->mmc->class_dev, "queue request: state=%d\n",
                 host->state);
 
-       spin_lock_bh(&host->lock);
        slot->mrq = mrq;
 
        if (host->state == STATE_IDLE) {
@@ -639,8 +650,6 @@ static void dw_mci_queue_request(struct dw_mci *host, struct dw_mci_slot *slot,
        } else {
                list_add_tail(&slot->queue_node, &host->queue);
        }
-
-       spin_unlock_bh(&host->lock);
 }
 
 static void dw_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
@@ -650,14 +659,23 @@ static void dw_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
 
        WARN_ON(slot->mrq);
 
+       /*
+        * The check for card presence and queueing of the request must be
+        * atomic, otherwise the card could be removed in between and the
+        * request wouldn't fail until another card was inserted.
+        */
+       spin_lock_bh(&host->lock);
+
        if (!test_bit(DW_MMC_CARD_PRESENT, &slot->flags)) {
+               spin_unlock_bh(&host->lock);
                mrq->cmd->error = -ENOMEDIUM;
                mmc_request_done(mmc, mrq);
                return;
        }
 
-       /* We don't support multiple blocks of weird lengths. */
        dw_mci_queue_request(host, slot, mrq);
+
+       spin_unlock_bh(&host->lock);
 }
 
 static void dw_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
@@ -831,7 +849,7 @@ static void dw_mci_tasklet_func(unsigned long priv)
        struct mmc_command *cmd;
        enum dw_mci_state state;
        enum dw_mci_state prev_state;
-       u32 status;
+       u32 status, ctrl;
 
        spin_lock(&host->lock);
 
@@ -891,13 +909,19 @@ static void dw_mci_tasklet_func(unsigned long priv)
 
                        if (status & DW_MCI_DATA_ERROR_FLAGS) {
                                if (status & SDMMC_INT_DTO) {
-                                       dev_err(&host->pdev->dev,
-                                               "data timeout error\n");
                                        data->error = -ETIMEDOUT;
                                } else if (status & SDMMC_INT_DCRC) {
-                                       dev_err(&host->pdev->dev,
-                                               "data CRC error\n");
                                        data->error = -EILSEQ;
+                               } else if (status & SDMMC_INT_EBE &&
+                                          host->dir_status ==
+                                                       DW_MCI_SEND_STATUS) {
+                                       /*
+                                        * No data CRC status was returned.
+                                        * The number of bytes transferred will
+                                        * be exaggerated in PIO mode.
+                                        */
+                                       data->bytes_xfered = 0;
+                                       data->error = -ETIMEDOUT;
                                } else {
                                        dev_err(&host->pdev->dev,
                                                "data FIFO error "
@@ -905,6 +929,16 @@ static void dw_mci_tasklet_func(unsigned long priv)
                                                status);
                                        data->error = -EIO;
                                }
+                               /*
+                                * After an error, there may be data lingering
+                                * in the FIFO, so reset it - doing so
+                                * generates a block interrupt, hence setting
+                                * the scatter-gather pointer to NULL.
+                                */
+                               host->sg = NULL;
+                               ctrl = mci_readl(host, CTRL);
+                               ctrl |= SDMMC_CTRL_FIFO_RESET;
+                               mci_writel(host, CTRL, ctrl);
                        } else {
                                data->bytes_xfered = data->blocks * data->blksz;
                                data->error = 0;
@@ -946,84 +980,278 @@ unlock:
 
 }
 
-static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt)
+/* push final bytes to part_buf, only use during push */
+static void dw_mci_set_part_bytes(struct dw_mci *host, void *buf, int cnt)
 {
-       u16 *pdata = (u16 *)buf;
+       memcpy((void *)&host->part_buf, buf, cnt);
+       host->part_buf_count = cnt;
+}
 
-       WARN_ON(cnt % 2 != 0);
+/* append bytes to part_buf, only use during push */
+static int dw_mci_push_part_bytes(struct dw_mci *host, void *buf, int cnt)
+{
+       cnt = min(cnt, (1 << host->data_shift) - host->part_buf_count);
+       memcpy((void *)&host->part_buf + host->part_buf_count, buf, cnt);
+       host->part_buf_count += cnt;
+       return cnt;
+}
 
-       cnt = cnt >> 1;
-       while (cnt > 0) {
-               mci_writew(host, DATA, *pdata++);
-               cnt--;
+/* pull first bytes from part_buf, only use during pull */
+static int dw_mci_pull_part_bytes(struct dw_mci *host, void *buf, int cnt)
+{
+       cnt = min(cnt, (int)host->part_buf_count);
+       if (cnt) {
+               memcpy(buf, (void *)&host->part_buf + host->part_buf_start,
+                      cnt);
+               host->part_buf_count -= cnt;
+               host->part_buf_start += cnt;
        }
+       return cnt;
 }
 
-static void dw_mci_pull_data16(struct dw_mci *host, void *buf, int cnt)
+/* pull final bytes from the part_buf, assuming it's just been filled */
+static void dw_mci_pull_final_bytes(struct dw_mci *host, void *buf, int cnt)
 {
-       u16 *pdata = (u16 *)buf;
+       memcpy(buf, &host->part_buf, cnt);
+       host->part_buf_start = cnt;
+       host->part_buf_count = (1 << host->data_shift) - cnt;
+}
 
-       WARN_ON(cnt % 2 != 0);
+static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt)
+{
+       /* try and push anything in the part_buf */
+       if (unlikely(host->part_buf_count)) {
+               int len = dw_mci_push_part_bytes(host, buf, cnt);
+               buf += len;
+               cnt -= len;
+               if (!sg_next(host->sg) || host->part_buf_count == 2) {
+                       mci_writew(host, DATA, host->part_buf16);
+                       host->part_buf_count = 0;
+               }
+       }
+#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
+       if (unlikely((unsigned long)buf & 0x1)) {
+               while (cnt >= 2) {
+                       u16 aligned_buf[64];
+                       int len = min(cnt & -2, (int)sizeof(aligned_buf));
+                       int items = len >> 1;
+                       int i;
+                       /* memcpy from input buffer into aligned buffer */
+                       memcpy(aligned_buf, buf, len);
+                       buf += len;
+                       cnt -= len;
+                       /* push data from aligned buffer into fifo */
+                       for (i = 0; i < items; ++i)
+                               mci_writew(host, DATA, aligned_buf[i]);
+               }
+       } else
+#endif
+       {
+               u16 *pdata = buf;
+               for (; cnt >= 2; cnt -= 2)
+                       mci_writew(host, DATA, *pdata++);
+               buf = pdata;
+       }
+       /* put anything remaining in the part_buf */
+       if (cnt) {
+               dw_mci_set_part_bytes(host, buf, cnt);
+               if (!sg_next(host->sg))
+                       mci_writew(host, DATA, host->part_buf16);
+       }
+}
 
-       cnt = cnt >> 1;
-       while (cnt > 0) {
-               *pdata++ = mci_readw(host, DATA);
-               cnt--;
+static void dw_mci_pull_data16(struct dw_mci *host, void *buf, int cnt)
+{
+#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
+       if (unlikely((unsigned long)buf & 0x1)) {
+               while (cnt >= 2) {
+                       /* pull data from fifo into aligned buffer */
+                       u16 aligned_buf[64];
+                       int len = min(cnt & -2, (int)sizeof(aligned_buf));
+                       int items = len >> 1;
+                       int i;
+                       for (i = 0; i < items; ++i)
+                               aligned_buf[i] = mci_readw(host, DATA);
+                       /* memcpy from aligned buffer into output buffer */
+                       memcpy(buf, aligned_buf, len);
+                       buf += len;
+                       cnt -= len;
+               }
+       } else
+#endif
+       {
+               u16 *pdata = buf;
+               for (; cnt >= 2; cnt -= 2)
+                       *pdata++ = mci_readw(host, DATA);
+               buf = pdata;
+       }
+       if (cnt) {
+               host->part_buf16 = mci_readw(host, DATA);
+               dw_mci_pull_final_bytes(host, buf, cnt);
        }
 }
 
 static void dw_mci_push_data32(struct dw_mci *host, void *buf, int cnt)
 {
-       u32 *pdata = (u32 *)buf;
-
-       WARN_ON(cnt % 4 != 0);
-       WARN_ON((unsigned long)pdata & 0x3);
-
-       cnt = cnt >> 2;
-       while (cnt > 0) {
-               mci_writel(host, DATA, *pdata++);
-               cnt--;
+       /* try and push anything in the part_buf */
+       if (unlikely(host->part_buf_count)) {
+               int len = dw_mci_push_part_bytes(host, buf, cnt);
+               buf += len;
+               cnt -= len;
+               if (!sg_next(host->sg) || host->part_buf_count == 4) {
+                       mci_writel(host, DATA, host->part_buf32);
+                       host->part_buf_count = 0;
+               }
+       }
+#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
+       if (unlikely((unsigned long)buf & 0x3)) {
+               while (cnt >= 4) {
+                       u32 aligned_buf[32];
+                       int len = min(cnt & -4, (int)sizeof(aligned_buf));
+                       int items = len >> 2;
+                       int i;
+                       /* memcpy from input buffer into aligned buffer */
+                       memcpy(aligned_buf, buf, len);
+                       buf += len;
+                       cnt -= len;
+                       /* push data from aligned buffer into fifo */
+                       for (i = 0; i < items; ++i)
+                               mci_writel(host, DATA, aligned_buf[i]);
+               }
+       } else
+#endif
+       {
+               u32 *pdata = buf;
+               for (; cnt >= 4; cnt -= 4)
+                       mci_writel(host, DATA, *pdata++);
+               buf = pdata;
+       }
+       /* put anything remaining in the part_buf */
+       if (cnt) {
+               dw_mci_set_part_bytes(host, buf, cnt);
+               if (!sg_next(host->sg))
+                       mci_writel(host, DATA, host->part_buf32);
        }
 }
 
 static void dw_mci_pull_data32(struct dw_mci *host, void *buf, int cnt)
 {
-       u32 *pdata = (u32 *)buf;
-
-       WARN_ON(cnt % 4 != 0);
-       WARN_ON((unsigned long)pdata & 0x3);
-
-       cnt = cnt >> 2;
-       while (cnt > 0) {
-               *pdata++ = mci_readl(host, DATA);
-               cnt--;
+#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
+       if (unlikely((unsigned long)buf & 0x3)) {
+               while (cnt >= 4) {
+                       /* pull data from fifo into aligned buffer */
+                       u32 aligned_buf[32];
+                       int len = min(cnt & -4, (int)sizeof(aligned_buf));
+                       int items = len >> 2;
+                       int i;
+                       for (i = 0; i < items; ++i)
+                               aligned_buf[i] = mci_readl(host, DATA);
+                       /* memcpy from aligned buffer into output buffer */
+                       memcpy(buf, aligned_buf, len);
+                       buf += len;
+                       cnt -= len;
+               }
+       } else
+#endif
+       {
+               u32 *pdata = buf;
+               for (; cnt >= 4; cnt -= 4)
+                       *pdata++ = mci_readl(host, DATA);
+               buf = pdata;
+       }
+       if (cnt) {
+               host->part_buf32 = mci_readl(host, DATA);
+               dw_mci_pull_final_bytes(host, buf, cnt);
        }
 }
 
 static void dw_mci_push_data64(struct dw_mci *host, void *buf, int cnt)
 {
-       u64 *pdata = (u64 *)buf;
-
-       WARN_ON(cnt % 8 != 0);
-
-       cnt = cnt >> 3;
-       while (cnt > 0) {
-               mci_writeq(host, DATA, *pdata++);
-               cnt--;
+       /* try and push anything in the part_buf */
+       if (unlikely(host->part_buf_count)) {
+               int len = dw_mci_push_part_bytes(host, buf, cnt);
+               buf += len;
+               cnt -= len;
+               if (!sg_next(host->sg) || host->part_buf_count == 8) {
+                       mci_writew(host, DATA, host->part_buf);
+                       host->part_buf_count = 0;
+               }
+       }
+#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
+       if (unlikely((unsigned long)buf & 0x7)) {
+               while (cnt >= 8) {
+                       u64 aligned_buf[16];
+                       int len = min(cnt & -8, (int)sizeof(aligned_buf));
+                       int items = len >> 3;
+                       int i;
+                       /* memcpy from input buffer into aligned buffer */
+                       memcpy(aligned_buf, buf, len);
+                       buf += len;
+                       cnt -= len;
+                       /* push data from aligned buffer into fifo */
+                       for (i = 0; i < items; ++i)
+                               mci_writeq(host, DATA, aligned_buf[i]);
+               }
+       } else
+#endif
+       {
+               u64 *pdata = buf;
+               for (; cnt >= 8; cnt -= 8)
+                       mci_writeq(host, DATA, *pdata++);
+               buf = pdata;
+       }
+       /* put anything remaining in the part_buf */
+       if (cnt) {
+               dw_mci_set_part_bytes(host, buf, cnt);
+               if (!sg_next(host->sg))
+                       mci_writeq(host, DATA, host->part_buf);
        }
 }
 
 static void dw_mci_pull_data64(struct dw_mci *host, void *buf, int cnt)
 {
-       u64 *pdata = (u64 *)buf;
+#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
+       if (unlikely((unsigned long)buf & 0x7)) {
+               while (cnt >= 8) {
+                       /* pull data from fifo into aligned buffer */
+                       u64 aligned_buf[16];
+                       int len = min(cnt & -8, (int)sizeof(aligned_buf));
+                       int items = len >> 3;
+                       int i;
+                       for (i = 0; i < items; ++i)
+                               aligned_buf[i] = mci_readq(host, DATA);
+                       /* memcpy from aligned buffer into output buffer */
+                       memcpy(buf, aligned_buf, len);
+                       buf += len;
+                       cnt -= len;
+               }
+       } else
+#endif
+       {
+               u64 *pdata = buf;
+               for (; cnt >= 8; cnt -= 8)
+                       *pdata++ = mci_readq(host, DATA);
+               buf = pdata;
+       }
+       if (cnt) {
+               host->part_buf = mci_readq(host, DATA);
+               dw_mci_pull_final_bytes(host, buf, cnt);
+       }
+}
 
-       WARN_ON(cnt % 8 != 0);
+static void dw_mci_pull_data(struct dw_mci *host, void *buf, int cnt)
+{
+       int len;
 
-       cnt = cnt >> 3;
-       while (cnt > 0) {
-               *pdata++ = mci_readq(host, DATA);
-               cnt--;
-       }
+       /* get remaining partial bytes */
+       len = dw_mci_pull_part_bytes(host, buf, cnt);
+       if (unlikely(len == cnt))
+               return;
+       buf += len;
+       cnt -= len;
+
+       /* get the rest of the data */
+       host->pull_data(host, buf, cnt);
 }
 
 static void dw_mci_read_data_pio(struct dw_mci *host)
@@ -1037,9 +1265,10 @@ static void dw_mci_read_data_pio(struct dw_mci *host)
        unsigned int nbytes = 0, len;
 
        do {
-               len = SDMMC_GET_FCNT(mci_readl(host, STATUS)) << shift;
+               len = host->part_buf_count +
+                       (SDMMC_GET_FCNT(mci_readl(host, STATUS)) << shift);
                if (offset + len <= sg->length) {
-                       host->pull_data(host, (void *)(buf + offset), len);
+                       dw_mci_pull_data(host, (void *)(buf + offset), len);
 
                        offset += len;
                        nbytes += len;
@@ -1055,8 +1284,8 @@ static void dw_mci_read_data_pio(struct dw_mci *host)
                        }
                } else {
                        unsigned int remaining = sg->length - offset;
-                       host->pull_data(host, (void *)(buf + offset),
-                                       remaining);
+                       dw_mci_pull_data(host, (void *)(buf + offset),
+                                        remaining);
                        nbytes += remaining;
 
                        flush_dcache_page(sg_page(sg));
@@ -1066,7 +1295,7 @@ static void dw_mci_read_data_pio(struct dw_mci *host)
 
                        offset = len - remaining;
                        buf = sg_virt(sg);
-                       host->pull_data(host, buf, offset);
+                       dw_mci_pull_data(host, buf, offset);
                        nbytes += offset;
                }
 
@@ -1083,7 +1312,6 @@ static void dw_mci_read_data_pio(struct dw_mci *host)
                        return;
                }
        } while (status & SDMMC_INT_RXDR); /*if the RXDR is ready read again*/
-       len = SDMMC_GET_FCNT(mci_readl(host, STATUS));
        host->pio_offset = offset;
        data->bytes_xfered += nbytes;
        return;
@@ -1105,8 +1333,9 @@ static void dw_mci_write_data_pio(struct dw_mci *host)
        unsigned int nbytes = 0, len;
 
        do {
-               len = SDMMC_FIFO_SZ -
-                       (SDMMC_GET_FCNT(mci_readl(host, STATUS)) << shift);
+               len = ((host->fifo_depth -
+                       SDMMC_GET_FCNT(mci_readl(host, STATUS))) << shift)
+                       - host->part_buf_count;
                if (offset + len <= sg->length) {
                        host->push_data(host, (void *)(buf + offset), len);
 
@@ -1151,10 +1380,8 @@ static void dw_mci_write_data_pio(struct dw_mci *host)
                        return;
                }
        } while (status & SDMMC_INT_TXDR); /* if TXDR write again */
-
        host->pio_offset = offset;
        data->bytes_xfered += nbytes;
-
        return;
 
 done:
@@ -1202,7 +1429,6 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
                        host->cmd_status = status;
                        smp_wmb();
                        set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
-                       tasklet_schedule(&host->tasklet);
                }
 
                if (pending & DW_MCI_DATA_ERROR_FLAGS) {
@@ -1211,7 +1437,9 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
                        host->data_status = status;
                        smp_wmb();
                        set_bit(EVENT_DATA_ERROR, &host->pending_events);
-                       tasklet_schedule(&host->tasklet);
+                       if (!(pending & (SDMMC_INT_DTO | SDMMC_INT_DCRC |
+                                        SDMMC_INT_SBE | SDMMC_INT_EBE)))
+                               tasklet_schedule(&host->tasklet);
                }
 
                if (pending & SDMMC_INT_DATA_OVER) {
@@ -1229,13 +1457,13 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
 
                if (pending & SDMMC_INT_RXDR) {
                        mci_writel(host, RINTSTS, SDMMC_INT_RXDR);
-                       if (host->sg)
+                       if (host->dir_status == DW_MCI_RECV_STATUS && host->sg)
                                dw_mci_read_data_pio(host);
                }
 
                if (pending & SDMMC_INT_TXDR) {
                        mci_writel(host, RINTSTS, SDMMC_INT_TXDR);
-                       if (host->sg)
+                       if (host->dir_status == DW_MCI_SEND_STATUS && host->sg)
                                dw_mci_write_data_pio(host);
                }
 
@@ -1246,7 +1474,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
 
                if (pending & SDMMC_INT_CD) {
                        mci_writel(host, RINTSTS, SDMMC_INT_CD);
-                       tasklet_schedule(&host->card_tasklet);
+                       queue_work(dw_mci_card_workqueue, &host->card_work);
                }
 
        } while (pass_count++ < 5);
@@ -1265,9 +1493,9 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
-static void dw_mci_tasklet_card(unsigned long data)
+static void dw_mci_work_routine_card(struct work_struct *work)
 {
-       struct dw_mci *host = (struct dw_mci *)data;
+       struct dw_mci *host = container_of(work, struct dw_mci, card_work);
        int i;
 
        for (i = 0; i < host->num_slots; i++) {
@@ -1279,22 +1507,21 @@ static void dw_mci_tasklet_card(unsigned long data)
 
                present = dw_mci_get_cd(mmc);
                while (present != slot->last_detect_state) {
-                       spin_lock(&host->lock);
-
                        dev_dbg(&slot->mmc->class_dev, "card %s\n",
                                present ? "inserted" : "removed");
 
+                       /* Power up slot (before spin_lock, may sleep) */
+                       if (present != 0 && host->pdata->setpower)
+                               host->pdata->setpower(slot->id, mmc->ocr_avail);
+
+                       spin_lock_bh(&host->lock);
+
                        /* Card change detected */
                        slot->last_detect_state = present;
 
-                       /* Power up slot */
-                       if (present != 0) {
-                               if (host->pdata->setpower)
-                                       host->pdata->setpower(slot->id,
-                                                             mmc->ocr_avail);
-
+                       /* Mark card as present if applicable */
+                       if (present != 0)
                                set_bit(DW_MMC_CARD_PRESENT, &slot->flags);
-                       }
 
                        /* Clean up queue if present */
                        mrq = slot->mrq;
@@ -1344,8 +1571,6 @@ static void dw_mci_tasklet_card(unsigned long data)
 
                        /* Power down slot */
                        if (present == 0) {
-                               if (host->pdata->setpower)
-                                       host->pdata->setpower(slot->id, 0);
                                clear_bit(DW_MMC_CARD_PRESENT, &slot->flags);
 
                                /*
@@ -1367,7 +1592,12 @@ static void dw_mci_tasklet_card(unsigned long data)
 
                        }
 
-                       spin_unlock(&host->lock);
+                       spin_unlock_bh(&host->lock);
+
+                       /* Power down slot (after spin_unlock, may sleep) */
+                       if (present == 0 && host->pdata->setpower)
+                               host->pdata->setpower(slot->id, 0);
+
                        present = dw_mci_get_cd(mmc);
                }
 
@@ -1467,7 +1697,7 @@ static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id)
         * Card may have been plugged in prior to boot so we
         * need to run the detect tasklet
         */
-       tasklet_schedule(&host->card_tasklet);
+       queue_work(dw_mci_card_workqueue, &host->card_work);
 
        return 0;
 }
@@ -1645,8 +1875,19 @@ static int dw_mci_probe(struct platform_device *pdev)
         * FIFO threshold settings  RxMark  = fifo_size / 2 - 1,
         *                          Tx Mark = fifo_size / 2 DMA Size = 8
         */
-       fifo_size = mci_readl(host, FIFOTH);
-       fifo_size = (fifo_size >> 16) & 0x7ff;
+       if (!host->pdata->fifo_depth) {
+               /*
+                * Power-on value of RX_WMark is FIFO_DEPTH-1, but this may
+                * have been overwritten by the bootloader, just like we're
+                * about to do, so if you know the value for your hardware, you
+                * should put it in the platform data.
+                */
+               fifo_size = mci_readl(host, FIFOTH);
+               fifo_size = 1 + ((fifo_size >> 16) & 0x7ff);
+       } else {
+               fifo_size = host->pdata->fifo_depth;
+       }
+       host->fifo_depth = fifo_size;
        host->fifoth_val = ((0x2 << 28) | ((fifo_size/2 - 1) << 16) |
                        ((fifo_size/2) << 0));
        mci_writel(host, FIFOTH, host->fifoth_val);
@@ -1656,12 +1897,15 @@ static int dw_mci_probe(struct platform_device *pdev)
        mci_writel(host, CLKSRC, 0);
 
        tasklet_init(&host->tasklet, dw_mci_tasklet_func, (unsigned long)host);
-       tasklet_init(&host->card_tasklet,
-                    dw_mci_tasklet_card, (unsigned long)host);
+       dw_mci_card_workqueue = alloc_workqueue("dw-mci-card",
+                       WQ_MEM_RECLAIM | WQ_NON_REENTRANT, 1);
+       if (!dw_mci_card_workqueue)
+               goto err_dmaunmap;
+       INIT_WORK(&host->card_work, dw_mci_work_routine_card);
 
        ret = request_irq(irq, dw_mci_interrupt, 0, "dw-mci", host);
        if (ret)
-               goto err_dmaunmap;
+               goto err_workqueue;
 
        platform_set_drvdata(pdev, host);
 
@@ -1690,7 +1934,9 @@ static int dw_mci_probe(struct platform_device *pdev)
        mci_writel(host, CTRL, SDMMC_CTRL_INT_ENABLE); /* Enable mci interrupt */
 
        dev_info(&pdev->dev, "DW MMC controller at irq %d, "
-                "%d bit host data width\n", irq, width);
+                "%d bit host data width, "
+                "%u deep fifo\n",
+                irq, width, fifo_size);
        if (host->quirks & DW_MCI_QUIRK_IDMAC_DTO)
                dev_info(&pdev->dev, "Internal DMAC interrupt fix enabled.\n");
 
@@ -1705,6 +1951,9 @@ err_init_slot:
        }
        free_irq(irq, host);
 
+err_workqueue:
+       destroy_workqueue(dw_mci_card_workqueue);
+
 err_dmaunmap:
        if (host->use_dma && host->dma_ops->exit)
                host->dma_ops->exit(host);
@@ -1744,6 +1993,7 @@ static int __exit dw_mci_remove(struct platform_device *pdev)
        mci_writel(host, CLKSRC, 0);
 
        free_irq(platform_get_irq(pdev, 0), host);
+       destroy_workqueue(dw_mci_card_workqueue);
        dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
 
        if (host->use_dma && host->dma_ops->exit)