Commit 85bcc130 authored by Pierre Ossman's avatar Pierre Ossman Committed by Russell King

[PATCH] MMC: wbsd update

Updates to the wbsd driver.
                                                                                
* Fix to handle DAT3 card detection.
* Fixed bug which could cause large writes to stall in FIFO mode.
* Plug 'n Play support. In most cases you need ACPI PNP for this to work.
* Uses generic DMA API (ISA dependency removed).
parent 88d7bd8c
...@@ -51,7 +51,7 @@ config MMC_PXA ...@@ -51,7 +51,7 @@ config MMC_PXA
config MMC_WBSD config MMC_WBSD
tristate "Winbond W83L51xD SD/MMC Card Interface support" tristate "Winbond W83L51xD SD/MMC Card Interface support"
depends on MMC && ISA && ISA_DMA_API depends on MMC && ISA_DMA_API
help help
This selects the Winbond(R) W83L51xD Secure digital and This selects the Winbond(R) W83L51xD Secure digital and
Multimedia card Interface. Multimedia card Interface.
......
...@@ -28,7 +28,9 @@ ...@@ -28,7 +28,9 @@
#include <linux/ioport.h> #include <linux/ioport.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/pnp.h>
#include <linux/highmem.h> #include <linux/highmem.h>
#include <linux/mmc/host.h> #include <linux/mmc/host.h>
#include <linux/mmc/protocol.h> #include <linux/mmc/protocol.h>
...@@ -40,7 +42,7 @@ ...@@ -40,7 +42,7 @@
#include "wbsd.h" #include "wbsd.h"
#define DRIVER_NAME "wbsd" #define DRIVER_NAME "wbsd"
#define DRIVER_VERSION "1.1" #define DRIVER_VERSION "1.2"
#ifdef CONFIG_MMC_DEBUG #ifdef CONFIG_MMC_DEBUG
#define DBG(x...) \ #define DBG(x...) \
...@@ -52,10 +54,6 @@ ...@@ -52,10 +54,6 @@
#define DBGF(x...) do { } while (0) #define DBGF(x...) do { } while (0)
#endif #endif
static unsigned int io = 0x248;
static unsigned int irq = 6;
static int dma = 2;
#ifdef CONFIG_MMC_DEBUG #ifdef CONFIG_MMC_DEBUG
void DBG_REG(int reg, u8 value) void DBG_REG(int reg, u8 value)
{ {
...@@ -78,29 +76,62 @@ void DBG_REG(int reg, u8 value) ...@@ -78,29 +76,62 @@ void DBG_REG(int reg, u8 value)
#define DBG_REG(r, v) do {} while (0) #define DBG_REG(r, v) do {} while (0)
#endif #endif
/*
* Device resources
*/
#ifdef CONFIG_PNP
static const struct pnp_device_id pnp_dev_table[] = {
{ "WEC0517", 0 },
{ "WEC0518", 0 },
{ "", 0 },
};
MODULE_DEVICE_TABLE(pnp, pnp_dev_table);
#endif /* CONFIG_PNP */
#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 * Basic functions
*/ */
static inline void wbsd_unlock_config(struct wbsd_host* host) 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);
outb(host->unlock_code, host->config); outb(host->unlock_code, host->config);
} }
static inline void wbsd_lock_config(struct wbsd_host* host) static inline void wbsd_lock_config(struct wbsd_host* host)
{ {
BUG_ON(host->config == 0);
outb(LOCK_CODE, host->config); outb(LOCK_CODE, host->config);
} }
static inline void wbsd_write_config(struct wbsd_host* host, u8 reg, u8 value) static inline void wbsd_write_config(struct wbsd_host* host, u8 reg, u8 value)
{ {
BUG_ON(host->config == 0);
outb(reg, host->config); outb(reg, host->config);
outb(value, host->config + 1); outb(value, host->config + 1);
} }
static inline u8 wbsd_read_config(struct wbsd_host* host, u8 reg) static inline u8 wbsd_read_config(struct wbsd_host* host, u8 reg)
{ {
BUG_ON(host->config == 0);
outb(reg, host->config); outb(reg, host->config);
return inb(host->config + 1); return inb(host->config + 1);
} }
...@@ -132,6 +163,13 @@ static void wbsd_init_device(struct wbsd_host* host) ...@@ -132,6 +163,13 @@ static void wbsd_init_device(struct wbsd_host* host)
setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET; setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET;
wbsd_write_index(host, WBSD_IDX_SETUP, setup); 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. * Read back default clock.
*/ */
...@@ -147,6 +185,14 @@ static void wbsd_init_device(struct wbsd_host* host) ...@@ -147,6 +185,14 @@ static void wbsd_init_device(struct wbsd_host* host)
*/ */
wbsd_write_index(host, WBSD_IDX_TAAC, 0x7F); 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. * Enable interesting interrupts.
*/ */
...@@ -407,8 +453,6 @@ static inline void wbsd_get_long_reply(struct wbsd_host* host, ...@@ -407,8 +453,6 @@ static inline void wbsd_get_long_reply(struct wbsd_host* host,
} }
} }
static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs);
static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd) static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd)
{ {
int i; int i;
...@@ -646,6 +690,13 @@ static void wbsd_fill_fifo(struct wbsd_host* host) ...@@ -646,6 +690,13 @@ static void wbsd_fill_fifo(struct wbsd_host* host)
} }
wbsd_kunmap_sg(host); wbsd_kunmap_sg(host);
/*
* The controller stops sending interrupts for
* 'FIFO empty' under certain conditions. So we
* need to be a bit more pro-active.
*/
tasklet_schedule(&host->fifo_tasklet);
} }
static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data) static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data)
...@@ -850,9 +901,11 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data) ...@@ -850,9 +901,11 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data)
wbsd_request_end(host, host->mrq); wbsd_request_end(host, host->mrq);
} }
/* /*****************************************************************************\
* MMC Callbacks * *
*/ * MMC layer callbacks *
* *
\*****************************************************************************/
static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq) static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq)
{ {
...@@ -874,7 +927,7 @@ static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq) ...@@ -874,7 +927,7 @@ static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq)
* If there is no card in the slot then * If there is no card in the slot then
* timeout immediatly. * timeout immediatly.
*/ */
if (!(inb(host->base + WBSD_CSR) & WBSD_CARDPRESENT)) if (!(host->flags & WBSD_FCARD_PRESENT))
{ {
cmd->error = MMC_ERR_TIMEOUT; cmd->error = MMC_ERR_TIMEOUT;
goto done; goto done;
...@@ -953,33 +1006,50 @@ static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios) ...@@ -953,33 +1006,50 @@ static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios)
host->clk = clk; host->clk = clk;
} }
/*
* Power up card.
*/
if (ios->power_mode != MMC_POWER_OFF) if (ios->power_mode != MMC_POWER_OFF)
{ {
/*
* Power up card.
*/
pwr = inb(host->base + WBSD_CSR); pwr = inb(host->base + WBSD_CSR);
pwr &= ~WBSD_POWER_N; pwr &= ~WBSD_POWER_N;
outb(pwr, host->base + WBSD_CSR); outb(pwr, host->base + WBSD_CSR);
/*
* This behaviour is stolen from the
* Windows driver. Don't know why, but
* it is needed.
*/
setup = wbsd_read_index(host, WBSD_IDX_SETUP);
if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
setup |= WBSD_DAT3_H;
else
setup &= ~WBSD_DAT3_H;
wbsd_write_index(host, WBSD_IDX_SETUP, setup);
mdelay(1);
} }
/*
* MMC cards need to have pin 1 high during init.
* Init time corresponds rather nicely with the bus mode.
* It wreaks havoc with the card detection though so
* that needs to be disabed.
*/
setup = wbsd_read_index(host, WBSD_IDX_SETUP);
if ((ios->power_mode == MMC_POWER_ON) &&
(ios->bus_mode == MMC_BUSMODE_OPENDRAIN))
{
setup |= WBSD_DAT3_H;
host->flags |= WBSD_FIGNORE_DETECT;
}
else
{
setup &= ~WBSD_DAT3_H;
host->flags &= ~WBSD_FIGNORE_DETECT;
}
wbsd_write_index(host, WBSD_IDX_SETUP, setup);
spin_unlock_bh(&host->lock); spin_unlock_bh(&host->lock);
} }
static struct mmc_host_ops wbsd_ops = {
.request = wbsd_request,
.set_ios = wbsd_set_ios,
};
/*****************************************************************************\
* *
* Interrupt handling *
* *
\*****************************************************************************/
/* /*
* Tasklets * Tasklets
*/ */
...@@ -1005,17 +1075,33 @@ static void wbsd_tasklet_card(unsigned long param) ...@@ -1005,17 +1075,33 @@ static void wbsd_tasklet_card(unsigned long param)
{ {
struct wbsd_host* host = (struct wbsd_host*)param; struct wbsd_host* host = (struct wbsd_host*)param;
u8 csr; u8 csr;
int change = 0;
spin_lock(&host->lock); spin_lock(&host->lock);
if (host->flags & WBSD_FIGNORE_DETECT)
{
spin_unlock(&host->lock);
return;
}
csr = inb(host->base + WBSD_CSR); csr = inb(host->base + WBSD_CSR);
WARN_ON(csr == 0xff); WARN_ON(csr == 0xff);
if (csr & WBSD_CARDPRESENT) if (csr & WBSD_CARDPRESENT)
DBG("Card inserted\n"); {
else if (!(host->flags & WBSD_FCARD_PRESENT))
{
DBG("Card inserted\n");
host->flags |= WBSD_FCARD_PRESENT;
change = 1;
}
}
else if (host->flags & WBSD_FCARD_PRESENT)
{ {
DBG("Card removed\n"); DBG("Card removed\n");
host->flags &= ~WBSD_FCARD_PRESENT;
change = 1;
if (host->mrq) if (host->mrq)
{ {
...@@ -1033,7 +1119,8 @@ static void wbsd_tasklet_card(unsigned long param) ...@@ -1033,7 +1119,8 @@ static void wbsd_tasklet_card(unsigned long param)
*/ */
spin_unlock(&host->lock); spin_unlock(&host->lock);
mmc_detect_change(host->mmc); if (change)
mmc_detect_change(host->mmc);
} }
static void wbsd_tasklet_fifo(unsigned long param) static void wbsd_tasklet_fifo(unsigned long param)
...@@ -1200,11 +1287,85 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs) ...@@ -1200,11 +1287,85 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs)
return IRQ_HANDLED; return IRQ_HANDLED;
} }
/*****************************************************************************\
* *
* Device initialisation and shutdown *
* *
\*****************************************************************************/
/* /*
* Support functions for probe * Allocate/free MMC structure.
*/ */
static int wbsd_scan(struct wbsd_host* host) 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;
spin_lock_init(&host->lock);
/*
* Maximum number of segments. Worst case is one sector per segment
* so this will be 64kB/512.
*/
mmc->max_hw_segs = 128;
mmc->max_phys_segs = 128;
/*
* Maximum number of sectors in one transfer. Also limited by 64kB
* buffer.
*/
mmc->max_sectors = 128;
/*
* Maximum segment size. Could be one segment with the maximum number
* of segments.
*/
mmc->max_seg_size = mmc->max_sectors * 512;
dev_set_drvdata(dev, mmc);
return 0;
}
static void __devexit wbsd_free_mmc(struct device* dev)
{
struct mmc_host* mmc;
mmc = dev_get_drvdata(dev);
if (!mmc)
return;
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 i, j, k;
int id; int id;
...@@ -1258,12 +1419,16 @@ static int wbsd_scan(struct wbsd_host* host) ...@@ -1258,12 +1419,16 @@ static int wbsd_scan(struct wbsd_host* host)
return -ENODEV; return -ENODEV;
} }
static int wbsd_request_regions(struct wbsd_host* host) /*
* Allocate/free io port ranges
*/
static int __devinit wbsd_request_region(struct wbsd_host* host, int base)
{ {
if (io & 0x7) if (io & 0x7)
return -EINVAL; return -EINVAL;
if (!request_region(io, 8, DRIVER_NAME)) if (!request_region(base, 8, DRIVER_NAME))
return -EIO; return -EIO;
host->base = io; host->base = io;
...@@ -1271,19 +1436,25 @@ static int wbsd_request_regions(struct wbsd_host* host) ...@@ -1271,19 +1436,25 @@ static int wbsd_request_regions(struct wbsd_host* host)
return 0; return 0;
} }
static void wbsd_release_regions(struct wbsd_host* host) static void __devexit wbsd_release_regions(struct wbsd_host* host)
{ {
if (host->base) if (host->base)
release_region(host->base, 8); release_region(host->base, 8);
host->base = 0;
if (host->config) if (host->config)
release_region(host->config, 2); release_region(host->config, 2);
host->config = 0;
} }
static void wbsd_init_dma(struct wbsd_host* host) /*
* Allocate/free DMA port and buffer
*/
static void __devinit wbsd_request_dma(struct wbsd_host* host, int dma)
{ {
host->dma = -1;
if (dma < 0) if (dma < 0)
return; return;
...@@ -1294,7 +1465,7 @@ static void wbsd_init_dma(struct wbsd_host* host) ...@@ -1294,7 +1465,7 @@ static void wbsd_init_dma(struct wbsd_host* host)
* We need to allocate a special buffer in * We need to allocate a special buffer in
* order for ISA to be able to DMA to it. * order for ISA to be able to DMA to it.
*/ */
host->dma_buffer = kmalloc(65536, host->dma_buffer = kmalloc(WBSD_DMA_SIZE,
GFP_NOIO | GFP_DMA | __GFP_REPEAT | __GFP_NOWARN); GFP_NOIO | GFP_DMA | __GFP_REPEAT | __GFP_NOWARN);
if (!host->dma_buffer) if (!host->dma_buffer)
goto free; goto free;
...@@ -1302,7 +1473,8 @@ static void wbsd_init_dma(struct wbsd_host* host) ...@@ -1302,7 +1473,8 @@ static void wbsd_init_dma(struct wbsd_host* host)
/* /*
* Translate the address to a physical address. * Translate the address to a physical address.
*/ */
host->dma_addr = isa_virt_to_bus(host->dma_buffer); host->dma_addr = dma_map_single(host->mmc->dev, host->dma_buffer,
WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
/* /*
* ISA DMA must be aligned on a 64k basis. * ISA DMA must be aligned on a 64k basis.
...@@ -1325,6 +1497,10 @@ static void wbsd_init_dma(struct wbsd_host* host) ...@@ -1325,6 +1497,10 @@ static void wbsd_init_dma(struct wbsd_host* host)
*/ */
BUG_ON(1); BUG_ON(1);
dma_unmap_single(host->mmc->dev, host->dma_addr, WBSD_DMA_SIZE,
DMA_BIDIRECTIONAL);
host->dma_addr = (dma_addr_t)NULL;
kfree(host->dma_buffer); kfree(host->dma_buffer);
host->dma_buffer = NULL; host->dma_buffer = NULL;
...@@ -1336,60 +1512,122 @@ static void wbsd_init_dma(struct wbsd_host* host) ...@@ -1336,60 +1512,122 @@ static void wbsd_init_dma(struct wbsd_host* host)
"Falling back on FIFO.\n", dma); "Falling back on FIFO.\n", dma);
} }
static struct mmc_host_ops wbsd_ops = { static void __devexit wbsd_release_dma(struct wbsd_host* host)
.request = wbsd_request, {
.set_ios = wbsd_set_ios, if (host->dma_addr)
}; dma_unmap_single(host->mmc->dev, host->dma_addr, WBSD_DMA_SIZE,
DMA_BIDIRECTIONAL);
if (host->dma_buffer)
kfree(host->dma_buffer);
if (host->dma >= 0)
free_dma(host->dma);
host->dma = -1;
host->dma_buffer = NULL;
host->dma_addr = (dma_addr_t)NULL;
}
/* /*
* Device probe * Allocate/free IRQ.
*/ */
static int wbsd_probe(struct device* dev) static int __devinit wbsd_request_irq(struct wbsd_host* host, int irq)
{ {
struct wbsd_host* host = NULL;
struct mmc_host* mmc = NULL;
int ret; int ret;
/* /*
* Allocate MMC structure. * Allocate interrupt.
*/ */
mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev);
if (!mmc) ret = request_irq(irq, wbsd_irq, SA_SHIRQ, DRIVER_NAME, host);
return -ENOMEM; if (ret)
return ret;
host = mmc_priv(mmc);
host->mmc = mmc;
host->irq = irq;
/* /*
* Scan for hardware. * Set up tasklets.
*/ */
ret = wbsd_scan(host); tasklet_init(&host->card_tasklet, wbsd_tasklet_card, (unsigned long)host);
if (ret) tasklet_init(&host->fifo_tasklet, wbsd_tasklet_fifo, (unsigned long)host);
goto freemmc; tasklet_init(&host->crc_tasklet, wbsd_tasklet_crc, (unsigned long)host);
tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout, (unsigned long)host);
tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish, (unsigned long)host);
tasklet_init(&host->block_tasklet, wbsd_tasklet_block, (unsigned long)host);
return 0;
}
/* static void __devexit wbsd_release_irq(struct wbsd_host* host)
* Reset the chip. {
*/ if (!host->irq)
wbsd_write_config(host, WBSD_CONF_SWRST, 1); return;
wbsd_write_config(host, WBSD_CONF_SWRST, 0);
free_irq(host->irq, host);
host->irq = 0;
tasklet_kill(&host->card_tasklet);
tasklet_kill(&host->fifo_tasklet);
tasklet_kill(&host->crc_tasklet);
tasklet_kill(&host->timeout_tasklet);
tasklet_kill(&host->finish_tasklet);
tasklet_kill(&host->block_tasklet);
}
/*
* Allocate all resources for the host.
*/
static int __devinit wbsd_request_resources(struct wbsd_host* host,
int base, int irq, int dma)
{
int ret;
/* /*
* Allocate I/O ports. * Allocate I/O ports.
*/ */
ret = wbsd_request_regions(host); ret = wbsd_request_region(host, base);
if (ret) if (ret)
goto release; return ret;
/* /*
* Set host parameters. * Allocate interrupt.
*/ */
mmc->ops = &wbsd_ops; ret = wbsd_request_irq(host, irq);
mmc->f_min = 375000; if (ret)
mmc->f_max = 24000000; return ret;
mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
/*
* Allocate DMA.
*/
wbsd_request_dma(host, dma);
spin_lock_init(&host->lock); return 0;
}
/*
* Release all resources for the host.
*/
static void __devexit wbsd_release_resources(struct wbsd_host* host)
{
wbsd_release_dma(host);
wbsd_release_irq(host);
wbsd_release_regions(host);
}
/*
* Configure the resources the chip should use.
*/
static void __devinit wbsd_chip_config(struct wbsd_host* host)
{
/*
* Reset the chip.
*/
wbsd_write_config(host, WBSD_CONF_SWRST, 1);
wbsd_write_config(host, WBSD_CONF_SWRST, 0);
/* /*
* Select SD/MMC function. * Select SD/MMC function.
...@@ -1399,165 +1637,241 @@ static int wbsd_probe(struct device* dev) ...@@ -1399,165 +1637,241 @@ static int wbsd_probe(struct device* dev)
/* /*
* Set up card detection. * Set up card detection.
*/ */
wbsd_write_config(host, WBSD_CONF_PINS, 0x02); wbsd_write_config(host, WBSD_CONF_PINS, WBSD_PINS_DETECT_GP11);
/* /*
* Configure I/O port. * Configure chip
*/ */
wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8); 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_PORT_LO, host->base & 0xff);
/*
* Allocate interrupt.
*/
ret = request_irq(irq, wbsd_irq, SA_SHIRQ, DRIVER_NAME, host);
if (ret)
goto release;
host->irq = irq; wbsd_write_config(host, WBSD_CONF_IRQ, host->irq);
/* if (host->dma >= 0)
* Set up tasklets. wbsd_write_config(host, WBSD_CONF_DRQ, host->dma);
*/
tasklet_init(&host->card_tasklet, wbsd_tasklet_card, (unsigned long)host);
tasklet_init(&host->fifo_tasklet, wbsd_tasklet_fifo, (unsigned long)host);
tasklet_init(&host->crc_tasklet, wbsd_tasklet_crc, (unsigned long)host);
tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout, (unsigned long)host);
tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish, (unsigned long)host);
tasklet_init(&host->block_tasklet, wbsd_tasklet_block, (unsigned long)host);
/* /*
* Configure interrupt. * Enable and power up chip.
*/ */
wbsd_write_config(host, WBSD_CONF_IRQ, host->irq); wbsd_write_config(host, WBSD_CONF_ENABLE, 1);
wbsd_write_config(host, WBSD_CONF_POWER, 0x20);
}
/*
* Check that configured resources are correct.
*/
static int __devinit wbsd_chip_validate(struct wbsd_host* host)
{
int base, irq, dma;
/* /*
* Allocate DMA. * Select SD/MMC function.
*/ */
wbsd_init_dma(host); wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
/* /*
* If all went well, then configure DMA. * Read configuration.
*/ */
if (host->dma >= 0) base = wbsd_read_config(host, WBSD_CONF_PORT_HI) << 8;
wbsd_write_config(host, WBSD_CONF_DRQ, host->dma); base |= wbsd_read_config(host, WBSD_CONF_PORT_LO);
/* irq = wbsd_read_config(host, WBSD_CONF_IRQ);
* Maximum number of segments. Worst case is one sector per segment
* so this will be 64kB/512. dma = wbsd_read_config(host, WBSD_CONF_DRQ);
*/
mmc->max_hw_segs = 128;
mmc->max_phys_segs = 128;
/* /*
* Maximum number of sectors in one transfer. Also limited by 64kB * Validate against given configuration.
* buffer.
*/ */
mmc->max_sectors = 128; if (base != host->base)
return 0;
if (irq != host->irq)
return 0;
if ((dma != host->dma) && (host->dma != -1))
return 0;
return 1;
}
/*****************************************************************************\
* *
* Devices setup and shutdown *
* *
\*****************************************************************************/
static int __devinit wbsd_init(struct device* dev, int base, int irq, int dma,
int pnp)
{
struct wbsd_host* host = NULL;
struct mmc_host* mmc = NULL;
int ret;
ret = wbsd_alloc_mmc(dev);
if (ret)
return ret;
mmc = dev_get_drvdata(dev);
host = mmc_priv(mmc);
/* /*
* Maximum segment size. Could be one segment with the maximum number * Scan for hardware.
* of segments.
*/ */
mmc->max_seg_size = mmc->max_sectors * 512; 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;
}
}
/* /*
* Enable chip. * Request resources.
*/ */
wbsd_write_config(host, WBSD_CONF_ENABLE, 1); ret = wbsd_request_resources(host, io, irq, dma);
if (ret)
{
wbsd_release_resources(host);
wbsd_free_mmc(dev);
return ret;
}
/* /*
* Power up chip. * See if chip needs to be configured.
*/ */
wbsd_write_config(host, WBSD_CONF_POWER, 0x20); if (pnp && (host->config != 0))
{
if (!wbsd_chip_validate(host))
{
printk(KERN_WARNING DRIVER_NAME
": PnP active but chip not configured! "
"You probably have a buggy BIOS. "
"Configuring chip manually.\n");
wbsd_chip_config(host);
}
}
else
wbsd_chip_config(host);
/* /*
* Power Management stuff. No idea how this works. * Power Management stuff. No idea how this works.
* Not tested. * Not tested.
*/ */
#ifdef CONFIG_PM #ifdef CONFIG_PM
wbsd_write_config(host, WBSD_CONF_PME, 0xA0); if (host->config)
wbsd_write_config(host, WBSD_CONF_PME, 0xA0);
#endif #endif
/*
* Allow device to initialise itself properly.
*/
mdelay(5);
/* /*
* Reset the chip into a known state. * Reset the chip into a known state.
*/ */
wbsd_init_device(host); wbsd_init_device(host);
dev_set_drvdata(dev, mmc);
/*
* Add host to MMC layer.
*/
mmc_add_host(mmc); mmc_add_host(mmc);
printk(KERN_INFO "%s: W83L51xD id %x at 0x%x irq %d dma %d\n", printk(KERN_INFO "%s: W83L51xD", mmc->host_name);
mmc->host_name, (int)host->chip_id, (int)host->base, if (host->chip_id != 0)
(int)host->irq, (int)host->dma); 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; return 0;
release:
wbsd_release_regions(host);
freemmc:
mmc_free_host(mmc);
return ret;
} }
/* static void __devexit wbsd_shutdown(struct device* dev, int pnp)
* Device remove
*/
static int wbsd_remove(struct device* dev)
{ {
struct mmc_host* mmc = dev_get_drvdata(dev); struct mmc_host* mmc = dev_get_drvdata(dev);
struct wbsd_host* host; struct wbsd_host* host;
if (!mmc) if (!mmc)
return 0; return;
host = mmc_priv(mmc); host = mmc_priv(mmc);
/*
* Unregister host with MMC layer.
*/
mmc_remove_host(mmc); mmc_remove_host(mmc);
/* if (!pnp)
* Power down the SD/MMC function. {
*/ /*
wbsd_unlock_config(host); * Power down the SD/MMC function.
wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD); */
wbsd_write_config(host, WBSD_CONF_ENABLE, 0); wbsd_unlock_config(host);
wbsd_lock_config(host); wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
wbsd_write_config(host, WBSD_CONF_ENABLE, 0);
wbsd_lock_config(host);
}
/* wbsd_release_resources(host);
* Free resources.
*/
if (host->dma_buffer)
kfree(host->dma_buffer);
if (host->dma >= 0) wbsd_free_mmc(dev);
free_dma(host->dma); }
free_irq(host->irq, host); /*
* Non-PnP
*/
static int __devinit wbsd_probe(struct device* dev)
{
return wbsd_init(dev, io, irq, dma, 0);
}
static int __devexit wbsd_remove(struct device* dev)
{
wbsd_shutdown(dev, 0);
return 0;
}
/*
* PnP
*/
#ifdef CONFIG_PNP
static int __devinit
wbsd_pnp_probe(struct pnp_dev * pnpdev, const struct pnp_device_id *dev_id)
{
int io, irq, dma;
tasklet_kill(&host->card_tasklet); /*
tasklet_kill(&host->fifo_tasklet); * Get resources from PnP layer.
tasklet_kill(&host->crc_tasklet); */
tasklet_kill(&host->timeout_tasklet); io = pnp_port_start(pnpdev, 0);
tasklet_kill(&host->finish_tasklet); irq = pnp_irq(pnpdev, 0);
tasklet_kill(&host->block_tasklet); if (pnp_dma_valid(pnpdev, 0))
dma = pnp_dma(pnpdev, 0);
else
dma = -1;
wbsd_release_regions(host); DBGF("PnP resources: port %3x irq %d dma %d\n", io, irq, dma);
mmc_free_host(mmc); return wbsd_init(&pnpdev->dev, io, irq, dma, 1);
}
return 0; static void __devexit wbsd_pnp_remove(struct pnp_dev * dev)
{
wbsd_shutdown(&dev->dev, 1);
} }
#endif /* CONFIG_PNP */
/* /*
* Power management * Power management
*/ */
...@@ -1581,17 +1895,7 @@ static int wbsd_resume(struct device *dev, u32 level) ...@@ -1581,17 +1895,7 @@ static int wbsd_resume(struct device *dev, u32 level)
#define wbsd_resume NULL #define wbsd_resume NULL
#endif #endif
static void wbsd_release(struct device *dev) static struct platform_device *wbsd_device;
{
}
static struct platform_device wbsd_device = {
.name = DRIVER_NAME,
.id = -1,
.dev = {
.release = wbsd_release,
},
};
static struct device_driver wbsd_driver = { static struct device_driver wbsd_driver = {
.name = DRIVER_NAME, .name = DRIVER_NAME,
...@@ -1603,6 +1907,17 @@ static struct device_driver wbsd_driver = { ...@@ -1603,6 +1907,17 @@ static struct device_driver wbsd_driver = {
.resume = wbsd_resume, .resume = wbsd_resume,
}; };
#ifdef CONFIG_PNP
static struct pnp_driver wbsd_pnp_driver = {
.name = DRIVER_NAME,
.id_table = pnp_dev_table,
.probe = wbsd_pnp_probe,
.remove = wbsd_pnp_remove,
};
#endif /* CONFIG_PNP */
/* /*
* Module loading/unloading * Module loading/unloading
*/ */
...@@ -1615,29 +1930,57 @@ static int __init wbsd_drv_init(void) ...@@ -1615,29 +1930,57 @@ static int __init wbsd_drv_init(void)
": Winbond W83L51xD SD/MMC card interface driver, " ": Winbond W83L51xD SD/MMC card interface driver, "
DRIVER_VERSION "\n"); DRIVER_VERSION "\n");
printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n"); printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
result = driver_register(&wbsd_driver);
if (result < 0)
return result;
result = platform_device_register(&wbsd_device); #ifdef CONFIG_PNP
if (result < 0)
return result; if (!nopnp)
{
result = pnp_register_driver(&wbsd_pnp_driver);
if (result < 0)
return result;
}
#endif /* CONFIG_PNP */
if (nopnp)
{
result = driver_register(&wbsd_driver);
if (result < 0)
return result;
wbsd_device = platform_device_register_simple(DRIVER_NAME, -1,
NULL, 0);
if (IS_ERR(wbsd_device))
return PTR_ERR(wbsd_device);
}
return 0; return 0;
} }
static void __exit wbsd_drv_exit(void) static void __exit wbsd_drv_exit(void)
{ {
platform_device_unregister(&wbsd_device); #ifdef CONFIG_PNP
if (!nopnp)
pnp_unregister_driver(&wbsd_pnp_driver);
driver_unregister(&wbsd_driver); #endif /* CONFIG_PNP */
if (nopnp)
{
platform_device_unregister(wbsd_device);
driver_unregister(&wbsd_driver);
}
DBG("unloaded\n"); DBG("unloaded\n");
} }
module_init(wbsd_drv_init); module_init(wbsd_drv_init);
module_exit(wbsd_drv_exit); module_exit(wbsd_drv_exit);
#ifdef CONFIG_PNP
module_param(nopnp, uint, 0444);
#endif
module_param(io, uint, 0444); module_param(io, uint, 0444);
module_param(irq, uint, 0444); module_param(irq, uint, 0444);
module_param(dma, int, 0444); module_param(dma, int, 0444);
...@@ -1646,6 +1989,9 @@ MODULE_LICENSE("GPL"); ...@@ -1646,6 +1989,9 @@ MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver"); MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver");
MODULE_VERSION(DRIVER_VERSION); MODULE_VERSION(DRIVER_VERSION);
#ifdef CONFIG_PNP
MODULE_PARM_DESC(nopnp, "Scan for device instead of relying on PNP. (default 0)");
#endif
MODULE_PARM_DESC(io, "I/O base to allocate. Must be 8 byte aligned. (default 0x248)"); MODULE_PARM_DESC(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(irq, "IRQ to allocate. (default 6)");
MODULE_PARM_DESC(dma, "DMA channel to allocate. -1 for no DMA. (default 2)"); MODULE_PARM_DESC(dma, "DMA channel to allocate. -1 for no DMA. (default 2)");
...@@ -35,6 +35,12 @@ const int valid_ids[] = { ...@@ -35,6 +35,12 @@ const int valid_ids[] = {
#define DEVICE_SD 0x03 #define DEVICE_SD 0x03
#define WBSD_PINS_DAT3_HI 0x20
#define WBSD_PINS_DAT3_OUT 0x10
#define WBSD_PINS_GP11_HI 0x04
#define WBSD_PINS_DETECT_GP11 0x02
#define WBSD_PINS_DETECT_DAT3 0x01
#define WBSD_CMDR 0x00 #define WBSD_CMDR 0x00
#define WBSD_DFR 0x01 #define WBSD_DFR 0x01
#define WBSD_EIR 0x02 #define WBSD_EIR 0x02
...@@ -133,6 +139,7 @@ const int valid_ids[] = { ...@@ -133,6 +139,7 @@ const int valid_ids[] = {
#define WBSD_CRC_OK 0x05 /* S010E (00101) */ #define WBSD_CRC_OK 0x05 /* S010E (00101) */
#define WBSD_CRC_FAIL 0x0B /* S101E (01011) */ #define WBSD_CRC_FAIL 0x0B /* S101E (01011) */
#define WBSD_DMA_SIZE 65536
struct wbsd_host struct wbsd_host
{ {
...@@ -140,6 +147,11 @@ struct wbsd_host ...@@ -140,6 +147,11 @@ struct wbsd_host
spinlock_t lock; /* Mutex */ spinlock_t lock; /* Mutex */
int flags; /* Driver states */
#define WBSD_FCARD_PRESENT (1<<0) /* Card is present */
#define WBSD_FIGNORE_DETECT (1<<1) /* Ignore card detection */
struct mmc_request* mrq; /* Current request */ struct mmc_request* mrq; /* Current request */
u8 isr; /* Accumulated ISR */ u8 isr; /* Accumulated ISR */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment