root/drivers/mmc/host/wbsd.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  linux/drivers/mmc/host/wbsd.c - Winbond W83L51xD SD/MMC driver
 *
 *  Copyright (C) 2004-2007 Pierre Ossman, All Rights Reserved.
 *
 * Warning!
 *
 * Changes to the FIFO system should be done with extreme care since
 * the hardware is full of bugs related to the FIFO. Known issues are:
 *
 * - FIFO size field in FSR is always zero.
 *
 * - FIFO interrupts tend not to work as they should. Interrupts are
 *   triggered only for full/empty events, not for threshold values.
 *
 * - On APIC systems the FIFO empty interrupt is sometimes lost.
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/pnp.h>
#include <linux/highmem.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sd.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>

#include <asm/io.h>
#include <asm/dma.h>

#include "wbsd.h"

#define DRIVER_NAME "wbsd"

#define DBG(x...) \
        pr_debug(DRIVER_NAME ": " x)
#define DBGF(f, x...) \
        pr_debug(DRIVER_NAME " [%s()]: " f, __func__ , ##x)

/*
 * Device resources
 */

#ifdef CONFIG_PNP

static const struct pnp_device_id pnp_dev_table[] = {
        { "WEC0517", 0 },
        { "WEC0518", 0 },
        { "", 0 },
};

MODULE_DEVICE_TABLE(pnp, pnp_dev_table);

#endif /* CONFIG_PNP */

static const int config_ports[] = { 0x2E, 0x4E };
static const int unlock_codes[] = { 0x83, 0x87 };

static const int valid_ids[] = {
        0x7112,
};

#ifdef CONFIG_PNP
static unsigned int param_nopnp = 0;
#else
static const unsigned int param_nopnp = 1;
#endif
static unsigned int param_io = 0x248;
static unsigned int param_irq = 6;
static int param_dma = 2;

/*
 * Basic functions
 */

static inline void wbsd_unlock_config(struct wbsd_host *host)
{
        BUG_ON(host->config == 0);

        outb(host->unlock_code, host->config);
        outb(host->unlock_code, host->config);
}

static inline void wbsd_lock_config(struct wbsd_host *host)
{
        BUG_ON(host->config == 0);

        outb(LOCK_CODE, host->config);
}

static inline void wbsd_write_config(struct wbsd_host *host, u8 reg, u8 value)
{
        BUG_ON(host->config == 0);

        outb(reg, host->config);
        outb(value, host->config + 1);
}

static inline u8 wbsd_read_config(struct wbsd_host *host, u8 reg)
{
        BUG_ON(host->config == 0);

        outb(reg, host->config);
        return inb(host->config + 1);
}

static inline void wbsd_write_index(struct wbsd_host *host, u8 index, u8 value)
{
        outb(index, host->base + WBSD_IDXR);
        outb(value, host->base + WBSD_DATAR);
}

static inline u8 wbsd_read_index(struct wbsd_host *host, u8 index)
{
        outb(index, host->base + WBSD_IDXR);
        return inb(host->base + WBSD_DATAR);
}

/*
 * Common routines
 */

static void wbsd_init_device(struct wbsd_host *host)
{
        u8 setup, ier;

        /*
         * Reset chip (SD/MMC part) and fifo.
         */
        setup = wbsd_read_index(host, WBSD_IDX_SETUP);
        setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET;
        wbsd_write_index(host, WBSD_IDX_SETUP, setup);

        /*
         * Set DAT3 to input
         */
        setup &= ~WBSD_DAT3_H;
        wbsd_write_index(host, WBSD_IDX_SETUP, setup);
        host->flags &= ~WBSD_FIGNORE_DETECT;

        /*
         * Read back default clock.
         */
        host->clk = wbsd_read_index(host, WBSD_IDX_CLK);

        /*
         * Power down port.
         */
        outb(WBSD_POWER_N, host->base + WBSD_CSR);

        /*
         * Set maximum timeout.
         */
        wbsd_write_index(host, WBSD_IDX_TAAC, 0x7F);

        /*
         * Test for card presence
         */
        if (inb(host->base + WBSD_CSR) & WBSD_CARDPRESENT)
                host->flags |= WBSD_FCARD_PRESENT;
        else
                host->flags &= ~WBSD_FCARD_PRESENT;

        /*
         * Enable interesting interrupts.
         */
        ier = 0;
        ier |= WBSD_EINT_CARD;
        ier |= WBSD_EINT_FIFO_THRE;
        ier |= WBSD_EINT_CRC;
        ier |= WBSD_EINT_TIMEOUT;
        ier |= WBSD_EINT_TC;

        outb(ier, host->base + WBSD_EIR);

        /*
         * Clear interrupts.
         */
        inb(host->base + WBSD_ISR);
}

static void wbsd_reset(struct wbsd_host *host)
{
        u8 setup;

        pr_err("%s: Resetting chip\n", mmc_hostname(host->mmc));

        /*
         * Soft reset of chip (SD/MMC part).
         */
        setup = wbsd_read_index(host, WBSD_IDX_SETUP);
        setup |= WBSD_SOFT_RESET;
        wbsd_write_index(host, WBSD_IDX_SETUP, setup);
}

static void wbsd_request_end(struct wbsd_host *host, struct mmc_request *mrq)
{
        unsigned long dmaflags;

        if (host->dma >= 0) {
                /*
                 * Release ISA DMA controller.
                 */
                dmaflags = claim_dma_lock();
                disable_dma(host->dma);
                clear_dma_ff(host->dma);
                release_dma_lock(dmaflags);

                /*
                 * Disable DMA on host.
                 */
                wbsd_write_index(host, WBSD_IDX_DMA, 0);
        }

        host->mrq = NULL;

        /*
         * MMC layer might call back into the driver so first unlock.
         */
        spin_unlock(&host->lock);
        mmc_request_done(host->mmc, mrq);
        spin_lock(&host->lock);
}

/*
 * Scatter/gather functions
 */

static inline void wbsd_init_sg(struct wbsd_host *host, struct mmc_data *data)
{
        /*
         * Get info. about SG list from data structure.
         */
        host->cur_sg = data->sg;
        host->num_sg = data->sg_len;

        host->offset = 0;
        host->remain = host->cur_sg->length;
}

static inline int wbsd_next_sg(struct wbsd_host *host)
{
        /*
         * Skip to next SG entry.
         */
        host->cur_sg++;
        host->num_sg--;

        /*
         * Any entries left?
         */
        if (host->num_sg > 0) {
                host->offset = 0;
                host->remain = host->cur_sg->length;
        }

        return host->num_sg;
}

static inline char *wbsd_map_sg(struct wbsd_host *host)
{
        return kmap_local_page(sg_page(host->cur_sg)) + host->cur_sg->offset;
}

static inline void wbsd_sg_to_dma(struct wbsd_host *host, struct mmc_data *data)
{
        size_t len = 0;
        int i;

        for (i = 0; i < data->sg_len; i++)
                len += data->sg[i].length;
        sg_copy_to_buffer(data->sg, data->sg_len, host->dma_buffer, len);
}

static inline void wbsd_dma_to_sg(struct wbsd_host *host, struct mmc_data *data)
{
        size_t len = 0;
        int i;

        for (i = 0; i < data->sg_len; i++)
                len += data->sg[i].length;
        sg_copy_from_buffer(data->sg, data->sg_len, host->dma_buffer, len);
}

/*
 * Command handling
 */

static inline void wbsd_get_short_reply(struct wbsd_host *host,
                                        struct mmc_command *cmd)
{
        /*
         * Correct response type?
         */
        if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT) {
                cmd->error = -EILSEQ;
                return;
        }

        cmd->resp[0]  = wbsd_read_index(host, WBSD_IDX_RESP12) << 24;
        cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP13) << 16;
        cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP14) << 8;
        cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP15) << 0;
        cmd->resp[1]  = wbsd_read_index(host, WBSD_IDX_RESP16) << 24;
}

