fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / mmc / wbsd.c
index 0871134..7a28267 100644 (file)
 /*
- *  linux/drivers/mmc/wbsd.c
+ *  linux/drivers/mmc/wbsd.c - Winbond W83L51xD SD/MMC driver
  *
- *  Copyright (C) 2004 Pierre Ossman, All Rights Reserved.
+ *  Copyright (C) 2004-2005 Pierre Ossman, All Rights Reserved.
  *
  * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ *
+ * Warning!
+ *
+ * Changes to the FIFO system should be done with extreme care since
+ * the hardware is full of bugs related to the FIFO. Known issues are:
+ *
+ * - FIFO size field in FSR is always zero.
+ *
+ * - FIFO interrupts tend not to work as they should. Interrupts are
+ *   triggered only for full/empty events, not for threshold values.
+ *
+ * - On APIC systems the FIFO empty interrupt is sometimes lost.
  */
 
-#include <linux/config.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
 #include <linux/init.h>
 #include <linux/ioport.h>
-#include <linux/device.h>
+#include <linux/platform_device.h>
 #include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
 #include <linux/delay.h>
-#include <linux/blkdev.h>
-
+#include <linux/pnp.h>
+#include <linux/highmem.h>
 #include <linux/mmc/host.h>
 #include <linux/mmc/protocol.h>
 
 #include <asm/io.h>
 #include <asm/dma.h>
+#include <asm/scatterlist.h>
 
 #include "wbsd.h"
 
 #define DRIVER_NAME "wbsd"
-#define DRIVER_VERSION "1.0"
+#define DRIVER_VERSION "1.6"
 
-#ifdef CONFIG_MMC_DEBUG
 #define DBG(x...) \
-       printk(KERN_DEBUG DRIVER_NAME ": " x)
+       pr_debug(DRIVER_NAME ": " x)
 #define DBGF(f, x...) \
-       printk(KERN_DEBUG DRIVER_NAME " [%s()]: " f, __func__, ##x)
+       pr_debug(DRIVER_NAME " [%s()]: " f, __func__ , ##x)
+
+/*
+ * Device resources
+ */
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_device_id pnp_dev_table[] = {
+       { "WEC0517", 0 },
+       { "WEC0518", 0 },
+       { "", 0 },
+};
+
+MODULE_DEVICE_TABLE(pnp, pnp_dev_table);
+
+#endif /* CONFIG_PNP */
+
+static const int config_ports[] = { 0x2E, 0x4E };
+static const int unlock_codes[] = { 0x83, 0x87 };
+
+static const int valid_ids[] = {
+       0x7112,
+       };
+
+#ifdef CONFIG_PNP
+static unsigned int nopnp = 0;
 #else
-#define DBG(x...)      do { } while (0)
-#define DBGF(x...)     do { } while (0)
+static const unsigned int nopnp = 1;
 #endif
-
 static unsigned int io = 0x248;
 static unsigned int irq = 6;
 static int dma = 2;
 
-#ifdef CONFIG_MMC_DEBUG
-void DBG_REG(int reg, u8 value)
-{
-       int i;
-       
-       printk(KERN_DEBUG "wbsd: Register %d: 0x%02X %3d '%c' ",
-               reg, (int)value, (int)value, (value < 0x20)?'.':value);
-       
-       for (i = 7;i >= 0;i--)
-       {
-               if (value & (1 << i))
-                       printk("x");
-               else
-                       printk(".");
-       }
-       
-       printk("\n");
-}
-#else
-#define DBG_REG(r, v) do {}  while (0)
-#endif
-
 /*
  * Basic functions
  */
 
-static inline void wbsd_unlock_config(struct wbsd_hosthost)
+static inline void wbsd_unlock_config(struct wbsd_host *host)
 {
+       BUG_ON(host->config == 0);
+
        outb(host->unlock_code, host->config);
        outb(host->unlock_code, host->config);
 }
 
-static inline void wbsd_lock_config(struct wbsd_hosthost)
+static inline void wbsd_lock_config(struct wbsd_host *host)
 {
+       BUG_ON(host->config == 0);
+
        outb(LOCK_CODE, host->config);
 }
 
-static inline void wbsd_write_config(struct wbsd_hosthost, u8 reg, u8 value)
+static inline void wbsd_write_config(struct wbsd_host *host, u8 reg, u8 value)
 {
+       BUG_ON(host->config == 0);
+
        outb(reg, host->config);
        outb(value, host->config + 1);
 }
 
-static inline u8 wbsd_read_config(struct wbsd_hosthost, u8 reg)
+static inline u8 wbsd_read_config(struct wbsd_host *host, u8 reg)
 {
+       BUG_ON(host->config == 0);
+
        outb(reg, host->config);
        return inb(host->config + 1);
 }
 
-static inline void wbsd_write_index(struct wbsd_hosthost, u8 index, u8 value)
+static inline void wbsd_write_index(struct wbsd_host *host, u8 index, u8 value)
 {
        outb(index, host->base + WBSD_IDXR);
        outb(value, host->base + WBSD_DATAR);
 }
 
-static inline u8 wbsd_read_index(struct wbsd_hosthost, u8 index)
+static inline u8 wbsd_read_index(struct wbsd_host *host, u8 index)
 {
        outb(index, host->base + WBSD_IDXR);
        return inb(host->base + WBSD_DATAR);
@@ -108,17 +132,24 @@ static inline u8 wbsd_read_index(struct wbsd_host* host, u8 index)
  * Common routines
  */
 
-static void wbsd_init_device(struct wbsd_hosthost)
+static void wbsd_init_device(struct wbsd_host *host)
 {
        u8 setup, ier;
-       
+
        /*
         * Reset chip (SD/MMC part) and fifo.
         */
        setup = wbsd_read_index(host, WBSD_IDX_SETUP);
        setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET;
        wbsd_write_index(host, WBSD_IDX_SETUP, setup);
-       
+
+       /*
+        * Set DAT3 to input
+        */
+       setup &= ~WBSD_DAT3_H;
+       wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+       host->flags &= ~WBSD_FIGNORE_DETECT;
+
        /*
         * Read back default clock.
         */
@@ -128,12 +159,20 @@ static void wbsd_init_device(struct wbsd_host* host)
         * Power down port.
         */
        outb(WBSD_POWER_N, host->base + WBSD_CSR);
-       
+
        /*
         * Set maximum timeout.
         */
        wbsd_write_index(host, WBSD_IDX_TAAC, 0x7F);
-       
+
+       /*
+        * Test for card presence
+        */
+       if (inb(host->base + WBSD_CSR) & WBSD_CARDPRESENT)
+               host->flags |= WBSD_FCARD_PRESENT;
+       else
+               host->flags &= ~WBSD_FCARD_PRESENT;
+
        /*
         * Enable interesting interrupts.
         */
@@ -153,12 +192,12 @@ static void wbsd_init_device(struct wbsd_host* host)
        inb(host->base + WBSD_ISR);
 }
 
-static void wbsd_reset(struct wbsd_hosthost)
+static void wbsd_reset(struct wbsd_host *host)
 {
        u8 setup;
-       
-       printk(KERN_ERR DRIVER_NAME ": Resetting chip\n");
-       
+
+       printk(KERN_ERR "%s: Resetting chip\n", mmc_hostname(host->mmc));
+
        /*
         * Soft reset of chip (SD/MMC part).
         */
@@ -167,14 +206,13 @@ static void wbsd_reset(struct wbsd_host* host)
        wbsd_write_index(host, WBSD_IDX_SETUP, setup);
 }
 
-static void wbsd_request_end(struct wbsd_host* host, struct mmc_request* mrq)
+static void wbsd_request_end(struct wbsd_host *host, struct mmc_request *mrq)
 {
        unsigned long dmaflags;
-       
+
        DBGF("Ending request, cmd (%x)\n", mrq->cmd->opcode);
-       
-       if (host->dma >= 0)
-       {
+
+       if (host->dma >= 0) {
                /*
                 * Release ISA DMA controller.
                 */
@@ -188,7 +226,7 @@ static void wbsd_request_end(struct wbsd_host* host, struct mmc_request* mrq)
                 */
                wbsd_write_index(host, WBSD_IDX_DMA, 0);
        }
-       
+
        host->mrq = NULL;
 
        /*
@@ -203,10 +241,8 @@ static void wbsd_request_end(struct wbsd_host* host, struct mmc_request* mrq)
  * Scatter/gather functions
  */
 
-static inline void wbsd_init_sg(struct wbsd_host* host, struct mmc_data* data)
+static inline void wbsd_init_sg(struct wbsd_host *host, struct mmc_data *data)
 {
-       struct request* req = data->req;
-       
        /*
         * Get info. about SG list from data structure.
         */
@@ -217,7 +253,7 @@ static inline void wbsd_init_sg(struct wbsd_host* host, struct mmc_data* data)
        host->remain = host->cur_sg->length;
 }
 
-static inline int wbsd_next_sg(struct wbsd_hosthost)
+static inline int wbsd_next_sg(struct wbsd_host *host)
 {
        /*
         * Skip to next SG entry.
@@ -228,162 +264,152 @@ static inline int wbsd_next_sg(struct wbsd_host* host)
        /*
         * Any entries left?
         */
-       if (host->num_sg > 0)
-         {
-           host->offset = 0;
-           host->remain = host->cur_sg->length;
-         }
-       
+       if (host->num_sg > 0) {
+               host->offset = 0;
+               host->remain = host->cur_sg->length;
+       }
+
        return host->num_sg;
 }
 
-static inline char* wbsd_kmap_sg(struct wbsd_host* host)
+static inline char *wbsd_kmap_sg(struct wbsd_host *host)
 {
-       return kmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ) +
+       host->mapped_sg = kmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ) +
                host->cur_sg->offset;
+       return host->mapped_sg;
 }
 
-static inline void wbsd_kunmap_sg(struct wbsd_hosthost)
+static inline void wbsd_kunmap_sg(struct wbsd_host *host)
 {
-       kunmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ);
+       kunmap_atomic(host->mapped_sg, KM_BIO_SRC_IRQ);
 }
 
-static inline void wbsd_sg_to_dma(struct wbsd_host* host, struct mmc_data* data)
+static inline void wbsd_sg_to_dma(struct wbsd_host *host, struct mmc_data *data)
 {
        unsigned int len, i, size;
-       struct scatterlistsg;
-       chardmabuf = host->dma_buffer;
-       charsgbuf;
-       
+       struct scatterlist *sg;
+       char *dmabuf = host->dma_buffer;
+       char *sgbuf;
+
        size = host->size;
-       
+
        sg = data->sg;
        len = data->sg_len;
-       
+
        /*
         * Just loop through all entries. Size might not
         * be the entire list though so make sure that
         * we do not transfer too much.
         */
-       for (i = 0;i < len;i++)
-       {
+       for (i = 0; i < len; i++) {
                sgbuf = kmap_atomic(sg[i].page, KM_BIO_SRC_IRQ) + sg[i].offset;
                if (size < sg[i].length)
                        memcpy(dmabuf, sgbuf, size);
                else
                        memcpy(dmabuf, sgbuf, sg[i].length);
-               kunmap_atomic(sg[i].page, KM_BIO_SRC_IRQ);
+               kunmap_atomic(sgbuf, KM_BIO_SRC_IRQ);
                dmabuf += sg[i].length;
-               
+
                if (size < sg[i].length)
                        size = 0;
                else
                        size -= sg[i].length;
-       
+
                if (size == 0)
                        break;
        }
-       
+
        /*
         * Check that we didn't get a request to transfer
         * more data than can fit into the SG list.
         */
-       
+
        BUG_ON(size != 0);
-       
+
        host->size -= size;
 }
 
-static inline void wbsd_dma_to_sg(struct wbsd_host* host, struct mmc_data* data)
+static inline void wbsd_dma_to_sg(struct wbsd_host *host, struct mmc_data *data)
 {
        unsigned int len, i, size;
-       struct scatterlistsg;
-       chardmabuf = host->dma_buffer;
-       charsgbuf;
-       
+       struct scatterlist *sg;
+       char *dmabuf = host->dma_buffer;
+       char *sgbuf;
+
        size = host->size;
-       
+
        sg = data->sg;
        len = data->sg_len;
-       
+
        /*
         * Just loop through all entries. Size might not
         * be the entire list though so make sure that
         * we do not transfer too much.
         */
-       for (i = 0;i < len;i++)
-       {
+       for (i = 0; i < len; i++) {
                sgbuf = kmap_atomic(sg[i].page, KM_BIO_SRC_IRQ) + sg[i].offset;
                if (size < sg[i].length)
                        memcpy(sgbuf, dmabuf, size);
                else
                        memcpy(sgbuf, dmabuf, sg[i].length);
-               kunmap_atomic(sg[i].page, KM_BIO_SRC_IRQ);
+               kunmap_atomic(sgbuf, KM_BIO_SRC_IRQ);
                dmabuf += sg[i].length;
-               
+
                if (size < sg[i].length)
                        size = 0;
                else
                        size -= sg[i].length;
-               
+
                if (size == 0)
                        break;
        }
-       
+
        /*
         * Check that we didn't get a request to transfer
         * more data than can fit into the SG list.
         */
-       
+
        BUG_ON(size != 0);
-       
+
        host->size -= size;
 }
 
 /*
  * Command handling
  */
-static inline void wbsd_get_short_reply(struct wbsd_hosthost,
-       struct mmc_command* cmd)
+
+static inline void wbsd_get_short_reply(struct wbsd_host *host,
+                                       struct mmc_command *cmd)
 {
        /*
         * Correct response type?
         */
-       if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT)
-       {
+       if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT) {
                cmd->error = MMC_ERR_INVALID;
                return;
        }
-       
-       cmd->resp[0] =
-               wbsd_read_index(host, WBSD_IDX_RESP12) << 24;
-       cmd->resp[0] |=
-               wbsd_read_index(host, WBSD_IDX_RESP13) << 16;
-       cmd->resp[0] |=
-               wbsd_read_index(host, WBSD_IDX_RESP14) << 8;
-       cmd->resp[0] |=
-               wbsd_read_index(host, WBSD_IDX_RESP15) << 0;
-       cmd->resp[1] =
-               wbsd_read_index(host, WBSD_IDX_RESP16) << 24;
+
+       cmd->resp[0]  = wbsd_read_index(host, WBSD_IDX_RESP12) << 24;
+       cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP13) << 16;
+       cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP14) << 8;
+       cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP15) << 0;
+       cmd->resp[1]  = wbsd_read_index(host, WBSD_IDX_RESP16) << 24;
 }
 
