2008-03-10 21:43:43 +03:00
/*
* jmb38x_ms . c - JMicron jmb38x MemoryStick card reader
*
* Copyright ( C ) 2008 Alex Dubov < oakad @ yahoo . com >
*
* 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/spinlock.h>
# include <linux/interrupt.h>
# include <linux/pci.h>
2008-03-20 03:01:03 +03:00
# include <linux/dma-mapping.h>
2008-03-10 21:43:43 +03:00
# include <linux/delay.h>
# include <linux/highmem.h>
# include <linux/memstick.h>
# define DRIVER_NAME "jmb38x_ms"
static int no_dma ;
module_param ( no_dma , bool , 0644 ) ;
enum {
DMA_ADDRESS = 0x00 ,
BLOCK = 0x04 ,
DMA_CONTROL = 0x08 ,
TPC_P0 = 0x0c ,
TPC_P1 = 0x10 ,
TPC = 0x14 ,
HOST_CONTROL = 0x18 ,
DATA = 0x1c ,
STATUS = 0x20 ,
INT_STATUS = 0x24 ,
INT_STATUS_ENABLE = 0x28 ,
INT_SIGNAL_ENABLE = 0x2c ,
TIMER = 0x30 ,
TIMER_CONTROL = 0x34 ,
PAD_OUTPUT_ENABLE = 0x38 ,
PAD_PU_PD = 0x3c ,
CLOCK_DELAY = 0x40 ,
ADMA_ADDRESS = 0x44 ,
CLOCK_CONTROL = 0x48 ,
LED_CONTROL = 0x4c ,
VERSION = 0x50
} ;
struct jmb38x_ms_host {
struct jmb38x_ms * chip ;
void __iomem * addr ;
spinlock_t lock ;
2008-07-26 06:45:02 +04:00
struct tasklet_struct notify ;
2008-03-10 21:43:43 +03:00
int id ;
2008-05-02 08:02:41 +04:00
char host_id [ 32 ] ;
2008-03-10 21:43:43 +03:00
int irq ;
unsigned int block_pos ;
unsigned long timeout_jiffies ;
struct timer_list timer ;
struct memstick_request * req ;
unsigned char cmd_flags ;
unsigned char io_pos ;
unsigned int io_word [ 2 ] ;
} ;
struct jmb38x_ms {
struct pci_dev * pdev ;
int host_cnt ;
struct memstick_host * hosts [ ] ;
} ;
# define BLOCK_COUNT_MASK 0xffff0000
# define BLOCK_SIZE_MASK 0x00000fff
# define DMA_CONTROL_ENABLE 0x00000001
# define TPC_DATA_SEL 0x00008000
# define TPC_DIR 0x00004000
# define TPC_WAIT_INT 0x00002000
# define TPC_GET_INT 0x00000800
# define TPC_CODE_SZ_MASK 0x00000700
# define TPC_DATA_SZ_MASK 0x00000007
2008-09-13 13:33:26 +04:00
# define HOST_CONTROL_TDELAY_EN 0x00040000
# define HOST_CONTROL_HW_OC_P 0x00010000
2008-03-10 21:43:43 +03:00
# define HOST_CONTROL_RESET_REQ 0x00008000
# define HOST_CONTROL_REI 0x00004000
# define HOST_CONTROL_LED 0x00000400
# define HOST_CONTROL_FAST_CLK 0x00000200
# define HOST_CONTROL_RESET 0x00000100
# define HOST_CONTROL_POWER_EN 0x00000080
# define HOST_CONTROL_CLOCK_EN 0x00000040
2008-09-13 13:33:26 +04:00
# define HOST_CONTROL_REO 0x00000008
2008-03-10 21:43:43 +03:00
# define HOST_CONTROL_IF_SHIFT 4
# define HOST_CONTROL_IF_SERIAL 0x0
# define HOST_CONTROL_IF_PAR4 0x1
# define HOST_CONTROL_IF_PAR8 0x3
2008-03-20 03:01:06 +03:00
# define STATUS_BUSY 0x00080000
# define STATUS_MS_DAT7 0x00040000
# define STATUS_MS_DAT6 0x00020000
# define STATUS_MS_DAT5 0x00010000
# define STATUS_MS_DAT4 0x00008000
# define STATUS_MS_DAT3 0x00004000
# define STATUS_MS_DAT2 0x00002000
# define STATUS_MS_DAT1 0x00001000
# define STATUS_MS_DAT0 0x00000800
2008-03-10 21:43:43 +03:00
# define STATUS_HAS_MEDIA 0x00000400
# define STATUS_FIFO_EMPTY 0x00000200
# define STATUS_FIFO_FULL 0x00000100
2008-03-20 03:01:06 +03:00
# define STATUS_MS_CED 0x00000080
# define STATUS_MS_ERR 0x00000040
# define STATUS_MS_BRQ 0x00000020
# define STATUS_MS_CNK 0x00000001
2008-03-10 21:43:43 +03:00
# define INT_STATUS_TPC_ERR 0x00080000
# define INT_STATUS_CRC_ERR 0x00040000
# define INT_STATUS_TIMER_TO 0x00020000
# define INT_STATUS_HSK_TO 0x00010000
# define INT_STATUS_ANY_ERR 0x00008000
# define INT_STATUS_FIFO_WRDY 0x00000080
# define INT_STATUS_FIFO_RRDY 0x00000040
# define INT_STATUS_MEDIA_OUT 0x00000010
# define INT_STATUS_MEDIA_IN 0x00000008
# define INT_STATUS_DMA_BOUNDARY 0x00000004
# define INT_STATUS_EOTRAN 0x00000002
# define INT_STATUS_EOTPC 0x00000001
# define INT_STATUS_ALL 0x000f801f
# define PAD_OUTPUT_ENABLE_MS 0x0F3F
# define PAD_PU_PD_OFF 0x7FFF0000
# define PAD_PU_PD_ON_MS_SOCK0 0x5f8f0000
# define PAD_PU_PD_ON_MS_SOCK1 0x0f0f0000
2008-03-20 03:01:08 +03:00
# define CLOCK_CONTROL_40MHZ 0x00000001
2008-09-13 13:33:26 +04:00
# define CLOCK_CONTROL_50MHZ 0x0000000a
2008-03-20 03:01:08 +03:00
# define CLOCK_CONTROL_60MHZ 0x00000008
# define CLOCK_CONTROL_62_5MHZ 0x0000000c
# define CLOCK_CONTROL_OFF 0x00000000
2008-09-13 13:33:26 +04:00
# define PCI_CTL_CLOCK_DLY_ADDR 0x000000b0
# define PCI_CTL_CLOCK_DLY_MASK_A 0x00000f00
# define PCI_CTL_CLOCK_DLY_MASK_B 0x0000f000
2008-03-10 21:43:43 +03:00
enum {
CMD_READY = 0x01 ,
FIFO_READY = 0x02 ,
REG_DATA = 0x04 ,
2008-03-20 03:01:06 +03:00
DMA_DATA = 0x08
2008-03-10 21:43:43 +03:00
} ;
static unsigned int jmb38x_ms_read_data ( struct jmb38x_ms_host * host ,
unsigned char * buf , unsigned int length )
{
unsigned int off = 0 ;
while ( host - > io_pos & & length ) {
buf [ off + + ] = host - > io_word [ 0 ] & 0xff ;
host - > io_word [ 0 ] > > = 8 ;
length - - ;
host - > io_pos - - ;
}
if ( ! length )
return off ;
while ( ! ( STATUS_FIFO_EMPTY & readl ( host - > addr + STATUS ) ) ) {
if ( length < 4 )
break ;
* ( unsigned int * ) ( buf + off ) = __raw_readl ( host - > addr + DATA ) ;
length - = 4 ;
off + = 4 ;
}
if ( length
& & ! ( STATUS_FIFO_EMPTY & readl ( host - > addr + STATUS ) ) ) {
host - > io_word [ 0 ] = readl ( host - > addr + DATA ) ;
for ( host - > io_pos = 4 ; host - > io_pos ; - - host - > io_pos ) {
buf [ off + + ] = host - > io_word [ 0 ] & 0xff ;
host - > io_word [ 0 ] > > = 8 ;
length - - ;
if ( ! length )
break ;
}
}
return off ;
}
static unsigned int jmb38x_ms_read_reg_data ( struct jmb38x_ms_host * host ,
unsigned char * buf ,
unsigned int length )
{
unsigned int off = 0 ;
while ( host - > io_pos > 4 & & length ) {
buf [ off + + ] = host - > io_word [ 0 ] & 0xff ;
host - > io_word [ 0 ] > > = 8 ;
length - - ;
host - > io_pos - - ;
}
if ( ! length )
return off ;
while ( host - > io_pos & & length ) {
buf [ off + + ] = host - > io_word [ 1 ] & 0xff ;
host - > io_word [ 1 ] > > = 8 ;
length - - ;
host - > io_pos - - ;
}
return off ;
}
static unsigned int jmb38x_ms_write_data ( struct jmb38x_ms_host * host ,
unsigned char * buf ,
unsigned int length )
{
unsigned int off = 0 ;
if ( host - > io_pos ) {
while ( host - > io_pos < 4 & & length ) {
host - > io_word [ 0 ] | = buf [ off + + ] < < ( host - > io_pos * 8 ) ;
host - > io_pos + + ;
length - - ;
}
}
if ( host - > io_pos = = 4
& & ! ( STATUS_FIFO_FULL & readl ( host - > addr + STATUS ) ) ) {
writel ( host - > io_word [ 0 ] , host - > addr + DATA ) ;
host - > io_pos = 0 ;
host - > io_word [ 0 ] = 0 ;
} else if ( host - > io_pos ) {
return off ;
}
if ( ! length )
return off ;
while ( ! ( STATUS_FIFO_FULL & readl ( host - > addr + STATUS ) ) ) {
if ( length < 4 )
break ;
__raw_writel ( * ( unsigned int * ) ( buf + off ) ,
host - > addr + DATA ) ;
length - = 4 ;
off + = 4 ;
}
switch ( length ) {
case 3 :
host - > io_word [ 0 ] | = buf [ off + 2 ] < < 16 ;
host - > io_pos + + ;
case 2 :
host - > io_word [ 0 ] | = buf [ off + 1 ] < < 8 ;
host - > io_pos + + ;
case 1 :
host - > io_word [ 0 ] | = buf [ off ] ;
host - > io_pos + + ;
}
off + = host - > io_pos ;
return off ;
}
static unsigned int jmb38x_ms_write_reg_data ( struct jmb38x_ms_host * host ,
unsigned char * buf ,
unsigned int length )
{
unsigned int off = 0 ;
while ( host - > io_pos < 4 & & length ) {
host - > io_word [ 0 ] & = ~ ( 0xff < < ( host - > io_pos * 8 ) ) ;
host - > io_word [ 0 ] | = buf [ off + + ] < < ( host - > io_pos * 8 ) ;
host - > io_pos + + ;
length - - ;
}
if ( ! length )
return off ;
while ( host - > io_pos < 8 & & length ) {
host - > io_word [ 1 ] & = ~ ( 0xff < < ( host - > io_pos * 8 ) ) ;
host - > io_word [ 1 ] | = buf [ off + + ] < < ( host - > io_pos * 8 ) ;
host - > io_pos + + ;
length - - ;
}
return off ;
}
static int jmb38x_ms_transfer_data ( struct jmb38x_ms_host * host )
{
unsigned int length ;
unsigned int off ;
2008-03-20 03:01:04 +03:00
unsigned int t_size , p_cnt ;
2008-03-10 21:43:43 +03:00
unsigned char * buf ;
struct page * pg ;
unsigned long flags = 0 ;
if ( host - > req - > long_data ) {
length = host - > req - > sg . length - host - > block_pos ;
off = host - > req - > sg . offset + host - > block_pos ;
} else {
length = host - > req - > data_len - host - > block_pos ;
off = 0 ;
}
while ( length ) {
2008-03-20 03:01:04 +03:00
unsigned int uninitialized_var ( p_off ) ;
2008-03-10 21:43:43 +03:00
if ( host - > req - > long_data ) {
pg = nth_page ( sg_page ( & host - > req - > sg ) ,
off > > PAGE_SHIFT ) ;
p_off = offset_in_page ( off ) ;
p_cnt = PAGE_SIZE - p_off ;
p_cnt = min ( p_cnt , length ) ;
local_irq_save ( flags ) ;
buf = kmap_atomic ( pg , KM_BIO_SRC_IRQ ) + p_off ;
} else {
buf = host - > req - > data + host - > block_pos ;
p_cnt = host - > req - > data_len - host - > block_pos ;
}
if ( host - > req - > data_dir = = WRITE )
t_size = ! ( host - > cmd_flags & REG_DATA )
? jmb38x_ms_write_data ( host , buf , p_cnt )
: jmb38x_ms_write_reg_data ( host , buf , p_cnt ) ;
else
t_size = ! ( host - > cmd_flags & REG_DATA )
? jmb38x_ms_read_data ( host , buf , p_cnt )
: jmb38x_ms_read_reg_data ( host , buf , p_cnt ) ;
if ( host - > req - > long_data ) {
kunmap_atomic ( buf - p_off , KM_BIO_SRC_IRQ ) ;
local_irq_restore ( flags ) ;
}
if ( ! t_size )
break ;
host - > block_pos + = t_size ;
length - = t_size ;
off + = t_size ;
}
if ( ! length & & host - > req - > data_dir = = WRITE ) {
if ( host - > cmd_flags & REG_DATA ) {
writel ( host - > io_word [ 0 ] , host - > addr + TPC_P0 ) ;
writel ( host - > io_word [ 1 ] , host - > addr + TPC_P1 ) ;
} else if ( host - > io_pos ) {
writel ( host - > io_word [ 0 ] , host - > addr + DATA ) ;
}
}
return length ;
}
static int jmb38x_ms_issue_cmd ( struct memstick_host * msh )
{
struct jmb38x_ms_host * host = memstick_priv ( msh ) ;
unsigned char * data ;
unsigned int data_len , cmd , t_val ;
if ( ! ( STATUS_HAS_MEDIA & readl ( host - > addr + STATUS ) ) ) {
2008-03-04 02:13:36 +03:00
dev_dbg ( & msh - > dev , " no media status \n " ) ;
2008-03-10 21:43:43 +03:00
host - > req - > error = - ETIME ;
return host - > req - > error ;
}
2008-09-13 13:33:26 +04:00
dev_dbg ( & msh - > dev , " control %08x \n " , readl ( host - > addr + HOST_CONTROL ) ) ;
2008-03-04 02:13:36 +03:00
dev_dbg ( & msh - > dev , " status %08x \n " , readl ( host - > addr + INT_STATUS ) ) ;
dev_dbg ( & msh - > dev , " hstatus %08x \n " , readl ( host - > addr + STATUS ) ) ;
2008-03-10 21:43:43 +03:00
host - > cmd_flags = 0 ;
host - > block_pos = 0 ;
host - > io_pos = 0 ;
host - > io_word [ 0 ] = 0 ;
host - > io_word [ 1 ] = 0 ;
cmd = host - > req - > tpc < < 16 ;
cmd | = TPC_DATA_SEL ;
if ( host - > req - > data_dir = = READ )
cmd | = TPC_DIR ;
if ( host - > req - > need_card_int )
cmd | = TPC_WAIT_INT ;
data = host - > req - > data ;
2008-03-20 03:01:06 +03:00
if ( ! no_dma )
host - > cmd_flags | = DMA_DATA ;
2008-03-10 21:43:43 +03:00
if ( host - > req - > long_data ) {
data_len = host - > req - > sg . length ;
} else {
data_len = host - > req - > data_len ;
2008-03-20 03:01:06 +03:00
host - > cmd_flags & = ~ DMA_DATA ;
2008-03-10 21:43:43 +03:00
}
if ( data_len < = 8 ) {
cmd & = ~ ( TPC_DATA_SEL | 0xf ) ;
host - > cmd_flags | = REG_DATA ;
cmd | = data_len & 0xf ;
2008-03-20 03:01:06 +03:00
host - > cmd_flags & = ~ DMA_DATA ;
2008-03-10 21:43:43 +03:00
}
2008-03-20 03:01:06 +03:00
if ( host - > cmd_flags & DMA_DATA ) {
2008-03-10 21:43:43 +03:00
if ( 1 ! = pci_map_sg ( host - > chip - > pdev , & host - > req - > sg , 1 ,
host - > req - > data_dir = = READ
? PCI_DMA_FROMDEVICE
: PCI_DMA_TODEVICE ) ) {
host - > req - > error = - ENOMEM ;
return host - > req - > error ;
}
data_len = sg_dma_len ( & host - > req - > sg ) ;
writel ( sg_dma_address ( & host - > req - > sg ) ,
host - > addr + DMA_ADDRESS ) ;
writel ( ( ( 1 < < 16 ) & BLOCK_COUNT_MASK )
| ( data_len & BLOCK_SIZE_MASK ) ,
host - > addr + BLOCK ) ;
writel ( DMA_CONTROL_ENABLE , host - > addr + DMA_CONTROL ) ;
} else if ( ! ( host - > cmd_flags & REG_DATA ) ) {
writel ( ( ( 1 < < 16 ) & BLOCK_COUNT_MASK )
| ( data_len & BLOCK_SIZE_MASK ) ,
host - > addr + BLOCK ) ;
t_val = readl ( host - > addr + INT_STATUS_ENABLE ) ;
t_val | = host - > req - > data_dir = = READ
? INT_STATUS_FIFO_RRDY
: INT_STATUS_FIFO_WRDY ;
writel ( t_val , host - > addr + INT_STATUS_ENABLE ) ;
writel ( t_val , host - > addr + INT_SIGNAL_ENABLE ) ;
} else {
cmd & = ~ ( TPC_DATA_SEL | 0xf ) ;
host - > cmd_flags | = REG_DATA ;
cmd | = data_len & 0xf ;
if ( host - > req - > data_dir = = WRITE ) {
jmb38x_ms_transfer_data ( host ) ;
writel ( host - > io_word [ 0 ] , host - > addr + TPC_P0 ) ;
writel ( host - > io_word [ 1 ] , host - > addr + TPC_P1 ) ;
}
}
mod_timer ( & host - > timer , jiffies + host - > timeout_jiffies ) ;
writel ( HOST_CONTROL_LED | readl ( host - > addr + HOST_CONTROL ) ,
host - > addr + HOST_CONTROL ) ;
host - > req - > error = 0 ;
writel ( cmd , host - > addr + TPC ) ;
2008-03-04 02:13:36 +03:00
dev_dbg ( & msh - > dev , " executing TPC %08x, len %x \n " , cmd , data_len ) ;
2008-03-10 21:43:43 +03:00
return 0 ;
}
static void jmb38x_ms_complete_cmd ( struct memstick_host * msh , int last )
{
struct jmb38x_ms_host * host = memstick_priv ( msh ) ;
unsigned int t_val = 0 ;
int rc ;
del_timer ( & host - > timer ) ;
2008-03-04 02:13:36 +03:00
dev_dbg ( & msh - > dev , " c control %08x \n " ,
2008-03-10 21:43:43 +03:00
readl ( host - > addr + HOST_CONTROL ) ) ;
2008-03-04 02:13:36 +03:00
dev_dbg ( & msh - > dev , " c status %08x \n " ,
2008-03-10 21:43:43 +03:00
readl ( host - > addr + INT_STATUS ) ) ;
2008-03-04 02:13:36 +03:00
dev_dbg ( & msh - > dev , " c hstatus %08x \n " , readl ( host - > addr + STATUS ) ) ;
2008-03-10 21:43:43 +03:00
2008-03-20 03:01:06 +03:00
host - > req - > int_reg = readl ( host - > addr + STATUS ) & 0xff ;
writel ( 0 , host - > addr + BLOCK ) ;
writel ( 0 , host - > addr + DMA_CONTROL ) ;
2008-03-10 21:43:43 +03:00
2008-03-20 03:01:06 +03:00
if ( host - > cmd_flags & DMA_DATA ) {
2008-03-10 21:43:43 +03:00
pci_unmap_sg ( host - > chip - > pdev , & host - > req - > sg , 1 ,
host - > req - > data_dir = = READ
? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE ) ;
} else {
t_val = readl ( host - > addr + INT_STATUS_ENABLE ) ;
if ( host - > req - > data_dir = = READ )
t_val & = ~ INT_STATUS_FIFO_RRDY ;
else
t_val & = ~ INT_STATUS_FIFO_WRDY ;
writel ( t_val , host - > addr + INT_STATUS_ENABLE ) ;
writel ( t_val , host - > addr + INT_SIGNAL_ENABLE ) ;
}
writel ( ( ~ HOST_CONTROL_LED ) & readl ( host - > addr + HOST_CONTROL ) ,
host - > addr + HOST_CONTROL ) ;
if ( ! last ) {
do {
rc = memstick_next_req ( msh , & host - > req ) ;
} while ( ! rc & & jmb38x_ms_issue_cmd ( msh ) ) ;
} else {
do {
rc = memstick_next_req ( msh , & host - > req ) ;
if ( ! rc )
host - > req - > error = - ETIME ;
} while ( ! rc ) ;
}
}
static irqreturn_t jmb38x_ms_isr ( int irq , void * dev_id )
{
struct memstick_host * msh = dev_id ;
struct jmb38x_ms_host * host = memstick_priv ( msh ) ;
unsigned int irq_status ;
spin_lock ( & host - > lock ) ;
irq_status = readl ( host - > addr + INT_STATUS ) ;
dev_dbg ( & host - > chip - > pdev - > dev , " irq_status = %08x \n " , irq_status ) ;
if ( irq_status = = 0 | | irq_status = = ( ~ 0 ) ) {
spin_unlock ( & host - > lock ) ;
return IRQ_NONE ;
}
if ( host - > req ) {
if ( irq_status & INT_STATUS_ANY_ERR ) {
if ( irq_status & INT_STATUS_CRC_ERR )
host - > req - > error = - EILSEQ ;
else
host - > req - > error = - ETIME ;
} else {
2008-03-20 03:01:06 +03:00
if ( host - > cmd_flags & DMA_DATA ) {
2008-03-10 21:43:43 +03:00
if ( irq_status & INT_STATUS_EOTRAN )
host - > cmd_flags | = FIFO_READY ;
} else {
if ( irq_status & ( INT_STATUS_FIFO_RRDY
| INT_STATUS_FIFO_WRDY ) )
jmb38x_ms_transfer_data ( host ) ;
if ( irq_status & INT_STATUS_EOTRAN ) {
jmb38x_ms_transfer_data ( host ) ;
host - > cmd_flags | = FIFO_READY ;
}
}
if ( irq_status & INT_STATUS_EOTPC ) {
host - > cmd_flags | = CMD_READY ;
if ( host - > cmd_flags & REG_DATA ) {
if ( host - > req - > data_dir = = READ ) {
host - > io_word [ 0 ]
= readl ( host - > addr
+ TPC_P0 ) ;
host - > io_word [ 1 ]
= readl ( host - > addr
+ TPC_P1 ) ;
host - > io_pos = 8 ;
jmb38x_ms_transfer_data ( host ) ;
}
host - > cmd_flags | = FIFO_READY ;
}
}
}
}
if ( irq_status & ( INT_STATUS_MEDIA_IN | INT_STATUS_MEDIA_OUT ) ) {
dev_dbg ( & host - > chip - > pdev - > dev , " media changed \n " ) ;
memstick_detect_change ( msh ) ;
}
writel ( irq_status , host - > addr + INT_STATUS ) ;
if ( host - > req
& & ( ( ( host - > cmd_flags & CMD_READY )
& & ( host - > cmd_flags & FIFO_READY ) )
| | host - > req - > error ) )
jmb38x_ms_complete_cmd ( msh , 0 ) ;
spin_unlock ( & host - > lock ) ;
return IRQ_HANDLED ;
}
static void jmb38x_ms_abort ( unsigned long data )
{
struct memstick_host * msh = ( struct memstick_host * ) data ;
struct jmb38x_ms_host * host = memstick_priv ( msh ) ;
unsigned long flags ;
dev_dbg ( & host - > chip - > pdev - > dev , " abort \n " ) ;
spin_lock_irqsave ( & host - > lock , flags ) ;
if ( host - > req ) {
host - > req - > error = - ETIME ;
jmb38x_ms_complete_cmd ( msh , 0 ) ;
}
spin_unlock_irqrestore ( & host - > lock , flags ) ;
}
2008-07-26 06:45:02 +04:00
static void jmb38x_ms_req_tasklet ( unsigned long data )
2008-03-10 21:43:43 +03:00
{
2008-07-26 06:45:02 +04:00
struct memstick_host * msh = ( struct memstick_host * ) data ;
2008-03-10 21:43:43 +03:00
struct jmb38x_ms_host * host = memstick_priv ( msh ) ;
unsigned long flags ;
int rc ;
spin_lock_irqsave ( & host - > lock , flags ) ;
2008-07-26 06:45:02 +04:00
if ( ! host - > req ) {
do {
rc = memstick_next_req ( msh , & host - > req ) ;
dev_dbg ( & host - > chip - > pdev - > dev , " tasklet req %d \n " , rc ) ;
} while ( ! rc & & jmb38x_ms_issue_cmd ( msh ) ) ;
2008-03-10 21:43:43 +03:00
}
spin_unlock_irqrestore ( & host - > lock , flags ) ;
}
2008-07-26 06:45:02 +04:00
static void jmb38x_ms_dummy_submit ( struct memstick_host * msh )
{
return ;
}
static void jmb38x_ms_submit_req ( struct memstick_host * msh )
{
struct jmb38x_ms_host * host = memstick_priv ( msh ) ;
tasklet_schedule ( & host - > notify ) ;
}
2008-07-26 06:45:00 +04:00
static int jmb38x_ms_reset ( struct jmb38x_ms_host * host )
2008-03-10 21:43:43 +03:00
{
2008-07-26 06:45:00 +04:00
int cnt ;
2008-03-10 21:43:43 +03:00
2008-07-26 06:45:00 +04:00
writel ( HOST_CONTROL_RESET_REQ | HOST_CONTROL_CLOCK_EN
| readl ( host - > addr + HOST_CONTROL ) ,
host - > addr + HOST_CONTROL ) ;
mmiowb ( ) ;
for ( cnt = 0 ; cnt < 20 ; + + cnt ) {
if ( ! ( HOST_CONTROL_RESET_REQ
& readl ( host - > addr + HOST_CONTROL ) ) )
goto reset_next ;
2008-03-10 21:43:43 +03:00
2008-03-20 03:01:08 +03:00
ndelay ( 20 ) ;
2008-03-10 21:43:43 +03:00
}
2008-07-26 06:45:00 +04:00
dev_dbg ( & host - > chip - > pdev - > dev , " reset_req timeout \n " ) ;
2008-09-13 13:33:26 +04:00
/* return -EIO; */
2008-03-10 21:43:43 +03:00
2008-07-26 06:45:00 +04:00
reset_next :
writel ( HOST_CONTROL_RESET | HOST_CONTROL_CLOCK_EN
| readl ( host - > addr + HOST_CONTROL ) ,
host - > addr + HOST_CONTROL ) ;
mmiowb ( ) ;
for ( cnt = 0 ; cnt < 20 ; + + cnt ) {
if ( ! ( HOST_CONTROL_RESET
& readl ( host - > addr + HOST_CONTROL ) ) )
goto reset_ok ;
ndelay ( 20 ) ;
}
dev_dbg ( & host - > chip - > pdev - > dev , " reset timeout \n " ) ;
return - EIO ;
reset_ok :
2008-03-20 03:01:08 +03:00
mmiowb ( ) ;
2008-03-10 21:43:43 +03:00
writel ( INT_STATUS_ALL , host - > addr + INT_SIGNAL_ENABLE ) ;
2008-03-20 03:01:08 +03:00
writel ( INT_STATUS_ALL , host - > addr + INT_STATUS_ENABLE ) ;
2008-07-26 06:45:00 +04:00
return 0 ;
2008-03-10 21:43:43 +03:00
}
2008-07-26 06:45:00 +04:00
static int jmb38x_ms_set_param ( struct memstick_host * msh ,
enum memstick_param param ,
int value )
2008-03-10 21:43:43 +03:00
{
struct jmb38x_ms_host * host = memstick_priv ( msh ) ;
2008-03-20 03:01:08 +03:00
unsigned int host_ctl = readl ( host - > addr + HOST_CONTROL ) ;
unsigned int clock_ctl = CLOCK_CONTROL_40MHZ , clock_delay = 0 ;
2008-07-26 06:45:00 +04:00
int rc = 0 ;
2008-03-10 21:43:43 +03:00
switch ( param ) {
case MEMSTICK_POWER :
if ( value = = MEMSTICK_POWER_ON ) {
2008-07-26 06:45:00 +04:00
rc = jmb38x_ms_reset ( host ) ;
if ( rc )
return rc ;
host_ctl = 7 ;
host_ctl | = HOST_CONTROL_POWER_EN
2008-09-13 13:33:26 +04:00
| HOST_CONTROL_CLOCK_EN
| HOST_CONTROL_HW_OC_P
| HOST_CONTROL_TDELAY_EN ;
2008-07-26 06:45:00 +04:00
writel ( host_ctl , host - > addr + HOST_CONTROL ) ;
2008-03-10 21:43:43 +03:00
writel ( host - > id ? PAD_PU_PD_ON_MS_SOCK1
2008-03-20 03:01:08 +03:00
: PAD_PU_PD_ON_MS_SOCK0 ,
2008-03-10 21:43:43 +03:00
host - > addr + PAD_PU_PD ) ;
writel ( PAD_OUTPUT_ENABLE_MS ,
host - > addr + PAD_OUTPUT_ENABLE ) ;
2008-07-26 06:45:00 +04:00
msleep ( 10 ) ;
2008-03-10 21:43:43 +03:00
dev_dbg ( & host - > chip - > pdev - > dev , " power on \n " ) ;
} else if ( value = = MEMSTICK_POWER_OFF ) {
2008-03-20 03:01:08 +03:00
host_ctl & = ~ ( HOST_CONTROL_POWER_EN
| HOST_CONTROL_CLOCK_EN ) ;
writel ( host_ctl , host - > addr + HOST_CONTROL ) ;
2008-03-10 21:43:43 +03:00
writel ( 0 , host - > addr + PAD_OUTPUT_ENABLE ) ;
writel ( PAD_PU_PD_OFF , host - > addr + PAD_PU_PD ) ;
dev_dbg ( & host - > chip - > pdev - > dev , " power off \n " ) ;
2008-07-26 06:45:00 +04:00
} else
return - EINVAL ;
2008-03-10 21:43:43 +03:00
break ;
case MEMSTICK_INTERFACE :
host_ctl & = ~ ( 3 < < HOST_CONTROL_IF_SHIFT ) ;
2008-09-13 13:33:26 +04:00
pci_read_config_dword ( host - > chip - > pdev ,
PCI_CTL_CLOCK_DLY_ADDR ,
& clock_delay ) ;
clock_delay & = host - > id ? ~ PCI_CTL_CLOCK_DLY_MASK_B
: ~ PCI_CTL_CLOCK_DLY_MASK_A ;
2008-03-10 21:43:43 +03:00
if ( value = = MEMSTICK_SERIAL ) {
host_ctl & = ~ HOST_CONTROL_FAST_CLK ;
2008-09-13 13:33:26 +04:00
host_ctl & = ~ HOST_CONTROL_REO ;
2008-03-10 21:43:43 +03:00
host_ctl | = HOST_CONTROL_IF_SERIAL
< < HOST_CONTROL_IF_SHIFT ;
host_ctl | = HOST_CONTROL_REI ;
2008-03-20 03:01:08 +03:00
clock_ctl = CLOCK_CONTROL_40MHZ ;
2008-03-10 21:43:43 +03:00
} else if ( value = = MEMSTICK_PAR4 ) {
2008-09-13 13:33:26 +04:00
host_ctl | = HOST_CONTROL_FAST_CLK | HOST_CONTROL_REO ;
2008-03-10 21:43:43 +03:00
host_ctl | = HOST_CONTROL_IF_PAR4
< < HOST_CONTROL_IF_SHIFT ;
host_ctl & = ~ HOST_CONTROL_REI ;
2008-03-20 03:01:08 +03:00
clock_ctl = CLOCK_CONTROL_40MHZ ;
2008-09-13 13:33:26 +04:00
clock_delay | = host - > id ? ( 4 < < 12 ) : ( 4 < < 8 ) ;
2008-03-10 21:43:43 +03:00
} else if ( value = = MEMSTICK_PAR8 ) {
host_ctl | = HOST_CONTROL_FAST_CLK ;
host_ctl | = HOST_CONTROL_IF_PAR8
< < HOST_CONTROL_IF_SHIFT ;
2008-09-13 13:33:26 +04:00
host_ctl & = ~ ( HOST_CONTROL_REI | HOST_CONTROL_REO ) ;
clock_ctl = CLOCK_CONTROL_50MHZ ;
2008-07-26 06:45:00 +04:00
} else
return - EINVAL ;
2008-09-13 13:33:26 +04:00
2008-03-10 21:43:43 +03:00
writel ( host_ctl , host - > addr + HOST_CONTROL ) ;
2008-03-20 03:01:08 +03:00
writel ( clock_ctl , host - > addr + CLOCK_CONTROL ) ;
2008-09-13 13:33:26 +04:00
pci_write_config_dword ( host - > chip - > pdev ,
PCI_CTL_CLOCK_DLY_ADDR ,
clock_delay ) ;
2008-03-10 21:43:43 +03:00
break ;
} ;
2008-07-26 06:45:00 +04:00
return 0 ;
2008-03-10 21:43:43 +03:00
}
# ifdef CONFIG_PM
static int jmb38x_ms_suspend ( struct pci_dev * dev , pm_message_t state )
{
struct jmb38x_ms * jm = pci_get_drvdata ( dev ) ;
int cnt ;
for ( cnt = 0 ; cnt < jm - > host_cnt ; + + cnt ) {
if ( ! jm - > hosts [ cnt ] )
break ;
memstick_suspend_host ( jm - > hosts [ cnt ] ) ;
}
pci_save_state ( dev ) ;
pci_enable_wake ( dev , pci_choose_state ( dev , state ) , 0 ) ;
pci_disable_device ( dev ) ;
pci_set_power_state ( dev , pci_choose_state ( dev , state ) ) ;
return 0 ;
}
static int jmb38x_ms_resume ( struct pci_dev * dev )
{
struct jmb38x_ms * jm = pci_get_drvdata ( dev ) ;
int rc ;
pci_set_power_state ( dev , PCI_D0 ) ;
pci_restore_state ( dev ) ;
rc = pci_enable_device ( dev ) ;
if ( rc )
return rc ;
pci_set_master ( dev ) ;
pci_read_config_dword ( dev , 0xac , & rc ) ;
pci_write_config_dword ( dev , 0xac , rc | 0x00470000 ) ;
for ( rc = 0 ; rc < jm - > host_cnt ; + + rc ) {
if ( ! jm - > hosts [ rc ] )
break ;
memstick_resume_host ( jm - > hosts [ rc ] ) ;
memstick_detect_change ( jm - > hosts [ rc ] ) ;
}
return 0 ;
}
# else
# define jmb38x_ms_suspend NULL
# define jmb38x_ms_resume NULL
# endif /* CONFIG_PM */
static int jmb38x_ms_count_slots ( struct pci_dev * pdev )
{
int cnt , rc = 0 ;
for ( cnt = 0 ; cnt < PCI_ROM_RESOURCE ; + + cnt ) {
if ( ! ( IORESOURCE_MEM & pci_resource_flags ( pdev , cnt ) ) )
break ;
if ( 256 ! = pci_resource_len ( pdev , cnt ) )
break ;
+ + rc ;
}
return rc ;
}
static struct memstick_host * jmb38x_ms_alloc_host ( struct jmb38x_ms * jm , int cnt )
{
struct memstick_host * msh ;
struct jmb38x_ms_host * host ;
msh = memstick_alloc_host ( sizeof ( struct jmb38x_ms_host ) ,
& jm - > pdev - > dev ) ;
if ( ! msh )
return NULL ;
host = memstick_priv ( msh ) ;
host - > chip = jm ;
host - > addr = ioremap ( pci_resource_start ( jm - > pdev , cnt ) ,
pci_resource_len ( jm - > pdev , cnt ) ) ;
if ( ! host - > addr )
goto err_out_free ;
spin_lock_init ( & host - > lock ) ;
host - > id = cnt ;
2008-05-02 08:02:41 +04:00
snprintf ( host - > host_id , sizeof ( host - > host_id ) , DRIVER_NAME " :slot%d " ,
2008-03-10 21:43:43 +03:00
host - > id ) ;
host - > irq = jm - > pdev - > irq ;
2008-03-20 03:01:06 +03:00
host - > timeout_jiffies = msecs_to_jiffies ( 1000 ) ;
2008-07-26 06:45:02 +04:00
tasklet_init ( & host - > notify , jmb38x_ms_req_tasklet , ( unsigned long ) msh ) ;
msh - > request = jmb38x_ms_submit_req ;
2008-03-10 21:43:43 +03:00
msh - > set_param = jmb38x_ms_set_param ;
2008-03-20 03:01:06 +03:00
2008-03-10 21:43:43 +03:00
msh - > caps = MEMSTICK_CAP_PAR4 | MEMSTICK_CAP_PAR8 ;
setup_timer ( & host - > timer , jmb38x_ms_abort , ( unsigned long ) msh ) ;
if ( ! request_irq ( host - > irq , jmb38x_ms_isr , IRQF_SHARED , host - > host_id ,
msh ) )
return msh ;
iounmap ( host - > addr ) ;
err_out_free :
kfree ( msh ) ;
return NULL ;
}
static void jmb38x_ms_free_host ( struct memstick_host * msh )
{
struct jmb38x_ms_host * host = memstick_priv ( msh ) ;
free_irq ( host - > irq , msh ) ;
iounmap ( host - > addr ) ;
memstick_free_host ( msh ) ;
}
static int jmb38x_ms_probe ( struct pci_dev * pdev ,
const struct pci_device_id * dev_id )
{
struct jmb38x_ms * jm ;
int pci_dev_busy = 0 ;
int rc , cnt ;
2009-04-07 06:01:15 +04:00
rc = pci_set_dma_mask ( pdev , DMA_BIT_MASK ( 32 ) ) ;
2008-03-10 21:43:43 +03:00
if ( rc )
return rc ;
rc = pci_enable_device ( pdev ) ;
if ( rc )
return rc ;
pci_set_master ( pdev ) ;
rc = pci_request_regions ( pdev , DRIVER_NAME ) ;
if ( rc ) {
pci_dev_busy = 1 ;
goto err_out ;
}
pci_read_config_dword ( pdev , 0xac , & rc ) ;
pci_write_config_dword ( pdev , 0xac , rc | 0x00470000 ) ;
cnt = jmb38x_ms_count_slots ( pdev ) ;
if ( ! cnt ) {
rc = - ENODEV ;
pci_dev_busy = 1 ;
goto err_out ;
}
jm = kzalloc ( sizeof ( struct jmb38x_ms )
+ cnt * sizeof ( struct memstick_host * ) , GFP_KERNEL ) ;
if ( ! jm ) {
rc = - ENOMEM ;
goto err_out_int ;
}
jm - > pdev = pdev ;
jm - > host_cnt = cnt ;
pci_set_drvdata ( pdev , jm ) ;
for ( cnt = 0 ; cnt < jm - > host_cnt ; + + cnt ) {
jm - > hosts [ cnt ] = jmb38x_ms_alloc_host ( jm , cnt ) ;
if ( ! jm - > hosts [ cnt ] )
break ;
rc = memstick_add_host ( jm - > hosts [ cnt ] ) ;
if ( rc ) {
jmb38x_ms_free_host ( jm - > hosts [ cnt ] ) ;
jm - > hosts [ cnt ] = NULL ;
break ;
}
}
if ( cnt )
return 0 ;
rc = - ENODEV ;
pci_set_drvdata ( pdev , NULL ) ;
kfree ( jm ) ;
err_out_int :
pci_release_regions ( pdev ) ;
err_out :
if ( ! pci_dev_busy )
pci_disable_device ( pdev ) ;
return rc ;
}
static void jmb38x_ms_remove ( struct pci_dev * dev )
{
struct jmb38x_ms * jm = pci_get_drvdata ( dev ) ;
struct jmb38x_ms_host * host ;
int cnt ;
unsigned long flags ;
for ( cnt = 0 ; cnt < jm - > host_cnt ; + + cnt ) {
if ( ! jm - > hosts [ cnt ] )
break ;
host = memstick_priv ( jm - > hosts [ cnt ] ) ;
2008-07-26 06:45:02 +04:00
jm - > hosts [ cnt ] - > request = jmb38x_ms_dummy_submit ;
tasklet_kill ( & host - > notify ) ;
2008-03-10 21:43:43 +03:00
writel ( 0 , host - > addr + INT_SIGNAL_ENABLE ) ;
writel ( 0 , host - > addr + INT_STATUS_ENABLE ) ;
mmiowb ( ) ;
dev_dbg ( & jm - > pdev - > dev , " interrupts off \n " ) ;
spin_lock_irqsave ( & host - > lock , flags ) ;
if ( host - > req ) {
host - > req - > error = - ETIME ;
jmb38x_ms_complete_cmd ( jm - > hosts [ cnt ] , 1 ) ;
}
spin_unlock_irqrestore ( & host - > lock , flags ) ;
memstick_remove_host ( jm - > hosts [ cnt ] ) ;
dev_dbg ( & jm - > pdev - > dev , " host removed \n " ) ;
jmb38x_ms_free_host ( jm - > hosts [ cnt ] ) ;
}
pci_set_drvdata ( dev , NULL ) ;
pci_release_regions ( dev ) ;
pci_disable_device ( dev ) ;
kfree ( jm ) ;
}
static struct pci_device_id jmb38x_ms_id_tbl [ ] = {
{ PCI_VENDOR_ID_JMICRON , PCI_DEVICE_ID_JMICRON_JMB38X_MS , PCI_ANY_ID ,
PCI_ANY_ID , 0 , 0 , 0 } ,
{ }
} ;
static struct pci_driver jmb38x_ms_driver = {
. name = DRIVER_NAME ,
. id_table = jmb38x_ms_id_tbl ,
. probe = jmb38x_ms_probe ,
. remove = jmb38x_ms_remove ,
. suspend = jmb38x_ms_suspend ,
. resume = jmb38x_ms_resume
} ;
static int __init jmb38x_ms_init ( void )
{
return pci_register_driver ( & jmb38x_ms_driver ) ;
}
static void __exit jmb38x_ms_exit ( void )
{
pci_unregister_driver ( & jmb38x_ms_driver ) ;
}
MODULE_AUTHOR ( " Alex Dubov " ) ;
MODULE_DESCRIPTION ( " JMicron jmb38x MemoryStick driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DEVICE_TABLE ( pci , jmb38x_ms_id_tbl ) ;
module_init ( jmb38x_ms_init ) ;
module_exit ( jmb38x_ms_exit ) ;