static inline void wbsd_get_long_reply(struct wbsd_host *host,
        struct mmc_command *cmd)
{
        int i;

        /*
         * Correct response type?
         */
        if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_LONG) {
                cmd->error = -EILSEQ;
                return;
        }

        for (i = 0; i < 4; i++) {
                cmd->resp[i] =
                        wbsd_read_index(host, WBSD_IDX_RESP1 + i * 4) << 24;
                cmd->resp[i] |=
                        wbsd_read_index(host, WBSD_IDX_RESP2 + i * 4) << 16;
                cmd->resp[i] |=
                        wbsd_read_index(host, WBSD_IDX_RESP3 + i * 4) << 8;
                cmd->resp[i] |=
                        wbsd_read_index(host, WBSD_IDX_RESP4 + i * 4) << 0;
        }
}

static void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd)
{
        int i;
        u8 status, isr;

        /*
         * Clear accumulated ISR. The interrupt routine
         * will fill this one with events that occur during
         * transfer.
         */
        host->isr = 0;

        /*
         * Send the command (CRC calculated by host).
         */
        outb(cmd->opcode, host->base + WBSD_CMDR);
        for (i = 3; i >= 0; i--)
                outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR);

        cmd->error = 0;

        /*
         * Wait for the request to complete.
         */
        do {
                status = wbsd_read_index(host, WBSD_IDX_STATUS);
        } while (status & WBSD_CARDTRAFFIC);

        /*
         * Do we expect a reply?
         */
        if (cmd->flags & MMC_RSP_PRESENT) {
                /*
                 * Read back status.
                 */
                isr = host->isr;

                /* Card removed? */
                if (isr & WBSD_INT_CARD)
                        cmd->error = -ENOMEDIUM;
                /* Timeout? */
                else if (isr & WBSD_INT_TIMEOUT)
                        cmd->error = -ETIMEDOUT;
                /* CRC? */
                else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC))
                        cmd->error = -EILSEQ;
                /* All ok */
                else {
                        if (cmd->flags & MMC_RSP_136)
                                wbsd_get_long_reply(host, cmd);
                        else
                                wbsd_get_short_reply(host, cmd);
                }
        }
}

/*
 * Data functions
 */

static void wbsd_empty_fifo(struct wbsd_host *host)
{
        struct mmc_data *data = host->mrq->cmd->data;
        char *buffer;
        int i, idx, fsr, fifo;

        /*
         * Handle excessive data.
         */
        if (host->num_sg == 0)
                return;

        buffer = wbsd_map_sg(host) + host->offset;
        idx = 0;

        /*
         * Drain the fifo. This has a tendency to loop longer
         * than the FIFO length (usually one block).
         */
        while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_EMPTY)) {
                /*
                 * The size field in the FSR is broken so we have to
                 * do some guessing.
                 */
                if (fsr & WBSD_FIFO_FULL)
                        fifo = 16;
                else if (fsr & WBSD_FIFO_FUTHRE)
                        fifo = 8;
                else
                        fifo = 1;

                for (i = 0; i < fifo; i++) {
                        buffer[idx++] = inb(host->base + WBSD_DFR);
                        host->offset++;
                        host->remain--;

                        data->bytes_xfered++;

                        /*
                         * End of scatter list entry?
                         */
                        if (host->remain == 0) {
                                kunmap_local(buffer);
                                /*
                                 * Get next entry. Check if last.
                                 */
                                if (!wbsd_next_sg(host))
                                        return;

                                buffer = wbsd_map_sg(host);
                                idx = 0;
                        }
                }
        }
        kunmap_local(buffer);

        /*
         * This is a very dirty hack to solve a
         * hardware problem. The chip doesn't trigger
         * FIFO threshold interrupts properly.
         */
        if ((data->blocks * data->blksz - data->bytes_xfered) < 16)
                queue_work(system_bh_wq, &host->fifo_bh_work);
}