-static inline void wbsd_get_long_reply(struct wbsd_hosthost,
-       struct mmc_commandcmd)
+static inline void wbsd_get_long_reply(struct wbsd_host *host,
+       struct mmc_command *cmd)
 {
        int i;
-       
+
        /*
         * Correct response type?
         */
-       if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_LONG)
-       {
+       if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_LONG) {
                cmd->error = MMC_ERR_INVALID;
                return;
        }
-       
-       for (i = 0;i < 4;i++)
-       {
+
+       for (i = 0; i < 4; i++) {
                cmd->resp[i] =
                        wbsd_read_index(host, WBSD_IDX_RESP1 + i * 4) << 24;
                cmd->resp[i] |=
@@ -395,31 +421,29 @@ static inline void wbsd_get_long_reply(struct wbsd_host* host,
        }
 }
 
-static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs);
-
-static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd)
+static void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd)
 {
        int i;
-       u8 status, eir, isr;
-       
+       u8 status, isr;
+
        DBGF("Sending cmd (%x)\n", cmd->opcode);
 
        /*
-        * Disable interrupts as the interrupt routine
-        * will destroy the contents of ISR.
+        * Clear accumulated ISR. The interrupt routine
+        * will fill this one with events that occur during
+        * transfer.
         */
-       eir = inb(host->base + WBSD_EIR);
-       outb(0, host->base + WBSD_EIR);
-       
+       host->isr = 0;
+
        /*
         * Send the command (CRC calculated by host).
         */
        outb(cmd->opcode, host->base + WBSD_CMDR);
-       for (i = 3;i >= 0;i--)
+       for (i = 3; i >= 0; i--)
                outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR);
-       
+
        cmd->error = MMC_ERR_NONE;
-       
+
        /*
         * Wait for the request to complete.
         */
@@ -430,13 +454,12 @@ static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd)
        /*
         * Do we expect a reply?
         */
-       if ((cmd->flags & MMC_RSP_MASK) != MMC_RSP_NONE)
-       {
+       if (cmd->flags & MMC_RSP_PRESENT) {
                /*
                 * Read back status.
                 */
-               isr = inb(host->base + WBSD_ISR);
-               
+               isr = host->isr;
+
                /* Card removed? */
                if (isr & WBSD_INT_CARD)
                        cmd->error = MMC_ERR_TIMEOUT;
@@ -447,26 +470,14 @@ static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd)
                else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC))
                        cmd->error = MMC_ERR_BADCRC;
                /* All ok */
-               else
-               {
-                       if ((cmd->flags & MMC_RSP_MASK) == MMC_RSP_SHORT)
-                               wbsd_get_short_reply(host, cmd);
-                       else
+               else {
+                       if (cmd->flags & MMC_RSP_136)
                                wbsd_get_long_reply(host, cmd);
+                       else
+                               wbsd_get_short_reply(host, cmd);
                }
        }
 
-       /*
-        * Restore interrupt mask to previous value.
-        */
-       outb(eir, host->base + WBSD_EIR);
-       
-       /*
-        * Call the interrupt routine to jump start
-        * interrupts.
-        */
-       wbsd_irq(0, host, NULL);
-
        DBGF("Sent cmd (%x), res %d\n", cmd->opcode, cmd->error);
 }
 
@@ -474,78 +485,97 @@ static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd)
  * Data functions
  */
 
