OpenCloudOS-Kernel/drivers/mmc/host/wbsd.c

2048 lines
40 KiB
C
Raw Normal View History

/*
* linux/drivers/mmc/host/wbsd.c - Winbond W83L51xD SD/MMC driver
*
* Copyright (C) 2004-2007 Pierre Ossman, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
*
* Warning!
*
* Changes to the FIFO system should be done with extreme care since
* the hardware is full of bugs related to the FIFO. Known issues are:
*
* - FIFO size field in FSR is always zero.
*
* - FIFO interrupts tend not to work as they should. Interrupts are
* triggered only for full/empty events, not for threshold values.
*
* - On APIC systems the FIFO empty interrupt is sometimes lost.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/pnp.h>
#include <linux/highmem.h>
#include <linux/mmc/host.h>
#include <linux/scatterlist.h>
#include <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 nopnp = 0;
#else
static const unsigned int nopnp = 1;
#endif
static unsigned int io = 0x248;
static unsigned int irq = 6;
static int dma = 2;
/*
* Basic functions
*/
static inline void wbsd_unlock_config(struct wbsd_host *host)
{
BUG_ON(host->config == 0);
outb(host->unlock_code, host->config);
outb(host->unlock_code, host->config);
}
static inline void wbsd_lock_config(struct wbsd_host *host)
{
BUG_ON(host->config == 0);
outb(LOCK_CODE, host->config);
}
static inline void wbsd_write_config(struct wbsd_host *host, u8 reg, u8 value)
{
BUG_ON(host->config == 0);
outb(reg, host->config);
outb(value, host->config + 1);
}
static inline u8 wbsd_read_config(struct wbsd_host *host, u8 reg)
{
BUG_ON(host->config == 0);
outb(reg, host->config);
return inb(host->config + 1);
}
static inline void wbsd_write_index(struct wbsd_host *host, u8 index, u8 value)
{
outb(index, host->base + WBSD_IDXR);
outb(value, host->base + WBSD_DATAR);
}
static inline u8 wbsd_read_index(struct wbsd_host *host, u8 index)
{
outb(index, host->base + WBSD_IDXR);
return inb(host->base + WBSD_DATAR);
}
/*
* Common routines
*/
static void wbsd_init_device(struct wbsd_host *host)
{
u8 setup, ier;
/*
* Reset chip (SD/MMC part) and fifo.
*/
setup = wbsd_read_index(host, WBSD_IDX_SETUP);
setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET;
wbsd_write_index(host, WBSD_IDX_SETUP, setup);
/*
* Set DAT3 to input
*/
setup &= ~WBSD_DAT3_H;
wbsd_write_index(host, WBSD_IDX_SETUP, setup);
host->flags &= ~WBSD_FIGNORE_DETECT;
/*
* Read back default clock.
*/
host->clk = wbsd_read_index(host, WBSD_IDX_CLK);
/*
* Power down port.
*/
outb(WBSD_POWER_N, host->base + WBSD_CSR);
/*
* Set maximum timeout.
*/
wbsd_write_index(host, WBSD_IDX_TAAC, 0x7F);
/*
* Test for card presence
*/
if (inb(host->base + WBSD_CSR) & WBSD_CARDPRESENT)
host->flags |= WBSD_FCARD_PRESENT;
else
host->flags &= ~WBSD_FCARD_PRESENT;
/*
* Enable interesting interrupts.
*/
ier = 0;
ier |= WBSD_EINT_CARD;
ier |= WBSD_EINT_FIFO_THRE;
ier |= WBSD_EINT_CRC;
ier |= WBSD_EINT_TIMEOUT;
ier |= WBSD_EINT_TC;
outb(ier, host->base + WBSD_EIR);
/*
* Clear interrupts.
*/
inb(host->base + WBSD_ISR);
}
static void wbsd_reset(struct wbsd_host *host)
{
u8 setup;
printk(KERN_ERR "%s: Resetting chip\n", mmc_hostname(host->mmc));
/*
* Soft reset of chip (SD/MMC part).
*/
setup = wbsd_read_index(host, WBSD_IDX_SETUP);
setup |= WBSD_SOFT_RESET;
wbsd_write_index(host, WBSD_IDX_SETUP, setup);
}
static void wbsd_request_end(struct wbsd_host *host, struct mmc_request *mrq)
{
unsigned long dmaflags;
if (host->dma >= 0) {
/*
* Release ISA DMA controller.
*/
dmaflags = claim_dma_lock();
disable_dma(host->dma);
clear_dma_ff(host->dma);
release_dma_lock(dmaflags);
/*
* Disable DMA on host.
*/
wbsd_write_index(host, WBSD_IDX_DMA, 0);
}
host->mrq = NULL;
/*
* MMC layer might call back into the driver so first unlock.
*/
spin_unlock(&host->lock);
mmc_request_done(host->mmc, mrq);
spin_lock(&host->lock);
}
/*
* Scatter/gather functions
*/
static inline void wbsd_init_sg(struct wbsd_host *host, struct mmc_data *data)
{
/*
* Get info. about SG list from data structure.
*/
host->cur_sg = data->sg;
host->num_sg = data->sg_len;
host->offset = 0;
host->remain = host->cur_sg->length;
}
static inline int wbsd_next_sg(struct wbsd_host *host)
{
/*
* Skip to next SG entry.
*/
host->cur_sg++;
host->num_sg--;
/*
* Any entries left?
*/
if (host->num_sg > 0) {
host->offset = 0;
host->remain = host->cur_sg->length;
}
return host->num_sg;
}
static inline char *wbsd_sg_to_buffer(struct wbsd_host *host)
{
return sg_virt(host->cur_sg);
}
static inline void wbsd_sg_to_dma(struct wbsd_host *host, struct mmc_data *data)
{
unsigned int len, i;
struct scatterlist *sg;
char *dmabuf = host->dma_buffer;
char *sgbuf;
sg = data->sg;
len = data->sg_len;
for (i = 0; i < len; i++) {
sgbuf = sg_virt(&sg[i]);
memcpy(dmabuf, sgbuf, sg[i].length);
dmabuf += sg[i].length;
}
}
static inline void wbsd_dma_to_sg(struct wbsd_host *host, struct mmc_data *data)
{
unsigned int len, i;
struct scatterlist *sg;
char *dmabuf = host->dma_buffer;
char *sgbuf;
sg = data->sg;
len = data->sg_len;
for (i = 0; i < len; i++) {
sgbuf = sg_virt(&sg[i]);
memcpy(sgbuf, dmabuf, sg[i].length);
dmabuf += sg[i].length;
}
}
/*
* Command handling
*/
static inline void wbsd_get_short_reply(struct wbsd_host *host,
struct mmc_command *cmd)
{
/*
* Correct response type?
*/
if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT) {
cmd->error = -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, fsr, fifo;
/*
* Handle excessive data.
*/
if (host->num_sg == 0)
return;
buffer = wbsd_sg_to_buffer(host) + host->offset;
/*
* Drain the fifo. This has a tendency to loop longer
* than the FIFO length (usually one block).
*/
while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_EMPTY)) {
/*
* The size field in the FSR is broken so we have to
* do some guessing.
*/
if (fsr & WBSD_FIFO_FULL)
fifo = 16;
else if (fsr & WBSD_FIFO_FUTHRE)
fifo = 8;
else
fifo = 1;
for (i = 0; i < fifo; i++) {
*buffer = inb(host->base + WBSD_DFR);
buffer++;
host->offset++;
host->remain--;
data->bytes_xfered++;
/*
* End of scatter list entry?
*/
if (host->remain == 0) {
/*
* Get next entry. Check if last.
*/
if (!wbsd_next_sg(host))
return;
buffer = wbsd_sg_to_buffer(host);
}
}
}
/*
* This is a very dirty hack to solve a
* hardware problem. The chip doesn't trigger
* FIFO threshold interrupts properly.
*/
if ((data->blocks * data->blksz - data->bytes_xfered) < 16)
tasklet_schedule(&host->fifo_tasklet);
}
static void wbsd_fill_fifo(struct wbsd_host *host)
{
struct mmc_data *data = host->mrq->cmd->data;
char *buffer;
int i, fsr, fifo;
/*
* Check that we aren't being called after the
* entire buffer has been transfered.
*/
if (host->num_sg == 0)
return;
buffer = wbsd_sg_to_buffer(host) + host->offset;
/*
* Fill the fifo. This has a tendency to loop longer
* than the FIFO length (usually one block).
*/
while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_FULL)) {
/*
* The size field in the FSR is broken so we have to
* do some guessing.
*/
if (fsr & WBSD_FIFO_EMPTY)
fifo = 0;
else if (fsr & WBSD_FIFO_EMTHRE)
fifo = 8;
else
fifo = 15;
for (i = 16; i > fifo; i--) {
outb(*buffer, host->base + WBSD_DFR);
buffer++;
host->offset++;
host->remain--;
data->bytes_xfered++;
/*
* End of scatter list entry?
*/
if (host->remain == 0) {
/*
* Get next entry. Check if last.
*/
if (!wbsd_next_sg(host))
return;
buffer = wbsd_sg_to_buffer(host);
}
}
}
/*
* The controller stops sending interrupts for
* 'FIFO empty' under certain conditions. So we
* need to be a bit more pro-active.
*/
tasklet_schedule(&host->fifo_tasklet);
}
static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data)
{
u16 blksize;
u8 setup;
unsigned long dmaflags;
unsigned int size;
/*
* 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) {
printk(KERN_ERR "%s: Incomplete DMA transfer. "
"%d bytes left.\n",
mmc_hostname(host->mmc), count);
if (!data->error)
data->error = -EIO;
} else {
/*
* Transfer data from DMA buffer to
* SG list.
*/
if (data->flags & MMC_DATA_READ)
wbsd_dma_to_sg(host, data);
}
if (data->error) {
if (data->bytes_xfered)
data->bytes_xfered -= data->blksz;
}
}
wbsd_request_end(host, host->mrq);
}
/*****************************************************************************\
* *
* MMC layer callbacks *
* *
\*****************************************************************************/
static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct wbsd_host *host = mmc_priv(mmc);
struct mmc_command *cmd;
/*
* Disable tasklets to avoid a deadlock.
*/
spin_lock_bh(&host->lock);
BUG_ON(host->mrq != NULL);
cmd = mrq->cmd;
host->mrq = mrq;
/*
* Check that there is actually a card in the slot.
*/
if (!(host->flags & WBSD_FCARD_PRESENT)) {
cmd->error = -ENOMEDIUM;
goto done;
}
if (cmd->data) {
/*
* The hardware is so delightfully stupid that it has a list
* of "data" commands. If a command isn't on this list, it'll
* just go back to the idle state and won't send any data
* interrupts.
*/
switch (cmd->opcode) {
case 11:
case 17:
case 18:
case 20:
case 24:
case 25:
case 26:
case 27:
case 30:
case 42:
case 56:
break;
/* ACMDs. We don't keep track of state, so we just treat them
* like any other command. */
case 51:
break;
default:
#ifdef CONFIG_MMC_DEBUG
printk(KERN_WARNING "%s: Data command %d is not "
"supported by this controller.\n",
mmc_hostname(host->mmc), cmd->opcode);
#endif
cmd->error = -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
* transfered.
*/
if (cmd->data && !cmd->error) {
/*
* Dirty fix for hardware bug.
*/
if (host->dma == -1)
tasklet_schedule(&host->fifo_tasklet);
spin_unlock_bh(&host->lock);
return;
}
done:
wbsd_request_end(host, mrq);
spin_unlock_bh(&host->lock);
}
static void wbsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct wbsd_host *host = mmc_priv(mmc);
u8 clk, setup, pwr;
spin_lock_bh(&host->lock);
/*
* Reset the chip on each power off.
* Should clear out any weird states.
*/
if (ios->power_mode == MMC_POWER_OFF)
wbsd_init_device(host);
if (ios->clock >= 24000000)
clk = WBSD_CLK_24M;
else if (ios->clock >= 16000000)
clk = WBSD_CLK_16M;
else if (ios->clock >= 12000000)
clk = WBSD_CLK_12M;
else
clk = WBSD_CLK_375K;
/*
* Only write to the clock register when
* there is an actual change.
*/
if (clk != host->clk) {
wbsd_write_index(host, WBSD_IDX_CLK, clk);
host->clk = clk;
}
/*
* Power up card.
*/
if (ios->power_mode != MMC_POWER_OFF) {
pwr = inb(host->base + WBSD_CSR);
pwr &= ~WBSD_POWER_N;
outb(pwr, host->base + WBSD_CSR);
}
/*
* MMC cards need to have pin 1 high during init.
* It wreaks havoc with the card detection though so
* that needs to be disabled.
*/
setup = wbsd_read_index(host, WBSD_IDX_SETUP);
if (ios->chip_select == MMC_CS_HIGH) {
BUG_ON(ios->bus_width != MMC_BUS_WIDTH_1);
setup |= WBSD_DAT3_H;
host->flags |= WBSD_FIGNORE_DETECT;
} else {
if (setup & WBSD_DAT3_H) {
setup &= ~WBSD_DAT3_H;
/*
* We cannot resume card detection immediatly
* because of capacitance and delays in the chip.
*/
mod_timer(&host->ignore_timer, jiffies + HZ / 100);
}
}
wbsd_write_index(host, WBSD_IDX_SETUP, setup);
/*
* Store bus width for later. Will be used when
* setting up the data transfer.
*/
host->bus_width = ios->bus_width;
spin_unlock_bh(&host->lock);
}
static int wbsd_get_ro(struct mmc_host *mmc)
{
struct wbsd_host *host = mmc_priv(mmc);
u8 csr;
spin_lock_bh(&host->lock);
csr = inb(host->base + WBSD_CSR);
csr |= WBSD_MSLED;
outb(csr, host->base + WBSD_CSR);
mdelay(1);
csr = inb(host->base + WBSD_CSR);
csr &= ~WBSD_MSLED;
outb(csr, host->base + WBSD_CSR);
spin_unlock_bh(&host->lock);
return csr & WBSD_WRPT;
}
static const struct mmc_host_ops wbsd_ops = {
.request = wbsd_request,
.set_ios = wbsd_set_ios,
.get_ro = wbsd_get_ro,
};
/*****************************************************************************\
* *
* Interrupt handling *
* *
\*****************************************************************************/
/*
* Helper function to reset detection ignore
*/
static void wbsd_reset_ignore(unsigned long data)
{
struct wbsd_host *host = (struct wbsd_host *)data;
BUG_ON(host == NULL);
DBG("Resetting card detection ignore\n");
spin_lock_bh(&host->lock);
host->flags &= ~WBSD_FIGNORE_DETECT;
/*
* Card status might have changed during the
* blackout.
*/
tasklet_schedule(&host->card_tasklet);
spin_unlock_bh(&host->lock);
}
/*
* Tasklets
*/
static inline struct mmc_data *wbsd_get_data(struct wbsd_host *host)
{
WARN_ON(!host->mrq);
if (!host->mrq)
return NULL;
WARN_ON(!host->mrq->cmd);
if (!host->mrq->cmd)
return NULL;
WARN_ON(!host->mrq->cmd->data);
if (!host->mrq->cmd->data)
return NULL;
return host->mrq->cmd->data;
}
static void wbsd_tasklet_card(unsigned long param)
{
struct wbsd_host *host = (struct wbsd_host *)param;
u8 csr;
int delay = -1;
spin_lock(&host->lock);
if (host->flags & WBSD_FIGNORE_DETECT) {
spin_unlock(&host->lock);
return;
}
csr = inb(host->base + WBSD_CSR);
WARN_ON(csr == 0xff);
if (csr & WBSD_CARDPRESENT) {
if (!(host->flags & WBSD_FCARD_PRESENT)) {
DBG("Card inserted\n");
host->flags |= WBSD_FCARD_PRESENT;
delay = 500;
}
} else if (host->flags & WBSD_FCARD_PRESENT) {
DBG("Card removed\n");
host->flags &= ~WBSD_FCARD_PRESENT;
if (host->mrq) {
printk(KERN_ERR "%s: Card removed during transfer!\n",
mmc_hostname(host->mmc));
wbsd_reset(host);
host->mrq->cmd->error = -ENOMEDIUM;
tasklet_schedule(&host->finish_tasklet);
}
delay = 0;
}
/*
* Unlock first since we might get a call back.
*/
spin_unlock(&host->lock);
if (delay != -1)
mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
}
static void wbsd_tasklet_fifo(unsigned long param)
{
struct wbsd_host *host = (struct wbsd_host *)param;
struct mmc_data *data;
spin_lock(&host->lock);
if (!host->mrq)
goto end;
data = wbsd_get_data(host);
if (!data)
goto end;
if (data->flags & MMC_DATA_WRITE)
wbsd_fill_fifo(host);
else
wbsd_empty_fifo(host);
/*
* Done?
*/
if (host->num_sg == 0) {
wbsd_write_index(host, WBSD_IDX_FIFOEN, 0);
tasklet_schedule(&host->finish_tasklet);
}
end:
spin_unlock(&host->lock);
}
static void wbsd_tasklet_crc(unsigned long param)
{
struct wbsd_host *host = (struct wbsd_host *)param;
struct mmc_data *data;
spin_lock(&host->lock);
if (!host->mrq)
goto end;
data = wbsd_get_data(host);
if (!data)
goto end;
DBGF("CRC error\n");
data->error = -EILSEQ;
tasklet_schedule(&host->finish_tasklet);
end:
spin_unlock(&host->lock);
}
static void wbsd_tasklet_timeout(unsigned long param)
{
struct wbsd_host *host = (struct wbsd_host *)param;
struct mmc_data *data;
spin_lock(&host->lock);
if (!host->mrq)
goto end;
data = wbsd_get_data(host);
if (!data)
goto end;
DBGF("Timeout\n");
data->error = -ETIMEDOUT;
tasklet_schedule(&host->finish_tasklet);
end:
spin_unlock(&host->lock);
}
static void wbsd_tasklet_finish(unsigned long param)
{
struct wbsd_host *host = (struct wbsd_host *)param;
struct mmc_data *data;
spin_lock(&host->lock);
WARN_ON(!host->mrq);
if (!host->mrq)
goto end;
data = wbsd_get_data(host);
if (!data)
goto end;
wbsd_finish_data(host, data);
end:
spin_unlock(&host->lock);
}
/*
* Interrupt handling
*/
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
static irqreturn_t wbsd_irq(int irq, void *dev_id)
{
struct wbsd_host *host = dev_id;
int isr;
isr = inb(host->base + WBSD_ISR);
/*
* Was it actually our hardware that caused the interrupt?
*/
if (isr == 0xff || isr == 0x00)
return IRQ_NONE;
host->isr |= isr;
/*
* Schedule tasklets as needed.
*/
if (isr & WBSD_INT_CARD)
tasklet_schedule(&host->card_tasklet);
if (isr & WBSD_INT_FIFO_THRE)
tasklet_schedule(&host->fifo_tasklet);
if (isr & WBSD_INT_CRC)
tasklet_hi_schedule(&host->crc_tasklet);
if (isr & WBSD_INT_TIMEOUT)
tasklet_hi_schedule(&host->timeout_tasklet);
if (isr & WBSD_INT_TC)
tasklet_schedule(&host->finish_tasklet);
return IRQ_HANDLED;
}
/*****************************************************************************\
* *
* Device initialisation and shutdown *
* *
\*****************************************************************************/
/*
* Allocate/free MMC structure.
*/
static int __devinit wbsd_alloc_mmc(struct device *dev)
{
struct mmc_host *mmc;
struct wbsd_host *host;
/*
* Allocate MMC structure.
*/
mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev);
if (!mmc)
return -ENOMEM;
host = mmc_priv(mmc);
host->mmc = mmc;
host->dma = -1;
/*
* Set host parameters.
*/
mmc->ops = &wbsd_ops;
mmc->f_min = 375000;
mmc->f_max = 24000000;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE;
spin_lock_init(&host->lock);
/*
* Set up timers
*/
init_timer(&host->ignore_timer);
host->ignore_timer.data = (unsigned long)host;
host->ignore_timer.function = wbsd_reset_ignore;
/*
* Maximum number of segments. Worst case is one sector per segment
* so this will be 64kB/512.
*/
mmc->max_hw_segs = 128;
mmc->max_phys_segs = 128;
/*
* Maximum request size. Also limited by 64KiB buffer.
*/
mmc->max_req_size = 65536;
/*
* Maximum segment size. Could be one segment with the maximum number
* of bytes.
*/
mmc->max_seg_size = mmc->max_req_size;
/*
* Maximum block size. We have 12 bits (= 4095) but have to subtract
* space for CRC. So the maximum is 4095 - 4*2 = 4087.
*/
mmc->max_blk_size = 4087;
/*
* Maximum block count. There is no real limit so the maximum
* request size will be the only restriction.
*/
mmc->max_blk_count = mmc->max_req_size;
dev_set_drvdata(dev, mmc);
return 0;
}
static void wbsd_free_mmc(struct device *dev)
{
struct mmc_host *mmc;
struct wbsd_host *host;
mmc = dev_get_drvdata(dev);
if (!mmc)
return;
host = mmc_priv(mmc);
BUG_ON(host == NULL);
del_timer_sync(&host->ignore_timer);
mmc_free_host(mmc);
dev_set_drvdata(dev, NULL);
}
/*
* Scan for known chip id:s
*/
static int __devinit wbsd_scan(struct wbsd_host *host)
{
int i, j, k;
int id;
/*
* Iterate through all ports, all codes to
* find hardware that is in our known list.
*/
for (i = 0; i < ARRAY_SIZE(config_ports); i++) {
if (!request_region(config_ports[i], 2, DRIVER_NAME))
continue;
for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) {
id = 0xFFFF;
host->config = config_ports[i];
host->unlock_code = unlock_codes[j];
wbsd_unlock_config(host);
outb(WBSD_CONF_ID_HI, config_ports[i]);
id = inb(config_ports[i] + 1) << 8;
outb(WBSD_CONF_ID_LO, config_ports[i]);
id |= inb(config_ports[i] + 1);
wbsd_lock_config(host);
for (k = 0; k < ARRAY_SIZE(valid_ids); k++) {
if (id == valid_ids[k]) {
host->chip_id = id;
return 0;
}
}
if (id != 0xFFFF) {
DBG("Unknown hardware (id %x) found at %x\n",
id, config_ports[i]);
}
}
release_region(config_ports[i], 2);
}
host->config = 0;
host->unlock_code = 0;
return -ENODEV;
}
/*
* Allocate/free io port ranges
*/
static int __devinit wbsd_request_region(struct wbsd_host *host, int base)
{
if (base & 0x7)
return -EINVAL;
if (!request_region(base, 8, DRIVER_NAME))
return -EIO;
host->base = base;
return 0;
}
static void wbsd_release_regions(struct wbsd_host *host)
{
if (host->base)
release_region(host->base, 8);
host->base = 0;
if (host->config)
release_region(host->config, 2);
host->config = 0;
}
/*
* Allocate/free DMA port and buffer
*/
static void __devinit wbsd_request_dma(struct wbsd_host *host, int dma)
{
if (dma < 0)
return;
if (request_dma(dma, DRIVER_NAME))
goto err;
/*
* We need to allocate a special buffer in
* order for ISA to be able to DMA to it.
*/
host->dma_buffer = kmalloc(WBSD_DMA_SIZE,
GFP_NOIO | GFP_DMA | __GFP_REPEAT | __GFP_NOWARN);
if (!host->dma_buffer)
goto free;
/*
* Translate the address to a physical address.
*/
host->dma_addr = dma_map_single(mmc_dev(host->mmc), host->dma_buffer,
WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
/*
* ISA DMA must be aligned on a 64k basis.
*/
if ((host->dma_addr & 0xffff) != 0)
goto kfree;
/*
* ISA cannot access memory above 16 MB.
*/
else if (host->dma_addr >= 0x1000000)
goto kfree;
host->dma = dma;
return;
kfree:
/*
* If we've gotten here then there is some kind of alignment bug
*/
BUG_ON(1);
dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
host->dma_addr = (dma_addr_t)NULL;
kfree(host->dma_buffer);
host->dma_buffer = NULL;
free:
free_dma(dma);
err:
printk(KERN_WARNING DRIVER_NAME ": Unable to allocate DMA %d. "
"Falling back on FIFO.\n", dma);
}
static void wbsd_release_dma(struct wbsd_host *host)
{
if (host->dma_addr) {
dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
}
kfree(host->dma_buffer);
if (host->dma >= 0)
free_dma(host->dma);
host->dma = -1;
host->dma_buffer = NULL;
host->dma_addr = (dma_addr_t)NULL;
}
/*
* Allocate/free IRQ.
*/
static int __devinit wbsd_request_irq(struct wbsd_host *host, int irq)
{
int ret;
/*
* Set up tasklets. Must be done before requesting interrupt.
*/
tasklet_init(&host->card_tasklet, wbsd_tasklet_card,
(unsigned long)host);
tasklet_init(&host->fifo_tasklet, wbsd_tasklet_fifo,
(unsigned long)host);
tasklet_init(&host->crc_tasklet, wbsd_tasklet_crc,
(unsigned long)host);
tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout,
(unsigned long)host);
tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish,
(unsigned long)host);
/*
* Allocate interrupt.
*/
ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host);
if (ret)
return ret;
host->irq = irq;
return 0;
}
static void wbsd_release_irq(struct wbsd_host *host)
{
if (!host->irq)
return;
free_irq(host->irq, host);
host->irq = 0;
tasklet_kill(&host->card_tasklet);
tasklet_kill(&host->fifo_tasklet);
tasklet_kill(&host->crc_tasklet);
tasklet_kill(&host->timeout_tasklet);
tasklet_kill(&host->finish_tasklet);
}
/*
* Allocate all resources for the host.
*/
static int __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 wbsd_release_resources(struct wbsd_host *host)
{
wbsd_release_dma(host);
wbsd_release_irq(host);
wbsd_release_regions(host);
}
/*
* Configure the resources the chip should use.
*/
static void wbsd_chip_config(struct wbsd_host *host)
{
wbsd_unlock_config(host);
/*
* Reset the chip.
*/
wbsd_write_config(host, WBSD_CONF_SWRST, 1);
wbsd_write_config(host, WBSD_CONF_SWRST, 0);
/*
* Select SD/MMC function.
*/
wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
/*
* Set up card detection.
*/
wbsd_write_config(host, WBSD_CONF_PINS, WBSD_PINS_DETECT_GP11);
/*
* Configure chip
*/
wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8);
wbsd_write_config(host, WBSD_CONF_PORT_LO, host->base & 0xff);
wbsd_write_config(host, WBSD_CONF_IRQ, host->irq);
if (host->dma >= 0)
wbsd_write_config(host, WBSD_CONF_DRQ, host->dma);
/*
* Enable and power up chip.
*/
wbsd_write_config(host, WBSD_CONF_ENABLE, 1);
wbsd_write_config(host, WBSD_CONF_POWER, 0x20);
wbsd_lock_config(host);
}
/*
* Check that configured resources are correct.
*/
static int wbsd_chip_validate(struct wbsd_host *host)
{
int base, irq, dma;
wbsd_unlock_config(host);
/*
* Select SD/MMC function.
*/
wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
/*
* Read configuration.
*/
base = wbsd_read_config(host, WBSD_CONF_PORT_HI) << 8;
base |= wbsd_read_config(host, WBSD_CONF_PORT_LO);
irq = wbsd_read_config(host, WBSD_CONF_IRQ);
dma = wbsd_read_config(host, WBSD_CONF_DRQ);
wbsd_lock_config(host);
/*
* Validate against given configuration.
*/
if (base != host->base)
return 0;
if (irq != host->irq)
return 0;
if ((dma != host->dma) && (host->dma != -1))
return 0;
return 1;
}
/*
* Powers down the SD function
*/
static void wbsd_chip_poweroff(struct wbsd_host *host)
{
wbsd_unlock_config(host);
wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
wbsd_write_config(host, WBSD_CONF_ENABLE, 0);
wbsd_lock_config(host);
}
/*****************************************************************************\
* *
* Devices setup and shutdown *
* *
\*****************************************************************************/
static int __devinit wbsd_init(struct device *dev, int base, int irq, int dma,
int pnp)
{
struct wbsd_host *host = NULL;
struct mmc_host *mmc = NULL;
int ret;
ret = wbsd_alloc_mmc(dev);
if (ret)
return ret;
mmc = dev_get_drvdata(dev);
host = mmc_priv(mmc);
/*
* Scan for hardware.
*/
ret = wbsd_scan(host);
if (ret) {
if (pnp && (ret == -ENODEV)) {
printk(KERN_WARNING DRIVER_NAME
": Unable to confirm device presence. You may "
"experience lock-ups.\n");
} else {
wbsd_free_mmc(dev);
return ret;
}
}
/*
* Request resources.
*/
ret = wbsd_request_resources(host, base, irq, dma);
if (ret) {
wbsd_release_resources(host);
wbsd_free_mmc(dev);
return ret;
}
/*
* See if chip needs to be configured.
*/
if (pnp) {
if ((host->config != 0) && !wbsd_chip_validate(host)) {
printk(KERN_WARNING DRIVER_NAME
": PnP active but chip not configured! "
"You probably have a buggy BIOS. "
"Configuring chip manually.\n");
wbsd_chip_config(host);
}
} else
wbsd_chip_config(host);
/*
* Power Management stuff. No idea how this works.
* Not tested.
*/
#ifdef CONFIG_PM
if (host->config) {
wbsd_unlock_config(host);
wbsd_write_config(host, WBSD_CONF_PME, 0xA0);
wbsd_lock_config(host);
}
#endif
/*
* Allow device to initialise itself properly.
*/
mdelay(5);
/*
* Reset the chip into a known state.
*/
wbsd_init_device(host);
mmc_add_host(mmc);
printk(KERN_INFO "%s: W83L51xD", mmc_hostname(mmc));
if (host->chip_id != 0)
printk(" id %x", (int)host->chip_id);
printk(" at 0x%x irq %d", (int)host->base, (int)host->irq);
if (host->dma >= 0)
printk(" dma %d", (int)host->dma);
else
printk(" FIFO");
if (pnp)
printk(" PnP");
printk("\n");
return 0;
}
static void __devexit wbsd_shutdown(struct device *dev, int pnp)
{
struct mmc_host *mmc = dev_get_drvdata(dev);
struct wbsd_host *host;
if (!mmc)
return;
host = mmc_priv(mmc);
mmc_remove_host(mmc);
/*
* Power down the SD/MMC function.
*/
if (!pnp)
wbsd_chip_poweroff(host);
wbsd_release_resources(host);
wbsd_free_mmc(dev);
}
/*
* Non-PnP
*/
static int __devinit wbsd_probe(struct platform_device *dev)
{
/* Use the module parameters for resources */
return wbsd_init(&dev->dev, io, irq, dma, 0);
}
static int __devexit wbsd_remove(struct platform_device *dev)
{
wbsd_shutdown(&dev->dev, 0);
return 0;
}
/*
* PnP
*/
#ifdef CONFIG_PNP
static int __devinit
wbsd_pnp_probe(struct pnp_dev *pnpdev, const struct pnp_device_id *dev_id)
{
int io, irq, dma;
/*
* Get resources from PnP layer.
*/
io = pnp_port_start(pnpdev, 0);
irq = pnp_irq(pnpdev, 0);
if (pnp_dma_valid(pnpdev, 0))
dma = pnp_dma(pnpdev, 0);
else
dma = -1;
DBGF("PnP resources: port %3x irq %d dma %d\n", io, irq, dma);
return wbsd_init(&pnpdev->dev, io, irq, dma, 1);
}
static void __devexit wbsd_pnp_remove(struct pnp_dev *dev)
{
wbsd_shutdown(&dev->dev, 1);
}
#endif /* CONFIG_PNP */
/*
* Power management
*/
#ifdef CONFIG_PM
static int wbsd_suspend(struct wbsd_host *host, pm_message_t state)
{
BUG_ON(host == NULL);
return mmc_suspend_host(host->mmc, state);
}
static int wbsd_resume(struct wbsd_host *host)
{
BUG_ON(host == NULL);
wbsd_init_device(host);
return mmc_resume_host(host->mmc);
}
static int wbsd_platform_suspend(struct platform_device *dev,
pm_message_t state)
{
struct mmc_host *mmc = platform_get_drvdata(dev);
struct wbsd_host *host;
int ret;
if (mmc == NULL)
return 0;
DBGF("Suspending...\n");
host = mmc_priv(mmc);
ret = wbsd_suspend(host, state);
if (ret)
return ret;
wbsd_chip_poweroff(host);
return 0;
}
static int wbsd_platform_resume(struct platform_device *dev)
{
struct mmc_host *mmc = platform_get_drvdata(dev);
struct wbsd_host *host;
if (mmc == NULL)
return 0;
DBGF("Resuming...\n");
host = mmc_priv(mmc);
wbsd_chip_config(host);
/*
* Allow device to initialise itself properly.
*/
mdelay(5);
return wbsd_resume(host);
}
#ifdef CONFIG_PNP
static int wbsd_pnp_suspend(struct pnp_dev *pnp_dev, pm_message_t state)
{
struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
struct wbsd_host *host;
if (mmc == NULL)
return 0;
DBGF("Suspending...\n");
host = mmc_priv(mmc);
return wbsd_suspend(host, state);
}
static int wbsd_pnp_resume(struct pnp_dev *pnp_dev)
{
struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
struct wbsd_host *host;
if (mmc == NULL)
return 0;
DBGF("Resuming...\n");
host = mmc_priv(mmc);
/*
* See if chip needs to be configured.
*/
if (host->config != 0) {
if (!wbsd_chip_validate(host)) {
printk(KERN_WARNING DRIVER_NAME
": PnP active but chip not configured! "
"You probably have a buggy BIOS. "
"Configuring chip manually.\n");
wbsd_chip_config(host);
}
}
/*
* Allow device to initialise itself properly.
*/
mdelay(5);
return wbsd_resume(host);
}
#endif /* CONFIG_PNP */
#else /* CONFIG_PM */
#define wbsd_platform_suspend NULL
#define wbsd_platform_resume NULL
#define wbsd_pnp_suspend NULL
#define wbsd_pnp_resume NULL
#endif /* CONFIG_PM */
static struct platform_device *wbsd_device;
static struct platform_driver wbsd_driver = {
.probe = wbsd_probe,
.remove = __devexit_p(wbsd_remove),
.suspend = wbsd_platform_suspend,
.resume = wbsd_platform_resume,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
};
#ifdef CONFIG_PNP
static struct pnp_driver wbsd_pnp_driver = {
.name = DRIVER_NAME,
.id_table = pnp_dev_table,
.probe = wbsd_pnp_probe,
.remove = __devexit_p(wbsd_pnp_remove),
.suspend = wbsd_pnp_suspend,
.resume = wbsd_pnp_resume,
};
#endif /* CONFIG_PNP */
/*
* Module loading/unloading
*/
static int __init wbsd_drv_init(void)
{
int result;
printk(KERN_INFO DRIVER_NAME
": Winbond W83L51xD SD/MMC card interface driver\n");
printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
#ifdef CONFIG_PNP
if (!nopnp) {
result = pnp_register_driver(&wbsd_pnp_driver);
if (result < 0)
return result;
}
#endif /* CONFIG_PNP */
if (nopnp) {
result = platform_driver_register(&wbsd_driver);
if (result < 0)
return result;
wbsd_device = platform_device_alloc(DRIVER_NAME, -1);
if (!wbsd_device) {
platform_driver_unregister(&wbsd_driver);
return -ENOMEM;
}
result = platform_device_add(wbsd_device);
if (result) {
platform_device_put(wbsd_device);
platform_driver_unregister(&wbsd_driver);
return result;
}
}
return 0;
}
static void __exit wbsd_drv_exit(void)
{
#ifdef CONFIG_PNP
if (!nopnp)
pnp_unregister_driver(&wbsd_pnp_driver);
#endif /* CONFIG_PNP */
if (nopnp) {
platform_device_unregister(wbsd_device);
platform_driver_unregister(&wbsd_driver);
}
DBG("unloaded\n");
}
module_init(wbsd_drv_init);
module_exit(wbsd_drv_exit);
#ifdef CONFIG_PNP
module_param(nopnp, uint, 0444);
#endif
module_param(io, uint, 0444);
module_param(irq, uint, 0444);
module_param(dma, int, 0444);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>");
MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver");
#ifdef CONFIG_PNP
MODULE_PARM_DESC(nopnp, "Scan for device instead of relying on PNP. (default 0)");
#endif
MODULE_PARM_DESC(io, "I/O base to allocate. Must be 8 byte aligned. (default 0x248)");
MODULE_PARM_DESC(irq, "IRQ to allocate. (default 6)");
MODULE_PARM_DESC(dma, "DMA channel to allocate. -1 for no DMA. (default 2)");