static void wbsd_fill_fifo(struct wbsd_host *host)
{
        struct mmc_data *data = host->mrq->cmd->data;
        char *buffer;
        int i, idx, fsr, fifo;

        /*
         * Check that we aren't being called after the
         * entire buffer has been transferred.
         */
        if (host->num_sg == 0)
                return;

        buffer = wbsd_map_sg(host) + host->offset;
        idx = 0;

        /*
         * Fill the fifo. This has a tendency to loop longer
         * than the FIFO length (usually one block).
         */
        while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_FULL)) {
                /*
                 * The size field in the FSR is broken so we have to
                 * do some guessing.
                 */
                if (fsr & WBSD_FIFO_EMPTY)
                        fifo = 0;
                else if (fsr & WBSD_FIFO_EMTHRE)
                        fifo = 8;
                else
                        fifo = 15;

                for (i = 16; i > fifo; i--) {
                        outb(buffer[idx], host->base + WBSD_DFR);
                        host->offset++;
                        host->remain--;

                        data->bytes_xfered++;

                        /*
                         * End of scatter list entry?
                         */
                        if (host->remain == 0) {
                                kunmap_local(buffer);
                                /*
                                 * Get next entry. Check if last.
                                 */
                                if (!wbsd_next_sg(host))
                                        return;

                                buffer = wbsd_map_sg(host);
                                idx = 0;
                        }
                }
        }
        kunmap_local(buffer);

        /*
         * The controller stops sending interrupts for
         * 'FIFO empty' under certain conditions. So we
         * need to be a bit more pro-active.
         */
        queue_work(system_bh_wq, &host->fifo_bh_work);
}

static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data)
{
        u16 blksize;
        u8 setup;
        unsigned long dmaflags;
        unsigned int size;

        /*
         * Calculate size.
         */
        size = data->blocks * data->blksz;

        /*
         * Check timeout values for overflow.
         * (Yes, some cards cause this value to overflow).
         */
        if (data->timeout_ns > 127000000)
                wbsd_write_index(host, WBSD_IDX_TAAC, 127);
        else {
                wbsd_write_index(host, WBSD_IDX_TAAC,
                        data->timeout_ns / 1000000);
        }

        if (data->timeout_clks > 255)
                wbsd_write_index(host, WBSD_IDX_NSAC, 255);
        else
                wbsd_write_index(host, WBSD_IDX_NSAC, data->timeout_clks);

        /*
         * Inform the chip of how large blocks will be
         * sent. It needs this to determine when to
         * calculate CRC.
         *
         * Space for CRC must be included in the size.
         * Two bytes are needed for each data line.
         */
        if (host->bus_width == MMC_BUS_WIDTH_1) {
                blksize = data->blksz + 2;

                wbsd_write_index(host, WBSD_IDX_PBSMSB, (blksize >> 4) & 0xF0);
                wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
        } else if (host->bus_width == MMC_BUS_WIDTH_4) {
                blksize = data->blksz + 2 * 4;

                wbsd_write_index(host, WBSD_IDX_PBSMSB,
                        ((blksize >> 4) & 0xF0) | WBSD_DATA_WIDTH);
                wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
        } else {
                data->error = -EINVAL;
                return;
        }

        /*
         * Clear the FIFO. This is needed even for DMA
         * transfers since the chip still uses the FIFO
         * internally.
         */
        setup = wbsd_read_index(host, WBSD_IDX_SETUP);
        setup |= WBSD_FIFO_RESET;
        wbsd_write_index(host, WBSD_IDX_SETUP, setup);

        /*
         * DMA transfer?
         */
        if (host->dma >= 0) {
                /*
                 * The buffer for DMA is only 64 kB.
                 */
                BUG_ON(size > 0x10000);
                if (size > 0x10000) {
                        data->error = -EINVAL;
                        return;
                }

                /*
                 * Transfer data from the SG list to
                 * the DMA buffer.
                 */
                if (data->flags & MMC_DATA_WRITE)
                        wbsd_sg_to_dma(host, data);

                /*
                 * Initialise the ISA DMA controller.
                 */
                dmaflags = claim_dma_lock();
                disable_dma(host->dma);
                clear_dma_ff(host->dma);
                if (data->flags & MMC_DATA_READ)
                        set_dma_mode(host->dma, DMA_MODE_READ & ~0x40);
                else
                        set_dma_mode(host->dma, DMA_MODE_WRITE & ~0x40);
                set_dma_addr(host->dma, host->dma_addr);
                set_dma_count(host->dma, size);

                enable_dma(host->dma);
                release_dma_lock(dmaflags);

                /*
                 * Enable DMA on the host.
                 */
                wbsd_write_index(host, WBSD_IDX_DMA, WBSD_DMA_ENABLE);
        } else {
                /*
                 * This flag is used to keep printk
                 * output to a minimum.
                 */
                host->firsterr = 1;

                /*
                 * Initialise the SG list.
                 */
                wbsd_init_sg(host, data);

                /*
                 * Turn off DMA.
                 */
                wbsd_write_index(host, WBSD_IDX_DMA, 0);

                /*
                 * Set up FIFO threshold levels (and fill
                 * buffer if doing a write).
                 */
                if (data->flags & MMC_DATA_READ) {
                        wbsd_write_index(host, WBSD_IDX_FIFOEN,
                                WBSD_FIFOEN_FULL | 8);
                } else {
                        wbsd_write_index(host, WBSD_IDX_FIFOEN,
                                WBSD_FIFOEN_EMPTY | 8);
                        wbsd_fill_fifo(host);
                }
        }

        data->error = 0;
}