-static void wbsd_empty_fifo(struct wbsd_hosthost)
+static void wbsd_empty_fifo(struct wbsd_host *host)
 {
-       struct mmc_data* data = host->mrq->cmd->data;
-       char* buffer;
-       
+       struct mmc_data *data = host->mrq->cmd->data;
+       char *buffer;
+       int i, fsr, fifo;
+
        /*
         * Handle excessive data.
         */
        if (data->bytes_xfered == host->size)
                return;
-       
+
        buffer = wbsd_kmap_sg(host) + host->offset;
 
        /*
         * Drain the fifo. This has a tendency to loop longer
         * than the FIFO length (usually one block).
         */
-       while (!(inb(host->base + WBSD_FSR) & WBSD_FIFO_EMPTY))
-       {
-               *buffer = inb(host->base + WBSD_DFR);
-               buffer++;
-               host->offset++;
-               host->remain--;
-
-               data->bytes_xfered++;
-               
-               /*
-                * Transfer done?
-                */
-               if (data->bytes_xfered == host->size)
-               {
-                       wbsd_kunmap_sg(host);                           
-                       return;
-               }
-               
+       while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_EMPTY)) {
                /*
-                * End of scatter list entry?
+                * The size field in the FSR is broken so we have to
+                * do some guessing.
                 */
-               if (host->remain == 0)
-               {
-                       wbsd_kunmap_sg(host);
-                       
+               if (fsr & WBSD_FIFO_FULL)
+                       fifo = 16;
+               else if (fsr & WBSD_FIFO_FUTHRE)
+                       fifo = 8;
+               else
+                       fifo = 1;
+
+               for (i = 0; i < fifo; i++) {
+                       *buffer = inb(host->base + WBSD_DFR);
+                       buffer++;
+                       host->offset++;
+                       host->remain--;
+
+                       data->bytes_xfered++;
+
+                       /*
+                        * Transfer done?
+                        */
+                       if (data->bytes_xfered == host->size) {
+                               wbsd_kunmap_sg(host);
+                               return;
+                       }
+
                        /*
-                        * Get next entry. Check if last.
+                        * End of scatter list entry?
                         */
-                       if (!wbsd_next_sg(host))
-                       {
+                       if (host->remain == 0) {
+                               wbsd_kunmap_sg(host);
+
                                /*
-                                * We should never reach this point.
-                                * It means that we're trying to
-                                * transfer more blocks than can fit
-                                * into the scatter list.
+                                * Get next entry. Check if last.
                                 */
-                               BUG_ON(1);
-                               
-                               host->size = data->bytes_xfered;
-                               
-                               return;
+                               if (!wbsd_next_sg(host)) {
+                                       /*
+                                        * We should never reach this point.
+                                        * It means that we're trying to
+                                        * transfer more blocks than can fit
+                                        * into the scatter list.
+                                        */
+                                       BUG_ON(1);
+
+                                       host->size = data->bytes_xfered;
+
+                                       return;
+                               }
+
+                               buffer = wbsd_kmap_sg(host);
                        }
-                       
-                       buffer = wbsd_kmap_sg(host);
                }
        }
-       
+
        wbsd_kunmap_sg(host);
+
+       /*
+        * This is a very dirty hack to solve a
+        * hardware problem. The chip doesn't trigger
+        * FIFO threshold interrupts properly.
+        */
+       if ((host->size - data->bytes_xfered) < 16)
+               tasklet_schedule(&host->fifo_tasklet);
 }
 
-static void wbsd_fill_fifo(struct wbsd_hosthost)
+static void wbsd_fill_fifo(struct wbsd_host *host)
 {
-       struct mmc_data* data = host->mrq->cmd->data;
-       char* buffer;
-       
+       struct mmc_data *data = host->mrq->cmd->data;
+       char *buffer;
+       int i, fsr, fifo;
+
        /*
         * Check that we aren't being called after the
         * entire buffer has been transfered.
@@ -559,71 +589,87 @@ static void wbsd_fill_fifo(struct wbsd_host* host)
         * Fill the fifo. This has a tendency to loop longer
         * than the FIFO length (usually one block).
         */
-       while (!(inb(host->base + WBSD_FSR) & WBSD_FIFO_FULL))
-       {
-               outb(*buffer, host->base + WBSD_DFR);
-               buffer++;
-               host->offset++;
-               host->remain--;
-               
-               data->bytes_xfered++;
-               
+       while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_FULL)) {
                /*
-                * Transfer done?
+                * The size field in the FSR is broken so we have to
+                * do some guessing.
                 */
-               if (data->bytes_xfered == host->size)
-               {
-                       wbsd_kunmap_sg(host);
-                       return;
-               }
+               if (fsr & WBSD_FIFO_EMPTY)
+                       fifo = 0;
+               else if (fsr & WBSD_FIFO_EMTHRE)
+                       fifo = 8;
+               else
+                       fifo = 15;
+
+               for (i = 16; i > fifo; i--) {
+                       outb(*buffer, host->base + WBSD_DFR);
+                       buffer++;
+                       host->offset++;
+                       host->remain--;
+
+                       data->bytes_xfered++;
 
-               /*
-                * End of scatter list entry?
-                */
-               if (host->remain == 0)
-               {
-                       wbsd_kunmap_sg(host);
-                       
                        /*
-                        * Get next entry. Check if last.
+                        * Transfer done?
                         */
-                       if (!wbsd_next_sg(host))
-                       {
+                       if (data->bytes_xfered == host->size) {
+                               wbsd_kunmap_sg(host);
+                               return;
+                       }
+
+                       /*
+                        * End of scatter list entry?
+                        */
+                       if (host->remain == 0) {
+                               wbsd_kunmap_sg(host);
+
                                /*
-                                * We should never reach this point.
-                                * It means that we're trying to
-                                * transfer more blocks than can fit
-                                * into the scatter list.
+                                * Get next entry. Check if last.
                                 */
-                               BUG_ON(1);
-                               
-                               host->size = data->bytes_xfered;
-                               
-                               return;
+                               if (!wbsd_next_sg(host)) {
+                                       /*
+                                        * We should never reach this point.
+                                        * It means that we're trying to
+                                        * transfer more blocks than can fit
+                                        * into the scatter list.
+                                        */
+                                       BUG_ON(1);
+
+                                       host->size = data->bytes_xfered;
+
+                                       return;
+                               }
+
+                               buffer = wbsd_kmap_sg(host);
                        }
-                       
-                       buffer = wbsd_kmap_sg(host);
                }
        }
-       
+
        wbsd_kunmap_sg(host);
+
+       /*
+        * The controller stops sending interrupts for
+        * 'FIFO empty' under certain conditions. So we
+        * need to be a bit more pro-active.
+        */
+       tasklet_schedule(&host->fifo_tasklet);
 }
 
-static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data)
+static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data)
 {
        u16 blksize;
        u8 setup;
        unsigned long dmaflags;
 
        DBGF("blksz %04x blks %04x flags %08x\n",
-               1 << data->blksz_bits, data->blocks, data->flags);
+               data->blksz, data->blocks, data->flags);
        DBGF("tsac %d ms nsac %d clk\n",
                data->timeout_ns / 1000000, data->timeout_clks);
-       
+
        /*
         * Calculate size.
         */
-       host->size = data->blocks << data->blksz_bits;
+       host->size = data->blocks * data->blksz;
 
        /*
         * Check timeout values for overflow.
@@ -631,25 +677,39 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data)
         */
        if (data->timeout_ns > 127000000)
                wbsd_write_index(host, WBSD_IDX_TAAC, 127);
-       else
-               wbsd_write_index(host, WBSD_IDX_TAAC, data->timeout_ns/1000000);
-       
+       else {
+               wbsd_write_index(host, WBSD_IDX_TAAC,
+                       data->timeout_ns / 1000000);
+       }
+
        if (data->timeout_clks > 255)
                wbsd_write_index(host, WBSD_IDX_NSAC, 255);
        else
                wbsd_write_index(host, WBSD_IDX_NSAC, data->timeout_clks);
-       
+
        /*
         * Inform the chip of how large blocks will be
         * sent. It needs this to determine when to
         * calculate CRC.
         *
         * Space for CRC must be included in the size.
+        * Two bytes are needed for each data line.
         */
-       blksize = (1 << data->blksz_bits) + 2;
-       
-       wbsd_write_index(host, WBSD_IDX_PBSMSB, (blksize >> 4) & 0xF0);
-       wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
+       if (host->bus_width == MMC_BUS_WIDTH_1) {
+               blksize = data->blksz + 2;
+
+               wbsd_write_index(host, WBSD_IDX_PBSMSB, (blksize >> 4) & 0xF0);
+               wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
+       } else if (host->bus_width == MMC_BUS_WIDTH_4) {
+               blksize = data->blksz + 2 * 4;
+
+               wbsd_write_index(host, WBSD_IDX_PBSMSB,
+                       ((blksize >> 4) & 0xF0) | WBSD_DATA_WIDTH);
+               wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
+       } else {
+               data->error = MMC_ERR_INVALID;
+               return;
+       }
 
        /*
         * Clear the FIFO. This is needed even for DMA
@@ -659,39 +719,37 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data)
        setup = wbsd_read_index(host, WBSD_IDX_SETUP);
        setup |= WBSD_FIFO_RESET;
        wbsd_write_index(host, WBSD_IDX_SETUP, setup);
-       
+
        /*
         * DMA transfer?
         */
