2006-03-24 14:18:17 +03:00
/*
* linux / drivers / mmc / sdhci . c - Secure Digital Host Controller Interface driver
*
* Copyright ( C ) 2005 - 2006 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 .
*/
# include <linux/delay.h>
# include <linux/highmem.h>
# include <linux/pci.h>
# include <linux/dma-mapping.h>
# include <linux/mmc/host.h>
# include <linux/mmc/protocol.h>
# include <asm/scatterlist.h>
# include "sdhci.h"
# define DRIVER_NAME "sdhci"
2006-06-30 13:22:32 +04:00
# define DRIVER_VERSION "0.12"
2006-03-24 14:18:17 +03:00
# define BUGMAIL "<sdhci-devel@list.drzeus.cx>"
# define DBG(f, x...) \
2006-03-29 12:30:20 +04:00
pr_debug ( DRIVER_NAME " [%s()]: " f , __func__ , # # x )
2006-03-24 14:18:17 +03:00
2006-06-30 13:22:31 +04:00
static unsigned int debug_nodma = 0 ;
static unsigned int debug_forcedma = 0 ;
2006-06-30 13:22:31 +04:00
static unsigned int debug_quirks = 0 ;
2006-06-30 13:22:31 +04:00
2006-06-30 13:22:33 +04:00
# define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0)
2006-06-30 13:22:34 +04:00
# define SDHCI_QUIRK_FORCE_DMA (1<<1)
2006-06-30 13:22:33 +04:00
2006-03-24 14:18:17 +03:00
static const struct pci_device_id pci_ids [ ] __devinitdata = {
2006-06-30 13:22:33 +04:00
{
. vendor = PCI_VENDOR_ID_RICOH ,
. device = PCI_DEVICE_ID_RICOH_R5C822 ,
. subvendor = PCI_VENDOR_ID_IBM ,
. subdevice = PCI_ANY_ID ,
2006-06-30 13:22:34 +04:00
. driver_data = SDHCI_QUIRK_CLOCK_BEFORE_RESET |
SDHCI_QUIRK_FORCE_DMA ,
} ,
{
. vendor = PCI_VENDOR_ID_RICOH ,
. device = PCI_DEVICE_ID_RICOH_R5C822 ,
. subvendor = PCI_ANY_ID ,
. subdevice = PCI_ANY_ID ,
. driver_data = SDHCI_QUIRK_FORCE_DMA ,
} ,
{
. vendor = PCI_VENDOR_ID_TI ,
. device = PCI_DEVICE_ID_TI_XX21_XX11_SD ,
. subvendor = PCI_ANY_ID ,
. subdevice = PCI_ANY_ID ,
. driver_data = SDHCI_QUIRK_FORCE_DMA ,
2006-06-30 13:22:33 +04:00
} ,
{ /* Generic SD host controller */
PCI_DEVICE_CLASS ( ( PCI_CLASS_SYSTEM_SDHCI < < 8 ) , 0xFFFF00 )
} ,
2006-03-24 14:18:17 +03:00
{ /* end: all zeroes */ } ,
} ;
MODULE_DEVICE_TABLE ( pci , pci_ids ) ;
static void sdhci_prepare_data ( struct sdhci_host * , struct mmc_data * ) ;
static void sdhci_finish_data ( struct sdhci_host * ) ;
static void sdhci_send_command ( struct sdhci_host * , struct mmc_command * ) ;
static void sdhci_finish_command ( struct sdhci_host * ) ;
static void sdhci_dumpregs ( struct sdhci_host * host )
{
printk ( KERN_DEBUG DRIVER_NAME " : ============== REGISTER DUMP ============== \n " ) ;
printk ( KERN_DEBUG DRIVER_NAME " : Sys addr: 0x%08x | Version: 0x%08x \n " ,
readl ( host - > ioaddr + SDHCI_DMA_ADDRESS ) ,
readw ( host - > ioaddr + SDHCI_HOST_VERSION ) ) ;
printk ( KERN_DEBUG DRIVER_NAME " : Blk size: 0x%08x | Blk cnt: 0x%08x \n " ,
readw ( host - > ioaddr + SDHCI_BLOCK_SIZE ) ,
readw ( host - > ioaddr + SDHCI_BLOCK_COUNT ) ) ;
printk ( KERN_DEBUG DRIVER_NAME " : Argument: 0x%08x | Trn mode: 0x%08x \n " ,
readl ( host - > ioaddr + SDHCI_ARGUMENT ) ,
readw ( host - > ioaddr + SDHCI_TRANSFER_MODE ) ) ;
printk ( KERN_DEBUG DRIVER_NAME " : Present: 0x%08x | Host ctl: 0x%08x \n " ,
readl ( host - > ioaddr + SDHCI_PRESENT_STATE ) ,
readb ( host - > ioaddr + SDHCI_HOST_CONTROL ) ) ;
printk ( KERN_DEBUG DRIVER_NAME " : Power: 0x%08x | Blk gap: 0x%08x \n " ,
readb ( host - > ioaddr + SDHCI_POWER_CONTROL ) ,
readb ( host - > ioaddr + SDHCI_BLOCK_GAP_CONTROL ) ) ;
printk ( KERN_DEBUG DRIVER_NAME " : Wake-up: 0x%08x | Clock: 0x%08x \n " ,
readb ( host - > ioaddr + SDHCI_WALK_UP_CONTROL ) ,
readw ( host - > ioaddr + SDHCI_CLOCK_CONTROL ) ) ;
printk ( KERN_DEBUG DRIVER_NAME " : Timeout: 0x%08x | Int stat: 0x%08x \n " ,
readb ( host - > ioaddr + SDHCI_TIMEOUT_CONTROL ) ,
readl ( host - > ioaddr + SDHCI_INT_STATUS ) ) ;
printk ( KERN_DEBUG DRIVER_NAME " : Int enab: 0x%08x | Sig enab: 0x%08x \n " ,
readl ( host - > ioaddr + SDHCI_INT_ENABLE ) ,
readl ( host - > ioaddr + SDHCI_SIGNAL_ENABLE ) ) ;
printk ( KERN_DEBUG DRIVER_NAME " : AC12 err: 0x%08x | Slot int: 0x%08x \n " ,
readw ( host - > ioaddr + SDHCI_ACMD12_ERR ) ,
readw ( host - > ioaddr + SDHCI_SLOT_INT_STATUS ) ) ;
printk ( KERN_DEBUG DRIVER_NAME " : Caps: 0x%08x | Max curr: 0x%08x \n " ,
readl ( host - > ioaddr + SDHCI_CAPABILITIES ) ,
readl ( host - > ioaddr + SDHCI_MAX_CURRENT ) ) ;
printk ( KERN_DEBUG DRIVER_NAME " : =========================================== \n " ) ;
}
/*****************************************************************************\
* *
* Low level functions *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void sdhci_reset ( struct sdhci_host * host , u8 mask )
{
2006-06-30 13:22:24 +04:00
unsigned long timeout ;
2006-03-24 14:18:17 +03:00
writeb ( mask , host - > ioaddr + SDHCI_SOFTWARE_RESET ) ;
2006-06-30 13:22:24 +04:00
if ( mask & SDHCI_RESET_ALL )
2006-03-24 14:18:17 +03:00
host - > clock = 0 ;
2006-06-30 13:22:24 +04:00
/* Wait max 100 ms */
timeout = 100 ;
/* hw clears the bit when it's done */
while ( readb ( host - > ioaddr + SDHCI_SOFTWARE_RESET ) & mask ) {
if ( timeout = = 0 ) {
printk ( KERN_ERR " %s: Reset 0x%x never completed. "
" Please report this to " BUGMAIL " . \n " ,
mmc_hostname ( host - > mmc ) , ( int ) mask ) ;
sdhci_dumpregs ( host ) ;
return ;
}
timeout - - ;
mdelay ( 1 ) ;
2006-03-24 14:18:17 +03:00
}
}
static void sdhci_init ( struct sdhci_host * host )
{
u32 intmask ;
sdhci_reset ( host , SDHCI_RESET_ALL ) ;
2006-06-30 13:22:26 +04:00
intmask = SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT |
SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX |
SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT |
SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT |
2006-07-02 19:50:59 +04:00
SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL |
2006-06-30 13:22:26 +04:00
SDHCI_INT_DMA_END | SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE ;
2006-03-24 14:18:17 +03:00
writel ( intmask , host - > ioaddr + SDHCI_INT_ENABLE ) ;
writel ( intmask , host - > ioaddr + SDHCI_SIGNAL_ENABLE ) ;
}
static void sdhci_activate_led ( struct sdhci_host * host )
{
u8 ctrl ;
ctrl = readb ( host - > ioaddr + SDHCI_HOST_CONTROL ) ;
ctrl | = SDHCI_CTRL_LED ;
writeb ( ctrl , host - > ioaddr + SDHCI_HOST_CONTROL ) ;
}
static void sdhci_deactivate_led ( struct sdhci_host * host )
{
u8 ctrl ;
ctrl = readb ( host - > ioaddr + SDHCI_HOST_CONTROL ) ;
ctrl & = ~ SDHCI_CTRL_LED ;
writeb ( ctrl , host - > ioaddr + SDHCI_HOST_CONTROL ) ;
}
/*****************************************************************************\
* *
* Core functions *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static inline char * sdhci_kmap_sg ( struct sdhci_host * host )
{
host - > mapped_sg = kmap_atomic ( host - > cur_sg - > page , KM_BIO_SRC_IRQ ) ;
return host - > mapped_sg + host - > cur_sg - > offset ;
}
static inline void sdhci_kunmap_sg ( struct sdhci_host * host )
{
kunmap_atomic ( host - > mapped_sg , KM_BIO_SRC_IRQ ) ;
}
static inline int sdhci_next_sg ( struct sdhci_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 ;
}
2006-07-02 19:50:59 +04:00
static void sdhci_read_block_pio ( struct sdhci_host * host )
2006-03-24 14:18:17 +03:00
{
2006-07-02 19:50:59 +04:00
int blksize , chunk_remain ;
u32 data ;
2006-03-24 14:18:17 +03:00
char * buffer ;
2006-07-02 19:50:59 +04:00
int size ;
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
DBG ( " PIO reading \n " ) ;
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
blksize = host - > data - > blksz ;
chunk_remain = 0 ;
data = 0 ;
2006-03-24 14:18:17 +03:00
buffer = sdhci_kmap_sg ( host ) + host - > offset ;
2006-07-02 19:50:59 +04:00
while ( blksize ) {
if ( chunk_remain = = 0 ) {
data = readl ( host - > ioaddr + SDHCI_BUFFER ) ;
chunk_remain = min ( blksize , 4 ) ;
}
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
size = min ( host - > size , host - > remain ) ;
size = min ( size , chunk_remain ) ;
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
chunk_remain - = size ;
blksize - = size ;
host - > offset + = size ;
host - > remain - = size ;
host - > size - = size ;
while ( size ) {
* buffer = data & 0xFF ;
buffer + + ;
data > > = 8 ;
size - - ;
}
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
if ( host - > remain = = 0 ) {
sdhci_kunmap_sg ( host ) ;
if ( sdhci_next_sg ( host ) = = 0 ) {
BUG_ON ( blksize ! = 0 ) ;
return ;
}
buffer = sdhci_kmap_sg ( host ) ;
2006-03-24 14:18:17 +03:00
}
2006-07-02 19:50:59 +04:00
}
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
sdhci_kunmap_sg ( host ) ;
}
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
static void sdhci_write_block_pio ( struct sdhci_host * host )
{
int blksize , chunk_remain ;
u32 data ;
char * buffer ;
int bytes , size ;
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
DBG ( " PIO writing \n " ) ;
blksize = host - > data - > blksz ;
chunk_remain = 4 ;
data = 0 ;
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
bytes = 0 ;
buffer = sdhci_kmap_sg ( host ) + host - > offset ;
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
while ( blksize ) {
size = min ( host - > size , host - > remain ) ;
size = min ( size , chunk_remain ) ;
chunk_remain - = size ;
blksize - = size ;
2006-03-24 14:18:17 +03:00
host - > offset + = size ;
host - > remain - = size ;
host - > size - = size ;
2006-07-02 19:50:59 +04:00
while ( size ) {
data > > = 8 ;
data | = ( u32 ) * buffer < < 24 ;
buffer + + ;
size - - ;
}
if ( chunk_remain = = 0 ) {
writel ( data , host - > ioaddr + SDHCI_BUFFER ) ;
chunk_remain = min ( blksize , 4 ) ;
}
2006-03-24 14:18:17 +03:00
if ( host - > remain = = 0 ) {
sdhci_kunmap_sg ( host ) ;
if ( sdhci_next_sg ( host ) = = 0 ) {
2006-07-02 19:50:59 +04:00
BUG_ON ( blksize ! = 0 ) ;
2006-03-24 14:18:17 +03:00
return ;
}
buffer = sdhci_kmap_sg ( host ) ;
}
}
sdhci_kunmap_sg ( host ) ;
2006-07-02 19:50:59 +04:00
}
static void sdhci_transfer_pio ( struct sdhci_host * host )
{
u32 mask ;
BUG_ON ( ! host - > data ) ;
if ( host - > size = = 0 )
return ;
if ( host - > data - > flags & MMC_DATA_READ )
mask = SDHCI_DATA_AVAILABLE ;
else
mask = SDHCI_SPACE_AVAILABLE ;
while ( readl ( host - > ioaddr + SDHCI_PRESENT_STATE ) & mask ) {
if ( host - > data - > flags & MMC_DATA_READ )
sdhci_read_block_pio ( host ) ;
else
sdhci_write_block_pio ( host ) ;
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
if ( host - > size = = 0 )
break ;
BUG_ON ( host - > num_sg = = 0 ) ;
}
2006-03-24 14:18:17 +03:00
2006-07-02 19:50:59 +04:00
DBG ( " PIO transfer complete. \n " ) ;
2006-03-24 14:18:17 +03:00
}
static void sdhci_prepare_data ( struct sdhci_host * host , struct mmc_data * data )
{
2006-06-30 13:22:25 +04:00
u8 count ;
unsigned target_timeout , current_timeout ;
2006-03-24 14:18:17 +03:00
WARN_ON ( host - > data ) ;
2006-06-30 13:22:25 +04:00
if ( data = = NULL )
2006-03-24 14:18:17 +03:00
return ;
DBG ( " blksz %04x blks %04x flags %08x \n " ,
2006-06-04 20:51:15 +04:00
data - > blksz , data - > blocks , data - > flags ) ;
2006-03-24 14:18:17 +03:00
DBG ( " tsac %d ms nsac %d clk \n " ,
data - > timeout_ns / 1000000 , data - > timeout_clks ) ;
2006-07-02 19:51:35 +04:00
/* Sanity checks */
BUG_ON ( data - > blksz * data - > blocks > 524288 ) ;
2006-07-02 19:52:10 +04:00
BUG_ON ( data - > blksz > host - > max_block ) ;
BUG_ON ( data - > blocks > 65535 ) ;
2006-03-24 14:18:17 +03:00
2006-06-30 13:22:25 +04:00
/* timeout in us */
target_timeout = data - > timeout_ns / 1000 +
data - > timeout_clks / host - > clock ;
2006-03-24 14:18:17 +03:00
2006-06-30 13:22:25 +04:00
/*
* Figure out needed cycles .
* We do this in steps in order to fit inside a 32 bit int .
* The first step is the minimum timeout , which will have a
* minimum resolution of 6 bits :
* ( 1 ) 2 ^ 13 * 1000 > 2 ^ 22 ,
* ( 2 ) host - > timeout_clk < 2 ^ 16
* = >
* ( 1 ) / ( 2 ) > 2 ^ 6
*/
count = 0 ;
current_timeout = ( 1 < < 13 ) * 1000 / host - > timeout_clk ;
while ( current_timeout < target_timeout ) {
count + + ;
current_timeout < < = 1 ;
if ( count > = 0xF )
break ;
}
if ( count > = 0xF ) {
printk ( KERN_WARNING " %s: Too large timeout requested! \n " ,
mmc_hostname ( host - > mmc ) ) ;
count = 0xE ;
}
writeb ( count , host - > ioaddr + SDHCI_TIMEOUT_CONTROL ) ;
2006-03-24 14:18:17 +03:00
if ( host - > flags & SDHCI_USE_DMA ) {
int count ;
count = pci_map_sg ( host - > chip - > pdev , data - > sg , data - > sg_len ,
( data - > flags & MMC_DATA_READ ) ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE ) ;
BUG_ON ( count ! = 1 ) ;
writel ( sg_dma_address ( data - > sg ) , host - > ioaddr + SDHCI_DMA_ADDRESS ) ;
} else {
2006-06-04 20:51:15 +04:00
host - > size = data - > blksz * data - > blocks ;
2006-03-24 14:18:17 +03:00
host - > cur_sg = data - > sg ;
host - > num_sg = data - > sg_len ;
host - > offset = 0 ;
host - > remain = host - > cur_sg - > length ;
}
2006-06-30 13:22:25 +04:00
2006-07-02 19:51:35 +04:00
/* We do not handle DMA boundaries, so set it to max (512 KiB) */
writew ( SDHCI_MAKE_BLKSZ ( 7 , data - > blksz ) ,
host - > ioaddr + SDHCI_BLOCK_SIZE ) ;
2006-06-30 13:22:25 +04:00
writew ( data - > blocks , host - > ioaddr + SDHCI_BLOCK_COUNT ) ;
}
static void sdhci_set_transfer_mode ( struct sdhci_host * host ,
struct mmc_data * data )
{
u16 mode ;
WARN_ON ( host - > data ) ;
if ( data = = NULL )
return ;
mode = SDHCI_TRNS_BLK_CNT_EN ;
if ( data - > blocks > 1 )
mode | = SDHCI_TRNS_MULTI ;
if ( data - > flags & MMC_DATA_READ )
mode | = SDHCI_TRNS_READ ;
if ( host - > flags & SDHCI_USE_DMA )
mode | = SDHCI_TRNS_DMA ;
writew ( mode , host - > ioaddr + SDHCI_TRANSFER_MODE ) ;
2006-03-24 14:18:17 +03:00
}
static void sdhci_finish_data ( struct sdhci_host * host )
{
struct mmc_data * data ;
u16 blocks ;
BUG_ON ( ! host - > data ) ;
data = host - > data ;
host - > data = NULL ;
if ( host - > flags & SDHCI_USE_DMA ) {
pci_unmap_sg ( host - > chip - > pdev , data - > sg , data - > sg_len ,
( data - > flags & MMC_DATA_READ ) ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE ) ;
}
/*
* Controller doesn ' t count down when in single block mode .
*/
if ( ( data - > blocks = = 1 ) & & ( data - > error = = MMC_ERR_NONE ) )
blocks = 0 ;
else
blocks = readw ( host - > ioaddr + SDHCI_BLOCK_COUNT ) ;
2006-06-04 20:51:15 +04:00
data - > bytes_xfered = data - > blksz * ( data - > blocks - blocks ) ;
2006-03-24 14:18:17 +03:00
if ( ( data - > error = = MMC_ERR_NONE ) & & blocks ) {
printk ( KERN_ERR " %s: Controller signalled completion even "
" though there were blocks left. Please report this "
" to " BUGMAIL " . \n " , mmc_hostname ( host - > mmc ) ) ;
data - > error = MMC_ERR_FAILED ;
2006-06-30 13:22:34 +04:00
} else if ( host - > size ! = 0 ) {
2006-03-24 14:18:17 +03:00
printk ( KERN_ERR " %s: %d bytes were left untransferred. "
" Please report this to " BUGMAIL " . \n " ,
mmc_hostname ( host - > mmc ) , host - > size ) ;
data - > error = MMC_ERR_FAILED ;
}
DBG ( " Ending data transfer (%d bytes) \n " , data - > bytes_xfered ) ;
if ( data - > stop ) {
/*
* The controller needs a reset of internal state machines
* upon error conditions .
*/
if ( data - > error ! = MMC_ERR_NONE ) {
sdhci_reset ( host , SDHCI_RESET_CMD ) ;
sdhci_reset ( host , SDHCI_RESET_DATA ) ;
}
sdhci_send_command ( host , data - > stop ) ;
} else
tasklet_schedule ( & host - > finish_tasklet ) ;
}
static void sdhci_send_command ( struct sdhci_host * host , struct mmc_command * cmd )
{
int flags ;
2006-06-30 13:22:28 +04:00
u32 mask ;
2006-06-30 13:22:23 +04:00
unsigned long timeout ;
2006-03-24 14:18:17 +03:00
WARN_ON ( host - > cmd ) ;
DBG ( " Sending cmd (%x) \n " , cmd - > opcode ) ;
/* Wait max 10 ms */
2006-06-30 13:22:23 +04:00
timeout = 10 ;
2006-06-30 13:22:28 +04:00
mask = SDHCI_CMD_INHIBIT ;
if ( ( cmd - > data ! = NULL ) | | ( cmd - > flags & MMC_RSP_BUSY ) )
mask | = SDHCI_DATA_INHIBIT ;
/* We shouldn't wait for data inihibit for stop commands, even
though they might use busy signaling */
if ( host - > mrq - > data & & ( cmd = = host - > mrq - > data - > stop ) )
mask & = ~ SDHCI_DATA_INHIBIT ;
while ( readl ( host - > ioaddr + SDHCI_PRESENT_STATE ) & mask ) {
2006-06-30 13:22:23 +04:00
if ( timeout = = 0 ) {
2006-03-24 14:18:17 +03:00
printk ( KERN_ERR " %s: Controller never released "
2006-06-30 13:22:28 +04:00
" inhibit bit(s). Please report this to "
2006-03-24 14:18:17 +03:00
BUGMAIL " . \n " , mmc_hostname ( host - > mmc ) ) ;
sdhci_dumpregs ( host ) ;
cmd - > error = MMC_ERR_FAILED ;
tasklet_schedule ( & host - > finish_tasklet ) ;
return ;
}
2006-06-30 13:22:23 +04:00
timeout - - ;
mdelay ( 1 ) ;
}
2006-03-24 14:18:17 +03:00
mod_timer ( & host - > timer , jiffies + 10 * HZ ) ;
host - > cmd = cmd ;
sdhci_prepare_data ( host , cmd - > data ) ;
writel ( cmd - > arg , host - > ioaddr + SDHCI_ARGUMENT ) ;
2006-06-30 13:22:25 +04:00
sdhci_set_transfer_mode ( host , cmd - > data ) ;
2006-03-24 14:18:17 +03:00
if ( ( cmd - > flags & MMC_RSP_136 ) & & ( cmd - > flags & MMC_RSP_BUSY ) ) {
printk ( KERN_ERR " %s: Unsupported response type! "
" Please report this to " BUGMAIL " . \n " ,
mmc_hostname ( host - > mmc ) ) ;
cmd - > error = MMC_ERR_INVALID ;
tasklet_schedule ( & host - > finish_tasklet ) ;
return ;
}
if ( ! ( cmd - > flags & MMC_RSP_PRESENT ) )
flags = SDHCI_CMD_RESP_NONE ;
else if ( cmd - > flags & MMC_RSP_136 )
flags = SDHCI_CMD_RESP_LONG ;
else if ( cmd - > flags & MMC_RSP_BUSY )
flags = SDHCI_CMD_RESP_SHORT_BUSY ;
else
flags = SDHCI_CMD_RESP_SHORT ;
if ( cmd - > flags & MMC_RSP_CRC )
flags | = SDHCI_CMD_CRC ;
if ( cmd - > flags & MMC_RSP_OPCODE )
flags | = SDHCI_CMD_INDEX ;
if ( cmd - > data )
flags | = SDHCI_CMD_DATA ;
2006-07-11 23:06:48 +04:00
writew ( SDHCI_MAKE_CMD ( cmd - > opcode , flags ) ,
2006-03-24 14:18:17 +03:00
host - > ioaddr + SDHCI_COMMAND ) ;
}
static void sdhci_finish_command ( struct sdhci_host * host )
{
int i ;
BUG_ON ( host - > cmd = = NULL ) ;
if ( host - > cmd - > flags & MMC_RSP_PRESENT ) {
if ( host - > cmd - > flags & MMC_RSP_136 ) {
/* CRC is stripped so we need to do some shifting. */
for ( i = 0 ; i < 4 ; i + + ) {
host - > cmd - > resp [ i ] = readl ( host - > ioaddr +
SDHCI_RESPONSE + ( 3 - i ) * 4 ) < < 8 ;
if ( i ! = 3 )
host - > cmd - > resp [ i ] | =
readb ( host - > ioaddr +
SDHCI_RESPONSE + ( 3 - i ) * 4 - 1 ) ;
}
} else {
host - > cmd - > resp [ 0 ] = readl ( host - > ioaddr + SDHCI_RESPONSE ) ;
}
}
host - > cmd - > error = MMC_ERR_NONE ;
DBG ( " Ending cmd (%x) \n " , host - > cmd - > opcode ) ;
2006-06-30 13:22:26 +04:00
if ( host - > cmd - > data )
2006-03-24 14:18:17 +03:00
host - > data = host - > cmd - > data ;
2006-06-30 13:22:26 +04:00
else
2006-03-24 14:18:17 +03:00
tasklet_schedule ( & host - > finish_tasklet ) ;
host - > cmd = NULL ;
}
static void sdhci_set_clock ( struct sdhci_host * host , unsigned int clock )
{
int div ;
u16 clk ;
2006-06-30 13:22:23 +04:00
unsigned long timeout ;
2006-03-24 14:18:17 +03:00
if ( clock = = host - > clock )
return ;
writew ( 0 , host - > ioaddr + SDHCI_CLOCK_CONTROL ) ;
if ( clock = = 0 )
goto out ;
for ( div = 1 ; div < 256 ; div * = 2 ) {
if ( ( host - > max_clk / div ) < = clock )
break ;
}
div > > = 1 ;
clk = div < < SDHCI_DIVIDER_SHIFT ;
clk | = SDHCI_CLOCK_INT_EN ;
writew ( clk , host - > ioaddr + SDHCI_CLOCK_CONTROL ) ;
/* Wait max 10 ms */
2006-06-30 13:22:23 +04:00
timeout = 10 ;
while ( ! ( ( clk = readw ( host - > ioaddr + SDHCI_CLOCK_CONTROL ) )
& SDHCI_CLOCK_INT_STABLE ) ) {
if ( timeout = = 0 ) {
2006-03-24 14:18:17 +03:00
printk ( KERN_ERR " %s: Internal clock never stabilised. "
" Please report this to " BUGMAIL " . \n " ,
mmc_hostname ( host - > mmc ) ) ;
sdhci_dumpregs ( host ) ;
return ;
}
2006-06-30 13:22:23 +04:00
timeout - - ;
mdelay ( 1 ) ;
}
2006-03-24 14:18:17 +03:00
clk | = SDHCI_CLOCK_CARD_EN ;
writew ( clk , host - > ioaddr + SDHCI_CLOCK_CONTROL ) ;
out :
host - > clock = clock ;
}
2006-06-30 13:22:23 +04:00
static void sdhci_set_power ( struct sdhci_host * host , unsigned short power )
{
u8 pwr ;
if ( host - > power = = power )
return ;
writeb ( 0 , host - > ioaddr + SDHCI_POWER_CONTROL ) ;
if ( power = = ( unsigned short ) - 1 )
goto out ;
pwr = SDHCI_POWER_ON ;
switch ( power ) {
case MMC_VDD_170 :
case MMC_VDD_180 :
case MMC_VDD_190 :
pwr | = SDHCI_POWER_180 ;
break ;
case MMC_VDD_290 :
case MMC_VDD_300 :
case MMC_VDD_310 :
pwr | = SDHCI_POWER_300 ;
break ;
case MMC_VDD_320 :
case MMC_VDD_330 :
case MMC_VDD_340 :
pwr | = SDHCI_POWER_330 ;
break ;
default :
BUG ( ) ;
}
writeb ( pwr , host - > ioaddr + SDHCI_POWER_CONTROL ) ;
out :
host - > power = power ;
}
2006-03-24 14:18:17 +03:00
/*****************************************************************************\
* *
* MMC callbacks *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void sdhci_request ( struct mmc_host * mmc , struct mmc_request * mrq )
{
struct sdhci_host * host ;
unsigned long flags ;
host = mmc_priv ( mmc ) ;
spin_lock_irqsave ( & host - > lock , flags ) ;
WARN_ON ( host - > mrq ! = NULL ) ;
sdhci_activate_led ( host ) ;
host - > mrq = mrq ;
if ( ! ( readl ( host - > ioaddr + SDHCI_PRESENT_STATE ) & SDHCI_CARD_PRESENT ) ) {
host - > mrq - > cmd - > error = MMC_ERR_TIMEOUT ;
tasklet_schedule ( & host - > finish_tasklet ) ;
} else
sdhci_send_command ( host , mrq - > cmd ) ;
spin_unlock_irqrestore ( & host - > lock , flags ) ;
}
static void sdhci_set_ios ( struct mmc_host * mmc , struct mmc_ios * ios )
{
struct sdhci_host * host ;
unsigned long flags ;
u8 ctrl ;
host = mmc_priv ( mmc ) ;
spin_lock_irqsave ( & host - > lock , flags ) ;
/*
* Reset the chip on each power off .
* Should clear out any weird states .
*/
if ( ios - > power_mode = = MMC_POWER_OFF ) {
writel ( 0 , host - > ioaddr + SDHCI_SIGNAL_ENABLE ) ;
sdhci_init ( host ) ;
}
sdhci_set_clock ( host , ios - > clock ) ;
if ( ios - > power_mode = = MMC_POWER_OFF )
2006-06-30 13:22:23 +04:00
sdhci_set_power ( host , - 1 ) ;
2006-03-24 14:18:17 +03:00
else
2006-06-30 13:22:23 +04:00
sdhci_set_power ( host , ios - > vdd ) ;
2006-03-24 14:18:17 +03:00
ctrl = readb ( host - > ioaddr + SDHCI_HOST_CONTROL ) ;
if ( ios - > bus_width = = MMC_BUS_WIDTH_4 )
ctrl | = SDHCI_CTRL_4BITBUS ;
else
ctrl & = ~ SDHCI_CTRL_4BITBUS ;
writeb ( ctrl , host - > ioaddr + SDHCI_HOST_CONTROL ) ;
spin_unlock_irqrestore ( & host - > lock , flags ) ;
}
static int sdhci_get_ro ( struct mmc_host * mmc )
{
struct sdhci_host * host ;
unsigned long flags ;
int present ;
host = mmc_priv ( mmc ) ;
spin_lock_irqsave ( & host - > lock , flags ) ;
present = readl ( host - > ioaddr + SDHCI_PRESENT_STATE ) ;
spin_unlock_irqrestore ( & host - > lock , flags ) ;
return ! ( present & SDHCI_WRITE_PROTECT ) ;
}
static struct mmc_host_ops sdhci_ops = {
. request = sdhci_request ,
. set_ios = sdhci_set_ios ,
. get_ro = sdhci_get_ro ,
} ;
/*****************************************************************************\
* *
* Tasklets *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void sdhci_tasklet_card ( unsigned long param )
{
struct sdhci_host * host ;
unsigned long flags ;
host = ( struct sdhci_host * ) param ;
spin_lock_irqsave ( & host - > lock , flags ) ;
if ( ! ( readl ( host - > ioaddr + SDHCI_PRESENT_STATE ) & SDHCI_CARD_PRESENT ) ) {
if ( host - > mrq ) {
printk ( KERN_ERR " %s: Card removed during transfer! \n " ,
mmc_hostname ( host - > mmc ) ) ;
printk ( KERN_ERR " %s: Resetting controller. \n " ,
mmc_hostname ( host - > mmc ) ) ;
sdhci_reset ( host , SDHCI_RESET_CMD ) ;
sdhci_reset ( host , SDHCI_RESET_DATA ) ;
host - > mrq - > cmd - > error = MMC_ERR_FAILED ;
tasklet_schedule ( & host - > finish_tasklet ) ;
}
}
spin_unlock_irqrestore ( & host - > lock , flags ) ;
mmc_detect_change ( host - > mmc , msecs_to_jiffies ( 500 ) ) ;
}
static void sdhci_tasklet_finish ( unsigned long param )
{
struct sdhci_host * host ;
unsigned long flags ;
struct mmc_request * mrq ;
host = ( struct sdhci_host * ) param ;
spin_lock_irqsave ( & host - > lock , flags ) ;
del_timer ( & host - > timer ) ;
mrq = host - > mrq ;
DBG ( " Ending request, cmd (%x) \n " , mrq - > cmd - > opcode ) ;
/*
* The controller needs a reset of internal state machines
* upon error conditions .
*/
if ( ( mrq - > cmd - > error ! = MMC_ERR_NONE ) | |
( mrq - > data & & ( ( mrq - > data - > error ! = MMC_ERR_NONE ) | |
( mrq - > data - > stop & & ( mrq - > data - > stop - > error ! = MMC_ERR_NONE ) ) ) ) ) {
2006-06-30 13:22:33 +04:00
/* Some controllers need this kick or reset won't work here */
if ( host - > chip - > quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET ) {
unsigned int clock ;
/* This is to force an update */
clock = host - > clock ;
host - > clock = 0 ;
sdhci_set_clock ( host , clock ) ;
}
/* Spec says we should do both at the same time, but Ricoh
controllers do not like that . */
2006-03-24 14:18:17 +03:00
sdhci_reset ( host , SDHCI_RESET_CMD ) ;
sdhci_reset ( host , SDHCI_RESET_DATA ) ;
}
host - > mrq = NULL ;
host - > cmd = NULL ;
host - > data = NULL ;
sdhci_deactivate_led ( host ) ;
spin_unlock_irqrestore ( & host - > lock , flags ) ;
mmc_request_done ( host - > mmc , mrq ) ;
}
static void sdhci_timeout_timer ( unsigned long data )
{
struct sdhci_host * host ;
unsigned long flags ;
host = ( struct sdhci_host * ) data ;
spin_lock_irqsave ( & host - > lock , flags ) ;
if ( host - > mrq ) {
printk ( KERN_ERR " %s: Timeout waiting for hardware interrupt. "
" Please report this to " BUGMAIL " . \n " ,
mmc_hostname ( host - > mmc ) ) ;
sdhci_dumpregs ( host ) ;
if ( host - > data ) {
host - > data - > error = MMC_ERR_TIMEOUT ;
sdhci_finish_data ( host ) ;
} else {
if ( host - > cmd )
host - > cmd - > error = MMC_ERR_TIMEOUT ;
else
host - > mrq - > cmd - > error = MMC_ERR_TIMEOUT ;
tasklet_schedule ( & host - > finish_tasklet ) ;
}
}
spin_unlock_irqrestore ( & host - > lock , flags ) ;
}
/*****************************************************************************\
* *
* Interrupt handling *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void sdhci_cmd_irq ( struct sdhci_host * host , u32 intmask )
{
BUG_ON ( intmask = = 0 ) ;
if ( ! host - > cmd ) {
printk ( KERN_ERR " %s: Got command interrupt even though no "
" command operation was in progress. \n " ,
mmc_hostname ( host - > mmc ) ) ;
printk ( KERN_ERR " %s: Please report this to " BUGMAIL " . \n " ,
mmc_hostname ( host - > mmc ) ) ;
sdhci_dumpregs ( host ) ;
return ;
}
if ( intmask & SDHCI_INT_RESPONSE )
sdhci_finish_command ( host ) ;
else {
if ( intmask & SDHCI_INT_TIMEOUT )
host - > cmd - > error = MMC_ERR_TIMEOUT ;
else if ( intmask & SDHCI_INT_CRC )
host - > cmd - > error = MMC_ERR_BADCRC ;
else if ( intmask & ( SDHCI_INT_END_BIT | SDHCI_INT_INDEX ) )
host - > cmd - > error = MMC_ERR_FAILED ;
else
host - > cmd - > error = MMC_ERR_INVALID ;
tasklet_schedule ( & host - > finish_tasklet ) ;
}
}
static void sdhci_data_irq ( struct sdhci_host * host , u32 intmask )
{
BUG_ON ( intmask = = 0 ) ;
if ( ! host - > data ) {
/*
* A data end interrupt is sent together with the response
* for the stop command .
*/
if ( intmask & SDHCI_INT_DATA_END )
return ;
printk ( KERN_ERR " %s: Got data interrupt even though no "
" data operation was in progress. \n " ,
mmc_hostname ( host - > mmc ) ) ;
printk ( KERN_ERR " %s: Please report this to " BUGMAIL " . \n " ,
mmc_hostname ( host - > mmc ) ) ;
sdhci_dumpregs ( host ) ;
return ;
}
if ( intmask & SDHCI_INT_DATA_TIMEOUT )
host - > data - > error = MMC_ERR_TIMEOUT ;
else if ( intmask & SDHCI_INT_DATA_CRC )
host - > data - > error = MMC_ERR_BADCRC ;
else if ( intmask & SDHCI_INT_DATA_END_BIT )
host - > data - > error = MMC_ERR_FAILED ;
if ( host - > data - > error ! = MMC_ERR_NONE )
sdhci_finish_data ( host ) ;
else {
2006-07-02 19:50:59 +04:00
if ( intmask & ( SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL ) )
2006-03-24 14:18:17 +03:00
sdhci_transfer_pio ( host ) ;
if ( intmask & SDHCI_INT_DATA_END )
sdhci_finish_data ( host ) ;
}
}
static irqreturn_t sdhci_irq ( int irq , void * dev_id , struct pt_regs * regs )
{
irqreturn_t result ;
struct sdhci_host * host = dev_id ;
u32 intmask ;
spin_lock ( & host - > lock ) ;
intmask = readl ( host - > ioaddr + SDHCI_INT_STATUS ) ;
if ( ! intmask ) {
result = IRQ_NONE ;
goto out ;
}
DBG ( " *** %s got interrupt: 0x%08x \n " , host - > slot_descr , intmask ) ;
2006-06-30 13:22:26 +04:00
if ( intmask & ( SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE ) ) {
writel ( intmask & ( SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE ) ,
host - > ioaddr + SDHCI_INT_STATUS ) ;
2006-03-24 14:18:17 +03:00
tasklet_schedule ( & host - > card_tasklet ) ;
2006-06-30 13:22:26 +04:00
}
2006-03-24 14:18:17 +03:00
2006-06-30 13:22:26 +04:00
intmask & = ~ ( SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE ) ;
2006-03-24 14:18:17 +03:00
2006-06-30 13:22:26 +04:00
if ( intmask & SDHCI_INT_CMD_MASK ) {
2006-03-24 14:18:17 +03:00
writel ( intmask & SDHCI_INT_CMD_MASK ,
host - > ioaddr + SDHCI_INT_STATUS ) ;
2006-06-30 13:22:26 +04:00
sdhci_cmd_irq ( host , intmask & SDHCI_INT_CMD_MASK ) ;
2006-03-24 14:18:17 +03:00
}
if ( intmask & SDHCI_INT_DATA_MASK ) {
writel ( intmask & SDHCI_INT_DATA_MASK ,
host - > ioaddr + SDHCI_INT_STATUS ) ;
2006-06-30 13:22:26 +04:00
sdhci_data_irq ( host , intmask & SDHCI_INT_DATA_MASK ) ;
2006-03-24 14:18:17 +03:00
}
intmask & = ~ ( SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK ) ;
if ( intmask & SDHCI_INT_BUS_POWER ) {
2006-06-30 13:22:26 +04:00
printk ( KERN_ERR " %s: Card is consuming too much power! \n " ,
2006-03-24 14:18:17 +03:00
mmc_hostname ( host - > mmc ) ) ;
2006-06-30 13:22:26 +04:00
writel ( SDHCI_INT_BUS_POWER , host - > ioaddr + SDHCI_INT_STATUS ) ;
2006-03-24 14:18:17 +03:00
}
2006-06-30 13:22:26 +04:00
intmask & = SDHCI_INT_BUS_POWER ;
if ( intmask ) {
printk ( KERN_ERR " %s: Unexpected interrupt 0x%08x. Please "
2006-03-24 14:18:17 +03:00
" report this to " BUGMAIL " . \n " ,
2006-06-30 13:22:26 +04:00
mmc_hostname ( host - > mmc ) , intmask ) ;
2006-03-24 14:18:17 +03:00
sdhci_dumpregs ( host ) ;
writel ( intmask , host - > ioaddr + SDHCI_INT_STATUS ) ;
2006-06-30 13:22:26 +04:00
}
2006-03-24 14:18:17 +03:00
result = IRQ_HANDLED ;
out :
spin_unlock ( & host - > lock ) ;
return result ;
}
/*****************************************************************************\
* *
* Suspend / resume *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# ifdef CONFIG_PM
static int sdhci_suspend ( struct pci_dev * pdev , pm_message_t state )
{
struct sdhci_chip * chip ;
int i , ret ;
chip = pci_get_drvdata ( pdev ) ;
if ( ! chip )
return 0 ;
DBG ( " Suspending... \n " ) ;
for ( i = 0 ; i < chip - > num_slots ; i + + ) {
if ( ! chip - > hosts [ i ] )
continue ;
ret = mmc_suspend_host ( chip - > hosts [ i ] - > mmc , state ) ;
if ( ret ) {
for ( i - - ; i > = 0 ; i - - )
mmc_resume_host ( chip - > hosts [ i ] - > mmc ) ;
return ret ;
}
}
pci_save_state ( pdev ) ;
pci_enable_wake ( pdev , pci_choose_state ( pdev , state ) , 0 ) ;
pci_disable_device ( pdev ) ;
pci_set_power_state ( pdev , pci_choose_state ( pdev , state ) ) ;
return 0 ;
}
static int sdhci_resume ( struct pci_dev * pdev )
{
struct sdhci_chip * chip ;
int i , ret ;
chip = pci_get_drvdata ( pdev ) ;
if ( ! chip )
return 0 ;
DBG ( " Resuming... \n " ) ;
pci_set_power_state ( pdev , PCI_D0 ) ;
pci_restore_state ( pdev ) ;
pci_enable_device ( pdev ) ;
for ( i = 0 ; i < chip - > num_slots ; i + + ) {
if ( ! chip - > hosts [ i ] )
continue ;
if ( chip - > hosts [ i ] - > flags & SDHCI_USE_DMA )
pci_set_master ( pdev ) ;
sdhci_init ( chip - > hosts [ i ] ) ;
ret = mmc_resume_host ( chip - > hosts [ i ] - > mmc ) ;
if ( ret )
return ret ;
}
return 0 ;
}
# else /* CONFIG_PM */
# define sdhci_suspend NULL
# define sdhci_resume NULL
# endif /* CONFIG_PM */
/*****************************************************************************\
* *
* Device probing / removal *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static int __devinit sdhci_probe_slot ( struct pci_dev * pdev , int slot )
{
int ret ;
2006-06-30 13:22:29 +04:00
unsigned int version ;
2006-03-24 14:18:17 +03:00
struct sdhci_chip * chip ;
struct mmc_host * mmc ;
struct sdhci_host * host ;
u8 first_bar ;
unsigned int caps ;
chip = pci_get_drvdata ( pdev ) ;
BUG_ON ( ! chip ) ;
ret = pci_read_config_byte ( pdev , PCI_SLOT_INFO , & first_bar ) ;
if ( ret )
return ret ;
first_bar & = PCI_SLOT_INFO_FIRST_BAR_MASK ;
if ( first_bar > 5 ) {
printk ( KERN_ERR DRIVER_NAME " : Invalid first BAR. Aborting. \n " ) ;
return - ENODEV ;
}
if ( ! ( pci_resource_flags ( pdev , first_bar + slot ) & IORESOURCE_MEM ) ) {
printk ( KERN_ERR DRIVER_NAME " : BAR is not iomem. Aborting. \n " ) ;
return - ENODEV ;
}
if ( pci_resource_len ( pdev , first_bar + slot ) ! = 0x100 ) {
printk ( KERN_ERR DRIVER_NAME " : Invalid iomem size. Aborting. \n " ) ;
return - ENODEV ;
}
2006-06-30 13:22:31 +04:00
if ( ( pdev - > class & 0x0000FF ) = = PCI_SDHCI_IFVENDOR ) {
printk ( KERN_ERR DRIVER_NAME " : Vendor specific interface. Aborting. \n " ) ;
return - ENODEV ;
}
if ( ( pdev - > class & 0x0000FF ) > PCI_SDHCI_IFVENDOR ) {
printk ( KERN_ERR DRIVER_NAME " : Unknown interface. Aborting. \n " ) ;
return - ENODEV ;
}
2006-03-24 14:18:17 +03:00
mmc = mmc_alloc_host ( sizeof ( struct sdhci_host ) , & pdev - > dev ) ;
if ( ! mmc )
return - ENOMEM ;
host = mmc_priv ( mmc ) ;
host - > mmc = mmc ;
host - > bar = first_bar + slot ;
host - > addr = pci_resource_start ( pdev , host - > bar ) ;
host - > irq = pdev - > irq ;
DBG ( " slot %d at 0x%08lx, irq %d \n " , slot , host - > addr , host - > irq ) ;
snprintf ( host - > slot_descr , 20 , " sdhci:slot%d " , slot ) ;
ret = pci_request_region ( pdev , host - > bar , host - > slot_descr ) ;
if ( ret )
goto free ;
host - > ioaddr = ioremap_nocache ( host - > addr ,
pci_resource_len ( pdev , host - > bar ) ) ;
if ( ! host - > ioaddr ) {
ret = - ENOMEM ;
goto release ;
}
2006-06-30 13:22:30 +04:00
sdhci_reset ( host , SDHCI_RESET_ALL ) ;
2006-06-30 13:22:29 +04:00
version = readw ( host - > ioaddr + SDHCI_HOST_VERSION ) ;
version = ( version & SDHCI_SPEC_VER_MASK ) > > SDHCI_SPEC_VER_SHIFT ;
if ( version ! = 0 ) {
printk ( KERN_ERR " %s: Unknown controller version (%d). "
2006-07-11 23:07:10 +04:00
" You may experience problems. \n " , host - > slot_descr ,
2006-06-30 13:22:29 +04:00
version ) ;
}
2006-03-24 14:18:17 +03:00
caps = readl ( host - > ioaddr + SDHCI_CAPABILITIES ) ;
2006-06-30 13:22:31 +04:00
if ( debug_nodma )
DBG ( " DMA forced off \n " ) ;
else if ( debug_forcedma ) {
DBG ( " DMA forced on \n " ) ;
host - > flags | = SDHCI_USE_DMA ;
2006-06-30 13:22:34 +04:00
} else if ( chip - > quirks & SDHCI_QUIRK_FORCE_DMA )
host - > flags | = SDHCI_USE_DMA ;
else if ( ( pdev - > class & 0x0000FF ) ! = PCI_SDHCI_IFDMA )
2006-06-30 13:22:31 +04:00
DBG ( " Controller doesn't have DMA interface \n " ) ;
else if ( ! ( caps & SDHCI_CAN_DO_DMA ) )
DBG ( " Controller doesn't have DMA capability \n " ) ;
else
2006-03-24 14:18:17 +03:00
host - > flags | = SDHCI_USE_DMA ;
if ( host - > flags & SDHCI_USE_DMA ) {
if ( pci_set_dma_mask ( pdev , DMA_32BIT_MASK ) ) {
printk ( KERN_WARNING " %s: No suitable DMA available. "
" Falling back to PIO. \n " , host - > slot_descr ) ;
host - > flags & = ~ SDHCI_USE_DMA ;
}
}
if ( host - > flags & SDHCI_USE_DMA )
pci_set_master ( pdev ) ;
else /* XXX: Hack to get MMC layer to avoid highmem */
pdev - > dma_mask = 0 ;
2006-06-30 13:22:21 +04:00
host - > max_clk =
( caps & SDHCI_CLOCK_BASE_MASK ) > > SDHCI_CLOCK_BASE_SHIFT ;
if ( host - > max_clk = = 0 ) {
printk ( KERN_ERR " %s: Hardware doesn't specify base clock "
" frequency. \n " , host - > slot_descr ) ;
ret = - ENODEV ;
goto unmap ;
}
2006-03-24 14:18:17 +03:00
host - > max_clk * = 1000000 ;
2006-06-30 13:22:25 +04:00
host - > timeout_clk =
( caps & SDHCI_TIMEOUT_CLK_MASK ) > > SDHCI_TIMEOUT_CLK_SHIFT ;
if ( host - > timeout_clk = = 0 ) {
printk ( KERN_ERR " %s: Hardware doesn't specify timeout clock "
" frequency. \n " , host - > slot_descr ) ;
ret = - ENODEV ;
goto unmap ;
}
if ( caps & SDHCI_TIMEOUT_CLK_UNIT )
host - > timeout_clk * = 1000 ;
2006-03-24 14:18:17 +03:00
2006-07-02 19:52:10 +04:00
host - > max_block = ( caps & SDHCI_MAX_BLOCK_MASK ) > > SDHCI_MAX_BLOCK_SHIFT ;
if ( host - > max_block > = 3 ) {
printk ( KERN_ERR " %s: Invalid maximum block size. \n " ,
host - > slot_descr ) ;
ret = - ENODEV ;
goto unmap ;
}
host - > max_block = 512 < < host - > max_block ;
2006-03-24 14:18:17 +03:00
/*
* Set host parameters .
*/
mmc - > ops = & sdhci_ops ;
mmc - > f_min = host - > max_clk / 256 ;
mmc - > f_max = host - > max_clk ;
mmc - > caps = MMC_CAP_4_BIT_DATA ;
2006-06-30 13:22:23 +04:00
mmc - > ocr_avail = 0 ;
if ( caps & SDHCI_CAN_VDD_330 )
mmc - > ocr_avail | = MMC_VDD_32_33 | MMC_VDD_33_34 ;
else if ( caps & SDHCI_CAN_VDD_300 )
mmc - > ocr_avail | = MMC_VDD_29_30 | MMC_VDD_30_31 ;
else if ( caps & SDHCI_CAN_VDD_180 )
mmc - > ocr_avail | = MMC_VDD_17_18 | MMC_VDD_18_19 ;
if ( mmc - > ocr_avail = = 0 ) {
printk ( KERN_ERR " %s: Hardware doesn't report any "
" support voltages. \n " , host - > slot_descr ) ;
ret = - ENODEV ;
goto unmap ;
}
2006-03-24 14:18:17 +03:00
spin_lock_init ( & host - > lock ) ;
/*
* Maximum number of segments . Hardware cannot do scatter lists .
*/
if ( host - > flags & SDHCI_USE_DMA )
mmc - > max_hw_segs = 1 ;
else
mmc - > max_hw_segs = 16 ;
mmc - > max_phys_segs = 16 ;
/*
2006-07-02 19:51:35 +04:00
* Maximum number of sectors in one transfer . Limited by DMA boundary
* size ( 512 KiB ) , which means ( 512 KiB / 512 = ) 1024 entries .
2006-03-24 14:18:17 +03:00
*/
2006-07-02 19:51:35 +04:00
mmc - > max_sectors = 1024 ;
2006-03-24 14:18:17 +03:00
/*
* Maximum segment size . Could be one segment with the maximum number
* of sectors .
*/
mmc - > max_seg_size = mmc - > max_sectors * 512 ;
/*
* Init tasklets .
*/
tasklet_init ( & host - > card_tasklet ,
sdhci_tasklet_card , ( unsigned long ) host ) ;
tasklet_init ( & host - > finish_tasklet ,
sdhci_tasklet_finish , ( unsigned long ) host ) ;
2006-06-13 01:10:22 +04:00
setup_timer ( & host - > timer , sdhci_timeout_timer , ( long ) host ) ;
2006-03-24 14:18:17 +03:00
2006-07-02 06:29:38 +04:00
ret = request_irq ( host - > irq , sdhci_irq , IRQF_SHARED ,
2006-03-24 14:18:17 +03:00
host - > slot_descr , host ) ;
if ( ret )
2006-06-30 13:22:21 +04:00
goto untasklet ;
2006-03-24 14:18:17 +03:00
sdhci_init ( host ) ;
# ifdef CONFIG_MMC_DEBUG
sdhci_dumpregs ( host ) ;
# endif
host - > chip = chip ;
chip - > hosts [ slot ] = host ;
mmc_add_host ( mmc ) ;
printk ( KERN_INFO " %s: SDHCI at 0x%08lx irq %d %s \n " , mmc_hostname ( mmc ) ,
host - > addr , host - > irq ,
( host - > flags & SDHCI_USE_DMA ) ? " DMA " : " PIO " ) ;
return 0 ;
2006-06-30 13:22:21 +04:00
untasklet :
2006-03-24 14:18:17 +03:00
tasklet_kill ( & host - > card_tasklet ) ;
tasklet_kill ( & host - > finish_tasklet ) ;
2006-06-30 13:22:21 +04:00
unmap :
2006-03-24 14:18:17 +03:00
iounmap ( host - > ioaddr ) ;
release :
pci_release_region ( pdev , host - > bar ) ;
free :
mmc_free_host ( mmc ) ;
return ret ;
}
static void sdhci_remove_slot ( struct pci_dev * pdev , int slot )
{
struct sdhci_chip * chip ;
struct mmc_host * mmc ;
struct sdhci_host * host ;
chip = pci_get_drvdata ( pdev ) ;
host = chip - > hosts [ slot ] ;
mmc = host - > mmc ;
chip - > hosts [ slot ] = NULL ;
mmc_remove_host ( mmc ) ;
sdhci_reset ( host , SDHCI_RESET_ALL ) ;
free_irq ( host - > irq , host ) ;
del_timer_sync ( & host - > timer ) ;
tasklet_kill ( & host - > card_tasklet ) ;
tasklet_kill ( & host - > finish_tasklet ) ;
iounmap ( host - > ioaddr ) ;
pci_release_region ( pdev , host - > bar ) ;
mmc_free_host ( mmc ) ;
}
static int __devinit sdhci_probe ( struct pci_dev * pdev ,
const struct pci_device_id * ent )
{
int ret , i ;
2006-06-30 13:22:22 +04:00
u8 slots , rev ;
2006-03-24 14:18:17 +03:00
struct sdhci_chip * chip ;
BUG_ON ( pdev = = NULL ) ;
BUG_ON ( ent = = NULL ) ;
2006-06-30 13:22:22 +04:00
pci_read_config_byte ( pdev , PCI_CLASS_REVISION , & rev ) ;
printk ( KERN_INFO DRIVER_NAME
" : SDHCI controller found at %s [%04x:%04x] (rev %x) \n " ,
pci_name ( pdev ) , ( int ) pdev - > vendor , ( int ) pdev - > device ,
( int ) rev ) ;
2006-03-24 14:18:17 +03:00
ret = pci_read_config_byte ( pdev , PCI_SLOT_INFO , & slots ) ;
if ( ret )
return ret ;
slots = PCI_SLOT_INFO_SLOTS ( slots ) + 1 ;
DBG ( " found %d slot(s) \n " , slots ) ;
if ( slots = = 0 )
return - ENODEV ;
ret = pci_enable_device ( pdev ) ;
if ( ret )
return ret ;
chip = kzalloc ( sizeof ( struct sdhci_chip ) +
sizeof ( struct sdhci_host * ) * slots , GFP_KERNEL ) ;
if ( ! chip ) {
ret = - ENOMEM ;
goto err ;
}
chip - > pdev = pdev ;
2006-06-30 13:22:31 +04:00
chip - > quirks = ent - > driver_data ;
if ( debug_quirks )
chip - > quirks = debug_quirks ;
2006-03-24 14:18:17 +03:00
chip - > num_slots = slots ;
pci_set_drvdata ( pdev , chip ) ;
for ( i = 0 ; i < slots ; i + + ) {
ret = sdhci_probe_slot ( pdev , i ) ;
if ( ret ) {
for ( i - - ; i > = 0 ; i - - )
sdhci_remove_slot ( pdev , i ) ;
goto free ;
}
}
return 0 ;
free :
pci_set_drvdata ( pdev , NULL ) ;
kfree ( chip ) ;
err :
pci_disable_device ( pdev ) ;
return ret ;
}
static void __devexit sdhci_remove ( struct pci_dev * pdev )
{
int i ;
struct sdhci_chip * chip ;
chip = pci_get_drvdata ( pdev ) ;
if ( chip ) {
for ( i = 0 ; i < chip - > num_slots ; i + + )
sdhci_remove_slot ( pdev , i ) ;
pci_set_drvdata ( pdev , NULL ) ;
kfree ( chip ) ;
}
pci_disable_device ( pdev ) ;
}
static struct pci_driver sdhci_driver = {
. name = DRIVER_NAME ,
. id_table = pci_ids ,
. probe = sdhci_probe ,
. remove = __devexit_p ( sdhci_remove ) ,
. suspend = sdhci_suspend ,
. resume = sdhci_resume ,
} ;
/*****************************************************************************\
* *
* Driver init / exit *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static int __init sdhci_drv_init ( void )
{
printk ( KERN_INFO DRIVER_NAME
" : Secure Digital Host Controller Interface driver, "
DRIVER_VERSION " \n " ) ;
printk ( KERN_INFO DRIVER_NAME " : Copyright(c) Pierre Ossman \n " ) ;
return pci_register_driver ( & sdhci_driver ) ;
}
static void __exit sdhci_drv_exit ( void )
{
DBG ( " Exiting \n " ) ;
pci_unregister_driver ( & sdhci_driver ) ;
}
module_init ( sdhci_drv_init ) ;
module_exit ( sdhci_drv_exit ) ;
2006-06-30 13:22:31 +04:00
module_param ( debug_nodma , uint , 0444 ) ;
module_param ( debug_forcedma , uint , 0444 ) ;
2006-06-30 13:22:31 +04:00
module_param ( debug_quirks , uint , 0444 ) ;
2006-06-30 13:22:31 +04:00
2006-03-24 14:18:17 +03:00
MODULE_AUTHOR ( " Pierre Ossman <drzeus@drzeus.cx> " ) ;
MODULE_DESCRIPTION ( " Secure Digital Host Controller Interface driver " ) ;
MODULE_VERSION ( DRIVER_VERSION ) ;
MODULE_LICENSE ( " GPL " ) ;
2006-06-30 13:22:31 +04:00
MODULE_PARM_DESC ( debug_nodma , " Forcefully disable DMA transfers. (default 0) " ) ;
MODULE_PARM_DESC ( debug_forcedma , " Forcefully enable DMA transfers. (default 0) " ) ;
2006-06-30 13:22:31 +04:00
MODULE_PARM_DESC ( debug_quirks , " Force certain quirks. " ) ;