static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data)
{
        unsigned long dmaflags;
        int count;
        u8 status;

        WARN_ON(host->mrq == NULL);

        /*
         * Send a stop command if needed.
         */
        if (data->stop)
                wbsd_send_command(host, data->stop);

        /*
         * Wait for the controller to leave data
         * transfer state.
         */
        do {
                status = wbsd_read_index(host, WBSD_IDX_STATUS);
        } while (status & (WBSD_BLOCK_READ | WBSD_BLOCK_WRITE));

        /*
         * DMA transfer?
         */
        if (host->dma >= 0) {
                /*
                 * Disable DMA on the host.
                 */
                wbsd_write_index(host, WBSD_IDX_DMA, 0);

                /*
                 * Turn of ISA DMA controller.
                 */
                dmaflags = claim_dma_lock();
                disable_dma(host->dma);
                clear_dma_ff(host->dma);
                count = get_dma_residue(host->dma);
                release_dma_lock(dmaflags);

                data->bytes_xfered = host->mrq->data->blocks *
                        host->mrq->data->blksz - count;
                data->bytes_xfered -= data->bytes_xfered % data->blksz;

                /*
                 * Any leftover data?
                 */
                if (count) {
                        pr_err("%s: Incomplete DMA transfer. "
                                "%d bytes left.\n",
                                mmc_hostname(host->mmc), count);

                        if (!data->error)
                                data->error = -EIO;
                } else {
                        /*
                         * Transfer data from DMA buffer to
                         * SG list.
                         */
                        if (data->flags & MMC_DATA_READ)
                                wbsd_dma_to_sg(host, data);
                }

                if (data->error) {
                        if (data->bytes_xfered)
                                data->bytes_xfered -= data->blksz;
                }
        }

        wbsd_request_end(host, host->mrq);
}

/*****************************************************************************\
 *                                                                           *
 * MMC layer callbacks                                                       *
 *                                                                           *
\*****************************************************************************/

static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
        struct wbsd_host *host = mmc_priv(mmc);
        struct mmc_command *cmd;

        /*
         * Disable bh works to avoid a deadlock.
         */
        spin_lock_bh(&host->lock);

        BUG_ON(host->mrq != NULL);

        cmd = mrq->cmd;

        host->mrq = mrq;

        /*
         * Check that there is actually a card in the slot.
         */
        if (!(host->flags & WBSD_FCARD_PRESENT)) {
                cmd->error = -ENOMEDIUM;
                goto done;
        }

        if (cmd->data) {
                /*
                 * The hardware is so delightfully stupid that it has a list
                 * of "data" commands. If a command isn't on this list, it'll
                 * just go back to the idle state and won't send any data
                 * interrupts.
                 */
                switch (cmd->opcode) {
                case SD_SWITCH_VOLTAGE:
                case MMC_READ_SINGLE_BLOCK:
                case MMC_READ_MULTIPLE_BLOCK:
                case MMC_WRITE_DAT_UNTIL_STOP:
                case MMC_WRITE_BLOCK:
                case MMC_WRITE_MULTIPLE_BLOCK:
                case MMC_PROGRAM_CID:
                case MMC_PROGRAM_CSD:
                case MMC_SEND_WRITE_PROT:
                case MMC_LOCK_UNLOCK:
                case MMC_GEN_CMD:
                        break;

                /* ACMDs. We don't keep track of state, so we just treat them
                 * like any other command. */
                case SD_APP_SEND_SCR:
                        break;

                default:
                        pr_warn("%s: Data command %d is not supported by this controller\n",
                                mmc_hostname(host->mmc), cmd->opcode);
                        cmd->error = -EINVAL;

                        goto done;
                }
        }

        /*
         * Does the request include data?
         */
        if (cmd->data) {
                wbsd_prepare_data(host, cmd->data);

                if (cmd->data->error)
                        goto done;
        }

        wbsd_send_command(host, cmd);

        /*
         * If this is a data transfer the request
         * will be finished after the data has
         * transferred.
         */
        if (cmd->data && !cmd->error) {
                /*
                 * Dirty fix for hardware bug.
                 */
                if (host->dma == -1)
                        queue_work(system_bh_wq, &host->fifo_bh_work);

                spin_unlock_bh(&host->lock);

                return;
        }

done:
        wbsd_request_end(host, mrq);

        spin_unlock_bh(&host->lock);
}

static void wbsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
        struct wbsd_host *host = mmc_priv(mmc);
        u8 clk, setup, pwr;

        spin_lock_bh(&host->lock);

        /*
         * Reset the chip on each power off.
         * Should clear out any weird states.
         */
        if (ios->power_mode == MMC_POWER_OFF)
                wbsd_init_device(host);

        if (ios->clock >= 24000000)
                clk = WBSD_CLK_24M;
        else if (ios->clock >= 16000000)
                clk = WBSD_CLK_16M;
        else if (ios->clock >= 12000000)
                clk = WBSD_CLK_12M;
        else
                clk = WBSD_CLK_375K;

        /*
         * Only write to the clock register when
         * there is an actual change.
         */
        if (clk != host->clk) {
                wbsd_write_index(host, WBSD_IDX_CLK, clk);
                host->clk = clk;
        }

        /*
         * Power up card.
         */
        if (ios->power_mode != MMC_POWER_OFF) {
                pwr = inb(host->base + WBSD_CSR);
                pwr &= ~WBSD_POWER_N;
                outb(pwr, host->base + WBSD_CSR);
        }

        /*
         * MMC cards need to have pin 1 high during init.
         * It wreaks havoc with the card detection though so
         * that needs to be disabled.
         */
        setup = wbsd_read_index(host, WBSD_IDX_SETUP);
        if (ios->chip_select == MMC_CS_HIGH) {
                BUG_ON(ios->bus_width != MMC_BUS_WIDTH_1);
                setup |= WBSD_DAT3_H;
                host->flags |= WBSD_FIGNORE_DETECT;
        } else {
                if (setup & WBSD_DAT3_H) {
                        setup &= ~WBSD_DAT3_H;

                        /*
                         * We cannot resume card detection immediately
                         * because of capacitance and delays in the chip.
                         */
                        mod_timer(&host->ignore_timer, jiffies + HZ / 100);
                }
        }
        wbsd_write_index(host, WBSD_IDX_SETUP, setup);

        /*
         * Store bus width for later. Will be used when
         * setting up the data transfer.
         */
        host->bus_width = ios->bus_width;

        spin_unlock_bh(&host->lock);
}

static int wbsd_get_ro(struct mmc_host *mmc)
{
        struct wbsd_host *host = mmc_priv(mmc);
        u8 csr;

        spin_lock_bh(&host->lock);

        csr = inb(host->base + WBSD_CSR);
        csr |= WBSD_MSLED;
        outb(csr, host->base + WBSD_CSR);

        mdelay(1);

        csr = inb(host->base + WBSD_CSR);
        csr &= ~WBSD_MSLED;
        outb(csr, host->base + WBSD_CSR);

        spin_unlock_bh(&host->lock);

        return !!(csr & WBSD_WRPT);
}