-       if (host->dma >= 0)
-       {       
+       if (host->dma >= 0) {
                /*
                 * The buffer for DMA is only 64 kB.
                 */
                BUG_ON(host->size > 0x10000);
-               if (host->size > 0x10000)
-               {
+               if (host->size > 0x10000) {
                        data->error = MMC_ERR_INVALID;
                        return;
                }
-               
+
                /*
                 * Transfer data from the SG list to
                 * the DMA buffer.
                 */
                if (data->flags & MMC_DATA_WRITE)
                        wbsd_sg_to_dma(host, data);
-               
+
                /*
                 * Initialise the ISA DMA controller.
-                */     
+                */
                dmaflags = claim_dma_lock();
                disable_dma(host->dma);
                clear_dma_ff(host->dma);
                if (data->flags & MMC_DATA_READ)
-                       set_dma_mode(host->dma, DMA_MODE_READ);
+                       set_dma_mode(host->dma, DMA_MODE_READ & ~0x40);
                else
-                       set_dma_mode(host->dma, DMA_MODE_WRITE);
+                       set_dma_mode(host->dma, DMA_MODE_WRITE & ~0x40);
                set_dma_addr(host->dma, host->dma_addr);
                set_dma_count(host->dma, host->size);
 
@@ -701,52 +759,47 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data)
                /*
                 * Enable DMA on the host.
                 */
-               wbsd_write_index(host, WBSD_IDX_DMA,
-                       WBSD_DMA_SINGLE | WBSD_DMA_ENABLE);
-       }
-       else
-       {
+               wbsd_write_index(host, WBSD_IDX_DMA, WBSD_DMA_ENABLE);
+       } else {
                /*
                 * This flag is used to keep printk
                 * output to a minimum.
                 */
                host->firsterr = 1;
-               
+
                /*
                 * Initialise the SG list.
                 */
                wbsd_init_sg(host, data);
-       
+
                /*
                 * Turn off DMA.
                 */
                wbsd_write_index(host, WBSD_IDX_DMA, 0);
-       
+
                /*
                 * Set up FIFO threshold levels (and fill
                 * buffer if doing a write).
                 */
-               if (data->flags & MMC_DATA_READ)
-               {
+               if (data->flags & MMC_DATA_READ) {
                        wbsd_write_index(host, WBSD_IDX_FIFOEN,
                                WBSD_FIFOEN_FULL | 8);
-               }
-               else
-               {
+               } else {
                        wbsd_write_index(host, WBSD_IDX_FIFOEN,
                                WBSD_FIFOEN_EMPTY | 8);
                        wbsd_fill_fifo(host);
                }
-       }       
-               
+       }
+
        data->error = MMC_ERR_NONE;
 }
 
-static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data)
+static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data)
 {
        unsigned long dmaflags;
        int count;
-       
+       u8 status;
+
        WARN_ON(host->mrq == NULL);
 
        /*
@@ -754,17 +807,24 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data)
         */
        if (data->stop)
                wbsd_send_command(host, data->stop);
-       
+
+       /*
+        * Wait for the controller to leave data
+        * transfer state.
+        */
+       do {
+               status = wbsd_read_index(host, WBSD_IDX_STATUS);
+       } while (status & (WBSD_BLOCK_READ | WBSD_BLOCK_WRITE));
+
        /*
         * DMA transfer?
         */
-       if (host->dma >= 0)
-       {
+       if (host->dma >= 0) {
                /*
                 * Disable DMA on the host.
                 */
                wbsd_write_index(host, WBSD_IDX_DMA, 0);
-               
+
                /*
                 * Turn of ISA DMA controller.
                 */
@@ -773,43 +833,43 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data)
                clear_dma_ff(host->dma);
                count = get_dma_residue(host->dma);
                release_dma_lock(dmaflags);
-               
+
                /*
                 * Any leftover data?
                 */
-               if (count)
-               {
-                       printk(KERN_ERR DRIVER_NAME ": Incomplete DMA "
-                               "transfer. %d bytes left.\n", count);
-                       
+               if (count) {
+                       printk(KERN_ERR "%s: Incomplete DMA transfer. "
+                               "%d bytes left.\n",
+                               mmc_hostname(host->mmc), count);
+
                        data->error = MMC_ERR_FAILED;
-               }
-               else
-               {
+               } else {
                        /*
                         * Transfer data from DMA buffer to
                         * SG list.
                         */
                        if (data->flags & MMC_DATA_READ)
                                wbsd_dma_to_sg(host, data);
-                       
+
                        data->bytes_xfered = host->size;
                }
        }
-       
+
        DBGF("Ending data transfer (%d bytes)\n", data->bytes_xfered);
-       
+
        wbsd_request_end(host, host->mrq);
 }
 
-/*
- * MMC Callbacks
- */
+/*****************************************************************************\
+ *                                                                           *
+ * MMC layer callbacks                                                       *
+ *                                                                           *
+\*****************************************************************************/
 
-static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq)
+static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
 {
-       struct wbsd_hosthost = mmc_priv(mmc);
-       struct mmc_commandcmd;
+       struct wbsd_host *host = mmc_priv(mmc);
+       struct mmc_command *cmd;
 
        /*
         * Disable tasklets to avoid a deadlock.
@@ -821,13 +881,12 @@ static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq)
        cmd = mrq->cmd;
 
        host->mrq = mrq;
-       
+
        /*
         * If there is no card in the slot then
         * timeout immediatly.
         */
-       if (!(inb(host->base + WBSD_CSR) & WBSD_CARDPRESENT))
-       {
+       if (!(host->flags & WBSD_FCARD_PRESENT)) {
                cmd->error = MMC_ERR_TIMEOUT;
                goto done;
        }
@@ -835,41 +894,42 @@ static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq)
        /*
         * Does the request include data?
         */
-       if (cmd->data)
-       {
+       if (cmd->data) {
                wbsd_prepare_data(host, cmd->data);
-               
+
                if (cmd->data->error != MMC_ERR_NONE)
                        goto done;
        }
-       
+
        wbsd_send_command(host, cmd);
 
        /*
         * If this is a data transfer the request
         * will be finished after the data has
         * transfered.
-        */     
-       if (cmd->data && (cmd->error == MMC_ERR_NONE))
-       {               
+        */
+       if (cmd->data && (cmd->error == MMC_ERR_NONE)) {
+               /*
+                * Dirty fix for hardware bug.
+                */
+               if (host->dma == -1)
+                       tasklet_schedule(&host->fifo_tasklet);
+
                spin_unlock_bh(&host->lock);
 
                return;
        }
-               
+
 done:
        wbsd_request_end(host, mrq);
 
        spin_unlock_bh(&host->lock);
 }
 
-static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios)
+static void wbsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
-       struct wbsd_hosthost = mmc_priv(mmc);
+       struct wbsd_host *host = mmc_priv(mmc);
        u8 clk, setup, pwr;
-       
-       DBGF("clock %uHz busmode %u powermode %u Vdd %u\n",
-               ios->clock, ios->bus_mode, ios->power_mode, ios->vdd);
 
        spin_lock_bh(&host->lock);
 
@@ -879,7 +939,7 @@ static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios)
         */
        if (ios->power_mode == MMC_POWER_OFF)
                wbsd_init_device(host);
-       
+
        if (ios->clock >= 24000000)
                clk = WBSD_CLK_24M;
        else if (ios->clock >= 16000000)
@@ -893,35 +953,107 @@ static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios)
         * Only write to the clock register when
         * there is an actual change.
         */
-       if (clk != host->clk)
-       {
+       if (clk != host->clk) {
                wbsd_write_index(host, WBSD_IDX_CLK, clk);
                host->clk = clk;
        }
 
-       if (ios->power_mode != MMC_POWER_OFF)
-       {
-               /*
-                * Power up card.
-                */
+       /*
+        * Power up card.
+        */
+       if (ios->power_mode != MMC_POWER_OFF) {
                pwr = inb(host->base + WBSD_CSR);
                pwr &= ~WBSD_POWER_N;
                outb(pwr, host->base + WBSD_CSR);
+       }
 
-               /*
-                * This behaviour is stolen from the
-                * Windows driver. Don't know why, but
-                * it is needed.
-                */
-               setup = wbsd_read_index(host, WBSD_IDX_SETUP);
-               if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
-                       setup |= WBSD_DAT3_H;
-               else
+       /*
+        * MMC cards need to have pin 1 high during init.
+        * It wreaks havoc with the card detection though so
+        * that needs to be disabled.
+        */
+       setup = wbsd_read_index(host, WBSD_IDX_SETUP);
+       if (ios->chip_select == MMC_CS_HIGH) {
+               BUG_ON(ios->bus_width != MMC_BUS_WIDTH_1);
+               setup |= WBSD_DAT3_H;
+               host->flags |= WBSD_FIGNORE_DETECT;
+       } else {
+               if (setup & WBSD_DAT3_H) {
                        setup &= ~WBSD_DAT3_H;
-               wbsd_write_index(host, WBSD_IDX_SETUP, setup);
 
-               mdelay(1);
+                       /*
+                        * We cannot resume card detection immediatly
+                        * because of capacitance and delays in the chip.
+                        */
+                       mod_timer(&host->ignore_timer, jiffies + HZ / 100);
+               }
        }
+       wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+
+       /*
+        * Store bus width for later. Will be used when
+        * setting up the data transfer.
+        */
+       host->bus_width = ios->bus_width;
+
+       spin_unlock_bh(&host->lock);
+}
+
+static int wbsd_get_ro(struct mmc_host *mmc)
+{
+       struct wbsd_host *host = mmc_priv(mmc);
+       u8 csr;
+
+       spin_lock_bh(&host->lock);
+
+       csr = inb(host->base + WBSD_CSR);
+       csr |= WBSD_MSLED;
+       outb(csr, host->base + WBSD_CSR);
+
+       mdelay(1);
+
+       csr = inb(host->base + WBSD_CSR);
+       csr &= ~WBSD_MSLED;
+       outb(csr, host->base + WBSD_CSR);
+
+       spin_unlock_bh(&host->lock);
+
+       return csr & WBSD_WRPT;
+}
+
+static const struct mmc_host_ops wbsd_ops = {
+       .request        = wbsd_request,
+       .set_ios        = wbsd_set_ios,
+       .get_ro         = wbsd_get_ro,
+};
+
+/*****************************************************************************\
+ *                                                                           *
+ * Interrupt handling                                                        *
+ *                                                                           *
+\*****************************************************************************/
+
+/*
+ * Helper function to reset detection ignore
+ */
+
+static void wbsd_reset_ignore(unsigned long data)
+{
+       struct wbsd_host *host = (struct wbsd_host *)data;
+
+       BUG_ON(host == NULL);
+
+       DBG("Resetting card detection ignore\n");
+
+       spin_lock_bh(&host->lock);
+
+       host->flags &= ~WBSD_FIGNORE_DETECT;
+
+       /*
+        * Card status might have changed during the
+        * blackout.
+        */
+       tasklet_schedule(&host->card_tasklet);
 
        spin_unlock_bh(&host->lock);
 }
@@ -930,7 +1062,7 @@ static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios)
  * Tasklets
  */
 
