diff options
Diffstat (limited to 'drivers/mmc/host/wbsd.c')
| -rw-r--r-- | drivers/mmc/host/wbsd.c | 2061 | 
1 files changed, 2061 insertions, 0 deletions
diff --git a/drivers/mmc/host/wbsd.c b/drivers/mmc/host/wbsd.c new file mode 100644 index 00000000000..867ca6a6929 --- /dev/null +++ b/drivers/mmc/host/wbsd.c @@ -0,0 +1,2061 @@ +/* + *  linux/drivers/mmc/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 <asm/io.h> +#include <asm/dma.h> +#include <asm/scatterlist.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 nopnp = 0; +#else +static const unsigned int nopnp = 1; +#endif +static unsigned int io = 0x248; +static unsigned int irq = 6; +static int 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; + +	printk(KERN_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; + +	DBGF("Ending request, cmd (%x)\n", mrq->cmd->opcode); + +	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_sg_to_buffer(struct wbsd_host *host) +{ +	return page_address(host->cur_sg->page) + host->cur_sg->offset; +} + +static inline void wbsd_sg_to_dma(struct wbsd_host *host, struct mmc_data *data) +{ +	unsigned int len, i; +	struct scatterlist *sg; +	char *dmabuf = host->dma_buffer; +	char *sgbuf; + +	sg = data->sg; +	len = data->sg_len; + +	for (i = 0; i < len; i++) { +		sgbuf = page_address(sg[i].page) + sg[i].offset; +		memcpy(dmabuf, sgbuf, sg[i].length); +		dmabuf += sg[i].length; +	} +} + +static inline void wbsd_dma_to_sg(struct wbsd_host *host, struct mmc_data *data) +{ +	unsigned int len, i; +	struct scatterlist *sg; +	char *dmabuf = host->dma_buffer; +	char *sgbuf; + +	sg = data->sg; +	len = data->sg_len; + +	for (i = 0; i < len; i++) { +		sgbuf = page_address(sg[i].page) + sg[i].offset; +		memcpy(sgbuf, dmabuf, sg[i].length); +		dmabuf += sg[i].length; +	} +} + +/* + * 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 = 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; +} + +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 = MMC_ERR_INVALID; +		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; + +	DBGF("Sending cmd (%x)\n", cmd->opcode); + +	/* +	 * 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 = MMC_ERR_NONE; + +	/* +	 * 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 = MMC_ERR_TIMEOUT; +		/* Timeout? */ +		else if (isr & WBSD_INT_TIMEOUT) +			cmd->error = MMC_ERR_TIMEOUT; +		/* CRC? */ +		else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC)) +			cmd->error = MMC_ERR_BADCRC; +		/* All ok */ +		else { +			if (cmd->flags & MMC_RSP_136) +				wbsd_get_long_reply(host, cmd); +			else +				wbsd_get_short_reply(host, cmd); +		} +	} + +	DBGF("Sent cmd (%x), res %d\n", cmd->opcode, cmd->error); +} + +/* + * Data functions + */ + +static void wbsd_empty_fifo(struct wbsd_host *host) +{ +	struct mmc_data *data = host->mrq->cmd->data; +	char *buffer; +	int i, fsr, fifo; + +	/* +	 * Handle excessive data. +	 */ +	if (host->num_sg == 0) +		return; + +	buffer = wbsd_sg_to_buffer(host) + host->offset; + +	/* +	 * 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 = inb(host->base + WBSD_DFR); +			buffer++; +			host->offset++; +			host->remain--; + +			data->bytes_xfered++; + +			/* +			 * End of scatter list entry? +			 */ +			if (host->remain == 0) { +				/* +				 * Get next entry. Check if last. +				 */ +				if (!wbsd_next_sg(host)) +					return; + +				buffer = wbsd_sg_to_buffer(host); +			} +		} +	} + +	/* +	 * 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, fsr, fifo; + +	/* +	 * Check that we aren't being called after the +	 * entire buffer has been transfered. +	 */ +	if (host->num_sg == 0) +		return; + +	buffer = wbsd_sg_to_buffer(host) + host->offset; + +	/* +	 * 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, host->base + WBSD_DFR); +			buffer++; +			host->offset++; +			host->remain--; + +			data->bytes_xfered++; + +			/* +			 * End of scatter list entry? +			 */ +			if (host->remain == 0) { +				/* +				 * Get next entry. Check if last. +				 */ +				if (!wbsd_next_sg(host)) +					return; + +				buffer = wbsd_sg_to_buffer(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) +{ +	u16 blksize; +	u8 setup; +	unsigned long dmaflags; +	unsigned int size; + +	DBGF("blksz %04x blks %04x flags %08x\n", +		data->blksz, data->blocks, data->flags); +	DBGF("tsac %d ms nsac %d clk\n", +		data->timeout_ns / 1000000, data->timeout_clks); + +	/* +	 * 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 = MMC_ERR_INVALID; +		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 = 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 & ~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 = MMC_ERR_NONE; +} + +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) { +			printk(KERN_ERR "%s: Incomplete DMA transfer. " +				"%d bytes left.\n", +				mmc_hostname(host->mmc), count); + +			if (data->error == MMC_ERR_NONE) +				data->error = MMC_ERR_FAILED; +		} else { +			/* +			 * Transfer data from DMA buffer to +			 * SG list. +			 */ +			if (data->flags & MMC_DATA_READ) +				wbsd_dma_to_sg(host, data); +		} + +		if (data->error != MMC_ERR_NONE) { +			if (data->bytes_xfered) +				data->bytes_xfered -= data->blksz; +		} +	} + +	DBGF("Ending data transfer (%d bytes)\n", data->bytes_xfered); + +	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; + +	/* +	 * If there is no card in the slot then +	 * timeout immediatly. +	 */ +	if (!(host->flags & WBSD_FCARD_PRESENT)) { +		cmd->error = MMC_ERR_TIMEOUT; +		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: +#ifdef CONFIG_MMC_DEBUG +			printk(KERN_WARNING "%s: Data command %d is not " +				"supported by this controller.\n", +				mmc_hostname(host->mmc), cmd->opcode); +#endif +			cmd->error = MMC_ERR_INVALID; + +			goto done; +		}; +	} + +	/* +	 * Does the request include 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)) { +		/* +		 * 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 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); +} + +/* + * 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) { +			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); + +	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 = MMC_ERR_BADCRC; + +	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 = MMC_ERR_TIMEOUT; + +	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 __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 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 __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 < 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 __devinit 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 __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; +} + +/* + * Allocate/free DMA port and buffer + */ + +static void __devinit 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_REPEAT | __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); + +	/* +	 * ISA DMA must be aligned on a 64k basis. +	 */ +	if ((host->dma_addr & 0xffff) != 0) +		goto kfree; +	/* +	 * ISA cannot access memory above 16 MB. +	 */ +	else if (host->dma_addr >= 0x1000000) +		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; + +free: +	free_dma(dma); + +err: +	printk(KERN_WARNING DRIVER_NAME ": Unable to allocate DMA %d. " +		"Falling back on FIFO.\n", dma); +} + +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; +} + +/* + * Allocate/free IRQ. + */ + +static int __devinit wbsd_request_irq(struct wbsd_host *host, int irq) +{ +	int ret; + +	/* +	 * Allocate interrupt. +	 */ + +	ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host); +	if (ret) +		return ret; + +	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); + +	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); +} + +/* + * 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_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 __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, 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 __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); + +	/* +	 * Scan for hardware. +	 */ +	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; +		} +	} + +	/* +	 * 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)) { +			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 +	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); + +	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; +} + +static void __devexit 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 __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; + +	/* +	 * 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 __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 wbsd_host *host, pm_message_t state) +{ +	BUG_ON(host == NULL); + +	return mmc_suspend_host(host->mmc, state); +} + +static int wbsd_resume(struct wbsd_host *host) +{ +	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; +} + +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); +} + +#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, +	}, +}; + +#ifdef CONFIG_PNP + +static struct pnp_driver wbsd_pnp_driver = { +	.name		= DRIVER_NAME, +	.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 + */ + +static int __init wbsd_drv_init(void) +{ +	int result; + +	printk(KERN_INFO DRIVER_NAME +		": Winbond W83L51xD SD/MMC card interface driver\n"); +	printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n"); + +#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) +{ +#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"); + +#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)");  | 