static const struct mmc_host_ops wbsd_ops = {
        .request        = wbsd_request,
        .set_ios        = wbsd_set_ios,
        .get_ro         = wbsd_get_ro,
};

/*****************************************************************************\
 *                                                                           *
 * Interrupt handling                                                        *
 *                                                                           *
\*****************************************************************************/

/*
 * Helper function to reset detection ignore
 */

static void wbsd_reset_ignore(struct timer_list *t)
{
        struct wbsd_host *host = timer_container_of(host, t, ignore_timer);

        BUG_ON(host == NULL);

        DBG("Resetting card detection ignore\n");

        spin_lock_bh(&host->lock);

        host->flags &= ~WBSD_FIGNORE_DETECT;

        /*
         * Card status might have changed during the
         * blackout.
         */
        queue_work(system_bh_wq, &host->card_bh_work);

        spin_unlock_bh(&host->lock);
}

/*
 * BH Works
 */

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_card_bh_work(struct work_struct *t)
{
        struct wbsd_host *host = from_work(host, t, card_bh_work);
        u8 csr;
        int delay = -1;

        spin_lock(&host->lock);

        if (host->flags & WBSD_FIGNORE_DETECT) {
                spin_unlock(&host->lock);
                return;
        }

        csr = inb(host->base + WBSD_CSR);
        WARN_ON(csr == 0xff);

        if (csr & WBSD_CARDPRESENT) {
                if (!(host->flags & WBSD_FCARD_PRESENT)) {
                        DBG("Card inserted\n");
                        host->flags |= WBSD_FCARD_PRESENT;

                        delay = 500;
                }
        } else if (host->flags & WBSD_FCARD_PRESENT) {
                DBG("Card removed\n");
                host->flags &= ~WBSD_FCARD_PRESENT;

                if (host->mrq) {
                        pr_err("%s: Card removed during transfer!\n",
                                mmc_hostname(host->mmc));
                        wbsd_reset(host);

                        host->mrq->cmd->error = -ENOMEDIUM;
                        queue_work(system_bh_wq, &host->finish_bh_work);
                }

                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_fifo_bh_work(struct work_struct *t)
{
        struct wbsd_host *host = from_work(host, t, fifo_bh_work);
        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);
                queue_work(system_bh_wq, &host->finish_bh_work);
        }

end:
        spin_unlock(&host->lock);
}

static void wbsd_crc_bh_work(struct work_struct *t)
{
        struct wbsd_host *host = from_work(host, t, crc_bh_work);
        struct mmc_data *data;

        spin_lock(&host->lock);

        if (!host->mrq)
                goto end;

        data = wbsd_get_data(host);
        if (!data)
                goto end;

        DBGF("CRC error\n");

        data->error = -EILSEQ;

        queue_work(system_bh_wq, &host->finish_bh_work);

end:
        spin_unlock(&host->lock);
}

static void wbsd_timeout_bh_work(struct work_struct *t)
{
        struct wbsd_host *host = from_work(host, t, timeout_bh_work);
        struct mmc_data *data;

        spin_lock(&host->lock);

        if (!host->mrq)
                goto end;

        data = wbsd_get_data(host);
        if (!data)
                goto end;

        DBGF("Timeout\n");

        data->error = -ETIMEDOUT;

        queue_work(system_bh_wq, &host->finish_bh_work);

end:
        spin_unlock(&host->lock);
}

static void wbsd_finish_bh_work(struct work_struct *t)
{
        struct wbsd_host *host = from_work(host, t, finish_bh_work);
        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 bh work as needed.
         */
        if (isr & WBSD_INT_CARD)
                queue_work(system_bh_wq, &host->card_bh_work);
        if (isr & WBSD_INT_FIFO_THRE)
                queue_work(system_bh_wq, &host->fifo_bh_work);
        if (isr & WBSD_INT_CRC)
                queue_work(system_bh_highpri_wq, &host->crc_bh_work);
        if (isr & WBSD_INT_TIMEOUT)
                queue_work(system_bh_highpri_wq, &host->timeout_bh_work);
        if (isr & WBSD_INT_TC)
                queue_work(system_bh_wq, &host->finish_bh_work);

        return IRQ_HANDLED;
}

/*****************************************************************************\
 *                                                                           *
 * Device initialisation and shutdown                                        *
 *                                                                           *
\*****************************************************************************/

/*
 * Allocate/free MMC structure.
 */

static int wbsd_alloc_mmc(struct device *dev)
{
        struct mmc_host *mmc;
        struct wbsd_host *host;

        /*
         * Allocate MMC structure.
         */
        mmc = devm_mmc_alloc_host(dev, sizeof(*host));
        if (!mmc)
                return -ENOMEM;

        host = mmc_priv(mmc);
        host->mmc = mmc;

        host->dma = -1;

        /*
         * Set host parameters.
         */
        mmc->ops = &wbsd_ops;
        mmc->f_min = 375000;
        mmc->f_max = 24000000;
        mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
        mmc->caps = MMC_CAP_4_BIT_DATA;

        spin_lock_init(&host->lock);

        /*
         * Set up timers
         */
        timer_setup(&host->ignore_timer, wbsd_reset_ignore, 0);

        /*
         * Maximum number of segments. Worst case is one sector per segment
         * so this will be 64kB/512.
         */
        mmc->max_segs = 128;

        /*
         * Maximum request size. Also limited by 64KiB buffer.
         */
        mmc->max_req_size = 65536;

        /*
         * Maximum segment size. Could be one segment with the maximum number
         * of bytes.
         */
        mmc->max_seg_size = mmc->max_req_size;

        /*
         * Maximum block size. We have 12 bits (= 4095) but have to subtract
         * space for CRC. So the maximum is 4095 - 4*2 = 4087.
         */
        mmc->max_blk_size = 4087;

        /*
         * Maximum block count. There is no real limit so the maximum
         * request size will be the only restriction.
         */
        mmc->max_blk_count = mmc->max_req_size;

        dev_set_drvdata(dev, mmc);

        return 0;
}