-inline static struct mmc_data* wbsd_get_data(struct wbsd_host* host)
+static inline struct mmc_data *wbsd_get_data(struct wbsd_host *host)
 {
        WARN_ON(!host->mrq);
        if (!host->mrq)
@@ -943,55 +1075,69 @@ inline static struct mmc_data* wbsd_get_data(struct wbsd_host* host)
        WARN_ON(!host->mrq->cmd->data);
        if (!host->mrq->cmd->data)
                return NULL;
-       
+
        return host->mrq->cmd->data;
 }
 
 static void wbsd_tasklet_card(unsigned long param)
 {
-       struct wbsd_host* host = (struct wbsd_host*)param;
+       struct wbsd_host *host = (struct wbsd_host *)param;
        u8 csr;
-       
+       int delay = -1;
+
        spin_lock(&host->lock);
-       
+
+       if (host->flags & WBSD_FIGNORE_DETECT) {
+               spin_unlock(&host->lock);
+               return;
+       }
+
        csr = inb(host->base + WBSD_CSR);
        WARN_ON(csr == 0xff);
-       
-       if (csr & WBSD_CARDPRESENT)
-               DBG("Card inserted\n");
-       else
-       {
+
+       if (csr & WBSD_CARDPRESENT) {
+               if (!(host->flags & WBSD_FCARD_PRESENT)) {
+                       DBG("Card inserted\n");
+                       host->flags |= WBSD_FCARD_PRESENT;
+
+                       delay = 500;
+               }
+       } else if (host->flags & WBSD_FCARD_PRESENT) {
                DBG("Card removed\n");
-               
-               if (host->mrq)
-               {
-                       printk(KERN_ERR DRIVER_NAME
-                               ": Card removed during transfer!\n");
+               host->flags &= ~WBSD_FCARD_PRESENT;
+
+               if (host->mrq) {
+                       printk(KERN_ERR "%s: Card removed during transfer!\n",
+                               mmc_hostname(host->mmc));
                        wbsd_reset(host);
-                       
+
                        host->mrq->cmd->error = MMC_ERR_FAILED;
                        tasklet_schedule(&host->finish_tasklet);
                }
+
+               delay = 0;
        }
-       
+
        /*
         * Unlock first since we might get a call back.
         */
+
        spin_unlock(&host->lock);
 
-       mmc_detect_change(host->mmc);
+       if (delay != -1)
+               mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
 }
 
 static void wbsd_tasklet_fifo(unsigned long param)
 {
-       struct wbsd_host* host = (struct wbsd_host*)param;
-       struct mmc_datadata;
-       
+       struct wbsd_host *host = (struct wbsd_host *)param;
+       struct mmc_data *data;
+
        spin_lock(&host->lock);
-               
+
        if (!host->mrq)
                goto end;
-       
+
        data = wbsd_get_data(host);
        if (!data)
                goto end;
@@ -1004,109 +1150,105 @@ static void wbsd_tasklet_fifo(unsigned long param)
        /*
         * Done?
         */
-       if (host->size == data->bytes_xfered)
-       {
+       if (host->size == data->bytes_xfered) {
                wbsd_write_index(host, WBSD_IDX_FIFOEN, 0);
                tasklet_schedule(&host->finish_tasklet);
        }
 
-end:   
+end:
        spin_unlock(&host->lock);
 }
 
 static void wbsd_tasklet_crc(unsigned long param)
 {
-       struct wbsd_host* host = (struct wbsd_host*)param;
-       struct mmc_datadata;
-       
+       struct wbsd_host *host = (struct wbsd_host *)param;
+       struct mmc_data *data;
+
        spin_lock(&host->lock);
-       
-       WARN_ON(!host->mrq);
+
        if (!host->mrq)
                goto end;
-       
+
        data = wbsd_get_data(host);
        if (!data)
                goto end;
-       
+
        DBGF("CRC error\n");
 
        data->error = MMC_ERR_BADCRC;
-       
+
        tasklet_schedule(&host->finish_tasklet);
 
-end:           
+end:
        spin_unlock(&host->lock);
 }
 
 static void wbsd_tasklet_timeout(unsigned long param)
 {
-       struct wbsd_host* host = (struct wbsd_host*)param;
-       struct mmc_datadata;
-       
+       struct wbsd_host *host = (struct wbsd_host *)param;
+       struct mmc_data *data;
+
        spin_lock(&host->lock);
-       
-       WARN_ON(!host->mrq);
+
        if (!host->mrq)
                goto end;
-       
+
        data = wbsd_get_data(host);
        if (!data)
                goto end;
-       
+
        DBGF("Timeout\n");
 
        data->error = MMC_ERR_TIMEOUT;
-       
+
        tasklet_schedule(&host->finish_tasklet);
 
-end:   
+end:
        spin_unlock(&host->lock);
 }
 
 static void wbsd_tasklet_finish(unsigned long param)
 {
-       struct wbsd_host* host = (struct wbsd_host*)param;
-       struct mmc_datadata;
-       
+       struct wbsd_host *host = (struct wbsd_host *)param;
+       struct mmc_data *data;
+
        spin_lock(&host->lock);
-       
+
        WARN_ON(!host->mrq);
        if (!host->mrq)
                goto end;
-       
+
        data = wbsd_get_data(host);
        if (!data)
                goto end;
 
        wbsd_finish_data(host, data);
-       
-end:   
+
+end:
        spin_unlock(&host->lock);
 }
 
 static void wbsd_tasklet_block(unsigned long param)
 {
-       struct wbsd_host* host = (struct wbsd_host*)param;
-       struct mmc_datadata;
-       
+       struct wbsd_host *host = (struct wbsd_host *)param;
+       struct mmc_data *data;
+
        spin_lock(&host->lock);
 
        if ((wbsd_read_index(host, WBSD_IDX_CRCSTATUS) & WBSD_CRC_MASK) !=
-               WBSD_CRC_OK)
-       {
+               WBSD_CRC_OK) {
                data = wbsd_get_data(host);
                if (!data)
                        goto end;
-               
+
                DBGF("CRC error\n");
 
                data->error = MMC_ERR_BADCRC;
-       
+
                tasklet_schedule(&host->finish_tasklet);
        }
 
-end:   
+end:
        spin_unlock(&host->lock);
 }
 
@@ -1114,11 +1256,11 @@ end:
  * Interrupt handling
  */
 
-static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs)
+static irqreturn_t wbsd_irq(int irq, void *dev_id)
 {
-       struct wbsd_hosthost = dev_id;
+       struct wbsd_host *host = dev_id;
        int isr;
-       
+
        isr = inb(host->base + WBSD_ISR);
 
        /*
@@ -1127,13 +1269,15 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs)
        if (isr == 0xff || isr == 0x00)
                return IRQ_NONE;
 
+       host->isr |= isr;
+
        /*
         * Schedule tasklets as needed.
         */
        if (isr & WBSD_INT_CARD)
                tasklet_schedule(&host->card_tasklet);
        if (isr & WBSD_INT_FIFO_THRE)
-               tasklet_hi_schedule(&host->fifo_tasklet);
+               tasklet_schedule(&host->fifo_tasklet);
        if (isr & WBSD_INT_CRC)
                tasklet_hi_schedule(&host->crc_tasklet);
        if (isr & WBSD_INT_TIMEOUT)
@@ -1142,105 +1286,201 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs)
                tasklet_hi_schedule(&host->block_tasklet);
        if (isr & WBSD_INT_TC)
                tasklet_schedule(&host->finish_tasklet);
-       
+
        return IRQ_HANDLED;
 }
 
+/*****************************************************************************\
+ *                                                                           *
+ * Device initialisation and shutdown                                        *
+ *                                                                           *
+\*****************************************************************************/
+
 /*
- * Support functions for probe
+ * Allocate/free MMC structure.
  */
 
-static int wbsd_scan(struct wbsd_host* host)
+static int __devinit wbsd_alloc_mmc(struct device *dev)
+{
+       struct mmc_host *mmc;
+       struct wbsd_host *host;
+
+       /*
+        * Allocate MMC structure.
+        */
+       mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev);
+       if (!mmc)
+               return -ENOMEM;
+
+       host = mmc_priv(mmc);
+       host->mmc = mmc;
+
+       host->dma = -1;
+
+       /*
+        * Set host parameters.
+        */
+       mmc->ops = &wbsd_ops;
+       mmc->f_min = 375000;
+       mmc->f_max = 24000000;
+       mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+       mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK;
+
+       spin_lock_init(&host->lock);
+
+       /*
+        * Set up timers
+        */
+       init_timer(&host->ignore_timer);
+       host->ignore_timer.data = (unsigned long)host;
+       host->ignore_timer.function = wbsd_reset_ignore;
+
+       /*
+        * Maximum number of segments. Worst case is one sector per segment
+        * so this will be 64kB/512.
+        */
+       mmc->max_hw_segs = 128;
+       mmc->max_phys_segs = 128;
+
+       /*
+        * Maximum number of sectors in one transfer. Also limited by 64kB
+        * buffer.
+        */
+       mmc->max_sectors = 128;
+
+       /*
+        * Maximum segment size. Could be one segment with the maximum number
+        * of segments.
+        */
+       mmc->max_seg_size = mmc->max_sectors * 512;
+
+       dev_set_drvdata(dev, mmc);
+
+       return 0;
+}
+
+static void __devexit wbsd_free_mmc(struct device *dev)
+{
+       struct mmc_host *mmc;
+       struct wbsd_host *host;
+
+       mmc = dev_get_drvdata(dev);
+       if (!mmc)
+               return;
+
+       host = mmc_priv(mmc);
+       BUG_ON(host == NULL);
+
+       del_timer_sync(&host->ignore_timer);
+
+       mmc_free_host(mmc);
+
+       dev_set_drvdata(dev, NULL);
+}
+
+/*
+ * Scan for known chip id:s
+ */
+
+static int __devinit wbsd_scan(struct wbsd_host *host)
 {
        int i, j, k;
        int id;
-       
+
        /*
         * Iterate through all ports, all codes to
         * find hardware that is in our known list.
         */
-       for (i = 0;i < sizeof(config_ports)/sizeof(int);i++)
-       {
+       for (i = 0; i < ARRAY_SIZE(config_ports); i++) {
                if (!request_region(config_ports[i], 2, DRIVER_NAME))
                        continue;
-                       
-               for (j = 0;j < sizeof(unlock_codes)/sizeof(int);j++)
-               {
+
+               for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) {
                        id = 0xFFFF;
-                       
-                       outb(unlock_codes[j], config_ports[i]);
-                       outb(unlock_codes[j], config_ports[i]);
-                       
+
+                       host->config = config_ports[i];
+                       host->unlock_code = unlock_codes[j];
+
+                       wbsd_unlock_config(host);
+
                        outb(WBSD_CONF_ID_HI, config_ports[i]);
                        id = inb(config_ports[i] + 1) << 8;
 
                        outb(WBSD_CONF_ID_LO, config_ports[i]);
                        id |= inb(config_ports[i] + 1);
-                       
-                       for (k = 0;k < sizeof(valid_ids)/sizeof(int);k++)
-                       {
-                               if (id == valid_ids[k])
-                               {                               
+
+                       wbsd_lock_config(host);
+
+                       for (k = 0; k < ARRAY_SIZE(valid_ids); k++) {
+                               if (id == valid_ids[k]) {
                                        host->chip_id = id;
-                                       host->config = config_ports[i];
-                                       host->unlock_code = unlock_codes[i];
-                               
+
                                        return 0;
                                }
                        }
-                       
-                       if (id != 0xFFFF)
-                       {
+
+                       if (id != 0xFFFF) {
                                DBG("Unknown hardware (id %x) found at %x\n",
                                        id, config_ports[i]);
                        }
-
-                       outb(LOCK_CODE, config_ports[i]);
                }
-               
+
                release_region(config_ports[i], 2);
        }
-       
+
+       host->config = 0;
+       host->unlock_code = 0;
+
        return -ENODEV;
 }
 
-static int wbsd_request_regions(struct wbsd_host* host)
+/*
+ * Allocate/free io port ranges
+ */
+
+static int __devinit wbsd_request_region(struct wbsd_host *host, int base)
 {
-       if (io & 0x7)
+       if (base & 0x7)
                return -EINVAL;
-       
-       if (!request_region(io, 8, DRIVER_NAME))
+
+       if (!request_region(base, 8, DRIVER_NAME))
                return -EIO;
-       
-       host->base = io;
-               
+
+       host->base = base;
+
        return 0;
 }
 
-static void wbsd_release_regions(struct wbsd_host* host)
+static void __devexit wbsd_release_regions(struct wbsd_host *host)
 {
        if (host->base)
                release_region(host->base, 8);
 
+       host->base = 0;
+
        if (host->config)
                release_region(host->config, 2);
+
+       host->config = 0;
 }
 
-static void wbsd_init_dma(struct wbsd_host* host)
+/*
+ * Allocate/free DMA port and buffer
+ */
+
+static void __devinit wbsd_request_dma(struct wbsd_host *host, int dma)
 {
-       host->dma = -1;
-       
        if (dma < 0)
                return;
-       
+
        if (request_dma(dma, DRIVER_NAME))
                goto err;
-       
+
        /*
         * We need to allocate a special buffer in
         * order for ISA to be able to DMA to it.
         */
-       host->dma_buffer = kmalloc(65536,
+       host->dma_buffer = kmalloc(WBSD_DMA_SIZE,
                GFP_NOIO | GFP_DMA | __GFP_REPEAT | __GFP_NOWARN);
        if (!host->dma_buffer)
                goto free;
@@ -1248,8 +1488,9 @@ static void wbsd_init_dma(struct wbsd_host* host)
        /*
         * Translate the address to a physical address.
         */
-       host->dma_addr = isa_virt_to_bus(host->dma_buffer);
-                       
+       host->dma_addr = dma_map_single(mmc_dev(host->mmc), host->dma_buffer,
+               WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
+
        /*
         * ISA DMA must be aligned on a 64k basis.
         */
@@ -1262,15 +1503,19 @@ static void wbsd_init_dma(struct wbsd_host* host)
                goto kfree;
 
        host->dma = dma;
-       
+
        return;
-       
+
 kfree:
        /*
         * If we've gotten here then there is some kind of alignment bug
         */
        BUG_ON(1);
-       
+
+       dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
+               WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
+       host->dma_addr = (dma_addr_t)NULL;
+
        kfree(host->dma_buffer);
        host->dma_buffer = NULL;
 
@@ -1282,273 +1527,541 @@ err:
                "Falling back on FIFO.\n", dma);
 }
 
-static struct mmc_host_ops wbsd_ops = {
-       .request        = wbsd_request,
-       .set_ios        = wbsd_set_ios,
-};
+static void __devexit wbsd_release_dma(struct wbsd_host *host)
+{
+       if (host->dma_addr) {
+               dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
+                       WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
+       }
+       kfree(host->dma_buffer);
+       if (host->dma >= 0)
+               free_dma(host->dma);
+
+       host->dma = -1;
+       host->dma_buffer = NULL;
+       host->dma_addr = (dma_addr_t)NULL;
+}
 
 /*
- * Device probe
+ * Allocate/free IRQ.
  */
 
-static int wbsd_probe(struct device* dev)
+static int __devinit wbsd_request_irq(struct wbsd_host *host, int irq)
 {
-       struct wbsd_host* host = NULL;
-       struct mmc_host* mmc = NULL;
        int ret;
-       
-       /*
-        * Allocate MMC structure.
-        */
-       mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev);
-       if (!mmc)
-               return -ENOMEM;
-       
-       host = mmc_priv(mmc);
-       host->mmc = mmc;
-       
+
        /*
-        * Scan for hardware.
+        * Allocate interrupt.
         */
-       ret = wbsd_scan(host);
+
+       ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host);
        if (ret)
-               goto freemmc;
+               return ret;
+
+       host->irq = irq;
 
        /*
-        * Reset the chip.
-        */     
-       wbsd_write_config(host, WBSD_CONF_SWRST, 1);
-       wbsd_write_config(host, WBSD_CONF_SWRST, 0);
+        * Set up tasklets.
+        */
+       tasklet_init(&host->card_tasklet, wbsd_tasklet_card,
+                       (unsigned long)host);
+       tasklet_init(&host->fifo_tasklet, wbsd_tasklet_fifo,
+                       (unsigned long)host);
+       tasklet_init(&host->crc_tasklet, wbsd_tasklet_crc,
+                       (unsigned long)host);
+       tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout,
+                       (unsigned long)host);
+       tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish,
+                       (unsigned long)host);
+       tasklet_init(&host->block_tasklet, wbsd_tasklet_block,
+                       (unsigned long)host);
+
+       return 0;
+}
+
+static void __devexit wbsd_release_irq(struct wbsd_host *host)
+{
+       if (!host->irq)
+               return;
+
+       free_irq(host->irq, host);
+
+       host->irq = 0;
+
+       tasklet_kill(&host->card_tasklet);
+       tasklet_kill(&host->fifo_tasklet);
+       tasklet_kill(&host->crc_tasklet);
+       tasklet_kill(&host->timeout_tasklet);
+       tasklet_kill(&host->finish_tasklet);
+       tasklet_kill(&host->block_tasklet);
+}
+
+/*
+ * Allocate all resources for the host.
+ */
+
+static int __devinit wbsd_request_resources(struct wbsd_host *host,
+       int base, int irq, int dma)
+{
+       int ret;
 
        /*
         * Allocate I/O ports.
         */
-       ret = wbsd_request_regions(host);
+       ret = wbsd_request_region(host, base);
        if (ret)
-               goto release;
+               return ret;
 
        /*
-        * Set host parameters.
+        * Allocate interrupt.
         */
-       mmc->ops = &wbsd_ops;
-       mmc->f_min = 375000;
-       mmc->f_max = 24000000;
-       mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
-       
-       spin_lock_init(&host->lock);
+       ret = wbsd_request_irq(host, irq);
+       if (ret)
+               return ret;
+
+       /*
+        * Allocate DMA.
+        */
+       wbsd_request_dma(host, dma);
+
+       return 0;
+}
+
+/*
+ * Release all resources for the host.
+ */
+
+static void __devexit wbsd_release_resources(struct wbsd_host *host)
+{
+       wbsd_release_dma(host);
+       wbsd_release_irq(host);
+       wbsd_release_regions(host);
+}
+
+/*
+ * Configure the resources the chip should use.
+ */
+
+static void wbsd_chip_config(struct wbsd_host *host)
+{
+       wbsd_unlock_config(host);
+
+       /*
+        * Reset the chip.
+        */
+       wbsd_write_config(host, WBSD_CONF_SWRST, 1);
+       wbsd_write_config(host, WBSD_CONF_SWRST, 0);
 
        /*
         * Select SD/MMC function.
         */
        wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
-       
+
        /*
         * Set up card detection.
         */
-       wbsd_write_config(host, WBSD_CONF_PINS, 0x02);
-       
+       wbsd_write_config(host, WBSD_CONF_PINS, WBSD_PINS_DETECT_GP11);
+
        /*
-        * Configure I/O port.
+        * Configure chip
         */
        wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8);
        wbsd_write_config(host, WBSD_CONF_PORT_LO, host->base & 0xff);
 
-       /*
-        * Allocate interrupt.
-        */
-       ret = request_irq(irq, wbsd_irq, SA_SHIRQ, DRIVER_NAME, host);
-       if (ret)
-               goto release;
-       
-       host->irq = irq;
-       
-       /*
-        * Set up tasklets.
-        */
-       tasklet_init(&host->card_tasklet, wbsd_tasklet_card, (unsigned long)host);
-       tasklet_init(&host->fifo_tasklet, wbsd_tasklet_fifo, (unsigned long)host);
-       tasklet_init(&host->crc_tasklet, wbsd_tasklet_crc, (unsigned long)host);
-       tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout, (unsigned long)host);
-       tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish, (unsigned long)host);
-       tasklet_init(&host->block_tasklet, wbsd_tasklet_block, (unsigned long)host);
-       
-       /*
-        * Configure interrupt.
-        */
        wbsd_write_config(host, WBSD_CONF_IRQ, host->irq);
