|  | /* | 
|  | *  linux/drivers/mmc/wbsd.c - Winbond W83L51xD SD/MMC driver | 
|  | * | 
|  | *  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. | 
|  | * | 
|  | * | 
|  | * 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/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/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.4" | 
|  |  | 
|  | #ifdef CONFIG_MMC_DEBUG | 
|  | #define DBG(x...) \ | 
|  | printk(KERN_DEBUG DRIVER_NAME ": " x) | 
|  | #define DBGF(f, x...) \ | 
|  | printk(KERN_DEBUG DRIVER_NAME " [%s()]: " f, __func__ , ##x) | 
|  | #else | 
|  | #define DBG(x...)	do { } while (0) | 
|  | #define DBGF(x...)	do { } while (0) | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * 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_CCRC; | 
|  | ier |= WBSD_EINT_TIMEOUT; | 
|  | ier |= WBSD_EINT_CRC; | 
|  | 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 DRIVER_NAME ": Resetting chip\n"); | 
|  |  | 
|  | /* | 
|  | * 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_kmap_sg(struct wbsd_host* host) | 
|  | { | 
|  | 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_host* host) | 
|  | { | 
|  | kunmap_atomic(host->mapped_sg, KM_BIO_SRC_IRQ); | 
|  | } | 
|  |  | 
|  | static inline void wbsd_sg_to_dma(struct wbsd_host* host, struct mmc_data* data) | 
|  | { | 
|  | unsigned int len, i, size; | 
|  | 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++) | 
|  | { | 
|  | 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(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) | 
|  | { | 
|  | unsigned int len, i, size; | 
|  | 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++) | 
|  | { | 
|  | 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(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_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_MASK) != MMC_RSP_NONE) | 
|  | { | 
|  | /* | 
|  | * 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_MASK) == MMC_RSP_SHORT) | 
|  | wbsd_get_short_reply(host, cmd); | 
|  | else | 
|  | wbsd_get_long_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 (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 (!((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++; | 
|  |  | 
|  | /* | 
|  | * Transfer done? | 
|  | */ | 
|  | if (data->bytes_xfered == host->size) | 
|  | { | 
|  | wbsd_kunmap_sg(host); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * End of scatter list entry? | 
|  | */ | 
|  | if (host->remain == 0) | 
|  | { | 
|  | wbsd_kunmap_sg(host); | 
|  |  | 
|  | /* | 
|  | * Get next entry. Check if last. | 
|  | */ | 
|  | 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); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | 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_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 (data->bytes_xfered == host->size) | 
|  | return; | 
|  |  | 
|  | buffer = wbsd_kmap_sg(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++; | 
|  |  | 
|  | /* | 
|  | * Transfer done? | 
|  | */ | 
|  | if (data->bytes_xfered == host->size) | 
|  | { | 
|  | wbsd_kunmap_sg(host); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * End of scatter list entry? | 
|  | */ | 
|  | if (host->remain == 0) | 
|  | { | 
|  | wbsd_kunmap_sg(host); | 
|  |  | 
|  | /* | 
|  | * Get next entry. Check if last. | 
|  | */ | 
|  | 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); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | 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) | 
|  | { | 
|  | u16 blksize; | 
|  | u8 setup; | 
|  | unsigned long dmaflags; | 
|  |  | 
|  | DBGF("blksz %04x blks %04x flags %08x\n", | 
|  | 1 << data->blksz_bits, 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; | 
|  |  | 
|  | /* | 
|  | * 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 = (1 << data->blksz_bits) + 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 = (1 << data->blksz_bits) + 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(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 & ~0x40); | 
|  | else | 
|  | set_dma_mode(host->dma, DMA_MODE_WRITE & ~0x40); | 
|  | set_dma_addr(host->dma, host->dma_addr); | 
|  | set_dma_count(host->dma, host->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); | 
|  |  | 
|  | /* | 
|  | * Any leftover data? | 
|  | */ | 
|  | if (count) | 
|  | { | 
|  | printk(KERN_ERR DRIVER_NAME ": Incomplete DMA " | 
|  | "transfer. %d bytes left.\n", count); | 
|  |  | 
|  | 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); | 
|  |  | 
|  | data->bytes_xfered = host->size; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * 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; | 
|  |  | 
|  | DBGF("clock %uHz busmode %u powermode %u cs %u Vdd %u width %u\n", | 
|  | ios->clock, ios->bus_mode, ios->power_mode, ios->chip_select, | 
|  | ios->vdd, ios->bus_width); | 
|  |  | 
|  | 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 | 
|  | { | 
|  | 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 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 DRIVER_NAME | 
|  | ": Card removed during transfer!\n"); | 
|  | 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->size == data->bytes_xfered) | 
|  | { | 
|  | 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); | 
|  | } | 
|  |  | 
|  | static void wbsd_tasklet_block(unsigned long param) | 
|  | { | 
|  | 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) | 
|  | { | 
|  | 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); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Interrupt handling | 
|  | */ | 
|  |  | 
|  | static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs) | 
|  | { | 
|  | 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_BUSYEND) | 
|  | tasklet_hi_schedule(&host->block_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; | 
|  |  | 
|  | 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++) | 
|  | { | 
|  | if (!request_region(config_ports[i], 2, DRIVER_NAME)) | 
|  | continue; | 
|  |  | 
|  | for (j = 0;j < sizeof(unlock_codes)/sizeof(int);j++) | 
|  | { | 
|  | id = 0xFFFF; | 
|  |  | 
|  | outb(unlock_codes[j], config_ports[i]); | 
|  | outb(unlock_codes[j], config_ports[i]); | 
|  |  | 
|  | 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]) | 
|  | { | 
|  | host->chip_id = id; | 
|  | host->config = config_ports[i]; | 
|  | host->unlock_code = unlock_codes[i]; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Allocate/free io port ranges | 
|  | */ | 
|  |  | 
|  | static int __devinit wbsd_request_region(struct wbsd_host* host, int base) | 
|  | { | 
|  | if (io & 0x7) | 
|  | return -EINVAL; | 
|  |  | 
|  | if (!request_region(base, 8, DRIVER_NAME)) | 
|  | return -EIO; | 
|  |  | 
|  | host->base = io; | 
|  |  | 
|  | 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(host->mmc->dev, 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(host->mmc->dev, 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(host->mmc->dev, host->dma_addr, WBSD_DMA_SIZE, | 
|  | DMA_BIDIRECTIONAL); | 
|  | if (host->dma_buffer) | 
|  | 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, SA_SHIRQ, 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); | 
|  | 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_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 __devinit wbsd_chip_config(struct wbsd_host* 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); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Check that configured resources are correct. | 
|  | */ | 
|  |  | 
|  | static int __devinit wbsd_chip_validate(struct wbsd_host* host) | 
|  | { | 
|  | int base, irq, dma; | 
|  |  | 
|  | /* | 
|  | * 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); | 
|  |  | 
|  | /* | 
|  | * 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; | 
|  | } | 
|  |  | 
|  | /*****************************************************************************\ | 
|  | *                                                                           * | 
|  | * 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, io, irq, dma); | 
|  | if (ret) | 
|  | { | 
|  | wbsd_release_resources(host); | 
|  | wbsd_free_mmc(dev); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * See if chip needs to be configured. | 
|  | */ | 
|  | if (pnp && (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); | 
|  | } | 
|  | } | 
|  | else | 
|  | wbsd_chip_config(host); | 
|  |  | 
|  | /* | 
|  | * Power Management stuff. No idea how this works. | 
|  | * Not tested. | 
|  | */ | 
|  | #ifdef CONFIG_PM | 
|  | if (host->config) | 
|  | wbsd_write_config(host, WBSD_CONF_PME, 0xA0); | 
|  | #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); | 
|  |  | 
|  | if (!pnp) | 
|  | { | 
|  | /* | 
|  | * 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); | 
|  | } | 
|  |  | 
|  | wbsd_release_resources(host); | 
|  |  | 
|  | wbsd_free_mmc(dev); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Non-PnP | 
|  | */ | 
|  |  | 
|  | static int __devinit wbsd_probe(struct device* dev) | 
|  | { | 
|  | return wbsd_init(dev, io, irq, dma, 0); | 
|  | } | 
|  |  | 
|  | static int __devexit wbsd_remove(struct device* dev) | 
|  | { | 
|  | wbsd_shutdown(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 device *dev, pm_message_t state, u32 level) | 
|  | { | 
|  | DBGF("Not yet supported\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int wbsd_resume(struct device *dev, u32 level) | 
|  | { | 
|  | DBGF("Not yet supported\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | #else | 
|  | #define wbsd_suspend NULL | 
|  | #define wbsd_resume NULL | 
|  | #endif | 
|  |  | 
|  | static struct platform_device *wbsd_device; | 
|  |  | 
|  | static struct device_driver wbsd_driver = { | 
|  | .name		= DRIVER_NAME, | 
|  | .bus		= &platform_bus_type, | 
|  | .probe		= wbsd_probe, | 
|  | .remove		= wbsd_remove, | 
|  |  | 
|  | .suspend	= wbsd_suspend, | 
|  | .resume		= wbsd_resume, | 
|  | }; | 
|  |  | 
|  | #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, | 
|  | }; | 
|  |  | 
|  | #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, " | 
|  | DRIVER_VERSION "\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 = driver_register(&wbsd_driver); | 
|  | if (result < 0) | 
|  | return result; | 
|  |  | 
|  | wbsd_device = platform_device_register_simple(DRIVER_NAME, -1, | 
|  | NULL, 0); | 
|  | if (IS_ERR(wbsd_device)) | 
|  | return PTR_ERR(wbsd_device); | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | 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_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)"); |