static void wbsd_free_mmc(struct device *dev)
{
        struct mmc_host *mmc;
        struct wbsd_host *host;

        mmc = dev_get_drvdata(dev);
        if (!mmc)
                return;

        host = mmc_priv(mmc);
        BUG_ON(host == NULL);

        timer_delete_sync(&host->ignore_timer);
}

/*
 * Scan for known chip id:s
 */

static int wbsd_scan(struct wbsd_host *host)
{
        int i, j, k;
        int id;

        /*
         * Iterate through all ports, all codes to
         * find hardware that is in our known list.
         */
        for (i = 0; i < ARRAY_SIZE(config_ports); i++) {
                if (!request_region(config_ports[i], 2, DRIVER_NAME))
                        continue;

                for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) {
                        host->config = config_ports[i];
                        host->unlock_code = unlock_codes[j];

                        wbsd_unlock_config(host);

                        outb(WBSD_CONF_ID_HI, config_ports[i]);
                        id = inb(config_ports[i] + 1) << 8;

                        outb(WBSD_CONF_ID_LO, config_ports[i]);
                        id |= inb(config_ports[i] + 1);

                        wbsd_lock_config(host);

                        for (k = 0; k < ARRAY_SIZE(valid_ids); k++) {
                                if (id == valid_ids[k]) {
                                        host->chip_id = id;

                                        return 0;
                                }
                        }

                        if (id != 0xFFFF) {
                                DBG("Unknown hardware (id %x) found at %x\n",
                                        id, config_ports[i]);
                        }
                }

                release_region(config_ports[i], 2);
        }

        host->config = 0;
        host->unlock_code = 0;

        return -ENODEV;
}

/*
 * Allocate/free io port ranges
 */

static int wbsd_request_region(struct wbsd_host *host, int base)
{
        if (base & 0x7)
                return -EINVAL;

        if (!request_region(base, 8, DRIVER_NAME))
                return -EIO;

        host->base = base;

        return 0;
}

static void wbsd_release_regions(struct wbsd_host *host)
{
        if (host->base)
                release_region(host->base, 8);

        host->base = 0;

        if (host->config)
                release_region(host->config, 2);

        host->config = 0;
}

/*
 * Allocate/free DMA port and buffer
 */

static void wbsd_request_dma(struct wbsd_host *host, int dma)
{
        if (dma < 0)
                return;

        if (request_dma(dma, DRIVER_NAME))
                goto err;

        /*
         * We need to allocate a special buffer in
         * order for ISA to be able to DMA to it.
         */
        host->dma_buffer = kmalloc(WBSD_DMA_SIZE,
                GFP_NOIO | GFP_DMA | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
        if (!host->dma_buffer)
                goto free;

        /*
         * Translate the address to a physical address.
         */
        host->dma_addr = dma_map_single(mmc_dev(host->mmc), host->dma_buffer,
                WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
        if (dma_mapping_error(mmc_dev(host->mmc), host->dma_addr))
                goto kfree;

        /*
         * ISA DMA must be aligned on a 64k basis.
         */
        if ((host->dma_addr & 0xffff) != 0)
                goto unmap;
        /*
         * ISA cannot access memory above 16 MB.
         */
        else if (host->dma_addr >= 0x1000000)
                goto unmap;

        host->dma = dma;

        return;

unmap:
        /*
         * If we've gotten here then there is some kind of alignment bug
         */
        BUG_ON(1);

        dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
                WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
        host->dma_addr = 0;

kfree:
        kfree(host->dma_buffer);
        host->dma_buffer = NULL;

free:
        free_dma(dma);

err:
        pr_warn(DRIVER_NAME ": Unable to allocate DMA %d - falling back on FIFO\n",
                dma);
}

static void wbsd_release_dma(struct wbsd_host *host)
{
        /*
         * host->dma_addr is valid here iff host->dma_buffer is not NULL.
         */
        if (host->dma_buffer) {
                dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
                        WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
                kfree(host->dma_buffer);
        }
        if (host->dma >= 0)
                free_dma(host->dma);

        host->dma = -1;
        host->dma_buffer = NULL;
        host->dma_addr = 0;
}

/*
 * Allocate/free IRQ.
 */

static int wbsd_request_irq(struct wbsd_host *host, int irq)
{
        int ret;

        /*
         * Set up bh works. Must be done before requesting interrupt.
         */
        INIT_WORK(&host->card_bh_work, wbsd_card_bh_work);
        INIT_WORK(&host->fifo_bh_work, wbsd_fifo_bh_work);
        INIT_WORK(&host->crc_bh_work, wbsd_crc_bh_work);
        INIT_WORK(&host->timeout_bh_work, wbsd_timeout_bh_work);
        INIT_WORK(&host->finish_bh_work, wbsd_finish_bh_work);

        /*
         * Allocate interrupt.
         */
        ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host);
        if (ret)
                return ret;

        host->irq = irq;

        return 0;
}

static void  wbsd_release_irq(struct wbsd_host *host)
{
        if (!host->irq)
                return;

        free_irq(host->irq, host);

        host->irq = 0;

        cancel_work_sync(&host->card_bh_work);
        cancel_work_sync(&host->fifo_bh_work);
        cancel_work_sync(&host->crc_bh_work);
        cancel_work_sync(&host->timeout_bh_work);
        cancel_work_sync(&host->finish_bh_work);
}

/*
 * Allocate all resources for the host.
 */

static int wbsd_request_resources(struct wbsd_host *host,
        int base, int irq, int dma)
{
        int ret;

        /*
         * Allocate I/O ports.
         */
        ret = wbsd_request_region(host, base);
        if (ret)
                return ret;

        /*
         * Allocate interrupt.
         */
        ret = wbsd_request_irq(host, irq);
        if (ret)
                return ret;

        /*
         * Allocate DMA.
         */
        wbsd_request_dma(host, dma);

        return 0;
}

/*
 * Release all resources for the host.
 */

static void wbsd_release_resources(struct wbsd_host *host)
{
        wbsd_release_dma(host);
        wbsd_release_irq(host);
        wbsd_release_regions(host);
}

/*
 * Configure the resources the chip should use.
 */

static void wbsd_chip_config(struct wbsd_host *host)
{
        wbsd_unlock_config(host);

        /*
         * Reset the chip.
         */
        wbsd_write_config(host, WBSD_CONF_SWRST, 1);
        wbsd_write_config(host, WBSD_CONF_SWRST, 0);

        /*
         * Select SD/MMC function.
         */
        wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);

        /*
         * Set up card detection.
         */
        wbsd_write_config(host, WBSD_CONF_PINS, WBSD_PINS_DETECT_GP11);

        /*
         * Configure chip
         */
        wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8);
        wbsd_write_config(host, WBSD_CONF_PORT_LO, host->base & 0xff);

        wbsd_write_config(host, WBSD_CONF_IRQ, host->irq);

        if (host->dma >= 0)
                wbsd_write_config(host, WBSD_CONF_DRQ, host->dma);

        /*
         * Enable and power up chip.
         */
        wbsd_write_config(host, WBSD_CONF_ENABLE, 1);
        wbsd_write_config(host, WBSD_CONF_POWER, 0x20);

        wbsd_lock_config(host);
}

