2005-04-17 02:20:36 +04:00
/*
* linux / drivers / mmc / wbsd . c - Winbond W83L51xD SD / MMC driver
*
* Copyright ( C ) 2004 - 2005 Pierre Ossman , All Rights Reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
*
* Warning !
*
* Changes to the FIFO system should be done with extreme care since
* the hardware is full of bugs related to the FIFO . Known issues are :
*
* - FIFO size field in FSR is always zero .
*
* - FIFO interrupts tend not to work as they should . Interrupts are
* triggered only for full / empty events , not for threshold values .
*
* - On APIC systems the FIFO empty interrupt is sometimes lost .
*/
# include <linux/config.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/init.h>
# include <linux/ioport.h>
# include <linux/device.h>
# include <linux/interrupt.h>
2005-05-08 22:35:27 +04:00
# include <linux/dma-mapping.h>
2005-04-17 02:20:36 +04:00
# include <linux/delay.h>
2005-05-08 22:35:27 +04:00
# include <linux/pnp.h>
2005-04-17 02:20:36 +04:00
# include <linux/highmem.h>
# include <linux/mmc/host.h>
# include <linux/mmc/protocol.h>
# include <asm/io.h>
# include <asm/dma.h>
# include <asm/scatterlist.h>
# include "wbsd.h"
# define DRIVER_NAME "wbsd"
2005-09-03 19:45:49 +04:00
# define DRIVER_VERSION "1.4"
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_MMC_DEBUG
# define DBG(x...) \
printk ( KERN_DEBUG DRIVER_NAME " : " x )
# define DBGF(f, x...) \
printk ( KERN_DEBUG DRIVER_NAME " [%s()]: " f , __func__ , # # x )
# else
# define DBG(x...) do { } while (0)
# define DBGF(x...) do { } while (0)
# endif
2005-05-08 22:35:27 +04:00
/*
* 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 */
2005-07-01 16:07:37 +04:00
static const int config_ports [ ] = { 0x2E , 0x4E } ;
static const int unlock_codes [ ] = { 0x83 , 0x87 } ;
static const int valid_ids [ ] = {
0x7112 ,
} ;
2005-05-08 22:35:27 +04:00
# 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 ;
2005-04-17 02:20:36 +04:00
/*
* Basic functions
*/
static inline void wbsd_unlock_config ( struct wbsd_host * host )
{
2005-05-08 22:35:27 +04:00
BUG_ON ( host - > config = = 0 ) ;
2005-04-17 02:20:36 +04:00
outb ( host - > unlock_code , host - > config ) ;
outb ( host - > unlock_code , host - > config ) ;
}
static inline void wbsd_lock_config ( struct wbsd_host * host )
{
2005-05-08 22:35:27 +04:00
BUG_ON ( host - > config = = 0 ) ;
2005-04-17 02:20:36 +04:00
outb ( LOCK_CODE , host - > config ) ;
}
static inline void wbsd_write_config ( struct wbsd_host * host , u8 reg , u8 value )
{
2005-05-08 22:35:27 +04:00
BUG_ON ( host - > config = = 0 ) ;
2005-04-17 02:20:36 +04:00
outb ( reg , host - > config ) ;
outb ( value , host - > config + 1 ) ;
}
static inline u8 wbsd_read_config ( struct wbsd_host * host , u8 reg )
{
2005-05-08 22:35:27 +04:00
BUG_ON ( host - > config = = 0 ) ;
2005-04-17 02:20:36 +04:00
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 ) ;
2005-05-08 22:35:27 +04:00
/*
* Set DAT3 to input
*/
setup & = ~ WBSD_DAT3_H ;
wbsd_write_index ( host , WBSD_IDX_SETUP , setup ) ;
host - > flags & = ~ WBSD_FIGNORE_DETECT ;
2005-04-17 02:20:36 +04:00
/*
* 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 ) ;
2005-05-08 22:35:27 +04:00
/*
* Test for card presence
*/
if ( inb ( host - > base + WBSD_CSR ) & WBSD_CARDPRESENT )
host - > flags | = WBSD_FCARD_PRESENT ;
else
host - > flags & = ~ WBSD_FCARD_PRESENT ;
2005-04-17 02:20:36 +04:00
/*
* Enable interesting interrupts .
*/
ier = 0 ;
ier | = WBSD_EINT_CARD ;
ier | = WBSD_EINT_FIFO_THRE ;
ier | = WBSD_EINT_CCRC ;
ier | = WBSD_EINT_TIMEOUT ;
ier | = WBSD_EINT_CRC ;
ier | = WBSD_EINT_TC ;
outb ( ier , host - > base + WBSD_EIR ) ;
/*
* Clear interrupts .
*/
inb ( host - > base + WBSD_ISR ) ;
}
static void wbsd_reset ( struct wbsd_host * host )
{
u8 setup ;
printk ( KERN_ERR DRIVER_NAME " : Resetting chip \n " ) ;
/*
* Soft reset of chip ( SD / MMC part ) .
*/
setup = wbsd_read_index ( host , WBSD_IDX_SETUP ) ;
setup | = WBSD_SOFT_RESET ;
wbsd_write_index ( host , WBSD_IDX_SETUP , setup ) ;
}
static void wbsd_request_end ( struct wbsd_host * host , struct mmc_request * mrq )
{
unsigned long dmaflags ;
DBGF ( " Ending request, cmd (%x) \n " , mrq - > cmd - > opcode ) ;
if ( host - > dma > = 0 )
{
/*
* Release ISA DMA controller .
*/
dmaflags = claim_dma_lock ( ) ;
disable_dma ( host - > dma ) ;
clear_dma_ff ( host - > dma ) ;
release_dma_lock ( dmaflags ) ;
/*
* Disable DMA on host .
*/
wbsd_write_index ( host , WBSD_IDX_DMA , 0 ) ;
}
host - > mrq = NULL ;
/*
* MMC layer might call back into the driver so first unlock .
*/
spin_unlock ( & host - > lock ) ;
mmc_request_done ( host - > mmc , mrq ) ;
spin_lock ( & host - > lock ) ;
}
/*
* Scatter / gather functions
*/
static inline void wbsd_init_sg ( struct wbsd_host * host , struct mmc_data * data )
{
/*
* Get info . about SG list from data structure .
*/
host - > cur_sg = data - > sg ;
host - > num_sg = data - > sg_len ;
host - > offset = 0 ;
host - > remain = host - > cur_sg - > length ;
}
static inline int wbsd_next_sg ( struct wbsd_host * host )
{
/*
* Skip to next SG entry .
*/
host - > cur_sg + + ;
host - > num_sg - - ;
/*
* Any entries left ?
*/
if ( host - > num_sg > 0 )
{
host - > offset = 0 ;
host - > remain = host - > cur_sg - > length ;
}
return host - > num_sg ;
}
static inline char * wbsd_kmap_sg ( struct wbsd_host * host )
{
host - > mapped_sg = kmap_atomic ( host - > cur_sg - > page , KM_BIO_SRC_IRQ ) +
host - > cur_sg - > offset ;
return host - > mapped_sg ;
}
static inline void wbsd_kunmap_sg ( struct wbsd_host * host )
{
kunmap_atomic ( host - > mapped_sg , KM_BIO_SRC_IRQ ) ;
}
static inline void wbsd_sg_to_dma ( struct wbsd_host * host , struct mmc_data * data )
{
unsigned int len , i , size ;
struct scatterlist * sg ;
char * dmabuf = host - > dma_buffer ;
char * sgbuf ;
size = host - > size ;
sg = data - > sg ;
len = data - > sg_len ;
/*
* Just loop through all entries . Size might not
* be the entire list though so make sure that
* we do not transfer too much .
*/
for ( i = 0 ; i < len ; i + + )
{
sgbuf = kmap_atomic ( sg [ i ] . page , KM_BIO_SRC_IRQ ) + sg [ i ] . offset ;
if ( size < sg [ i ] . length )
memcpy ( dmabuf , sgbuf , size ) ;
else
memcpy ( dmabuf , sgbuf , sg [ i ] . length ) ;
kunmap_atomic ( sgbuf , KM_BIO_SRC_IRQ ) ;
dmabuf + = sg [ i ] . length ;
if ( size < sg [ i ] . length )
size = 0 ;
else
size - = sg [ i ] . length ;
if ( size = = 0 )
break ;
}
/*
* Check that we didn ' t get a request to transfer
* more data than can fit into the SG list .
*/
BUG_ON ( size ! = 0 ) ;
host - > size - = size ;
}
static inline void wbsd_dma_to_sg ( struct wbsd_host * host , struct mmc_data * data )
{
unsigned int len , i , size ;
struct scatterlist * sg ;
char * dmabuf = host - > dma_buffer ;
char * sgbuf ;
size = host - > size ;
sg = data - > sg ;
len = data - > sg_len ;
/*
* Just loop through all entries . Size might not
* be the entire list though so make sure that
* we do not transfer too much .
*/
for ( i = 0 ; i < len ; i + + )
{
sgbuf = kmap_atomic ( sg [ i ] . page , KM_BIO_SRC_IRQ ) + sg [ i ] . offset ;
if ( size < sg [ i ] . length )
memcpy ( sgbuf , dmabuf , size ) ;
else
memcpy ( sgbuf , dmabuf , sg [ i ] . length ) ;
kunmap_atomic ( sgbuf , KM_BIO_SRC_IRQ ) ;
dmabuf + = sg [ i ] . length ;
if ( size < sg [ i ] . length )
size = 0 ;
else
size - = sg [ i ] . length ;
if ( size = = 0 )
break ;
}
/*
* Check that we didn ' t get a request to transfer
* more data than can fit into the SG list .
*/
BUG_ON ( size ! = 0 ) ;
host - > size - = size ;
}
/*
* Command handling
*/
static inline void wbsd_get_short_reply ( struct wbsd_host * host ,
struct mmc_command * cmd )
{
/*
* Correct response type ?
*/
if ( wbsd_read_index ( host , WBSD_IDX_RSPLEN ) ! = WBSD_RSP_SHORT )
{
cmd - > error = MMC_ERR_INVALID ;
return ;
}
cmd - > resp [ 0 ] =
wbsd_read_index ( host , WBSD_IDX_RESP12 ) < < 24 ;
cmd - > resp [ 0 ] | =
wbsd_read_index ( host , WBSD_IDX_RESP13 ) < < 16 ;
cmd - > resp [ 0 ] | =
wbsd_read_index ( host , WBSD_IDX_RESP14 ) < < 8 ;
cmd - > resp [ 0 ] | =
wbsd_read_index ( host , WBSD_IDX_RESP15 ) < < 0 ;
cmd - > resp [ 1 ] =
wbsd_read_index ( host , WBSD_IDX_RESP16 ) < < 24 ;
}
static inline void wbsd_get_long_reply ( struct wbsd_host * host ,
struct mmc_command * cmd )
{
int i ;
/*
* Correct response type ?
*/
if ( wbsd_read_index ( host , WBSD_IDX_RSPLEN ) ! = WBSD_RSP_LONG )
{
cmd - > error = MMC_ERR_INVALID ;
return ;
}
for ( i = 0 ; i < 4 ; i + + )
{
cmd - > resp [ i ] =
wbsd_read_index ( host , WBSD_IDX_RESP1 + i * 4 ) < < 24 ;
cmd - > resp [ i ] | =
wbsd_read_index ( host , WBSD_IDX_RESP2 + i * 4 ) < < 16 ;
cmd - > resp [ i ] | =
wbsd_read_index ( host , WBSD_IDX_RESP3 + i * 4 ) < < 8 ;
cmd - > resp [ i ] | =
wbsd_read_index ( host , WBSD_IDX_RESP4 + i * 4 ) < < 0 ;
}
}
static void wbsd_send_command ( struct wbsd_host * host , struct mmc_command * cmd )
{
int i ;
u8 status , isr ;
DBGF ( " Sending cmd (%x) \n " , cmd - > opcode ) ;
/*
* Clear accumulated ISR . The interrupt routine
* will fill this one with events that occur during
* transfer .
*/
host - > isr = 0 ;
/*
* Send the command ( CRC calculated by host ) .
*/
outb ( cmd - > opcode , host - > base + WBSD_CMDR ) ;
for ( i = 3 ; i > = 0 ; i - - )
outb ( ( cmd - > arg > > ( i * 8 ) ) & 0xff , host - > base + WBSD_CMDR ) ;
cmd - > error = MMC_ERR_NONE ;
/*
* Wait for the request to complete .
*/
do {
status = wbsd_read_index ( host , WBSD_IDX_STATUS ) ;
} while ( status & WBSD_CARDTRAFFIC ) ;
/*
* Do we expect a reply ?
*/
if ( ( cmd - > flags & MMC_RSP_MASK ) ! = MMC_RSP_NONE )
{
/*
* Read back status .
*/
isr = host - > isr ;
/* Card removed? */
if ( isr & WBSD_INT_CARD )
cmd - > error = MMC_ERR_TIMEOUT ;
/* Timeout? */
else if ( isr & WBSD_INT_TIMEOUT )
cmd - > error = MMC_ERR_TIMEOUT ;
/* CRC? */
else if ( ( cmd - > flags & MMC_RSP_CRC ) & & ( isr & WBSD_INT_CRC ) )
cmd - > error = MMC_ERR_BADCRC ;
/* All ok */
else
{
if ( ( cmd - > flags & MMC_RSP_MASK ) = = MMC_RSP_SHORT )
wbsd_get_short_reply ( host , cmd ) ;
else
wbsd_get_long_reply ( host , cmd ) ;
}
}
DBGF ( " Sent cmd (%x), res %d \n " , cmd - > opcode , cmd - > error ) ;
}
/*
* Data functions
*/
static void wbsd_empty_fifo ( struct wbsd_host * host )
{
struct mmc_data * data = host - > mrq - > cmd - > data ;
char * buffer ;
int i , fsr , fifo ;
/*
* Handle excessive data .
*/
if ( data - > bytes_xfered = = host - > size )
return ;
buffer = wbsd_kmap_sg ( host ) + host - > offset ;
/*
* Drain the fifo . This has a tendency to loop longer
* than the FIFO length ( usually one block ) .
*/
while ( ! ( ( fsr = inb ( host - > base + WBSD_FSR ) ) & WBSD_FIFO_EMPTY ) )
{
/*
* The size field in the FSR is broken so we have to
* do some guessing .
*/
if ( fsr & WBSD_FIFO_FULL )
fifo = 16 ;
else if ( fsr & WBSD_FIFO_FUTHRE )
fifo = 8 ;
else
fifo = 1 ;
for ( i = 0 ; i < fifo ; i + + )
{
* buffer = inb ( host - > base + WBSD_DFR ) ;
buffer + + ;
host - > offset + + ;
host - > remain - - ;
data - > bytes_xfered + + ;
/*
* Transfer done ?
*/
if ( data - > bytes_xfered = = host - > size )
{
wbsd_kunmap_sg ( host ) ;
return ;
}
/*
* End of scatter list entry ?
*/
if ( host - > remain = = 0 )
{
wbsd_kunmap_sg ( host ) ;
/*
* Get next entry . Check if last .
*/
if ( ! wbsd_next_sg ( host ) )
{
/*
* We should never reach this point .
* It means that we ' re trying to
* transfer more blocks than can fit
* into the scatter list .
*/
BUG_ON ( 1 ) ;
host - > size = data - > bytes_xfered ;
return ;
}
buffer = wbsd_kmap_sg ( host ) ;
}
}
}
wbsd_kunmap_sg ( host ) ;
/*
* This is a very dirty hack to solve a
* hardware problem . The chip doesn ' t trigger
* FIFO threshold interrupts properly .
*/
if ( ( host - > size - data - > bytes_xfered ) < 16 )
tasklet_schedule ( & host - > fifo_tasklet ) ;
}
static void wbsd_fill_fifo ( struct wbsd_host * host )
{
struct mmc_data * data = host - > mrq - > cmd - > data ;
char * buffer ;
int i , fsr , fifo ;
/*
* Check that we aren ' t being called after the
* entire buffer has been transfered .
*/
if ( data - > bytes_xfered = = host - > size )
return ;
buffer = wbsd_kmap_sg ( host ) + host - > offset ;
/*
* Fill the fifo . This has a tendency to loop longer
* than the FIFO length ( usually one block ) .
*/
while ( ! ( ( fsr = inb ( host - > base + WBSD_FSR ) ) & WBSD_FIFO_FULL ) )
{
/*
* The size field in the FSR is broken so we have to
* do some guessing .
*/
if ( fsr & WBSD_FIFO_EMPTY )
fifo = 0 ;
else if ( fsr & WBSD_FIFO_EMTHRE )
fifo = 8 ;
else
fifo = 15 ;
for ( i = 16 ; i > fifo ; i - - )
{
outb ( * buffer , host - > base + WBSD_DFR ) ;
buffer + + ;
host - > offset + + ;
host - > remain - - ;
data - > bytes_xfered + + ;
/*
* Transfer done ?
*/
if ( data - > bytes_xfered = = host - > size )
{
wbsd_kunmap_sg ( host ) ;
return ;
}
/*
* End of scatter list entry ?
*/
if ( host - > remain = = 0 )
{
wbsd_kunmap_sg ( host ) ;
/*
* Get next entry . Check if last .
*/
if ( ! wbsd_next_sg ( host ) )
{
/*
* We should never reach this point .
* It means that we ' re trying to
* transfer more blocks than can fit
* into the scatter list .
*/
BUG_ON ( 1 ) ;
host - > size = data - > bytes_xfered ;
return ;
}
buffer = wbsd_kmap_sg ( host ) ;
}
}
}
wbsd_kunmap_sg ( host ) ;
2005-05-08 22:35:27 +04:00
/*
* 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 ) ;
2005-04-17 02:20:36 +04:00
}
static void wbsd_prepare_data ( struct wbsd_host * host , struct mmc_data * data )
{
u16 blksize ;
u8 setup ;
unsigned long dmaflags ;
DBGF ( " blksz %04x blks %04x flags %08x \n " ,
1 < < data - > blksz_bits , data - > blocks , data - > flags ) ;
DBGF ( " tsac %d ms nsac %d clk \n " ,
data - > timeout_ns / 1000000 , data - > timeout_clks ) ;
/*
* Calculate size .
*/
host - > size = data - > blocks < < data - > blksz_bits ;
/*
* Check timeout values for overflow .
* ( Yes , some cards cause this value to overflow ) .
*/
if ( data - > timeout_ns > 127000000 )
wbsd_write_index ( host , WBSD_IDX_TAAC , 127 ) ;
else
wbsd_write_index ( host , WBSD_IDX_TAAC , data - > timeout_ns / 1000000 ) ;
if ( data - > timeout_clks > 255 )
wbsd_write_index ( host , WBSD_IDX_NSAC , 255 ) ;
else
wbsd_write_index ( host , WBSD_IDX_NSAC , data - > timeout_clks ) ;
/*
* Inform the chip of how large blocks will be
* sent . It needs this to determine when to
* calculate CRC .
*
* Space for CRC must be included in the size .
2005-09-07 02:18:57 +04:00
* Two bytes are needed for each data line .
2005-04-17 02:20:36 +04:00
*/
2005-09-07 02:18:57 +04:00
if ( host - > bus_width = = MMC_BUS_WIDTH_1 )
{
blksize = ( 1 < < data - > blksz_bits ) + 2 ;
wbsd_write_index ( host , WBSD_IDX_PBSMSB , ( blksize > > 4 ) & 0xF0 ) ;
wbsd_write_index ( host , WBSD_IDX_PBSLSB , blksize & 0xFF ) ;
}
else if ( host - > bus_width = = MMC_BUS_WIDTH_4 )
{
blksize = ( 1 < < data - > blksz_bits ) + 2 * 4 ;
2005-04-17 02:20:36 +04:00
2005-09-07 02:18:57 +04:00
wbsd_write_index ( host , WBSD_IDX_PBSMSB , ( ( blksize > > 4 ) & 0xF0 )
| WBSD_DATA_WIDTH ) ;
wbsd_write_index ( host , WBSD_IDX_PBSLSB , blksize & 0xFF ) ;
}
else
{
data - > error = MMC_ERR_INVALID ;
return ;
}
2005-04-17 02:20:36 +04:00
/*
* Clear the FIFO . This is needed even for DMA
* transfers since the chip still uses the FIFO
* internally .
*/
setup = wbsd_read_index ( host , WBSD_IDX_SETUP ) ;
setup | = WBSD_FIFO_RESET ;
wbsd_write_index ( host , WBSD_IDX_SETUP , setup ) ;
/*
* DMA transfer ?
*/
if ( host - > dma > = 0 )
{
/*
* The buffer for DMA is only 64 kB .
*/
BUG_ON ( host - > size > 0x10000 ) ;
if ( host - > size > 0x10000 )
{
data - > error = MMC_ERR_INVALID ;
return ;
}
/*
* Transfer data from the SG list to
* the DMA buffer .
*/
if ( data - > flags & MMC_DATA_WRITE )
wbsd_sg_to_dma ( host , data ) ;
/*
* Initialise the ISA DMA controller .
*/
dmaflags = claim_dma_lock ( ) ;
disable_dma ( host - > dma ) ;
clear_dma_ff ( host - > dma ) ;
if ( data - > flags & MMC_DATA_READ )
set_dma_mode ( host - > dma , DMA_MODE_READ & ~ 0x40 ) ;
else
set_dma_mode ( host - > dma , DMA_MODE_WRITE & ~ 0x40 ) ;
set_dma_addr ( host - > dma , host - > dma_addr ) ;
set_dma_count ( host - > dma , host - > size ) ;
enable_dma ( host - > dma ) ;
release_dma_lock ( dmaflags ) ;
/*
* Enable DMA on the host .
*/
wbsd_write_index ( host , WBSD_IDX_DMA , WBSD_DMA_ENABLE ) ;
}
else
{
/*
* This flag is used to keep printk
* output to a minimum .
*/
host - > firsterr = 1 ;
/*
* Initialise the SG list .
*/
wbsd_init_sg ( host , data ) ;
/*
* Turn off DMA .
*/
wbsd_write_index ( host , WBSD_IDX_DMA , 0 ) ;
/*
* Set up FIFO threshold levels ( and fill
* buffer if doing a write ) .
*/
if ( data - > flags & MMC_DATA_READ )
{
wbsd_write_index ( host , WBSD_IDX_FIFOEN ,
WBSD_FIFOEN_FULL | 8 ) ;
}
else
{
wbsd_write_index ( host , WBSD_IDX_FIFOEN ,
WBSD_FIFOEN_EMPTY | 8 ) ;
wbsd_fill_fifo ( host ) ;
}
}
data - > error = MMC_ERR_NONE ;
}
static void wbsd_finish_data ( struct wbsd_host * host , struct mmc_data * data )
{
unsigned long dmaflags ;
int count ;
u8 status ;
WARN_ON ( host - > mrq = = NULL ) ;
/*
* Send a stop command if needed .
*/
if ( data - > stop )
wbsd_send_command ( host , data - > stop ) ;
/*
* Wait for the controller to leave data
* transfer state .
*/
do
{
status = wbsd_read_index ( host , WBSD_IDX_STATUS ) ;
} while ( status & ( WBSD_BLOCK_READ | WBSD_BLOCK_WRITE ) ) ;
/*
* DMA transfer ?
*/
if ( host - > dma > = 0 )
{
/*
* Disable DMA on the host .
*/
wbsd_write_index ( host , WBSD_IDX_DMA , 0 ) ;
/*
* Turn of ISA DMA controller .
*/
dmaflags = claim_dma_lock ( ) ;
disable_dma ( host - > dma ) ;
clear_dma_ff ( host - > dma ) ;
count = get_dma_residue ( host - > dma ) ;
release_dma_lock ( dmaflags ) ;
/*
* Any leftover data ?
*/
if ( count )
{
printk ( KERN_ERR DRIVER_NAME " : Incomplete DMA "
" transfer. %d bytes left. \n " , count ) ;
data - > error = MMC_ERR_FAILED ;
}
else
{
/*
* Transfer data from DMA buffer to
* SG list .
*/
if ( data - > flags & MMC_DATA_READ )
wbsd_dma_to_sg ( host , data ) ;
data - > bytes_xfered = host - > size ;
}
}
DBGF ( " Ending data transfer (%d bytes) \n " , data - > bytes_xfered ) ;
wbsd_request_end ( host , host - > mrq ) ;
}
2005-05-08 22:35:27 +04:00
/*****************************************************************************\
* *
* MMC layer callbacks *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2005-04-17 02:20:36 +04:00
static void wbsd_request ( struct mmc_host * mmc , struct mmc_request * mrq )
{
struct wbsd_host * host = mmc_priv ( mmc ) ;
struct mmc_command * cmd ;
/*
* Disable tasklets to avoid a deadlock .
*/
spin_lock_bh ( & host - > lock ) ;
BUG_ON ( host - > mrq ! = NULL ) ;
cmd = mrq - > cmd ;
host - > mrq = mrq ;
/*
* If there is no card in the slot then
* timeout immediatly .
*/
2005-05-08 22:35:27 +04:00
if ( ! ( host - > flags & WBSD_FCARD_PRESENT ) )
2005-04-17 02:20:36 +04:00
{
cmd - > error = MMC_ERR_TIMEOUT ;
goto done ;
}
/*
* Does the request include data ?
*/
if ( cmd - > data )
{
wbsd_prepare_data ( host , cmd - > data ) ;
if ( cmd - > data - > error ! = MMC_ERR_NONE )
goto done ;
}
wbsd_send_command ( host , cmd ) ;
/*
* If this is a data transfer the request
* will be finished after the data has
* transfered .
*/
if ( cmd - > data & & ( cmd - > error = = MMC_ERR_NONE ) )
{
/*
* Dirty fix for hardware bug .
*/
if ( host - > dma = = - 1 )
tasklet_schedule ( & host - > fifo_tasklet ) ;
spin_unlock_bh ( & host - > lock ) ;
return ;
}
done :
wbsd_request_end ( host , mrq ) ;
spin_unlock_bh ( & host - > lock ) ;
}
static void wbsd_set_ios ( struct mmc_host * mmc , struct mmc_ios * ios )
{
struct wbsd_host * host = mmc_priv ( mmc ) ;
u8 clk , setup , pwr ;
2005-09-07 02:18:57 +04:00
DBGF ( " clock %uHz busmode %u powermode %u cs %u Vdd %u width %u \n " ,
ios - > clock , ios - > bus_mode , ios - > power_mode , ios - > chip_select ,
ios - > vdd , ios - > bus_width ) ;
2005-04-17 02:20:36 +04:00
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 ;
}
2005-05-08 22:35:27 +04:00
/*
* Power up card .
*/
2005-04-17 02:20:36 +04:00
if ( ios - > power_mode ! = MMC_POWER_OFF )
{
pwr = inb ( host - > base + WBSD_CSR ) ;
pwr & = ~ WBSD_POWER_N ;
outb ( pwr , host - > base + WBSD_CSR ) ;
}
2005-05-08 22:35:27 +04:00
/*
* MMC cards need to have pin 1 high during init .
* It wreaks havoc with the card detection though so
2005-09-03 19:45:49 +04:00
* that needs to be disabled .
2005-05-08 22:35:27 +04:00
*/
setup = wbsd_read_index ( host , WBSD_IDX_SETUP ) ;
2005-09-03 19:45:49 +04:00
if ( ios - > chip_select = = MMC_CS_HIGH )
2005-05-08 22:35:27 +04:00
{
2005-09-07 02:18:57 +04:00
BUG_ON ( ios - > bus_width ! = MMC_BUS_WIDTH_1 ) ;
2005-05-08 22:35:27 +04:00
setup | = WBSD_DAT3_H ;
host - > flags | = WBSD_FIGNORE_DETECT ;
}
else
{
setup & = ~ WBSD_DAT3_H ;
2005-09-03 19:45:49 +04:00
/*
* We cannot resume card detection immediatly
* because of capacitance and delays in the chip .
*/
mod_timer ( & host - > ignore_timer , jiffies + HZ / 100 ) ;
2005-05-08 22:35:27 +04:00
}
wbsd_write_index ( host , WBSD_IDX_SETUP , setup ) ;
2005-09-07 02:18:57 +04:00
/*
* Store bus width for later . Will be used when
* setting up the data transfer .
*/
host - > bus_width = ios - > bus_width ;
2005-04-17 02:20:36 +04:00
spin_unlock_bh ( & host - > lock ) ;
}
2005-09-07 02:18:57 +04:00
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 ;
}
2005-05-08 22:35:27 +04:00
static struct mmc_host_ops wbsd_ops = {
. request = wbsd_request ,
. set_ios = wbsd_set_ios ,
2005-09-07 02:18:57 +04:00
. get_ro = wbsd_get_ro ,
2005-05-08 22:35:27 +04:00
} ;
/*****************************************************************************\
* *
* Interrupt handling *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2005-09-03 19:45:49 +04:00
/*
* 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 ) ;
}
2005-07-01 15:13:55 +04:00
/*
* Helper function for card detection
*/
static void wbsd_detect_card ( unsigned long data )
{
struct wbsd_host * host = ( struct wbsd_host * ) data ;
BUG_ON ( host = = NULL ) ;
DBG ( " Executing card detection \n " ) ;
mmc_detect_change ( host - > mmc ) ;
}
2005-04-17 02:20:36 +04:00
/*
* Tasklets
*/
2005-07-27 22:46:09 +04:00
static inline struct mmc_data * wbsd_get_data ( struct wbsd_host * host )
2005-04-17 02:20:36 +04:00
{
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 ;
spin_lock ( & host - > lock ) ;
2005-05-08 22:35:27 +04:00
if ( host - > flags & WBSD_FIGNORE_DETECT )
{
spin_unlock ( & host - > lock ) ;
return ;
}
2005-04-17 02:20:36 +04:00
csr = inb ( host - > base + WBSD_CSR ) ;
WARN_ON ( csr = = 0xff ) ;
if ( csr & WBSD_CARDPRESENT )
2005-05-08 22:35:27 +04:00
{
if ( ! ( host - > flags & WBSD_FCARD_PRESENT ) )
{
DBG ( " Card inserted \n " ) ;
host - > flags | = WBSD_FCARD_PRESENT ;
2005-07-01 15:13:55 +04:00
/*
* Delay card detection to allow electrical connections
* to stabilise .
*/
2005-09-03 19:45:49 +04:00
mod_timer ( & host - > detect_timer , jiffies + HZ / 2 ) ;
2005-05-08 22:35:27 +04:00
}
2005-07-01 15:13:55 +04:00
spin_unlock ( & host - > lock ) ;
2005-05-08 22:35:27 +04:00
}
else if ( host - > flags & WBSD_FCARD_PRESENT )
2005-04-17 02:20:36 +04:00
{
DBG ( " Card removed \n " ) ;
2005-05-08 22:35:27 +04:00
host - > flags & = ~ WBSD_FCARD_PRESENT ;
2005-04-17 02:20:36 +04:00
if ( host - > mrq )
{
printk ( KERN_ERR DRIVER_NAME
" : Card removed during transfer! \n " ) ;
wbsd_reset ( host ) ;
host - > mrq - > cmd - > error = MMC_ERR_FAILED ;
tasklet_schedule ( & host - > finish_tasklet ) ;
}
2005-07-01 15:13:55 +04:00
/*
* Unlock first since we might get a call back .
*/
spin_unlock ( & host - > lock ) ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
mmc_detect_change ( host - > mmc ) ;
2005-07-01 15:13:55 +04:00
}
2005-09-03 19:45:49 +04:00
else
spin_unlock ( & host - > lock ) ;
2005-04-17 02:20:36 +04:00
}
static void wbsd_tasklet_fifo ( unsigned long param )
{
struct wbsd_host * host = ( struct wbsd_host * ) param ;
struct mmc_data * data ;
spin_lock ( & host - > lock ) ;
if ( ! host - > mrq )
goto end ;
data = wbsd_get_data ( host ) ;
if ( ! data )
goto end ;
if ( data - > flags & MMC_DATA_WRITE )
wbsd_fill_fifo ( host ) ;
else
wbsd_empty_fifo ( host ) ;
/*
* Done ?
*/
if ( host - > size = = data - > bytes_xfered )
{
wbsd_write_index ( host , WBSD_IDX_FIFOEN , 0 ) ;
tasklet_schedule ( & host - > finish_tasklet ) ;
}
end :
spin_unlock ( & host - > lock ) ;
}
static void wbsd_tasklet_crc ( unsigned long param )
{
struct wbsd_host * host = ( struct wbsd_host * ) param ;
struct mmc_data * data ;
spin_lock ( & host - > lock ) ;
if ( ! host - > mrq )
goto end ;
data = wbsd_get_data ( host ) ;
if ( ! data )
goto end ;
DBGF ( " CRC error \n " ) ;
data - > error = MMC_ERR_BADCRC ;
tasklet_schedule ( & host - > finish_tasklet ) ;
end :
spin_unlock ( & host - > lock ) ;
}
static void wbsd_tasklet_timeout ( unsigned long param )
{
struct wbsd_host * host = ( struct wbsd_host * ) param ;
struct mmc_data * data ;
spin_lock ( & host - > lock ) ;
if ( ! host - > mrq )
goto end ;
data = wbsd_get_data ( host ) ;
if ( ! data )
goto end ;
DBGF ( " Timeout \n " ) ;
data - > error = MMC_ERR_TIMEOUT ;
tasklet_schedule ( & host - > finish_tasklet ) ;
end :
spin_unlock ( & host - > lock ) ;
}
static void wbsd_tasklet_finish ( unsigned long param )
{
struct wbsd_host * host = ( struct wbsd_host * ) param ;
struct mmc_data * data ;
spin_lock ( & host - > lock ) ;
WARN_ON ( ! host - > mrq ) ;
if ( ! host - > mrq )
goto end ;
data = wbsd_get_data ( host ) ;
if ( ! data )
goto end ;
wbsd_finish_data ( host , data ) ;
end :
spin_unlock ( & host - > lock ) ;
}
static void wbsd_tasklet_block ( unsigned long param )
{
struct wbsd_host * host = ( struct wbsd_host * ) param ;
struct mmc_data * data ;
spin_lock ( & host - > lock ) ;
if ( ( wbsd_read_index ( host , WBSD_IDX_CRCSTATUS ) & WBSD_CRC_MASK ) ! =
WBSD_CRC_OK )
{
data = wbsd_get_data ( host ) ;
if ( ! data )
goto end ;
DBGF ( " CRC error \n " ) ;
data - > error = MMC_ERR_BADCRC ;
tasklet_schedule ( & host - > finish_tasklet ) ;
}
end :
spin_unlock ( & host - > lock ) ;
}
/*
* Interrupt handling
*/
static irqreturn_t wbsd_irq ( int irq , void * dev_id , struct pt_regs * regs )
{
struct wbsd_host * host = dev_id ;
int isr ;
isr = inb ( host - > base + WBSD_ISR ) ;
/*
* Was it actually our hardware that caused the interrupt ?
*/
if ( isr = = 0xff | | isr = = 0x00 )
return IRQ_NONE ;
host - > isr | = isr ;
/*
* Schedule tasklets as needed .
*/
if ( isr & WBSD_INT_CARD )
tasklet_schedule ( & host - > card_tasklet ) ;
if ( isr & WBSD_INT_FIFO_THRE )
tasklet_schedule ( & host - > fifo_tasklet ) ;
if ( isr & WBSD_INT_CRC )
tasklet_hi_schedule ( & host - > crc_tasklet ) ;
if ( isr & WBSD_INT_TIMEOUT )
tasklet_hi_schedule ( & host - > timeout_tasklet ) ;
if ( isr & WBSD_INT_BUSYEND )
tasklet_hi_schedule ( & host - > block_tasklet ) ;
if ( isr & WBSD_INT_TC )
tasklet_schedule ( & host - > finish_tasklet ) ;
return IRQ_HANDLED ;
}
2005-05-08 22:35:27 +04:00
/*****************************************************************************\
* *
* Device initialisation and shutdown *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Allocate / free MMC structure .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
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 ;
2005-09-07 02:18:57 +04:00
mmc - > caps = MMC_CAP_4_BIT_DATA ;
2005-05-08 22:35:27 +04:00
spin_lock_init ( & host - > lock ) ;
2005-07-01 15:13:55 +04:00
/*
2005-09-03 19:45:49 +04:00
* Set up timers
2005-07-01 15:13:55 +04:00
*/
2005-09-03 19:45:49 +04:00
init_timer ( & host - > detect_timer ) ;
host - > detect_timer . data = ( unsigned long ) host ;
host - > detect_timer . function = wbsd_detect_card ;
init_timer ( & host - > ignore_timer ) ;
host - > ignore_timer . data = ( unsigned long ) host ;
host - > ignore_timer . function = wbsd_reset_ignore ;
2005-07-01 15:13:55 +04:00
2005-05-08 22:35:27 +04:00
/*
* Maximum number of segments . Worst case is one sector per segment
* so this will be 64 kB / 512.
*/
mmc - > max_hw_segs = 128 ;
mmc - > max_phys_segs = 128 ;
/*
* Maximum number of sectors in one transfer . Also limited by 64 kB
* 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 ;
2005-07-01 15:13:55 +04:00
struct wbsd_host * host ;
2005-05-08 22:35:27 +04:00
mmc = dev_get_drvdata ( dev ) ;
if ( ! mmc )
return ;
2005-07-01 15:13:55 +04:00
host = mmc_priv ( mmc ) ;
BUG_ON ( host = = NULL ) ;
2005-09-03 19:45:49 +04:00
del_timer_sync ( & host - > ignore_timer ) ;
del_timer_sync ( & host - > detect_timer ) ;
2005-07-01 15:13:55 +04:00
2005-05-08 22:35:27 +04:00
mmc_free_host ( mmc ) ;
dev_set_drvdata ( dev , NULL ) ;
}
/*
* Scan for known chip id : s
*/
static int __devinit wbsd_scan ( struct wbsd_host * host )
2005-04-17 02:20:36 +04:00
{
int i , j , k ;
int id ;
/*
* Iterate through all ports , all codes to
* find hardware that is in our known list .
*/
for ( i = 0 ; i < sizeof ( config_ports ) / sizeof ( int ) ; i + + )
{
if ( ! request_region ( config_ports [ i ] , 2 , DRIVER_NAME ) )
continue ;
for ( j = 0 ; j < sizeof ( unlock_codes ) / sizeof ( int ) ; j + + )
{
id = 0xFFFF ;
outb ( unlock_codes [ j ] , config_ports [ i ] ) ;
outb ( unlock_codes [ j ] , config_ports [ i ] ) ;
outb ( WBSD_CONF_ID_HI , config_ports [ i ] ) ;
id = inb ( config_ports [ i ] + 1 ) < < 8 ;
outb ( WBSD_CONF_ID_LO , config_ports [ i ] ) ;
id | = inb ( config_ports [ i ] + 1 ) ;
for ( k = 0 ; k < sizeof ( valid_ids ) / sizeof ( int ) ; k + + )
{
if ( id = = valid_ids [ k ] )
{
host - > chip_id = id ;
host - > config = config_ports [ i ] ;
host - > unlock_code = unlock_codes [ i ] ;
return 0 ;
}
}
if ( id ! = 0xFFFF )
{
DBG ( " Unknown hardware (id %x) found at %x \n " ,
id , config_ports [ i ] ) ;
}
outb ( LOCK_CODE , config_ports [ i ] ) ;
}
release_region ( config_ports [ i ] , 2 ) ;
}
return - ENODEV ;
}
2005-05-08 22:35:27 +04:00
/*
* Allocate / free io port ranges
*/
static int __devinit wbsd_request_region ( struct wbsd_host * host , int base )
2005-04-17 02:20:36 +04:00
{
if ( io & 0x7 )
return - EINVAL ;
2005-05-08 22:35:27 +04:00
if ( ! request_region ( base , 8 , DRIVER_NAME ) )
2005-04-17 02:20:36 +04:00
return - EIO ;
host - > base = io ;
return 0 ;
}
2005-05-08 22:35:27 +04:00
static void __devexit wbsd_release_regions ( struct wbsd_host * host )
2005-04-17 02:20:36 +04:00
{
if ( host - > base )
release_region ( host - > base , 8 ) ;
2005-05-08 22:35:27 +04:00
host - > base = 0 ;
2005-04-17 02:20:36 +04:00
if ( host - > config )
release_region ( host - > config , 2 ) ;
2005-05-08 22:35:27 +04:00
host - > config = 0 ;
2005-04-17 02:20:36 +04:00
}
2005-05-08 22:35:27 +04:00
/*
* Allocate / free DMA port and buffer
*/
static void __devinit wbsd_request_dma ( struct wbsd_host * host , int dma )
2005-04-17 02:20:36 +04:00
{
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 .
*/
2005-05-08 22:35:27 +04:00
host - > dma_buffer = kmalloc ( WBSD_DMA_SIZE ,
2005-04-17 02:20:36 +04:00
GFP_NOIO | GFP_DMA | __GFP_REPEAT | __GFP_NOWARN ) ;
if ( ! host - > dma_buffer )
goto free ;
/*
* Translate the address to a physical address .
*/
2005-05-08 22:35:27 +04:00
host - > dma_addr = dma_map_single ( host - > mmc - > dev , host - > dma_buffer ,
WBSD_DMA_SIZE , DMA_BIDIRECTIONAL ) ;
2005-04-17 02:20:36 +04:00
/*
* ISA DMA must be aligned on a 64 k 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 ) ;
2005-05-08 22:35:27 +04:00
dma_unmap_single ( host - > mmc - > dev , host - > dma_addr , WBSD_DMA_SIZE ,
DMA_BIDIRECTIONAL ) ;
host - > dma_addr = ( dma_addr_t ) NULL ;
2005-04-17 02:20:36 +04:00
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 ) ;
}
2005-05-08 22:35:27 +04:00
static void __devexit wbsd_release_dma ( struct wbsd_host * host )
{
if ( host - > dma_addr )
dma_unmap_single ( host - > mmc - > dev , host - > dma_addr , WBSD_DMA_SIZE ,
DMA_BIDIRECTIONAL ) ;
if ( host - > dma_buffer )
kfree ( host - > dma_buffer ) ;
if ( host - > dma > = 0 )
free_dma ( host - > dma ) ;
host - > dma = - 1 ;
host - > dma_buffer = NULL ;
host - > dma_addr = ( dma_addr_t ) NULL ;
}
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Allocate / free IRQ .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
static int __devinit wbsd_request_irq ( struct wbsd_host * host , int irq )
2005-04-17 02:20:36 +04:00
{
int ret ;
/*
2005-05-08 22:35:27 +04:00
* Allocate interrupt .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
ret = request_irq ( irq , wbsd_irq , SA_SHIRQ , DRIVER_NAME , host ) ;
if ( ret )
return ret ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
host - > irq = irq ;
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Set up tasklets .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
tasklet_init ( & host - > card_tasklet , wbsd_tasklet_card , ( unsigned long ) host ) ;
tasklet_init ( & host - > fifo_tasklet , wbsd_tasklet_fifo , ( unsigned long ) host ) ;
tasklet_init ( & host - > crc_tasklet , wbsd_tasklet_crc , ( unsigned long ) host ) ;
tasklet_init ( & host - > timeout_tasklet , wbsd_tasklet_timeout , ( unsigned long ) host ) ;
tasklet_init ( & host - > finish_tasklet , wbsd_tasklet_finish , ( unsigned long ) host ) ;
tasklet_init ( & host - > block_tasklet , wbsd_tasklet_block , ( unsigned long ) host ) ;
return 0 ;
}
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
static void __devexit wbsd_release_irq ( struct wbsd_host * host )
{
if ( ! host - > irq )
return ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
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 ;
2005-04-17 02:20:36 +04:00
/*
* Allocate I / O ports .
*/
2005-05-08 22:35:27 +04:00
ret = wbsd_request_region ( host , base ) ;
2005-04-17 02:20:36 +04:00
if ( ret )
2005-05-08 22:35:27 +04:00
return ret ;
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Allocate interrupt .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
ret = wbsd_request_irq ( host , irq ) ;
if ( ret )
return ret ;
/*
* Allocate DMA .
*/
wbsd_request_dma ( host , dma ) ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
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 ) ;
2005-04-17 02:20:36 +04:00
/*
* Select SD / MMC function .
*/
wbsd_write_config ( host , WBSD_CONF_DEVICE , DEVICE_SD ) ;
/*
* Set up card detection .
*/
2005-05-08 22:35:27 +04:00
wbsd_write_config ( host , WBSD_CONF_PINS , WBSD_PINS_DETECT_GP11 ) ;
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Configure chip
2005-04-17 02:20:36 +04:00
*/
wbsd_write_config ( host , WBSD_CONF_PORT_HI , host - > base > > 8 ) ;
wbsd_write_config ( host , WBSD_CONF_PORT_LO , host - > base & 0xff ) ;
2005-05-08 22:35:27 +04:00
wbsd_write_config ( host , WBSD_CONF_IRQ , host - > irq ) ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
if ( host - > dma > = 0 )
wbsd_write_config ( host , WBSD_CONF_DRQ , host - > dma ) ;
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Enable and power up chip .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
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 ;
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Select SD / MMC function .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
wbsd_write_config ( host , WBSD_CONF_DEVICE , DEVICE_SD ) ;
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Read configuration .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
base = wbsd_read_config ( host , WBSD_CONF_PORT_HI ) < < 8 ;
base | = wbsd_read_config ( host , WBSD_CONF_PORT_LO ) ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
irq = wbsd_read_config ( host , WBSD_CONF_IRQ ) ;
dma = wbsd_read_config ( host , WBSD_CONF_DRQ ) ;
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Validate against given configuration .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
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 ) ;
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Scan for hardware .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
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 ;
}
}
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* Request resources .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
ret = wbsd_request_resources ( host , io , irq , dma ) ;
if ( ret )
{
wbsd_release_resources ( host ) ;
wbsd_free_mmc ( dev ) ;
return ret ;
}
2005-04-17 02:20:36 +04:00
/*
2005-05-08 22:35:27 +04:00
* See if chip needs to be configured .
2005-04-17 02:20:36 +04:00
*/
2005-05-08 22:35:27 +04:00
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 ) ;
2005-04-17 02:20:36 +04:00
/*
* Power Management stuff . No idea how this works .
* Not tested .
*/
# ifdef CONFIG_PM
2005-05-08 22:35:27 +04:00
if ( host - > config )
wbsd_write_config ( host , WBSD_CONF_PME , 0xA0 ) ;
2005-04-17 02:20:36 +04:00
# endif
2005-05-08 22:35:27 +04:00
/*
* Allow device to initialise itself properly .
*/
mdelay ( 5 ) ;
2005-04-17 02:20:36 +04:00
/*
* Reset the chip into a known state .
*/
wbsd_init_device ( host ) ;
mmc_add_host ( mmc ) ;
2005-08-19 12:40:08 +04:00
printk ( KERN_INFO " %s: W83L51xD " , mmc_hostname ( mmc ) ) ;
2005-05-08 22:35:27 +04:00
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 " ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-05-08 22:35:27 +04:00
static void __devexit wbsd_shutdown ( struct device * dev , int pnp )
2005-04-17 02:20:36 +04:00
{
struct mmc_host * mmc = dev_get_drvdata ( dev ) ;
struct wbsd_host * host ;
if ( ! mmc )
2005-05-08 22:35:27 +04:00
return ;
2005-04-17 02:20:36 +04:00
host = mmc_priv ( mmc ) ;
mmc_remove_host ( mmc ) ;
2005-05-08 22:35:27 +04:00
if ( ! pnp )
{
/*
* Power down the SD / MMC function .
*/
wbsd_unlock_config ( host ) ;
wbsd_write_config ( host , WBSD_CONF_DEVICE , DEVICE_SD ) ;
wbsd_write_config ( host , WBSD_CONF_ENABLE , 0 ) ;
wbsd_lock_config ( host ) ;
}
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
wbsd_release_resources ( host ) ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
wbsd_free_mmc ( dev ) ;
}
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
/*
* 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 ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
/*
* 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 ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
DBGF ( " PnP resources: port %3x irq %d dma %d \n " , io , irq , dma ) ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
return wbsd_init ( & pnpdev - > dev , io , irq , dma , 1 ) ;
}
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
static void __devexit wbsd_pnp_remove ( struct pnp_dev * dev )
{
wbsd_shutdown ( & dev - > dev , 1 ) ;
2005-04-17 02:20:36 +04:00
}
2005-05-08 22:35:27 +04:00
# endif /* CONFIG_PNP */
2005-04-17 02:20:36 +04:00
/*
* Power management
*/
# ifdef CONFIG_PM
2005-04-17 02:25:29 +04:00
static int wbsd_suspend ( struct device * dev , pm_message_t state , u32 level )
2005-04-17 02:20:36 +04:00
{
DBGF ( " Not yet supported \n " ) ;
return 0 ;
}
static int wbsd_resume ( struct device * dev , u32 level )
{
DBGF ( " Not yet supported \n " ) ;
return 0 ;
}
# else
# define wbsd_suspend NULL
# define wbsd_resume NULL
# endif
2005-05-08 22:35:27 +04:00
static struct platform_device * wbsd_device ;
2005-04-17 02:20:36 +04:00
static struct device_driver wbsd_driver = {
. name = DRIVER_NAME ,
. bus = & platform_bus_type ,
. probe = wbsd_probe ,
. remove = wbsd_remove ,
. suspend = wbsd_suspend ,
. resume = wbsd_resume ,
} ;
2005-05-08 22:35:27 +04:00
# 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 */
2005-04-17 02:20:36 +04:00
/*
* Module loading / unloading
*/
static int __init wbsd_drv_init ( void )
{
int result ;
printk ( KERN_INFO DRIVER_NAME
" : Winbond W83L51xD SD/MMC card interface driver, "
DRIVER_VERSION " \n " ) ;
printk ( KERN_INFO DRIVER_NAME " : Copyright(c) Pierre Ossman \n " ) ;
2005-05-08 22:35:27 +04:00
# ifdef CONFIG_PNP
if ( ! nopnp )
{
result = pnp_register_driver ( & wbsd_pnp_driver ) ;
if ( result < 0 )
return result ;
}
# endif /* CONFIG_PNP */
if ( nopnp )
{
result = driver_register ( & wbsd_driver ) ;
if ( result < 0 )
return result ;
wbsd_device = platform_device_register_simple ( DRIVER_NAME , - 1 ,
NULL , 0 ) ;
if ( IS_ERR ( wbsd_device ) )
return PTR_ERR ( wbsd_device ) ;
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void __exit wbsd_drv_exit ( void )
{
2005-05-08 22:35:27 +04:00
# ifdef CONFIG_PNP
if ( ! nopnp )
pnp_unregister_driver ( & wbsd_pnp_driver ) ;
2005-04-17 02:20:36 +04:00
2005-05-08 22:35:27 +04:00
# endif /* CONFIG_PNP */
if ( nopnp )
{
platform_device_unregister ( wbsd_device ) ;
driver_unregister ( & wbsd_driver ) ;
}
2005-04-17 02:20:36 +04:00
DBG ( " unloaded \n " ) ;
}
module_init ( wbsd_drv_init ) ;
module_exit ( wbsd_drv_exit ) ;
2005-05-08 22:35:27 +04:00
# ifdef CONFIG_PNP
module_param ( nopnp , uint , 0444 ) ;
# endif
2005-04-17 02:20:36 +04:00
module_param ( io , uint , 0444 ) ;
module_param ( irq , uint , 0444 ) ;
module_param ( dma , int , 0444 ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " Winbond W83L51xD SD/MMC card interface driver " ) ;
MODULE_VERSION ( DRIVER_VERSION ) ;
2005-05-08 22:35:27 +04:00
# ifdef CONFIG_PNP
MODULE_PARM_DESC ( nopnp , " Scan for device instead of relying on PNP. (default 0) " ) ;
# endif
2005-04-17 02:20:36 +04:00
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) " ) ;