-       
+
+       if (host->dma >= 0)
+               wbsd_write_config(host, WBSD_CONF_DRQ, host->dma);
+
        /*
-        * Allocate DMA.
+        * Enable and power up chip.
         */
-       wbsd_init_dma(host);
-       
+       wbsd_write_config(host, WBSD_CONF_ENABLE, 1);
+       wbsd_write_config(host, WBSD_CONF_POWER, 0x20);
+
+       wbsd_lock_config(host);
+}
+
+/*
+ * Check that configured resources are correct.
+ */
+
+static int wbsd_chip_validate(struct wbsd_host *host)
+{
+       int base, irq, dma;
+
+       wbsd_unlock_config(host);
+
        /*
-        * If all went well, then configure DMA.
+        * Select SD/MMC function.
         */
-       if (host->dma >= 0)
-               wbsd_write_config(host, WBSD_CONF_DRQ, host->dma);
-       
+       wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
+
        /*
-        * Maximum number of segments. Worst case is one sector per segment
-        * so this will be 64kB/512.
+        * Read configuration.
         */
-       mmc->max_hw_segs = NR_SG;
-       mmc->max_phys_segs = NR_SG;
-       
+       base = wbsd_read_config(host, WBSD_CONF_PORT_HI) << 8;
+       base |= wbsd_read_config(host, WBSD_CONF_PORT_LO);
+
+       irq = wbsd_read_config(host, WBSD_CONF_IRQ);
+
+       dma = wbsd_read_config(host, WBSD_CONF_DRQ);
+
+       wbsd_lock_config(host);
+
        /*
-        * Maximum number of sectors in one transfer. Also limited by 64kB
-        * buffer.
+        * Validate against given configuration.
         */
-       mmc->max_sectors = 128;
-       
+       if (base != host->base)
+               return 0;
+       if (irq != host->irq)
+               return 0;
+       if ((dma != host->dma) && (host->dma != -1))
+               return 0;
+
+       return 1;
+}
+
+/*
+ * Powers down the SD function
+ */
+
+static void wbsd_chip_poweroff(struct wbsd_host *host)
+{
+       wbsd_unlock_config(host);
+
+       wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
+       wbsd_write_config(host, WBSD_CONF_ENABLE, 0);
+
+       wbsd_lock_config(host);
+}
+
+/*****************************************************************************\
+ *                                                                           *
+ * Devices setup and shutdown                                                *
+ *                                                                           *
+\*****************************************************************************/
+
+static int __devinit wbsd_init(struct device *dev, int base, int irq, int dma,
+       int pnp)
+{
+       struct wbsd_host *host = NULL;
+       struct mmc_host *mmc = NULL;
+       int ret;
+
+       ret = wbsd_alloc_mmc(dev);
+       if (ret)
+               return ret;
+
+       mmc = dev_get_drvdata(dev);
+       host = mmc_priv(mmc);
+
        /*
-        * Maximum segment size. Could be one segment with the maximum number
-        * of segments.
+        * Scan for hardware.
         */
-       mmc->max_seg_size = mmc->max_sectors * 512;
-       
+       ret = wbsd_scan(host);
+       if (ret) {
+               if (pnp && (ret == -ENODEV)) {
+                       printk(KERN_WARNING DRIVER_NAME
+                               ": Unable to confirm device presence. You may "
+                               "experience lock-ups.\n");
+               } else {
+                       wbsd_free_mmc(dev);
+                       return ret;
+               }
+       }
+
        /*
-        * Enable chip.
+        * Request resources.
         */
-       wbsd_write_config(host, WBSD_CONF_ENABLE, 1);
-       
+       ret = wbsd_request_resources(host, base, irq, dma);
+       if (ret) {
+               wbsd_release_resources(host);
+               wbsd_free_mmc(dev);
+               return ret;
+       }
+
        /*
-        * Power up chip.
+        * See if chip needs to be configured.
         */
-       wbsd_write_config(host, WBSD_CONF_POWER, 0x20);
-       
+       if (pnp) {
+               if ((host->config != 0) && !wbsd_chip_validate(host)) {
+                       printk(KERN_WARNING DRIVER_NAME
+                               ": PnP active but chip not configured! "
+                               "You probably have a buggy BIOS. "
+                               "Configuring chip manually.\n");
+                       wbsd_chip_config(host);
+               }
+       } else
+               wbsd_chip_config(host);
+
        /*
         * Power Management stuff. No idea how this works.
         * Not tested.
         */
 #ifdef CONFIG_PM
-       wbsd_write_config(host, WBSD_CONF_PME, 0xA0);
+       if (host->config) {
+               wbsd_unlock_config(host);
+               wbsd_write_config(host, WBSD_CONF_PME, 0xA0);
+               wbsd_lock_config(host);
+       }
 #endif
+       /*
+        * Allow device to initialise itself properly.
+        */
+       mdelay(5);
 
        /*
         * Reset the chip into a known state.
         */
        wbsd_init_device(host);
-       
-       dev_set_drvdata(dev, mmc);
-       
-       /*
-        * Add host to MMC layer.
-        */
+
        mmc_add_host(mmc);
 
-       printk(KERN_INFO "%s: W83L51xD id %x at 0x%x irq %d dma %d\n",
-               mmc->host_name, (int)host->chip_id, (int)host->base,
-               (int)host->irq, (int)host->dma);
+       printk(KERN_INFO "%s: W83L51xD", mmc_hostname(mmc));
+       if (host->chip_id != 0)
+               printk(" id %x", (int)host->chip_id);
+       printk(" at 0x%x irq %d", (int)host->base, (int)host->irq);
+       if (host->dma >= 0)
+               printk(" dma %d", (int)host->dma);
+       else
+               printk(" FIFO");
+       if (pnp)
+               printk(" PnP");
+       printk("\n");
 
        return 0;
-
-release:
-       wbsd_release_regions(host);
-
-freemmc:
-       mmc_free_host(mmc);
-
-       return ret;
 }
 