/*
 * Check that configured resources are correct.
 */

static int wbsd_chip_validate(struct wbsd_host *host)
{
        int base, irq, dma;

        wbsd_unlock_config(host);

        /*
         * Select SD/MMC function.
         */
        wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);

        /*
         * Read configuration.
         */
        base = wbsd_read_config(host, WBSD_CONF_PORT_HI) << 8;
        base |= wbsd_read_config(host, WBSD_CONF_PORT_LO);

        irq = wbsd_read_config(host, WBSD_CONF_IRQ);

        dma = wbsd_read_config(host, WBSD_CONF_DRQ);

        wbsd_lock_config(host);

        /*
         * Validate against given configuration.
         */
        if (base != host->base)
                return 0;
        if (irq != host->irq)
                return 0;
        if ((dma != host->dma) && (host->dma != -1))
                return 0;

        return 1;
}

/*
 * Powers down the SD function
 */

static void wbsd_chip_poweroff(struct wbsd_host *host)
{
        wbsd_unlock_config(host);

        wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
        wbsd_write_config(host, WBSD_CONF_ENABLE, 0);

        wbsd_lock_config(host);
}

/*****************************************************************************\
 *                                                                           *
 * Devices setup and shutdown                                                *
 *                                                                           *
\*****************************************************************************/

static int wbsd_init(struct device *dev, int base, int irq, int dma,
        int pnp)
{
        struct wbsd_host *host = NULL;
        struct mmc_host *mmc = NULL;
        int ret;

        ret = wbsd_alloc_mmc(dev);
        if (ret)
                return ret;

        mmc = dev_get_drvdata(dev);
        host = mmc_priv(mmc);

        /*
         * Scan for hardware.
         */
        ret = wbsd_scan(host);
        if (ret) {
                if (pnp && (ret == -ENODEV)) {
                        pr_warn(DRIVER_NAME ": Unable to confirm device presence - you may experience lock-ups\n");
                } else {
                        wbsd_free_mmc(dev);
                        return ret;
                }
        }

        /*
         * Request resources.
         */
        ret = wbsd_request_resources(host, base, irq, dma);
        if (ret) {
                wbsd_release_resources(host);
                wbsd_free_mmc(dev);
                return ret;
        }

        /*
         * See if chip needs to be configured.
         */
        if (pnp) {
                if ((host->config != 0) && !wbsd_chip_validate(host)) {
                        pr_warn(DRIVER_NAME ": PnP active but chip not configured! You probably have a buggy BIOS. Configuring chip manually.\n");
                        wbsd_chip_config(host);
                }
        } else
                wbsd_chip_config(host);

        /*
         * Power Management stuff. No idea how this works.
         * Not tested.
         */
#ifdef CONFIG_PM
        if (host->config) {
                wbsd_unlock_config(host);
                wbsd_write_config(host, WBSD_CONF_PME, 0xA0);
                wbsd_lock_config(host);
        }
#endif
        /*
         * Allow device to initialise itself properly.
         */
        mdelay(5);

        /*
         * Reset the chip into a known state.
         */
        wbsd_init_device(host);

        ret = mmc_add_host(mmc);
        if (ret) {
                if (!pnp)
                        wbsd_chip_poweroff(host);

                wbsd_release_resources(host);
                wbsd_free_mmc(dev);
                return ret;
        }

        pr_info("%s: W83L51xD", mmc_hostname(mmc));
        if (host->chip_id != 0)
                printk(" id %x", (int)host->chip_id);
        printk(" at 0x%x irq %d", (int)host->base, (int)host->irq);
        if (host->dma >= 0)
                printk(" dma %d", (int)host->dma);
        else
                printk(" FIFO");
        if (pnp)
                printk(" PnP");
        printk("\n");

        return 0;
}

static void wbsd_shutdown(struct device *dev, int pnp)
{
        struct mmc_host *mmc = dev_get_drvdata(dev);
        struct wbsd_host *host;

        if (!mmc)
                return;

        host = mmc_priv(mmc);

        mmc_remove_host(mmc);

        /*
         * Power down the SD/MMC function.
         */
        if (!pnp)
                wbsd_chip_poweroff(host);

        wbsd_release_resources(host);

        wbsd_free_mmc(dev);
}

/*
 * Non-PnP
 */

static int wbsd_probe(struct platform_device *dev)
{
        /* Use the module parameters for resources */
        return wbsd_init(&dev->dev, param_io, param_irq, param_dma, 0);
}

static void wbsd_remove(struct platform_device *dev)
{
        wbsd_shutdown(&dev->dev, 0);
}

/*
 * PnP
 */

#ifdef CONFIG_PNP

