v4.19.13 snapshot.
diff --git a/drivers/mmc/host/wbsd.c b/drivers/mmc/host/wbsd.c
new file mode 100644
index 0000000..1e54bbf
--- /dev/null
+++ b/drivers/mmc/host/wbsd.c
@@ -0,0 +1,2007 @@
+/*
+ *  linux/drivers/mmc/host/wbsd.c - Winbond W83L51xD SD/MMC driver
+ *
+ *  Copyright (C) 2004-2007 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 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/pnp.h>
+#include <linux/highmem.h>
+#include <linux/mmc/host.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+#include "wbsd.h"
+
+#define DRIVER_NAME "wbsd"
+
+#define DBG(x...) \
+	pr_debug(DRIVER_NAME ": " x)
+#define DBGF(f, 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 param_nopnp = 0;
+#else
+static const unsigned int param_nopnp = 1;
+#endif
+static unsigned int param_io = 0x248;
+static unsigned int param_irq = 6;
+static int param_dma = 2;
+
+/*
+ * Basic functions
+ */
+
+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_host *host)
+{
+	BUG_ON(host->config == 0);
+
+	outb(LOCK_CODE, host->config);
+}
+
+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_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_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_host *host, u8 index)
+{
+	outb(index, host->base + WBSD_IDXR);
+	return inb(host->base + WBSD_DATAR);
+}
+
+/*
+ * Common routines
+ */
+
+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.
+	 */
+	host->clk = wbsd_read_index(host, WBSD_IDX_CLK);
+
+	/*
+	 * 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.
+	 */
+	ier = 0;
+	ier |= WBSD_EINT_CARD;
+	ier |= WBSD_EINT_FIFO_THRE;
+	ier |= WBSD_EINT_CRC;
+	ier |= WBSD_EINT_TIMEOUT;
+	ier |= WBSD_EINT_TC;
+
+	outb(ier, host->base + WBSD_EIR);
+
+	/*
+	 * Clear interrupts.
+	 */
+	inb(host->base + WBSD_ISR);
+}
+
+static void wbsd_reset(struct wbsd_host *host)
+{
+	u8 setup;
+
+	pr_err("%s: Resetting chip\n", mmc_hostname(host->mmc));
+
+	/*
+	 * Soft reset of chip (SD/MMC part).
+	 */
+	setup = wbsd_read_index(host, WBSD_IDX_SETUP);
+	setup |= WBSD_SOFT_RESET;
+	wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+}
+
+static void wbsd_request_end(struct wbsd_host *host, struct mmc_request *mrq)
+{
+	unsigned long dmaflags;
+
+	if (host->dma >= 0) {
+		/*
+		 * Release ISA DMA controller.
+		 */
+		dmaflags = claim_dma_lock();
+		disable_dma(host->dma);
+		clear_dma_ff(host->dma);
+		release_dma_lock(dmaflags);
+
+		/*
+		 * Disable DMA on host.
+		 */
+		wbsd_write_index(host, WBSD_IDX_DMA, 0);
+	}
+
+	host->mrq = NULL;
+
+	/*
+	 * MMC layer might call back into the driver so first unlock.
+	 */
+	spin_unlock(&host->lock);
+	mmc_request_done(host->mmc, mrq);
+	spin_lock(&host->lock);
+}
+
+/*
+ * Scatter/gather functions
+ */
+
+static inline void wbsd_init_sg(struct wbsd_host *host, struct mmc_data *data)
+{
+	/*
+	 * Get info. about SG list from data structure.
+	 */
+	host->cur_sg = data->sg;
+	host->num_sg = data->sg_len;
+
+	host->offset = 0;
+	host->remain = host->cur_sg->length;
+}
+
+static inline int wbsd_next_sg(struct wbsd_host *host)
+{
+	/*
+	 * Skip to next SG entry.
+	 */
+	host->cur_sg++;
+	host->num_sg--;
+
+	/*
+	 * Any entries left?
+	 */
+	if (host->num_sg > 0) {
+		host->offset = 0;
+		host->remain = host->cur_sg->length;
+	}
+
+	return host->num_sg;
+}
+
+static inline char *wbsd_map_sg(struct wbsd_host *host)
+{
+	return kmap_atomic(sg_page(host->cur_sg)) + host->cur_sg->offset;
+}
+
+static inline void wbsd_sg_to_dma(struct wbsd_host *host, struct mmc_data *data)
+{
+	size_t len = 0;
+	int i;
+
+	for (i = 0; i < data->sg_len; i++)
+		len += data->sg[i].length;
+	sg_copy_to_buffer(data->sg, data->sg_len, host->dma_buffer, len);
+}
+
+static inline void wbsd_dma_to_sg(struct wbsd_host *host, struct mmc_data *data)
+{
+	size_t len = 0;
+	int i;
+
+	for (i = 0; i < data->sg_len; i++)
+		len += data->sg[i].length;
+	sg_copy_from_buffer(data->sg, data->sg_len, host->dma_buffer, len);
+}
+
+/*
+ * Command handling
+ */
+
+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) {
+		cmd->error = -EILSEQ;
+		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;
+}
+
+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) {
+		cmd->error = -EILSEQ;
+		return;
+	}
+
+	for (i = 0; i < 4; i++) {
+		cmd->resp[i] =
+			wbsd_read_index(host, WBSD_IDX_RESP1 + i * 4) << 24;
+		cmd->resp[i] |=
+			wbsd_read_index(host, WBSD_IDX_RESP2 + i * 4) << 16;
+		cmd->resp[i] |=
+			wbsd_read_index(host, WBSD_IDX_RESP3 + i * 4) << 8;
+		cmd->resp[i] |=
+			wbsd_read_index(host, WBSD_IDX_RESP4 + i * 4) << 0;
+	}
+}
+
+static void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd)
+{
+	int i;
+	u8 status, isr;
+
+	/*
+	 * Clear accumulated ISR. The interrupt routine
+	 * will fill this one with events that occur during
+	 * transfer.
+	 */
+	host->isr = 0;
+
+	/*
+	 * Send the command (CRC calculated by host).
+	 */
+	outb(cmd->opcode, host->base + WBSD_CMDR);
+	for (i = 3; i >= 0; i--)
+		outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR);
+
+	cmd->error = 0;
+
+	/*
+	 * Wait for the request to complete.
+	 */
+	do {
+		status = wbsd_read_index(host, WBSD_IDX_STATUS);
+	} while (status & WBSD_CARDTRAFFIC);
+
+	/*
+	 * Do we expect a reply?
+	 */
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		/*
+		 * Read back status.
+		 */
+		isr = host->isr;
+
+		/* Card removed? */
+		if (isr & WBSD_INT_CARD)
+			cmd->error = -ENOMEDIUM;
+		/* Timeout? */
+		else if (isr & WBSD_INT_TIMEOUT)
+			cmd->error = -ETIMEDOUT;
+		/* CRC? */
+		else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC))
+			cmd->error = -EILSEQ;
+		/* All ok */
+		else {
+			if (cmd->flags & MMC_RSP_136)
+				wbsd_get_long_reply(host, cmd);
+			else
+				wbsd_get_short_reply(host, cmd);
+		}
+	}
+}
+
+/*
+ * Data functions
+ */
+
+static void wbsd_empty_fifo(struct wbsd_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	char *buffer;
+	int i, idx, fsr, fifo;
+
+	/*
+	 * Handle excessive data.
+	 */
+	if (host->num_sg == 0)
+		return;
+
+	buffer = wbsd_map_sg(host) + host->offset;
+	idx = 0;
+
+	/*
+	 * Drain the fifo. This has a tendency to loop longer
+	 * than the FIFO length (usually one block).
+	 */
+	while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_EMPTY)) {
+		/*
+		 * The size field in the FSR is broken so we have to
+		 * do some guessing.
+		 */
+		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[idx++] = inb(host->base + WBSD_DFR);
+			host->offset++;
+			host->remain--;
+
+			data->bytes_xfered++;
+
+			/*
+			 * End of scatter list entry?
+			 */
+			if (host->remain == 0) {
+				kunmap_atomic(buffer);
+				/*
+				 * Get next entry. Check if last.
+				 */
+				if (!wbsd_next_sg(host))
+					return;
+
+				buffer = wbsd_map_sg(host);
+				idx = 0;
+			}
+		}
+	}
+	kunmap_atomic(buffer);
+
+	/*
+	 * This is a very dirty hack to solve a
+	 * hardware problem. The chip doesn't trigger
+	 * FIFO threshold interrupts properly.
+	 */
+	if ((data->blocks * data->blksz - data->bytes_xfered) < 16)
+		tasklet_schedule(&host->fifo_tasklet);
+}
+
+static void wbsd_fill_fifo(struct wbsd_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	char *buffer;
+	int i, idx, fsr, fifo;
+
+	/*
+	 * Check that we aren't being called after the
+	 * entire buffer has been transferred.
+	 */
+	if (host->num_sg == 0)
+		return;
+
+	buffer = wbsd_map_sg(host) + host->offset;
+	idx = 0;
+
+	/*
+	 * Fill the fifo. This has a tendency to loop longer
+	 * than the FIFO length (usually one block).
+	 */
+	while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_FULL)) {
+		/*
+		 * The size field in the FSR is broken so we have to
+		 * do some guessing.
+		 */
+		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[idx], host->base + WBSD_DFR);
+			host->offset++;
+			host->remain--;
+
+			data->bytes_xfered++;
+
+			/*
+			 * End of scatter list entry?
+			 */
+			if (host->remain == 0) {
+				kunmap_atomic(buffer);
+				/*
+				 * Get next entry. Check if last.
+				 */
+				if (!wbsd_next_sg(host))
+					return;
+
+				buffer = wbsd_map_sg(host);
+				idx = 0;
+			}
+		}
+	}
+	kunmap_atomic(buffer);
+
+	/*
+	 * 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)
+{
+	u16 blksize;
+	u8 setup;
+	unsigned long dmaflags;
+	unsigned int size;
+
+	/*
+	 * Calculate size.
+	 */
+	size = data->blocks * data->blksz;
+
+	/*
+	 * Check timeout values for overflow.
+	 * (Yes, some cards cause this value to overflow).
+	 */
+	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);
+	}
+
+	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.
+	 */
+	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 = -EINVAL;
+		return;
+	}
+
+	/*
+	 * Clear the FIFO. This is needed even for DMA
+	 * transfers since the chip still uses the FIFO
+	 * internally.
+	 */
+	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) {
+		/*
+		 * The buffer for DMA is only 64 kB.
+		 */
+		BUG_ON(size > 0x10000);
+		if (size > 0x10000) {
+			data->error = -EINVAL;
+			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 & ~0x40);
+		else
+			set_dma_mode(host->dma, DMA_MODE_WRITE & ~0x40);
+		set_dma_addr(host->dma, host->dma_addr);
+		set_dma_count(host->dma, size);
+
+		enable_dma(host->dma);
+		release_dma_lock(dmaflags);
+
+		/*
+		 * Enable DMA on the host.
+		 */
+		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) {
+			wbsd_write_index(host, WBSD_IDX_FIFOEN,
+				WBSD_FIFOEN_FULL | 8);
+		} else {
+			wbsd_write_index(host, WBSD_IDX_FIFOEN,
+				WBSD_FIFOEN_EMPTY | 8);
+			wbsd_fill_fifo(host);
+		}
+	}
+
+	data->error = 0;
+}
+
+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);
+
+	/*
+	 * Send a stop command if needed.
+	 */
+	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) {
+		/*
+		 * Disable DMA on the host.
+		 */
+		wbsd_write_index(host, WBSD_IDX_DMA, 0);
+
+		/*
+		 * Turn of ISA DMA controller.
+		 */
+		dmaflags = claim_dma_lock();
+		disable_dma(host->dma);
+		clear_dma_ff(host->dma);
+		count = get_dma_residue(host->dma);
+		release_dma_lock(dmaflags);
+
+		data->bytes_xfered = host->mrq->data->blocks *
+			host->mrq->data->blksz - count;
+		data->bytes_xfered -= data->bytes_xfered % data->blksz;
+
+		/*
+		 * Any leftover data?
+		 */
+		if (count) {
+			pr_err("%s: Incomplete DMA transfer. "
+				"%d bytes left.\n",
+				mmc_hostname(host->mmc), count);
+
+			if (!data->error)
+				data->error = -EIO;
+		} else {
+			/*
+			 * Transfer data from DMA buffer to
+			 * SG list.
+			 */
+			if (data->flags & MMC_DATA_READ)
+				wbsd_dma_to_sg(host, data);
+		}
+
+		if (data->error) {
+			if (data->bytes_xfered)
+				data->bytes_xfered -= data->blksz;
+		}
+	}
+
+	wbsd_request_end(host, host->mrq);
+}
+
+/*****************************************************************************\
+ *                                                                           *
+ * MMC layer callbacks                                                       *
+ *                                                                           *
+\*****************************************************************************/
+
+static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct wbsd_host *host = mmc_priv(mmc);
+	struct mmc_command *cmd;
+
+	/*
+	 * Disable tasklets to avoid a deadlock.
+	 */
+	spin_lock_bh(&host->lock);
+
+	BUG_ON(host->mrq != NULL);
+
+	cmd = mrq->cmd;
+
+	host->mrq = mrq;
+
+	/*
+	 * Check that there is actually a card in the slot.
+	 */
+	if (!(host->flags & WBSD_FCARD_PRESENT)) {
+		cmd->error = -ENOMEDIUM;
+		goto done;
+	}
+
+	if (cmd->data) {
+		/*
+		 * The hardware is so delightfully stupid that it has a list
+		 * of "data" commands. If a command isn't on this list, it'll
+		 * just go back to the idle state and won't send any data
+		 * interrupts.
+		 */
+		switch (cmd->opcode) {
+		case 11:
+		case 17:
+		case 18:
+		case 20:
+		case 24:
+		case 25:
+		case 26:
+		case 27:
+		case 30:
+		case 42:
+		case 56:
+			break;
+
+		/* ACMDs. We don't keep track of state, so we just treat them
+		 * like any other command. */
+		case 51:
+			break;
+
+		default:
+			pr_warn("%s: Data command %d is not supported by this controller\n",
+				mmc_hostname(host->mmc), cmd->opcode);
+			cmd->error = -EINVAL;
+
+			goto done;
+		}
+	}
+
+	/*
+	 * Does the request include data?
+	 */
+	if (cmd->data) {
+		wbsd_prepare_data(host, cmd->data);
+
+		if (cmd->data->error)
+			goto done;
+	}
+
+	wbsd_send_command(host, cmd);
+
+	/*
+	 * If this is a data transfer the request
+	 * will be finished after the data has
+	 * transferred.
+	 */
+	if (cmd->data && !cmd->error) {
+		/*
+		 * 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)
+{
+	struct wbsd_host *host = mmc_priv(mmc);
+	u8 clk, setup, pwr;
+
+	spin_lock_bh(&host->lock);
+
+	/*
+	 * Reset the chip on each power off.
+	 * Should clear out any weird states.
+	 */
+	if (ios->power_mode == MMC_POWER_OFF)
+		wbsd_init_device(host);
+
+	if (ios->clock >= 24000000)
+		clk = WBSD_CLK_24M;
+	else if (ios->clock >= 16000000)
+		clk = WBSD_CLK_16M;
+	else if (ios->clock >= 12000000)
+		clk = WBSD_CLK_12M;
+	else
+		clk = WBSD_CLK_375K;
+
+	/*
+	 * Only write to the clock register when
+	 * there is an actual change.
+	 */
+	if (clk != host->clk) {
+		wbsd_write_index(host, WBSD_IDX_CLK, clk);
+		host->clk = clk;
+	}
+
+	/*
+	 * 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);
+	}
+
+	/*
+	 * 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;
+
+			/*
+			 * We cannot resume card detection immediately
+			 * 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(struct timer_list *t)
+{
+	struct wbsd_host *host = from_timer(host, t, ignore_timer);
+
+	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);
+}
+
+/*
+ * Tasklets
+ */
+
+static inline struct mmc_data *wbsd_get_data(struct wbsd_host *host)
+{
+	WARN_ON(!host->mrq);
+	if (!host->mrq)
+		return NULL;
+
+	WARN_ON(!host->mrq->cmd);
+	if (!host->mrq->cmd)
+		return NULL;
+
+	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;
+	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) {
+		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");
+		host->flags &= ~WBSD_FCARD_PRESENT;
+
+		if (host->mrq) {
+			pr_err("%s: Card removed during transfer!\n",
+				mmc_hostname(host->mmc));
+			wbsd_reset(host);
+
+			host->mrq->cmd->error = -ENOMEDIUM;
+			tasklet_schedule(&host->finish_tasklet);
+		}
+
+		delay = 0;
+	}
+
+	/*
+	 * Unlock first since we might get a call back.
+	 */
+
+	spin_unlock(&host->lock);
+
+	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_data *data;
+
+	spin_lock(&host->lock);
+
+	if (!host->mrq)
+		goto end;
+
+	data = wbsd_get_data(host);
+	if (!data)
+		goto end;
+
+	if (data->flags & MMC_DATA_WRITE)
+		wbsd_fill_fifo(host);
+	else
+		wbsd_empty_fifo(host);
+
+	/*
+	 * Done?
+	 */
+	if (host->num_sg == 0) {
+		wbsd_write_index(host, WBSD_IDX_FIFOEN, 0);
+		tasklet_schedule(&host->finish_tasklet);
+	}
+
+end:
+	spin_unlock(&host->lock);
+}
+
+static void wbsd_tasklet_crc(unsigned long param)
+{
+	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;
+
+	DBGF("CRC error\n");
+
+	data->error = -EILSEQ;
+
+	tasklet_schedule(&host->finish_tasklet);
+
+end:
+	spin_unlock(&host->lock);
+}
+
+static void wbsd_tasklet_timeout(unsigned long param)
+{
+	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;
+
+	DBGF("Timeout\n");
+
+	data->error = -ETIMEDOUT;
+
+	tasklet_schedule(&host->finish_tasklet);
+
+end:
+	spin_unlock(&host->lock);
+}
+
+static void wbsd_tasklet_finish(unsigned long param)
+{
+	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:
+	spin_unlock(&host->lock);
+}
+
+/*
+ * Interrupt handling
+ */
+
+static irqreturn_t wbsd_irq(int irq, void *dev_id)
+{
+	struct wbsd_host *host = dev_id;
+	int isr;
+
+	isr = inb(host->base + WBSD_ISR);
+
+	/*
+	 * Was it actually our hardware that caused the interrupt?
+	 */
+	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_schedule(&host->fifo_tasklet);
+	if (isr & WBSD_INT_CRC)
+		tasklet_hi_schedule(&host->crc_tasklet);
+	if (isr & WBSD_INT_TIMEOUT)
+		tasklet_hi_schedule(&host->timeout_tasklet);
+	if (isr & WBSD_INT_TC)
+		tasklet_schedule(&host->finish_tasklet);
+
+	return IRQ_HANDLED;
+}
+
+/*****************************************************************************\
+ *                                                                           *
+ * Device initialisation and shutdown                                        *
+ *                                                                           *
+\*****************************************************************************/
+
+/*
+ * Allocate/free MMC structure.
+ */
+
+static int 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;
+
+	spin_lock_init(&host->lock);
+
+	/*
+	 * Set up timers
+	 */
+	timer_setup(&host->ignore_timer, wbsd_reset_ignore, 0);
+
+	/*
+	 * Maximum number of segments. Worst case is one sector per segment
+	 * so this will be 64kB/512.
+	 */
+	mmc->max_segs = 128;
+
+	/*
+	 * Maximum request size. Also limited by 64KiB buffer.
+	 */
+	mmc->max_req_size = 65536;
+
+	/*
+	 * Maximum segment size. Could be one segment with the maximum number
+	 * of bytes.
+	 */
+	mmc->max_seg_size = mmc->max_req_size;
+
+	/*
+	 * Maximum block size. We have 12 bits (= 4095) but have to subtract
+	 * space for CRC. So the maximum is 4095 - 4*2 = 4087.
+	 */
+	mmc->max_blk_size = 4087;
+
+	/*
+	 * Maximum block count. There is no real limit so the maximum
+	 * request size will be the only restriction.
+	 */
+	mmc->max_blk_count = mmc->max_req_size;
+
+	dev_set_drvdata(dev, mmc);
+
+	return 0;
+}
+
+static void 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 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 < ARRAY_SIZE(config_ports); i++) {
+		if (!request_region(config_ports[i], 2, DRIVER_NAME))
+			continue;
+
+		for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) {
+			id = 0xFFFF;
+
+			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);
+
+			wbsd_lock_config(host);
+
+			for (k = 0; k < ARRAY_SIZE(valid_ids); k++) {
+				if (id == valid_ids[k]) {
+					host->chip_id = id;
+
+					return 0;
+				}
+			}
+
+			if (id != 0xFFFF) {
+				DBG("Unknown hardware (id %x) found at %x\n",
+					id, config_ports[i]);
+			}
+		}
+
+		release_region(config_ports[i], 2);
+	}
+
+	host->config = 0;
+	host->unlock_code = 0;
+
+	return -ENODEV;
+}
+
+/*
+ * Allocate/free io port ranges
+ */
+
+static int wbsd_request_region(struct wbsd_host *host, int base)
+{
+	if (base & 0x7)
+		return -EINVAL;
+
+	if (!request_region(base, 8, DRIVER_NAME))
+		return -EIO;
+
+	host->base = base;
+
+	return 0;
+}
+
+static void 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;
+}
+
+/*
+ * Allocate/free DMA port and buffer
+ */
+
+static void wbsd_request_dma(struct wbsd_host *host, int dma)
+{
+	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(WBSD_DMA_SIZE,
+		GFP_NOIO | GFP_DMA | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
+	if (!host->dma_buffer)
+		goto free;
+
+	/*
+	 * Translate the address to a physical address.
+	 */
+	host->dma_addr = dma_map_single(mmc_dev(host->mmc), host->dma_buffer,
+		WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
+	if (dma_mapping_error(mmc_dev(host->mmc), host->dma_addr))
+		goto kfree;
+
+	/*
+	 * ISA DMA must be aligned on a 64k basis.
+	 */
+	if ((host->dma_addr & 0xffff) != 0)
+		goto unmap;
+	/*
+	 * ISA cannot access memory above 16 MB.
+	 */
+	else if (host->dma_addr >= 0x1000000)
+		goto unmap;
+
+	host->dma = dma;
+
+	return;
+
+unmap:
+	/*
+	 * 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 = 0;
+
+kfree:
+	kfree(host->dma_buffer);
+	host->dma_buffer = NULL;
+
+free:
+	free_dma(dma);
+
+err:
+	pr_warn(DRIVER_NAME ": Unable to allocate DMA %d - falling back on FIFO\n",
+		dma);
+}
+
+static void wbsd_release_dma(struct wbsd_host *host)
+{
+	/*
+	 * host->dma_addr is valid here iff host->dma_buffer is not NULL.
+	 */
+	if (host->dma_buffer) {
+		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 = 0;
+}
+
+/*
+ * Allocate/free IRQ.
+ */
+
+static int wbsd_request_irq(struct wbsd_host *host, int irq)
+{
+	int ret;
+
+	/*
+	 * Set up tasklets. Must be done before requesting interrupt.
+	 */
+	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);
+
+	/*
+	 * Allocate interrupt.
+	 */
+	ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host);
+	if (ret)
+		return ret;
+
+	host->irq = irq;
+
+	return 0;
+}
+
+static void  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);
+}
+
+/*
+ * Allocate all resources for the host.
+ */
+
+static int wbsd_request_resources(struct wbsd_host *host,
+	int base, int irq, int dma)
+{
+	int ret;
+
+	/*
+	 * Allocate I/O ports.
+	 */
+	ret = wbsd_request_region(host, base);
+	if (ret)
+		return ret;
+
+	/*
+	 * Allocate interrupt.
+	 */
+	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 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, WBSD_PINS_DETECT_GP11);
+
+	/*
+	 * Configure chip
+	 */
+	wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8);
+	wbsd_write_config(host, WBSD_CONF_PORT_LO, host->base & 0xff);
+
+	wbsd_write_config(host, WBSD_CONF_IRQ, host->irq);
+
+	if (host->dma >= 0)
+		wbsd_write_config(host, WBSD_CONF_DRQ, host->dma);
+
+	/*
+	 * Enable and power up chip.
+	 */
+	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);
+
+	/*
+	 * Select SD/MMC function.
+	 */
+	wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
+
+	/*
+	 * Read configuration.
+	 */
+	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);
+
+	/*
+	 * Validate against given configuration.
+	 */
+	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 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);
+
+	/*
+	 * Scan for hardware.
+	 */
+	ret = wbsd_scan(host);
+	if (ret) {
+		if (pnp && (ret == -ENODEV)) {
+			pr_warn(DRIVER_NAME ": Unable to confirm device presence - you may experience lock-ups\n");
+		} else {
+			wbsd_free_mmc(dev);
+			return ret;
+		}
+	}
+
+	/*
+	 * Request resources.
+	 */
+	ret = wbsd_request_resources(host, base, irq, dma);
+	if (ret) {
+		wbsd_release_resources(host);
+		wbsd_free_mmc(dev);
+		return ret;
+	}
+
+	/*
+	 * See if chip needs to be configured.
+	 */
+	if (pnp) {
+		if ((host->config != 0) && !wbsd_chip_validate(host)) {
+			pr_warn(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
+	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);
+
+	mmc_add_host(mmc);
+
+	pr_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;
+}
+
+static void wbsd_shutdown(struct device *dev, int pnp)
+{
+	struct mmc_host *mmc = dev_get_drvdata(dev);
+	struct wbsd_host *host;
+
+	if (!mmc)
+		return;
+
+	host = mmc_priv(mmc);
+
+	mmc_remove_host(mmc);
+
+	/*
+	 * Power down the SD/MMC function.
+	 */
+	if (!pnp)
+		wbsd_chip_poweroff(host);
+
+	wbsd_release_resources(host);
+
+	wbsd_free_mmc(dev);
+}
+
+/*
+ * Non-PnP
+ */
+
+static int wbsd_probe(struct platform_device *dev)
+{
+	/* Use the module parameters for resources */
+	return wbsd_init(&dev->dev, param_io, param_irq, param_dma, 0);
+}
+
+static int wbsd_remove(struct platform_device *dev)
+{
+	wbsd_shutdown(&dev->dev, 0);
+
+	return 0;
+}
+
+/*
+ * PnP
+ */
+
+#ifdef CONFIG_PNP
+
+static int
+wbsd_pnp_probe(struct pnp_dev *pnpdev, const struct pnp_device_id *dev_id)
+{
+	int io, irq, dma;
+
+	/*
+	 * Get resources from PnP layer.
+	 */
+	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;
+
+	DBGF("PnP resources: port %3x irq %d dma %d\n", io, irq, dma);
+
+	return wbsd_init(&pnpdev->dev, io, irq, dma, 1);
+}
+
+static void wbsd_pnp_remove(struct pnp_dev *dev)
+{
+	wbsd_shutdown(&dev->dev, 1);
+}
+
+#endif /* CONFIG_PNP */
+
+/*
+ * Power management
+ */
+
+#ifdef CONFIG_PM
+
+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;
+
+	if (mmc == NULL)
+		return 0;
+
+	DBGF("Suspending...\n");
+
+	host = mmc_priv(mmc);
+
+	wbsd_chip_poweroff(host);
+	return 0;
+}
+
+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);
+
+	wbsd_init_device(host);
+	return 0;
+}
+
+#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);
+
+	if (mmc == NULL)
+		return 0;
+
+	DBGF("Suspending...\n");
+	return 0;
+}
+
+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)) {
+			pr_warn(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);
+
+	wbsd_init_device(host);
+	return 0;
+}
+
+#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		= wbsd_remove,
+
+	.suspend	= wbsd_platform_suspend,
+	.resume		= wbsd_platform_resume,
+	.driver		= {
+		.name	= DRIVER_NAME,
+	},
+};
+
+#ifdef CONFIG_PNP
+
+static struct pnp_driver wbsd_pnp_driver = {
+	.name		= DRIVER_NAME,
+	.id_table	= pnp_dev_table,
+	.probe		= wbsd_pnp_probe,
+	.remove		= wbsd_pnp_remove,
+
+	.suspend	= wbsd_pnp_suspend,
+	.resume		= wbsd_pnp_resume,
+};
+
+#endif /* CONFIG_PNP */
+
+/*
+ * Module loading/unloading
+ */
+
+static int __init wbsd_drv_init(void)
+{
+	int result;
+
+	pr_info(DRIVER_NAME
+		": Winbond W83L51xD SD/MMC card interface driver\n");
+	pr_info(DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
+
+#ifdef CONFIG_PNP
+
+	if (!param_nopnp) {
+		result = pnp_register_driver(&wbsd_pnp_driver);
+		if (result < 0)
+			return result;
+	}
+#endif /* CONFIG_PNP */
+
+	if (param_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)
+{
+#ifdef CONFIG_PNP
+
+	if (!param_nopnp)
+		pnp_unregister_driver(&wbsd_pnp_driver);
+
+#endif /* CONFIG_PNP */
+
+	if (param_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_hw_named(nopnp, param_nopnp, uint, other, 0444);
+#endif
+module_param_hw_named(io, param_io, uint, ioport, 0444);
+module_param_hw_named(irq, param_irq, uint, irq, 0444);
+module_param_hw_named(dma, param_dma, int, dma, 0444);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pierre Ossman <pierre@ossman.eu>");
+MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver");
+
+#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)");