-/*
- * Device remove
- */
-
-static int wbsd_remove(struct device* dev)
+static void __devexit wbsd_shutdown(struct device *dev, int pnp)
 {
-       struct mmc_hostmmc = dev_get_drvdata(dev);
-       struct wbsd_hosthost;
-       
+       struct mmc_host *mmc = dev_get_drvdata(dev);
+       struct wbsd_host *host;
+
        if (!mmc)
-               return 0;
+               return;
 
        host = mmc_priv(mmc);
-       
-       /*
-        * Unregister host with MMC layer.
-        */
+
        mmc_remove_host(mmc);
 
        /*
         * Power down the SD/MMC function.
         */
-       wbsd_unlock_config(host);
-       wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
-       wbsd_write_config(host, WBSD_CONF_ENABLE, 0);
-       wbsd_lock_config(host);
-       
+       if (!pnp)
+               wbsd_chip_poweroff(host);
+
+       wbsd_release_resources(host);
+
+       wbsd_free_mmc(dev);
+}
+
+/*
+ * Non-PnP
+ */
+
+static int __devinit wbsd_probe(struct platform_device *dev)
+{
+       /* Use the module parameters for resources */
+       return wbsd_init(&dev->dev, io, irq, dma, 0);
+}
+
+static int __devexit wbsd_remove(struct platform_device *dev)
+{
+       wbsd_shutdown(&dev->dev, 0);
+
+       return 0;
+}
+
+/*
+ * PnP
+ */
+
+#ifdef CONFIG_PNP
+
+static int __devinit
+wbsd_pnp_probe(struct pnp_dev *pnpdev, const struct pnp_device_id *dev_id)
+{
+       int io, irq, dma;
+
        /*
-        * Free resources.
+        * Get resources from PnP layer.
         */
-       if (host->dma_buffer)
-               kfree(host->dma_buffer);
-       
-       if (host->dma >= 0)
-               free_dma(host->dma);
+       io = pnp_port_start(pnpdev, 0);
+       irq = pnp_irq(pnpdev, 0);
+       if (pnp_dma_valid(pnpdev, 0))
+               dma = pnp_dma(pnpdev, 0);
+       else
+               dma = -1;
 
-       free_irq(host->irq, host);
-       
-       tasklet_kill(&host->card_tasklet);
-       tasklet_kill(&host->fifo_tasklet);
-       tasklet_kill(&host->crc_tasklet);
-       tasklet_kill(&host->timeout_tasklet);
-       tasklet_kill(&host->finish_tasklet);
-       tasklet_kill(&host->block_tasklet);
-       
-       wbsd_release_regions(host);
-       
-       mmc_free_host(mmc);
+       DBGF("PnP resources: port %3x irq %d dma %d\n", io, irq, dma);
 
-       return 0;
+       return wbsd_init(&pnpdev->dev, io, irq, dma, 1);
 }
 