static int
wbsd_pnp_probe(struct pnp_dev *pnpdev, const struct pnp_device_id *dev_id)
{
        int io, irq, dma;

        /*
         * Get resources from PnP layer.
         */
        io = pnp_port_start(pnpdev, 0);
        irq = pnp_irq(pnpdev, 0);
        if (pnp_dma_valid(pnpdev, 0))
                dma = pnp_dma(pnpdev, 0);
        else
                dma = -1;

        DBGF("PnP resources: port %3x irq %d dma %d\n", io, irq, dma);

        return wbsd_init(&pnpdev->dev, io, irq, dma, 1);
}

static void wbsd_pnp_remove(struct pnp_dev *dev)
{
        wbsd_shutdown(&dev->dev, 1);
}

#endif /* CONFIG_PNP */

/*
 * Power management
 */

#ifdef CONFIG_PM

static int wbsd_platform_suspend(struct platform_device *dev,
                                 pm_message_t state)
{
        struct mmc_host *mmc = platform_get_drvdata(dev);
        struct wbsd_host *host;

        if (mmc == NULL)
                return 0;

        DBGF("Suspending...\n");

        host = mmc_priv(mmc);

        wbsd_chip_poweroff(host);
        return 0;
}

static int wbsd_platform_resume(struct platform_device *dev)
{
        struct mmc_host *mmc = platform_get_drvdata(dev);
        struct wbsd_host *host;

        if (mmc == NULL)
                return 0;

        DBGF("Resuming...\n");

        host = mmc_priv(mmc);

        wbsd_chip_config(host);

        /*
         * Allow device to initialise itself properly.
         */
        mdelay(5);

        wbsd_init_device(host);
        return 0;
}

#ifdef CONFIG_PNP

static int wbsd_pnp_suspend(struct pnp_dev *pnp_dev, pm_message_t state)
{
        struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);

        if (mmc == NULL)
                return 0;

        DBGF("Suspending...\n");
        return 0;
}

static int wbsd_pnp_resume(struct pnp_dev *pnp_dev)
{
        struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
        struct wbsd_host *host;

        if (mmc == NULL)
                return 0;

        DBGF("Resuming...\n");

        host = mmc_priv(mmc);

        /*
         * See if chip needs to be configured.
         */
        if (host->config != 0) {
                if (!wbsd_chip_validate(host)) {
                        pr_warn(DRIVER_NAME ": PnP active but chip not configured! You probably have a buggy BIOS. Configuring chip manually.\n");
                        wbsd_chip_config(host);
                }
        }

        /*
         * Allow device to initialise itself properly.
         */
        mdelay(5);

        wbsd_init_device(host);
        return 0;
}

#endif /* CONFIG_PNP */

#else /* CONFIG_PM */

#define wbsd_platform_suspend NULL
#define wbsd_platform_resume NULL

#define wbsd_pnp_suspend NULL
#define wbsd_pnp_resume NULL

#endif /* CONFIG_PM */

static struct platform_device *wbsd_device;

static struct platform_driver wbsd_driver = {
        .probe          = wbsd_probe,
        .remove         = wbsd_remove,
        .suspend        = wbsd_platform_suspend,
        .resume         = wbsd_platform_resume,
        .driver         = {
                .name   = DRIVER_NAME,
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
};

#ifdef CONFIG_PNP

static struct pnp_driver wbsd_pnp_driver = {
        .name           = DRIVER_NAME,
        .id_table       = pnp_dev_table,
        .probe          = wbsd_pnp_probe,
        .remove         = wbsd_pnp_remove,

        .suspend        = wbsd_pnp_suspend,
        .resume         = wbsd_pnp_resume,
};

#endif /* CONFIG_PNP */

/*
 * Module loading/unloading
 */

static int __init wbsd_drv_init(void)
{
        int result;

        pr_info(DRIVER_NAME
                ": Winbond W83L51xD SD/MMC card interface driver\n");
        pr_info(DRIVER_NAME ": Copyright(c) Pierre Ossman\n");

#ifdef CONFIG_PNP

        if (!param_nopnp) {
                result = pnp_register_driver(&wbsd_pnp_driver);
                if (result < 0)
                        return result;
        }
#endif /* CONFIG_PNP */

        if (param_nopnp) {
                result = platform_driver_register(&wbsd_driver);
                if (result < 0)
                        return result;

                wbsd_device = platform_device_alloc(DRIVER_NAME, -1);
                if (!wbsd_device) {
                        platform_driver_unregister(&wbsd_driver);
                        return -ENOMEM;
                }

                result = platform_device_add(wbsd_device);
                if (result) {
                        platform_device_put(wbsd_device);
                        platform_driver_unregister(&wbsd_driver);
                        return result;
                }
        }

        return 0;
}

static void __exit wbsd_drv_exit(void)
{
#ifdef CONFIG_PNP

        if (!param_nopnp)
                pnp_unregister_driver(&wbsd_pnp_driver);

#endif /* CONFIG_PNP */

        if (param_nopnp) {
                platform_device_unregister(wbsd_device);

                platform_driver_unregister(&wbsd_driver);
        }

        DBG("unloaded\n");
}

module_init(wbsd_drv_init);
module_exit(wbsd_drv_exit);
#ifdef CONFIG_PNP
module_param_hw_named(nopnp, param_nopnp, uint, other, 0444);
#endif
module_param_hw_named(io, param_io, uint, ioport, 0444);
module_param_hw_named(irq, param_irq, uint, irq, 0444);
module_param_hw_named(dma, param_dma, int, dma, 0444);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pierre Ossman <pierre@ossman.eu>");
MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver");

#ifdef CONFIG_PNP
MODULE_PARM_DESC(nopnp, "Scan for device instead of relying on PNP. (default 0)");
#endif
MODULE_PARM_DESC(io, "I/O base to allocate. Must be 8 byte aligned. (default 0x248)");
MODULE_PARM_DESC(irq, "IRQ to allocate. (default 6)");
MODULE_PARM_DESC(dma, "DMA channel to allocate. -1 for no DMA. (default 2)");