You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2013 lines
39 KiB
2013 lines
39 KiB
/* |
|
* 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 <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_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 transferred. |
|
*/ |
|
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) { |
|
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 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 |
|
pr_warn("%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 |
|
* transferred. |
|
*/ |
|
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 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(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) { |
|
pr_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 |
|
*/ |
|
|
|
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 wbsd_alloc_mmc(struct device *dev) |
|
{ |
|
struct mmc_host *mmc; |
|
struct wbsd_host *host; |
|
|
|
/* |
|
* Allocate MMC structure. |
|
*/ |
|
mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev); |
|
if (!mmc) |
|
return -ENOMEM; |
|
|
|
host = mmc_priv(mmc); |
|
host->mmc = mmc; |
|
|
|
host->dma = -1; |
|
|
|
/* |
|
* Set host parameters. |
|
*/ |
|
mmc->ops = &wbsd_ops; |
|
mmc->f_min = 375000; |
|
mmc->f_max = 24000000; |
|
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; |
|
mmc->caps = MMC_CAP_4_BIT_DATA; |
|
|
|
spin_lock_init(&host->lock); |
|
|
|
/* |
|
* Set up timers |
|
*/ |
|
init_timer(&host->ignore_timer); |
|
host->ignore_timer.data = (unsigned long)host; |
|
host->ignore_timer.function = wbsd_reset_ignore; |
|
|
|
/* |
|
* Maximum number of segments. Worst case is one sector per segment |
|
* so this will be 64kB/512. |
|
*/ |
|
mmc->max_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 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 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_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 = 0; |
|
|
|
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) |
|
{ |
|
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 = 0; |
|
} |
|
|
|
/* |
|
* Allocate/free IRQ. |
|
*/ |
|
|
|
static int 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 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); |
|
|
|
mmc_add_host(mmc); |
|
|
|
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 int wbsd_remove(struct platform_device *dev) |
|
{ |
|
wbsd_shutdown(&dev->dev, 0); |
|
|
|
return 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, |
|
}, |
|
}; |
|
|
|
#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_named(nopnp, param_nopnp, uint, 0444); |
|
#endif |
|
module_param_named(io, param_io, uint, 0444); |
|
module_param_named(irq, param_irq, uint, 0444); |
|
module_param_named(dma, param_dma, int, 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)");
|
|
|