2008-07-15 14:21:29 +02:00
/*
* sdricoh_cs . c - driver for Ricoh Secure Digital Card Readers that can be
* found on some Ricoh RL5c476 II cardbus bridge
*
* Copyright ( C ) 2006 - 2008 Sascha Sommer < saschasommer @ freenet . de >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
*/
/*
# define DEBUG
# define VERBOSE_DEBUG
*/
# include <linux/delay.h>
# include <linux/highmem.h>
# include <linux/pci.h>
# include <linux/ioport.h>
# include <linux/scatterlist.h>
# include <pcmcia/cistpl.h>
# include <pcmcia/ds.h>
# include <linux/io.h>
# include <linux/mmc/host.h>
# define DRIVER_NAME "sdricoh_cs"
static unsigned int switchlocked ;
/* i/o region */
# define SDRICOH_PCI_REGION 0
# define SDRICOH_PCI_REGION_SIZE 0x1000
/* registers */
# define R104_VERSION 0x104
# define R200_CMD 0x200
# define R204_CMD_ARG 0x204
# define R208_DATAIO 0x208
# define R20C_RESP 0x20c
# define R21C_STATUS 0x21c
# define R2E0_INIT 0x2e0
# define R2E4_STATUS_RESP 0x2e4
# define R2F0_RESET 0x2f0
# define R224_MODE 0x224
# define R226_BLOCKSIZE 0x226
# define R228_POWER 0x228
# define R230_DATA 0x230
/* flags for the R21C_STATUS register */
# define STATUS_CMD_FINISHED 0x00000001
# define STATUS_TRANSFER_FINISHED 0x00000004
# define STATUS_CARD_INSERTED 0x00000020
# define STATUS_CARD_LOCKED 0x00000080
# define STATUS_CMD_TIMEOUT 0x00400000
# define STATUS_READY_TO_READ 0x01000000
# define STATUS_READY_TO_WRITE 0x02000000
# define STATUS_BUSY 0x40000000
/* timeouts */
# define INIT_TIMEOUT 100
# define CMD_TIMEOUT 100000
# define TRANSFER_TIMEOUT 100000
# define BUSY_TIMEOUT 32767
/* list of supported pcmcia devices */
2011-05-03 19:29:01 -07:00
static const struct pcmcia_device_id pcmcia_ids [ ] = {
2008-07-15 14:21:29 +02:00
/* vendor and device strings followed by their crc32 hashes */
PCMCIA_DEVICE_PROD_ID12 ( " RICOH " , " Bay1Controller " , 0xd9f522ed ,
0xc3901202 ) ,
2008-11-29 07:51:19 +01:00
PCMCIA_DEVICE_PROD_ID12 ( " RICOH " , " Bay Controller " , 0xd9f522ed ,
0xace80909 ) ,
2008-07-15 14:21:29 +02:00
PCMCIA_DEVICE_NULL ,
} ;
MODULE_DEVICE_TABLE ( pcmcia , pcmcia_ids ) ;
/* mmc privdata */
struct sdricoh_host {
struct device * dev ;
struct mmc_host * mmc ; /* MMC structure */
unsigned char __iomem * iobase ;
struct pci_dev * pci_dev ;
int app_cmd ;
} ;
/***************** register i/o helper functions *****************************/
static inline unsigned int sdricoh_readl ( struct sdricoh_host * host ,
unsigned int reg )
{
unsigned int value = readl ( host - > iobase + reg ) ;
dev_vdbg ( host - > dev , " rl %x 0x%x \n " , reg , value ) ;
return value ;
}
static inline void sdricoh_writel ( struct sdricoh_host * host , unsigned int reg ,
unsigned int value )
{
writel ( value , host - > iobase + reg ) ;
dev_vdbg ( host - > dev , " wl %x 0x%x \n " , reg , value ) ;
}
static inline unsigned int sdricoh_readw ( struct sdricoh_host * host ,
unsigned int reg )
{
unsigned int value = readw ( host - > iobase + reg ) ;
dev_vdbg ( host - > dev , " rb %x 0x%x \n " , reg , value ) ;
return value ;
}
static inline void sdricoh_writew ( struct sdricoh_host * host , unsigned int reg ,
unsigned short value )
{
writew ( value , host - > iobase + reg ) ;
dev_vdbg ( host - > dev , " ww %x 0x%x \n " , reg , value ) ;
}
static inline unsigned int sdricoh_readb ( struct sdricoh_host * host ,
unsigned int reg )
{
unsigned int value = readb ( host - > iobase + reg ) ;
dev_vdbg ( host - > dev , " rb %x 0x%x \n " , reg , value ) ;
return value ;
}
static int sdricoh_query_status ( struct sdricoh_host * host , unsigned int wanted ,
unsigned int timeout ) {
unsigned int loop ;
unsigned int status = 0 ;
struct device * dev = host - > dev ;
for ( loop = 0 ; loop < timeout ; loop + + ) {
status = sdricoh_readl ( host , R21C_STATUS ) ;
sdricoh_writel ( host , R2E4_STATUS_RESP , status ) ;
if ( status & wanted )
break ;
}
if ( loop = = timeout ) {
dev_err ( dev , " query_status: timeout waiting for %x \n " , wanted ) ;
return - ETIMEDOUT ;
}
/* do not do this check in the loop as some commands fail otherwise */
if ( status & 0x7F0000 ) {
dev_err ( dev , " waiting for status bit %x failed \n " , wanted ) ;
return - EINVAL ;
}
return 0 ;
}
static int sdricoh_mmc_cmd ( struct sdricoh_host * host , unsigned char opcode ,
unsigned int arg )
{
unsigned int status ;
int result = 0 ;
unsigned int loop = 0 ;
/* reset status reg? */
sdricoh_writel ( host , R21C_STATUS , 0x18 ) ;
/* fill parameters */
sdricoh_writel ( host , R204_CMD_ARG , arg ) ;
sdricoh_writel ( host , R200_CMD , ( 0x10000 < < 8 ) | opcode ) ;
/* wait for command completion */
if ( opcode ) {
for ( loop = 0 ; loop < CMD_TIMEOUT ; loop + + ) {
status = sdricoh_readl ( host , R21C_STATUS ) ;
sdricoh_writel ( host , R2E4_STATUS_RESP , status ) ;
if ( status & STATUS_CMD_FINISHED )
break ;
}
/* don't check for timeout in the loop it is not always
reset correctly
*/
if ( loop = = CMD_TIMEOUT | | status & STATUS_CMD_TIMEOUT )
result = - ETIMEDOUT ;
}
return result ;
}
static int sdricoh_reset ( struct sdricoh_host * host )
{
dev_dbg ( host - > dev , " reset \n " ) ;
sdricoh_writel ( host , R2F0_RESET , 0x10001 ) ;
sdricoh_writel ( host , R2E0_INIT , 0x10000 ) ;
if ( sdricoh_readl ( host , R2E0_INIT ) ! = 0x10000 )
return - EIO ;
sdricoh_writel ( host , R2E0_INIT , 0x10007 ) ;
sdricoh_writel ( host , R224_MODE , 0x2000000 ) ;
sdricoh_writel ( host , R228_POWER , 0xe0 ) ;
/* status register ? */
sdricoh_writel ( host , R21C_STATUS , 0x18 ) ;
return 0 ;
}
static int sdricoh_blockio ( struct sdricoh_host * host , int read ,
u8 * buf , int len )
{
int size ;
u32 data = 0 ;
/* wait until the data is available */
if ( read ) {
if ( sdricoh_query_status ( host , STATUS_READY_TO_READ ,
TRANSFER_TIMEOUT ) )
return - ETIMEDOUT ;
sdricoh_writel ( host , R21C_STATUS , 0x18 ) ;
/* read data */
while ( len ) {
data = sdricoh_readl ( host , R230_DATA ) ;
size = min ( len , 4 ) ;
len - = size ;
while ( size ) {
* buf = data & 0xFF ;
buf + + ;
data > > = 8 ;
size - - ;
}
}
} else {
if ( sdricoh_query_status ( host , STATUS_READY_TO_WRITE ,
TRANSFER_TIMEOUT ) )
return - ETIMEDOUT ;
sdricoh_writel ( host , R21C_STATUS , 0x18 ) ;
/* write data */
while ( len ) {
size = min ( len , 4 ) ;
len - = size ;
while ( size ) {
data > > = 8 ;
data | = ( u32 ) * buf < < 24 ;
buf + + ;
size - - ;
}
sdricoh_writel ( host , R230_DATA , data ) ;
}
}
if ( len )
return - EIO ;
return 0 ;
}
static void sdricoh_request ( struct mmc_host * mmc , struct mmc_request * mrq )
{
struct sdricoh_host * host = mmc_priv ( mmc ) ;
struct mmc_command * cmd = mrq - > cmd ;
struct mmc_data * data = cmd - > data ;
struct device * dev = host - > dev ;
unsigned char opcode = cmd - > opcode ;
int i ;
dev_dbg ( dev , " ============================= \n " ) ;
dev_dbg ( dev , " sdricoh_request opcode=%i \n " , opcode ) ;
sdricoh_writel ( host , R21C_STATUS , 0x18 ) ;
/* MMC_APP_CMDs need some special handling */
if ( host - > app_cmd ) {
opcode | = 64 ;
host - > app_cmd = 0 ;
} else if ( opcode = = 55 )
host - > app_cmd = 1 ;
/* read/write commands seem to require this */
if ( data ) {
sdricoh_writew ( host , R226_BLOCKSIZE , data - > blksz ) ;
sdricoh_writel ( host , R208_DATAIO , 0 ) ;
}
cmd - > error = sdricoh_mmc_cmd ( host , opcode , cmd - > arg ) ;
/* read response buffer */
if ( cmd - > flags & MMC_RSP_PRESENT ) {
if ( cmd - > flags & MMC_RSP_136 ) {
/* CRC is stripped so we need to do some shifting. */
for ( i = 0 ; i < 4 ; i + + ) {
cmd - > resp [ i ] =
sdricoh_readl ( host ,
R20C_RESP + ( 3 - i ) * 4 ) < < 8 ;
if ( i ! = 3 )
cmd - > resp [ i ] | =
sdricoh_readb ( host , R20C_RESP +
( 3 - i ) * 4 - 1 ) ;
}
} else
cmd - > resp [ 0 ] = sdricoh_readl ( host , R20C_RESP ) ;
}
/* transfer data */
if ( data & & cmd - > error = = 0 ) {
dev_dbg ( dev , " transfer: blksz %i blocks %i sg_len %i "
" sg length %i \n " , data - > blksz , data - > blocks ,
data - > sg_len , data - > sg - > length ) ;
/* enter data reading mode */
sdricoh_writel ( host , R21C_STATUS , 0x837f031e ) ;
for ( i = 0 ; i < data - > blocks ; i + + ) {
size_t len = data - > blksz ;
u8 * buf ;
struct page * page ;
int result ;
page = sg_page ( data - > sg ) ;
buf = kmap ( page ) + data - > sg - > offset + ( len * i ) ;
result =
sdricoh_blockio ( host ,
data - > flags & MMC_DATA_READ , buf , len ) ;
kunmap ( page ) ;
flush_dcache_page ( page ) ;
if ( result ) {
dev_err ( dev , " sdricoh_request: cmd %i "
" block transfer failed \n " , cmd - > opcode ) ;
cmd - > error = result ;
break ;
} else
data - > bytes_xfered + = len ;
}
sdricoh_writel ( host , R208_DATAIO , 1 ) ;
if ( sdricoh_query_status ( host , STATUS_TRANSFER_FINISHED ,
TRANSFER_TIMEOUT ) ) {
dev_err ( dev , " sdricoh_request: transfer end error \n " ) ;
cmd - > error = - EINVAL ;
}
}
/* FIXME check busy flag */
mmc_request_done ( mmc , mrq ) ;
dev_dbg ( dev , " ============================= \n " ) ;
}
static void sdricoh_set_ios ( struct mmc_host * mmc , struct mmc_ios * ios )
{
struct sdricoh_host * host = mmc_priv ( mmc ) ;
dev_dbg ( host - > dev , " set_ios \n " ) ;
if ( ios - > power_mode = = MMC_POWER_ON ) {
sdricoh_writel ( host , R228_POWER , 0xc0e0 ) ;
if ( ios - > bus_width = = MMC_BUS_WIDTH_4 ) {
sdricoh_writel ( host , R224_MODE , 0x2000300 ) ;
sdricoh_writel ( host , R228_POWER , 0x40e0 ) ;
} else {
sdricoh_writel ( host , R224_MODE , 0x2000340 ) ;
}
} else if ( ios - > power_mode = = MMC_POWER_UP ) {
sdricoh_writel ( host , R224_MODE , 0x2000320 ) ;
sdricoh_writel ( host , R228_POWER , 0xe0 ) ;
}
}
static int sdricoh_get_ro ( struct mmc_host * mmc )
{
struct sdricoh_host * host = mmc_priv ( mmc ) ;
unsigned int status ;
status = sdricoh_readl ( host , R21C_STATUS ) ;
sdricoh_writel ( host , R2E4_STATUS_RESP , status ) ;
/* some notebooks seem to have the locked flag switched */
if ( switchlocked )
return ! ( status & STATUS_CARD_LOCKED ) ;
return ( status & STATUS_CARD_LOCKED ) ;
}
static struct mmc_host_ops sdricoh_ops = {
. request = sdricoh_request ,
. set_ios = sdricoh_set_ios ,
. get_ro = sdricoh_get_ro ,
} ;
/* initialize the control and register it to the mmc framework */
static int sdricoh_init_mmc ( struct pci_dev * pci_dev ,
struct pcmcia_device * pcmcia_dev )
{
int result = 0 ;
void __iomem * iobase = NULL ;
struct mmc_host * mmc = NULL ;
struct sdricoh_host * host = NULL ;
struct device * dev = & pcmcia_dev - > dev ;
/* map iomem */
if ( pci_resource_len ( pci_dev , SDRICOH_PCI_REGION ) ! =
SDRICOH_PCI_REGION_SIZE ) {
dev_dbg ( dev , " unexpected pci resource len \n " ) ;
return - ENODEV ;
}
iobase =
pci_iomap ( pci_dev , SDRICOH_PCI_REGION , SDRICOH_PCI_REGION_SIZE ) ;
if ( ! iobase ) {
dev_err ( dev , " unable to map iobase \n " ) ;
return - ENODEV ;
}
/* check version? */
if ( readl ( iobase + R104_VERSION ) ! = 0x4000 ) {
dev_dbg ( dev , " no supported mmc controller found \n " ) ;
result = - ENODEV ;
goto err ;
}
/* allocate privdata */
mmc = pcmcia_dev - > priv =
mmc_alloc_host ( sizeof ( struct sdricoh_host ) , & pcmcia_dev - > dev ) ;
if ( ! mmc ) {
dev_err ( dev , " mmc_alloc_host failed \n " ) ;
result = - ENOMEM ;
goto err ;
}
host = mmc_priv ( mmc ) ;
host - > iobase = iobase ;
host - > dev = dev ;
host - > pci_dev = pci_dev ;
mmc - > ops = & sdricoh_ops ;
/* FIXME: frequency and voltage handling is done by the controller
*/
mmc - > f_min = 450000 ;
mmc - > f_max = 24000000 ;
mmc - > ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34 ;
mmc - > caps | = MMC_CAP_4_BIT_DATA ;
mmc - > max_seg_size = 1024 * 512 ;
mmc - > max_blk_size = 512 ;
tree-wide: fix comment/printk typos
"gadget", "through", "command", "maintain", "maintain", "controller", "address",
"between", "initiali[zs]e", "instead", "function", "select", "already",
"equal", "access", "management", "hierarchy", "registration", "interest",
"relative", "memory", "offset", "already",
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-11-01 15:38:34 -04:00
/* reset the controller */
2008-07-15 14:21:29 +02:00
if ( sdricoh_reset ( host ) ) {
dev_dbg ( dev , " could not reset \n " ) ;
result = - EIO ;
goto err ;
}
result = mmc_add_host ( mmc ) ;
if ( ! result ) {
dev_dbg ( dev , " mmc host registered \n " ) ;
return 0 ;
}
err :
if ( iobase )
2008-12-13 21:21:33 +01:00
pci_iounmap ( pci_dev , iobase ) ;
2008-07-15 14:21:29 +02:00
if ( mmc )
mmc_free_host ( mmc ) ;
return result ;
}
/* search for supported mmc controllers */
static int sdricoh_pcmcia_probe ( struct pcmcia_device * pcmcia_dev )
{
struct pci_dev * pci_dev = NULL ;
dev_info ( & pcmcia_dev - > dev , " Searching MMC controller for pcmcia device "
" %s %s ... \n " , pcmcia_dev - > prod_id [ 0 ] , pcmcia_dev - > prod_id [ 1 ] ) ;
tree-wide: fix comment/printk typos
"gadget", "through", "command", "maintain", "maintain", "controller", "address",
"between", "initiali[zs]e", "instead", "function", "select", "already",
"equal", "access", "management", "hierarchy", "registration", "interest",
"relative", "memory", "offset", "already",
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-11-01 15:38:34 -04:00
/* search pci cardbus bridge that contains the mmc controller */
2008-07-15 14:21:29 +02:00
/* the io region is already claimed by yenta_socket... */
while ( ( pci_dev =
pci_get_device ( PCI_VENDOR_ID_RICOH , PCI_DEVICE_ID_RICOH_RL5C476 ,
pci_dev ) ) ) {
/* try to init the device */
if ( ! sdricoh_init_mmc ( pci_dev , pcmcia_dev ) ) {
dev_info ( & pcmcia_dev - > dev , " MMC controller found \n " ) ;
return 0 ;
}
}
dev_err ( & pcmcia_dev - > dev , " No MMC controller was found. \n " ) ;
return - ENODEV ;
}
static void sdricoh_pcmcia_detach ( struct pcmcia_device * link )
{
struct mmc_host * mmc = link - > priv ;
dev_dbg ( & link - > dev , " detach \n " ) ;
/* remove mmc host */
if ( mmc ) {
struct sdricoh_host * host = mmc_priv ( mmc ) ;
mmc_remove_host ( mmc ) ;
pci_iounmap ( host - > pci_dev , host - > iobase ) ;
pci_dev_put ( host - > pci_dev ) ;
mmc_free_host ( mmc ) ;
}
pcmcia_disable_device ( link ) ;
}
# ifdef CONFIG_PM
static int sdricoh_pcmcia_suspend ( struct pcmcia_device * link )
{
struct mmc_host * mmc = link - > priv ;
dev_dbg ( & link - > dev , " suspend \n " ) ;
2010-05-26 14:42:08 -07:00
mmc_suspend_host ( mmc ) ;
2008-07-15 14:21:29 +02:00
return 0 ;
}
static int sdricoh_pcmcia_resume ( struct pcmcia_device * link )
{
struct mmc_host * mmc = link - > priv ;
dev_dbg ( & link - > dev , " resume \n " ) ;
sdricoh_reset ( mmc_priv ( mmc ) ) ;
mmc_resume_host ( mmc ) ;
return 0 ;
}
# else
# define sdricoh_pcmcia_suspend NULL
# define sdricoh_pcmcia_resume NULL
# endif
static struct pcmcia_driver sdricoh_driver = {
2010-08-08 11:36:26 +02:00
. name = DRIVER_NAME ,
2008-07-15 14:21:29 +02:00
. probe = sdricoh_pcmcia_probe ,
. remove = sdricoh_pcmcia_detach ,
. id_table = pcmcia_ids ,
. suspend = sdricoh_pcmcia_suspend ,
. resume = sdricoh_pcmcia_resume ,
} ;
/*****************************************************************************\
* *
* Driver init / exit *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static int __init sdricoh_drv_init ( void )
{
return pcmcia_register_driver ( & sdricoh_driver ) ;
}
static void __exit sdricoh_drv_exit ( void )
{
pcmcia_unregister_driver ( & sdricoh_driver ) ;
}
module_init ( sdricoh_drv_init ) ;
module_exit ( sdricoh_drv_exit ) ;
module_param ( switchlocked , uint , 0444 ) ;
MODULE_AUTHOR ( " Sascha Sommer <saschasommer@freenet.de> " ) ;
MODULE_DESCRIPTION ( " Ricoh PCMCIA Secure Digital Interface driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_PARM_DESC ( switchlocked , " Switch the cards locked status. "
" Use this when unlocked cards are shown readonly (default 0) " ) ;