+static void __devexit wbsd_pnp_remove(struct pnp_dev *dev)
+{
+       wbsd_shutdown(&dev->dev, 1);
+}
+
+#endif /* CONFIG_PNP */
+
 /*
  * Power management
  */
 
 #ifdef CONFIG_PM
-static int wbsd_suspend(struct device *dev, u32 state, u32 level)
+
+static int wbsd_suspend(struct wbsd_host *host, pm_message_t state)
 {
-       DBGF("Not yet supported\n");
+       BUG_ON(host == NULL);
 
-       return 0;
+       return mmc_suspend_host(host->mmc, state);
 }
 
-static int wbsd_resume(struct device *dev, u32 level)
+static int wbsd_resume(struct wbsd_host *host)
 {
-       DBGF("Not yet supported\n");
+       BUG_ON(host == NULL);
+
+       wbsd_init_device(host);
+
+       return mmc_resume_host(host->mmc);
+}
+
+static int wbsd_platform_suspend(struct platform_device *dev,
+                                pm_message_t state)
+{
+       struct mmc_host *mmc = platform_get_drvdata(dev);
+       struct wbsd_host *host;
+       int ret;
+
+       if (mmc == NULL)
+               return 0;
+
+       DBGF("Suspending...\n");
+
+       host = mmc_priv(mmc);
+
+       ret = wbsd_suspend(host, state);
+       if (ret)
+               return ret;
+
+       wbsd_chip_poweroff(host);
 
        return 0;
 }
-#else
-#define wbsd_suspend NULL
-#define wbsd_resume NULL
-#endif
 
-static void wbsd_release(struct device *dev)
+static int wbsd_platform_resume(struct platform_device *dev)
 {
+       struct mmc_host *mmc = platform_get_drvdata(dev);
+       struct wbsd_host *host;
+
+       if (mmc == NULL)
+               return 0;
+
+       DBGF("Resuming...\n");
+
+       host = mmc_priv(mmc);
+
+       wbsd_chip_config(host);
+
+       /*
+        * Allow device to initialise itself properly.
+        */
+       mdelay(5);
+
+       return wbsd_resume(host);
 }
 
-static struct platform_device wbsd_device = {
-       .name           = DRIVER_NAME,
-       .id                     = -1,
-       .dev            = {
-               .release = wbsd_release,
+#ifdef CONFIG_PNP
+
+static int wbsd_pnp_suspend(struct pnp_dev *pnp_dev, pm_message_t state)
+{
+       struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
+       struct wbsd_host *host;
+
+       if (mmc == NULL)
+               return 0;
+
+       DBGF("Suspending...\n");
+
+       host = mmc_priv(mmc);
+
+       return wbsd_suspend(host, state);
+}
+
+static int wbsd_pnp_resume(struct pnp_dev *pnp_dev)
+{
+       struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
+       struct wbsd_host *host;
+
+       if (mmc == NULL)
+               return 0;
+
+       DBGF("Resuming...\n");
+
+       host = mmc_priv(mmc);
+
+       /*
+        * See if chip needs to be configured.
+        */
+       if (host->config != 0) {
+               if (!wbsd_chip_validate(host)) {
+                       printk(KERN_WARNING DRIVER_NAME
+                               ": PnP active but chip not configured! "
+                               "You probably have a buggy BIOS. "
+                               "Configuring chip manually.\n");
+                       wbsd_chip_config(host);
+               }
+       }
+
+       /*
+        * Allow device to initialise itself properly.
+        */
+       mdelay(5);
+
+       return wbsd_resume(host);
+}
+
+#endif /* CONFIG_PNP */
+
+#else /* CONFIG_PM */
+
+#define wbsd_platform_suspend NULL
+#define wbsd_platform_resume NULL
+
+#define wbsd_pnp_suspend NULL
+#define wbsd_pnp_resume NULL
+
+#endif /* CONFIG_PM */
+
+static struct platform_device *wbsd_device;
+
+static struct platform_driver wbsd_driver = {
+       .probe          = wbsd_probe,
+       .remove         = __devexit_p(wbsd_remove),
+
+       .suspend        = wbsd_platform_suspend,
+       .resume         = wbsd_platform_resume,
+       .driver         = {
+               .name   = DRIVER_NAME,
        },
 };
 
-static struct device_driver wbsd_driver = {
+#ifdef CONFIG_PNP
+
+static struct pnp_driver wbsd_pnp_driver = {
        .name           = DRIVER_NAME,
-       .bus            = &platform_bus_type,
-       .probe          = wbsd_probe,
-       .remove         = wbsd_remove,
-       
-       .suspend        = wbsd_suspend,
-       .resume         = wbsd_resume,
+       .id_table       = pnp_dev_table,
+       .probe          = wbsd_pnp_probe,
+       .remove         = __devexit_p(wbsd_pnp_remove),
+
+       .suspend        = wbsd_pnp_suspend,
+       .resume         = wbsd_pnp_resume,
 };
 
+#endif /* CONFIG_PNP */
+
 /*
  * Module loading/unloading
  */
@@ -1556,41 +2069,78 @@ static struct device_driver wbsd_driver = {
 static int __init wbsd_drv_init(void)
 {
        int result;
-       
+
        printk(KERN_INFO DRIVER_NAME
                ": Winbond W83L51xD SD/MMC card interface driver, "
                DRIVER_VERSION "\n");
        printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
-       
-       result = driver_register(&wbsd_driver);
-       if (result < 0)
-               return result;
 
-       result = platform_device_register(&wbsd_device);
-       if (result < 0)
-               return result;
+#ifdef CONFIG_PNP
+
+       if (!nopnp) {
+               result = pnp_register_driver(&wbsd_pnp_driver);
+               if (result < 0)
+                       return result;
+       }
+#endif /* CONFIG_PNP */
+
+       if (nopnp) {
+               result = platform_driver_register(&wbsd_driver);
+               if (result < 0)
+                       return result;
+
+               wbsd_device = platform_device_alloc(DRIVER_NAME, -1);
+               if (!wbsd_device) {
+                       platform_driver_unregister(&wbsd_driver);
+                       return -ENOMEM;
+               }
+
+               result = platform_device_add(wbsd_device);
+               if (result) {
+                       platform_device_put(wbsd_device);
+                       platform_driver_unregister(&wbsd_driver);
+                       return result;
+               }
+       }
 
        return 0;
 }
 
 static void __exit wbsd_drv_exit(void)
 {
-       platform_device_unregister(&wbsd_device);
-       
-       driver_unregister(&wbsd_driver);
+#ifdef CONFIG_PNP
+
+       if (!nopnp)
+               pnp_unregister_driver(&wbsd_pnp_driver);
+
+#endif /* CONFIG_PNP */
+
+       if (nopnp) {
+               platform_device_unregister(wbsd_device);
+
+               platform_driver_unregister(&wbsd_driver);
+       }
 
        DBG("unloaded\n");
 }
 
 module_init(wbsd_drv_init);
 module_exit(wbsd_drv_exit);
+#ifdef CONFIG_PNP
+module_param(nopnp, uint, 0444);
+#endif
 module_param(io, uint, 0444);
 module_param(irq, uint, 0444);
 module_param(dma, int, 0444);
 
 MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>");
 MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver");
+MODULE_VERSION(DRIVER_VERSION);
 
+#ifdef CONFIG_PNP
+MODULE_PARM_DESC(nopnp, "Scan for device instead of relying on PNP. (default 0)");
+#endif
 MODULE_PARM_DESC(io, "I/O base to allocate. Must be 8 byte aligned. (default 0x248)");
 MODULE_PARM_DESC(irq, "IRQ to allocate. (default 6)");
 MODULE_PARM_DESC(dma, "DMA channel to allocate. -1 for no DMA. (default 2)");