2008-07-01 01:40:24 +04:00
/*
* linux / drivers / mmc / s3cmci . h - Samsung S3C MCI driver
*
* Copyright ( C ) 2004 - 2006 maintech GmbH , Thomas Kleffel < tk @ maintech . de >
*
2008-10-15 03:17:19 +04:00
* Current driver maintained by Ben Dooks and Simtec Electronics
* Copyright ( C ) 2008 Simtec Electronics < ben - linux @ fluff . org >
*
2008-07-01 01:40:24 +04:00
* 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/module.h>
# include <linux/dma-mapping.h>
# include <linux/clk.h>
# include <linux/mmc/host.h>
# include <linux/platform_device.h>
2008-10-15 03:17:16 +04:00
# include <linux/cpufreq.h>
2009-10-02 02:44:17 +04:00
# include <linux/debugfs.h>
# include <linux/seq_file.h>
2009-05-14 01:52:24 +04:00
# include <linux/gpio.h>
2008-07-01 01:40:24 +04:00
# include <linux/irq.h>
# include <linux/io.h>
2014-01-14 18:05:22 +04:00
# include <plat/gpio-cfg.h>
2009-01-14 04:13:47 +03:00
# include <mach/dma.h>
2014-01-14 17:24:24 +04:00
# include <mach/gpio-samsung.h>
2008-07-01 01:40:24 +04:00
2012-08-24 17:22:12 +04:00
# include <linux/platform_data/mmc-s3cmci.h>
2008-07-01 01:40:29 +04:00
2008-07-01 01:40:24 +04:00
# include "s3cmci.h"
# define DRIVER_NAME "s3c-mci"
2013-02-02 04:40:17 +04:00
# define S3C2410_SDICON (0x00)
# define S3C2410_SDIPRE (0x04)
# define S3C2410_SDICMDARG (0x08)
# define S3C2410_SDICMDCON (0x0C)
# define S3C2410_SDICMDSTAT (0x10)
# define S3C2410_SDIRSP0 (0x14)
# define S3C2410_SDIRSP1 (0x18)
# define S3C2410_SDIRSP2 (0x1C)
# define S3C2410_SDIRSP3 (0x20)
# define S3C2410_SDITIMER (0x24)
# define S3C2410_SDIBSIZE (0x28)
# define S3C2410_SDIDCON (0x2C)
# define S3C2410_SDIDCNT (0x30)
# define S3C2410_SDIDSTA (0x34)
# define S3C2410_SDIFSTA (0x38)
# define S3C2410_SDIDATA (0x3C)
# define S3C2410_SDIIMSK (0x40)
# define S3C2440_SDIDATA (0x40)
# define S3C2440_SDIIMSK (0x3C)
# define S3C2440_SDICON_SDRESET (1 << 8)
# define S3C2410_SDICON_SDIOIRQ (1 << 3)
# define S3C2410_SDICON_FIFORESET (1 << 1)
# define S3C2410_SDICON_CLOCKTYPE (1 << 0)
# define S3C2410_SDICMDCON_LONGRSP (1 << 10)
# define S3C2410_SDICMDCON_WAITRSP (1 << 9)
# define S3C2410_SDICMDCON_CMDSTART (1 << 8)
# define S3C2410_SDICMDCON_SENDERHOST (1 << 6)
# define S3C2410_SDICMDCON_INDEX (0x3f)
# define S3C2410_SDICMDSTAT_CRCFAIL (1 << 12)
# define S3C2410_SDICMDSTAT_CMDSENT (1 << 11)
# define S3C2410_SDICMDSTAT_CMDTIMEOUT (1 << 10)
# define S3C2410_SDICMDSTAT_RSPFIN (1 << 9)
# define S3C2440_SDIDCON_DS_WORD (2 << 22)
# define S3C2410_SDIDCON_TXAFTERRESP (1 << 20)
# define S3C2410_SDIDCON_RXAFTERCMD (1 << 19)
# define S3C2410_SDIDCON_BLOCKMODE (1 << 17)
# define S3C2410_SDIDCON_WIDEBUS (1 << 16)
# define S3C2410_SDIDCON_DMAEN (1 << 15)
# define S3C2410_SDIDCON_STOP (1 << 14)
# define S3C2440_SDIDCON_DATSTART (1 << 14)
# define S3C2410_SDIDCON_XFER_RXSTART (2 << 12)
# define S3C2410_SDIDCON_XFER_TXSTART (3 << 12)
# define S3C2410_SDIDCON_BLKNUM_MASK (0xFFF)
# define S3C2410_SDIDSTA_SDIOIRQDETECT (1 << 9)
# define S3C2410_SDIDSTA_FIFOFAIL (1 << 8)
# define S3C2410_SDIDSTA_CRCFAIL (1 << 7)
# define S3C2410_SDIDSTA_RXCRCFAIL (1 << 6)
# define S3C2410_SDIDSTA_DATATIMEOUT (1 << 5)
# define S3C2410_SDIDSTA_XFERFINISH (1 << 4)
# define S3C2410_SDIDSTA_TXDATAON (1 << 1)
# define S3C2410_SDIDSTA_RXDATAON (1 << 0)
# define S3C2440_SDIFSTA_FIFORESET (1 << 16)
# define S3C2440_SDIFSTA_FIFOFAIL (3 << 14)
# define S3C2410_SDIFSTA_TFDET (1 << 13)
# define S3C2410_SDIFSTA_RFDET (1 << 12)
# define S3C2410_SDIFSTA_COUNTMASK (0x7f)
# define S3C2410_SDIIMSK_RESPONSECRC (1 << 17)
# define S3C2410_SDIIMSK_CMDSENT (1 << 16)
# define S3C2410_SDIIMSK_CMDTIMEOUT (1 << 15)
# define S3C2410_SDIIMSK_RESPONSEND (1 << 14)
# define S3C2410_SDIIMSK_SDIOIRQ (1 << 12)
# define S3C2410_SDIIMSK_FIFOFAIL (1 << 11)
# define S3C2410_SDIIMSK_CRCSTATUS (1 << 10)
# define S3C2410_SDIIMSK_DATACRC (1 << 9)
# define S3C2410_SDIIMSK_DATATIMEOUT (1 << 8)
# define S3C2410_SDIIMSK_DATAFINISH (1 << 7)
# define S3C2410_SDIIMSK_TXFIFOHALF (1 << 4)
# define S3C2410_SDIIMSK_RXFIFOLAST (1 << 2)
# define S3C2410_SDIIMSK_RXFIFOHALF (1 << 0)
2008-07-01 01:40:24 +04:00
enum dbg_channels {
dbg_err = ( 1 < < 0 ) ,
dbg_debug = ( 1 < < 1 ) ,
dbg_info = ( 1 < < 2 ) ,
dbg_irq = ( 1 < < 3 ) ,
dbg_sg = ( 1 < < 4 ) ,
dbg_dma = ( 1 < < 5 ) ,
dbg_pio = ( 1 < < 6 ) ,
dbg_fail = ( 1 < < 7 ) ,
dbg_conf = ( 1 < < 8 ) ,
} ;
2008-10-15 03:17:15 +04:00
static const int dbgmap_err = dbg_fail ;
2008-07-01 01:40:24 +04:00
static const int dbgmap_info = dbg_info | dbg_conf ;
2008-10-15 03:17:15 +04:00
static const int dbgmap_debug = dbg_err | dbg_debug ;
2008-07-01 01:40:24 +04:00
# define dbg(host, channels, args...) \
do { \
if ( dbgmap_err & channels ) \
dev_err ( & host - > pdev - > dev , args ) ; \
else if ( dbgmap_info & channels ) \
dev_info ( & host - > pdev - > dev , args ) ; \
else if ( dbgmap_debug & channels ) \
dev_dbg ( & host - > pdev - > dev , args ) ; \
} while ( 0 )
static struct s3c2410_dma_client s3cmci_dma_client = {
. name = " s3c-mci " ,
} ;
static void finalize_request ( struct s3cmci_host * host ) ;
static void s3cmci_send_request ( struct mmc_host * mmc ) ;
static void s3cmci_reset ( struct s3cmci_host * host ) ;
# ifdef CONFIG_MMC_DEBUG
static void dbg_dumpregs ( struct s3cmci_host * host , char * prefix )
{
u32 con , pre , cmdarg , cmdcon , cmdsta , r0 , r1 , r2 , r3 , timer , bsize ;
u32 datcon , datcnt , datsta , fsta , imask ;
con = readl ( host - > base + S3C2410_SDICON ) ;
pre = readl ( host - > base + S3C2410_SDIPRE ) ;
cmdarg = readl ( host - > base + S3C2410_SDICMDARG ) ;
cmdcon = readl ( host - > base + S3C2410_SDICMDCON ) ;
cmdsta = readl ( host - > base + S3C2410_SDICMDSTAT ) ;
r0 = readl ( host - > base + S3C2410_SDIRSP0 ) ;
r1 = readl ( host - > base + S3C2410_SDIRSP1 ) ;
r2 = readl ( host - > base + S3C2410_SDIRSP2 ) ;
r3 = readl ( host - > base + S3C2410_SDIRSP3 ) ;
timer = readl ( host - > base + S3C2410_SDITIMER ) ;
bsize = readl ( host - > base + S3C2410_SDIBSIZE ) ;
datcon = readl ( host - > base + S3C2410_SDIDCON ) ;
datcnt = readl ( host - > base + S3C2410_SDIDCNT ) ;
datsta = readl ( host - > base + S3C2410_SDIDSTA ) ;
fsta = readl ( host - > base + S3C2410_SDIFSTA ) ;
imask = readl ( host - > base + host - > sdiimsk ) ;
dbg ( host , dbg_debug , " %s CON:[%08x] PRE:[%08x] TMR:[%08x] \n " ,
prefix , con , pre , timer ) ;
dbg ( host , dbg_debug , " %s CCON:[%08x] CARG:[%08x] CSTA:[%08x] \n " ,
prefix , cmdcon , cmdarg , cmdsta ) ;
dbg ( host , dbg_debug , " %s DCON:[%08x] FSTA:[%08x] "
" DSTA:[%08x] DCNT:[%08x] \n " ,
prefix , datcon , fsta , datsta , datcnt ) ;
dbg ( host , dbg_debug , " %s R0:[%08x] R1:[%08x] "
" R2:[%08x] R3:[%08x] \n " ,
prefix , r0 , r1 , r2 , r3 ) ;
}
static void prepare_dbgmsg ( struct s3cmci_host * host , struct mmc_command * cmd ,
int stop )
{
snprintf ( host - > dbgmsg_cmd , 300 ,
" #%u%s op:%i arg:0x%08x flags:0x08%x retries:%u " ,
host - > ccnt , ( stop ? " (STOP) " : " " ) ,
cmd - > opcode , cmd - > arg , cmd - > flags , cmd - > retries ) ;
if ( cmd - > data ) {
snprintf ( host - > dbgmsg_dat , 300 ,
" #%u bsize:%u blocks:%u bytes:%u " ,
host - > dcnt , cmd - > data - > blksz ,
cmd - > data - > blocks ,
cmd - > data - > blocks * cmd - > data - > blksz ) ;
} else {
host - > dbgmsg_dat [ 0 ] = ' \0 ' ;
}
}
static void dbg_dumpcmd ( struct s3cmci_host * host , struct mmc_command * cmd ,
int fail )
{
unsigned int dbglvl = fail ? dbg_fail : dbg_debug ;
if ( ! cmd )
return ;
if ( cmd - > error = = 0 ) {
dbg ( host , dbglvl , " CMD[OK] %s R0:0x%08x \n " ,
host - > dbgmsg_cmd , cmd - > resp [ 0 ] ) ;
} else {
dbg ( host , dbglvl , " CMD[ERR %i] %s Status:%s \n " ,
cmd - > error , host - > dbgmsg_cmd , host - > status ) ;
}
if ( ! cmd - > data )
return ;
if ( cmd - > data - > error = = 0 ) {
dbg ( host , dbglvl , " DAT[OK] %s \n " , host - > dbgmsg_dat ) ;
} else {
dbg ( host , dbglvl , " DAT[ERR %i] %s DCNT:0x%08x \n " ,
cmd - > data - > error , host - > dbgmsg_dat ,
readl ( host - > base + S3C2410_SDIDCNT ) ) ;
}
}
# else
static void dbg_dumpcmd ( struct s3cmci_host * host ,
struct mmc_command * cmd , int fail ) { }
static void prepare_dbgmsg ( struct s3cmci_host * host , struct mmc_command * cmd ,
int stop ) { }
static void dbg_dumpregs ( struct s3cmci_host * host , char * prefix ) { }
# endif /* CONFIG_MMC_DEBUG */
2009-10-02 02:44:18 +04:00
/**
* s3cmci_host_usedma - return whether the host is using dma or pio
* @ host : The host state
*
* Return true if the host is using DMA to transfer data , else false
* to use PIO mode . Will return static data depending on the driver
* configuration .
*/
static inline bool s3cmci_host_usedma ( struct s3cmci_host * host )
{
# ifdef CONFIG_MMC_S3C_PIO
return false ;
# elif defined(CONFIG_MMC_S3C_DMA)
return true ;
# else
return host - > dodma ;
# endif
}
2009-10-02 02:44:19 +04:00
/**
* s3cmci_host_canpio - return true if host has pio code available
*
* Return true if the driver has been compiled with the PIO support code
* available .
*/
static inline bool s3cmci_host_canpio ( void )
{
# ifdef CONFIG_MMC_S3C_PIO
return true ;
# else
return false ;
# endif
}
2008-07-01 01:40:24 +04:00
static inline u32 enable_imask ( struct s3cmci_host * host , u32 imask )
{
u32 newmask ;
newmask = readl ( host - > base + host - > sdiimsk ) ;
newmask | = imask ;
writel ( newmask , host - > base + host - > sdiimsk ) ;
return newmask ;
}
static inline u32 disable_imask ( struct s3cmci_host * host , u32 imask )
{
u32 newmask ;
newmask = readl ( host - > base + host - > sdiimsk ) ;
newmask & = ~ imask ;
writel ( newmask , host - > base + host - > sdiimsk ) ;
return newmask ;
}
static inline void clear_imask ( struct s3cmci_host * host )
{
2009-10-02 02:44:18 +04:00
u32 mask = readl ( host - > base + host - > sdiimsk ) ;
/* preserve the SDIO IRQ mask state */
mask & = S3C2410_SDIIMSK_SDIOIRQ ;
writel ( mask , host - > base + host - > sdiimsk ) ;
}
/**
* s3cmci_check_sdio_irq - test whether the SDIO IRQ is being signalled
* @ host : The host to check .
*
* Test to see if the SDIO interrupt is being signalled in case the
* controller has failed to re - detect a card interrupt . Read GPE8 and
* see if it is low and if so , signal a SDIO interrupt .
*
* This is currently called if a request is finished ( we assume that the
* bus is now idle ) and when the SDIO IRQ is enabled in case the IRQ is
* already being indicated .
*/
static void s3cmci_check_sdio_irq ( struct s3cmci_host * host )
{
if ( host - > sdio_irqen ) {
if ( gpio_get_value ( S3C2410_GPE ( 8 ) ) = = 0 ) {
2011-10-11 10:14:09 +04:00
pr_debug ( " %s: signalling irq \n " , __func__ ) ;
2009-10-02 02:44:18 +04:00
mmc_signal_sdio_irq ( host - > mmc ) ;
}
}
2008-07-01 01:40:24 +04:00
}
static inline int get_data_buffer ( struct s3cmci_host * host ,
2008-10-15 03:17:17 +04:00
u32 * bytes , u32 * * pointer )
2008-07-01 01:40:24 +04:00
{
struct scatterlist * sg ;
if ( host - > pio_active = = XFER_NONE )
return - EINVAL ;
if ( ( ! host - > mrq ) | | ( ! host - > mrq - > data ) )
return - EINVAL ;
if ( host - > pio_sgptr > = host - > mrq - > data - > sg_len ) {
dbg ( host , dbg_debug , " no more buffers (%i/%i) \n " ,
host - > pio_sgptr , host - > mrq - > data - > sg_len ) ;
return - EBUSY ;
}
sg = & host - > mrq - > data - > sg [ host - > pio_sgptr ] ;
2008-10-15 03:17:17 +04:00
* bytes = sg - > length ;
2008-07-01 01:40:24 +04:00
* pointer = sg_virt ( sg ) ;
host - > pio_sgptr + + ;
dbg ( host , dbg_sg , " new buffer (%i/%i) \n " ,
host - > pio_sgptr , host - > mrq - > data - > sg_len ) ;
return 0 ;
}
static inline u32 fifo_count ( struct s3cmci_host * host )
{
u32 fifostat = readl ( host - > base + S3C2410_SDIFSTA ) ;
fifostat & = S3C2410_SDIFSTA_COUNTMASK ;
2008-10-15 03:17:17 +04:00
return fifostat ;
2008-07-01 01:40:24 +04:00
}
static inline u32 fifo_free ( struct s3cmci_host * host )
{
u32 fifostat = readl ( host - > base + S3C2410_SDIFSTA ) ;
fifostat & = S3C2410_SDIFSTA_COUNTMASK ;
2008-10-15 03:17:17 +04:00
return 63 - fifostat ;
2008-07-01 01:40:24 +04:00
}
2009-10-02 02:44:18 +04:00
/**
* s3cmci_enable_irq - enable IRQ , after having disabled it .
* @ host : The device state .
* @ more : True if more IRQs are expected from transfer .
*
* Enable the main IRQ if needed after it has been disabled .
*
* The IRQ can be one of the following states :
* - disabled during IDLE
* - disabled whilst processing data
* - enabled during transfer
* - enabled whilst awaiting SDIO interrupt detection
*/
static void s3cmci_enable_irq ( struct s3cmci_host * host , bool more )
{
unsigned long flags ;
bool enable = false ;
local_irq_save ( flags ) ;
host - > irq_enabled = more ;
host - > irq_disabled = false ;
enable = more | host - > sdio_irqen ;
if ( host - > irq_state ! = enable ) {
host - > irq_state = enable ;
if ( enable )
enable_irq ( host - > irq ) ;
else
disable_irq ( host - > irq ) ;
}
local_irq_restore ( flags ) ;
}
/**
*
*/
static void s3cmci_disable_irq ( struct s3cmci_host * host , bool transfer )
{
unsigned long flags ;
local_irq_save ( flags ) ;
2011-10-11 10:14:09 +04:00
/* pr_debug("%s: transfer %d\n", __func__, transfer); */
2009-10-02 02:44:18 +04:00
host - > irq_disabled = transfer ;
if ( transfer & & host - > irq_state ) {
host - > irq_state = false ;
disable_irq ( host - > irq ) ;
}
local_irq_restore ( flags ) ;
}
2008-07-01 01:40:24 +04:00
static void do_pio_read ( struct s3cmci_host * host )
{
int res ;
u32 fifo ;
2008-10-15 03:17:18 +04:00
u32 * ptr ;
2008-10-15 03:17:17 +04:00
u32 fifo_words ;
2008-07-01 01:40:24 +04:00
void __iomem * from_ptr ;
/* write real prescaler to host, it might be set slow to fix */
writel ( host - > prescaler , host - > base + S3C2410_SDIPRE ) ;
from_ptr = host - > base + host - > sdidata ;
while ( ( fifo = fifo_count ( host ) ) ) {
2008-10-15 03:17:17 +04:00
if ( ! host - > pio_bytes ) {
res = get_data_buffer ( host , & host - > pio_bytes ,
2008-07-01 01:40:24 +04:00
& host - > pio_ptr ) ;
if ( res ) {
host - > pio_active = XFER_NONE ;
host - > complete_what = COMPLETION_FINALIZE ;
dbg ( host , dbg_pio , " pio_read(): "
" complete (no more data). \n " ) ;
return ;
}
dbg ( host , dbg_pio ,
" pio_read(): new target: [%i]@[%p] \n " ,
2008-10-15 03:17:17 +04:00
host - > pio_bytes , host - > pio_ptr ) ;
2008-07-01 01:40:24 +04:00
}
dbg ( host , dbg_pio ,
" pio_read(): fifo:[%02i] buffer:[%03i] dcnt:[%08X] \n " ,
2008-10-15 03:17:17 +04:00
fifo , host - > pio_bytes ,
2008-07-01 01:40:24 +04:00
readl ( host - > base + S3C2410_SDIDCNT ) ) ;
2008-10-15 03:17:17 +04:00
/* If we have reached the end of the block, we can
* read a word and get 1 to 3 bytes . If we in the
* middle of the block , we have to read full words ,
* otherwise we will write garbage , so round down to
* an even multiple of 4. */
if ( fifo > = host - > pio_bytes )
fifo = host - > pio_bytes ;
else
fifo - = fifo & 3 ;
host - > pio_bytes - = fifo ;
2008-07-01 01:40:24 +04:00
host - > pio_count + = fifo ;
2008-10-15 03:17:17 +04:00
fifo_words = fifo > > 2 ;
2008-10-15 03:17:18 +04:00
ptr = host - > pio_ptr ;
2008-10-15 03:17:17 +04:00
while ( fifo_words - - )
2008-10-15 03:17:18 +04:00
* ptr + + = readl ( from_ptr ) ;
host - > pio_ptr = ptr ;
2008-10-15 03:17:17 +04:00
if ( fifo & 3 ) {
u32 n = fifo & 3 ;
u32 data = readl ( from_ptr ) ;
u8 * p = ( u8 * ) host - > pio_ptr ;
while ( n - - ) {
* p + + = data ;
data > > = 8 ;
}
}
2008-07-01 01:40:24 +04:00
}
2008-10-15 03:17:17 +04:00
if ( ! host - > pio_bytes ) {
res = get_data_buffer ( host , & host - > pio_bytes , & host - > pio_ptr ) ;
2008-07-01 01:40:24 +04:00
if ( res ) {
dbg ( host , dbg_pio ,
" pio_read(): complete (no more buffers). \n " ) ;
host - > pio_active = XFER_NONE ;
host - > complete_what = COMPLETION_FINALIZE ;
return ;
}
}
enable_imask ( host ,
S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST ) ;
}
static void do_pio_write ( struct s3cmci_host * host )
{
void __iomem * to_ptr ;
int res ;
u32 fifo ;
2008-10-15 03:17:18 +04:00
u32 * ptr ;
2008-07-01 01:40:24 +04:00
to_ptr = host - > base + host - > sdidata ;
2009-02-12 00:25:52 +03:00
while ( ( fifo = fifo_free ( host ) ) > 3 ) {
2008-10-15 03:17:17 +04:00
if ( ! host - > pio_bytes ) {
res = get_data_buffer ( host , & host - > pio_bytes ,
2008-07-01 01:40:24 +04:00
& host - > pio_ptr ) ;
if ( res ) {
dbg ( host , dbg_pio ,
" pio_write(): complete (no more data). \n " ) ;
host - > pio_active = XFER_NONE ;
return ;
}
dbg ( host , dbg_pio ,
" pio_write(): new source: [%i]@[%p] \n " ,
2008-10-15 03:17:17 +04:00
host - > pio_bytes , host - > pio_ptr ) ;
2008-07-01 01:40:24 +04:00
}
2008-10-15 03:17:17 +04:00
/* If we have reached the end of the block, we have to
* write exactly the remaining number of bytes . If we
* in the middle of the block , we have to write full
* words , so round down to an even multiple of 4. */
if ( fifo > = host - > pio_bytes )
fifo = host - > pio_bytes ;
else
fifo - = fifo & 3 ;
2008-07-01 01:40:24 +04:00
2008-10-15 03:17:17 +04:00
host - > pio_bytes - = fifo ;
2008-07-01 01:40:24 +04:00
host - > pio_count + = fifo ;
2008-10-15 03:17:17 +04:00
fifo = ( fifo + 3 ) > > 2 ;
2008-10-15 03:17:18 +04:00
ptr = host - > pio_ptr ;
2008-07-01 01:40:24 +04:00
while ( fifo - - )
2008-10-15 03:17:18 +04:00
writel ( * ptr + + , to_ptr ) ;
host - > pio_ptr = ptr ;
2008-07-01 01:40:24 +04:00
}
enable_imask ( host , S3C2410_SDIIMSK_TXFIFOHALF ) ;
}
static void pio_tasklet ( unsigned long data )
{
struct s3cmci_host * host = ( struct s3cmci_host * ) data ;
2009-10-02 02:44:18 +04:00
s3cmci_disable_irq ( host , true ) ;
2008-07-01 01:40:28 +04:00
2008-07-01 01:40:24 +04:00
if ( host - > pio_active = = XFER_WRITE )
do_pio_write ( host ) ;
if ( host - > pio_active = = XFER_READ )
do_pio_read ( host ) ;
if ( host - > complete_what = = COMPLETION_FINALIZE ) {
clear_imask ( host ) ;
if ( host - > pio_active ! = XFER_NONE ) {
dbg ( host , dbg_err , " unfinished %s "
2008-10-15 03:17:17 +04:00
" - pio_count:[%u] pio_bytes:[%u] \n " ,
2008-07-01 01:40:24 +04:00
( host - > pio_active = = XFER_READ ) ? " read " : " write " ,
2008-10-15 03:17:17 +04:00
host - > pio_count , host - > pio_bytes ) ;
2008-07-01 01:40:24 +04:00
2008-07-01 01:40:31 +04:00
if ( host - > mrq - > data )
host - > mrq - > data - > error = - EINVAL ;
2008-07-01 01:40:24 +04:00
}
2009-10-02 02:44:18 +04:00
s3cmci_enable_irq ( host , false ) ;
2008-07-01 01:40:24 +04:00
finalize_request ( host ) ;
2008-07-01 01:40:28 +04:00
} else
2009-10-02 02:44:18 +04:00
s3cmci_enable_irq ( host , true ) ;
2008-07-01 01:40:24 +04:00
}
/*
* ISR for SDI Interface IRQ
* Communication between driver and ISR works as follows :
* host - > mrq points to current request
* host - > complete_what Indicates when the request is considered done
* COMPLETION_CMDSENT when the command was sent
* COMPLETION_RSPFIN when a response was received
* COMPLETION_XFERFINISH when the data transfer is finished
* COMPLETION_XFERFINISH_RSPFIN both of the above .
* host - > complete_request is the completion - object the driver waits for
*
* 1 ) Driver sets up host - > mrq and host - > complete_what
* 2 ) Driver prepares the transfer
* 3 ) Driver enables interrupts
* 4 ) Driver starts transfer
* 5 ) Driver waits for host - > complete_rquest
* 6 ) ISR checks for request status ( errors and success )
* 6 ) ISR sets host - > mrq - > cmd - > error and host - > mrq - > data - > error
* 7 ) ISR completes host - > complete_request
* 8 ) ISR disables interrupts
* 9 ) Driver wakes up and takes care of the request
*
* Note : " ->error " - fields are expected to be set to 0 before the request
* was issued by mmc . c - therefore they are only set , when an error
* contition comes up
*/
static irqreturn_t s3cmci_irq ( int irq , void * dev_id )
{
struct s3cmci_host * host = dev_id ;
struct mmc_command * cmd ;
u32 mci_csta , mci_dsta , mci_fsta , mci_dcnt , mci_imsk ;
2009-10-02 02:44:18 +04:00
u32 mci_cclear = 0 , mci_dclear ;
2008-07-01 01:40:24 +04:00
unsigned long iflags ;
2009-10-02 02:44:18 +04:00
mci_dsta = readl ( host - > base + S3C2410_SDIDSTA ) ;
mci_imsk = readl ( host - > base + host - > sdiimsk ) ;
if ( mci_dsta & S3C2410_SDIDSTA_SDIOIRQDETECT ) {
if ( mci_imsk & S3C2410_SDIIMSK_SDIOIRQ ) {
mci_dclear = S3C2410_SDIDSTA_SDIOIRQDETECT ;
writel ( mci_dclear , host - > base + S3C2410_SDIDSTA ) ;
mmc_signal_sdio_irq ( host - > mmc ) ;
return IRQ_HANDLED ;
}
}
2008-07-01 01:40:24 +04:00
spin_lock_irqsave ( & host - > complete_lock , iflags ) ;
mci_csta = readl ( host - > base + S3C2410_SDICMDSTAT ) ;
mci_dcnt = readl ( host - > base + S3C2410_SDIDCNT ) ;
mci_fsta = readl ( host - > base + S3C2410_SDIFSTA ) ;
mci_dclear = 0 ;
if ( ( host - > complete_what = = COMPLETION_NONE ) | |
( host - > complete_what = = COMPLETION_FINALIZE ) ) {
host - > status = " nothing to complete " ;
clear_imask ( host ) ;
goto irq_out ;
}
if ( ! host - > mrq ) {
host - > status = " no active mrq " ;
clear_imask ( host ) ;
goto irq_out ;
}
cmd = host - > cmd_is_stop ? host - > mrq - > stop : host - > mrq - > cmd ;
if ( ! cmd ) {
host - > status = " no active cmd " ;
clear_imask ( host ) ;
goto irq_out ;
}
2009-10-02 02:44:18 +04:00
if ( ! s3cmci_host_usedma ( host ) ) {
2008-07-01 01:40:24 +04:00
if ( ( host - > pio_active = = XFER_WRITE ) & &
( mci_fsta & S3C2410_SDIFSTA_TFDET ) ) {
disable_imask ( host , S3C2410_SDIIMSK_TXFIFOHALF ) ;
tasklet_schedule ( & host - > pio_tasklet ) ;
host - > status = " pio tx " ;
}
if ( ( host - > pio_active = = XFER_READ ) & &
( mci_fsta & S3C2410_SDIFSTA_RFDET ) ) {
disable_imask ( host ,
S3C2410_SDIIMSK_RXFIFOHALF |
S3C2410_SDIIMSK_RXFIFOLAST ) ;
tasklet_schedule ( & host - > pio_tasklet ) ;
host - > status = " pio rx " ;
}
}
if ( mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT ) {
2008-07-01 01:40:37 +04:00
dbg ( host , dbg_err , " CMDSTAT: error CMDTIMEOUT \n " ) ;
2008-07-01 01:40:24 +04:00
cmd - > error = - ETIMEDOUT ;
host - > status = " error: command timeout " ;
goto fail_transfer ;
}
if ( mci_csta & S3C2410_SDICMDSTAT_CMDSENT ) {
if ( host - > complete_what = = COMPLETION_CMDSENT ) {
host - > status = " ok: command sent " ;
goto close_transfer ;
}
mci_cclear | = S3C2410_SDICMDSTAT_CMDSENT ;
}
if ( mci_csta & S3C2410_SDICMDSTAT_CRCFAIL ) {
if ( cmd - > flags & MMC_RSP_CRC ) {
2008-07-01 01:40:25 +04:00
if ( host - > mrq - > cmd - > flags & MMC_RSP_136 ) {
dbg ( host , dbg_irq ,
" fixup: ignore CRC fail with long rsp \n " ) ;
} else {
/* note, we used to fail the transfer
* here , but it seems that this is just
* the hardware getting it wrong .
*
* cmd - > error = - EILSEQ ;
* host - > status = " error: bad command crc " ;
* goto fail_transfer ;
*/
}
2008-07-01 01:40:24 +04:00
}
mci_cclear | = S3C2410_SDICMDSTAT_CRCFAIL ;
}
if ( mci_csta & S3C2410_SDICMDSTAT_RSPFIN ) {
if ( host - > complete_what = = COMPLETION_RSPFIN ) {
host - > status = " ok: command response received " ;
goto close_transfer ;
}
if ( host - > complete_what = = COMPLETION_XFERFINISH_RSPFIN )
host - > complete_what = COMPLETION_XFERFINISH ;
mci_cclear | = S3C2410_SDICMDSTAT_RSPFIN ;
}
/* errors handled after this point are only relevant
when a data transfer is in progress */
if ( ! cmd - > data )
goto clear_status_bits ;
/* Check for FIFO failure */
if ( host - > is2440 ) {
if ( mci_fsta & S3C2440_SDIFSTA_FIFOFAIL ) {
2008-07-01 01:40:37 +04:00
dbg ( host , dbg_err , " FIFO failure \n " ) ;
2008-07-01 01:40:24 +04:00
host - > mrq - > data - > error = - EILSEQ ;
host - > status = " error: 2440 fifo failure " ;
goto fail_transfer ;
}
} else {
if ( mci_dsta & S3C2410_SDIDSTA_FIFOFAIL ) {
2008-07-01 01:40:37 +04:00
dbg ( host , dbg_err , " FIFO failure \n " ) ;
2008-07-01 01:40:24 +04:00
cmd - > data - > error = - EILSEQ ;
host - > status = " error: fifo failure " ;
goto fail_transfer ;
}
}
if ( mci_dsta & S3C2410_SDIDSTA_RXCRCFAIL ) {
2008-07-01 01:40:37 +04:00
dbg ( host , dbg_err , " bad data crc (outgoing) \n " ) ;
2008-07-01 01:40:24 +04:00
cmd - > data - > error = - EILSEQ ;
host - > status = " error: bad data crc (outgoing) " ;
goto fail_transfer ;
}
if ( mci_dsta & S3C2410_SDIDSTA_CRCFAIL ) {
2008-07-01 01:40:37 +04:00
dbg ( host , dbg_err , " bad data crc (incoming) \n " ) ;
2008-07-01 01:40:24 +04:00
cmd - > data - > error = - EILSEQ ;
host - > status = " error: bad data crc (incoming) " ;
goto fail_transfer ;
}
if ( mci_dsta & S3C2410_SDIDSTA_DATATIMEOUT ) {
2008-07-01 01:40:37 +04:00
dbg ( host , dbg_err , " data timeout \n " ) ;
2008-07-01 01:40:24 +04:00
cmd - > data - > error = - ETIMEDOUT ;
host - > status = " error: data timeout " ;
goto fail_transfer ;
}
if ( mci_dsta & S3C2410_SDIDSTA_XFERFINISH ) {
if ( host - > complete_what = = COMPLETION_XFERFINISH ) {
host - > status = " ok: data transfer completed " ;
goto close_transfer ;
}
if ( host - > complete_what = = COMPLETION_XFERFINISH_RSPFIN )
host - > complete_what = COMPLETION_RSPFIN ;
mci_dclear | = S3C2410_SDIDSTA_XFERFINISH ;
}
clear_status_bits :
writel ( mci_cclear , host - > base + S3C2410_SDICMDSTAT ) ;
writel ( mci_dclear , host - > base + S3C2410_SDIDSTA ) ;
goto irq_out ;
fail_transfer :
host - > pio_active = XFER_NONE ;
close_transfer :
host - > complete_what = COMPLETION_FINALIZE ;
clear_imask ( host ) ;
tasklet_schedule ( & host - > pio_tasklet ) ;
goto irq_out ;
irq_out :
dbg ( host , dbg_irq ,
" csta:0x%08x dsta:0x%08x fsta:0x%08x dcnt:0x%08x status:%s. \n " ,
mci_csta , mci_dsta , mci_fsta , mci_dcnt , host - > status ) ;
spin_unlock_irqrestore ( & host - > complete_lock , iflags ) ;
return IRQ_HANDLED ;
}
/*
* ISR for the CardDetect Pin
*/
static irqreturn_t s3cmci_irq_cd ( int irq , void * dev_id )
{
struct s3cmci_host * host = ( struct s3cmci_host * ) dev_id ;
dbg ( host , dbg_irq , " card detect \n " ) ;
2008-07-01 01:40:35 +04:00
mmc_detect_change ( host - > mmc , msecs_to_jiffies ( 500 ) ) ;
2008-07-01 01:40:24 +04:00
return IRQ_HANDLED ;
}
2008-08-08 13:55:41 +04:00
static void s3cmci_dma_done_callback ( struct s3c2410_dma_chan * dma_ch ,
void * buf_id , int size ,
enum s3c2410_dma_buffresult result )
2008-07-01 01:40:24 +04:00
{
struct s3cmci_host * host = buf_id ;
unsigned long iflags ;
u32 mci_csta , mci_dsta , mci_fsta , mci_dcnt ;
mci_csta = readl ( host - > base + S3C2410_SDICMDSTAT ) ;
mci_dsta = readl ( host - > base + S3C2410_SDIDSTA ) ;
mci_fsta = readl ( host - > base + S3C2410_SDIFSTA ) ;
mci_dcnt = readl ( host - > base + S3C2410_SDIDCNT ) ;
BUG_ON ( ! host - > mrq ) ;
BUG_ON ( ! host - > mrq - > data ) ;
BUG_ON ( ! host - > dmatogo ) ;
spin_lock_irqsave ( & host - > complete_lock , iflags ) ;
if ( result ! = S3C2410_RES_OK ) {
dbg ( host , dbg_fail , " DMA FAILED: csta=0x%08x dsta=0x%08x "
" fsta=0x%08x dcnt:0x%08x result:0x%08x toGo:%u \n " ,
mci_csta , mci_dsta , mci_fsta ,
mci_dcnt , result , host - > dmatogo ) ;
goto fail_request ;
}
host - > dmatogo - - ;
if ( host - > dmatogo ) {
dbg ( host , dbg_dma , " DMA DONE Size:%i DSTA:[%08x] "
" DCNT:[%08x] toGo:%u \n " ,
size , mci_dsta , mci_dcnt , host - > dmatogo ) ;
goto out ;
}
dbg ( host , dbg_dma , " DMA FINISHED Size:%i DSTA:%08x DCNT:%08x \n " ,
size , mci_dsta , mci_dcnt ) ;
2009-10-02 02:44:19 +04:00
host - > dma_complete = 1 ;
2008-07-01 01:40:24 +04:00
host - > complete_what = COMPLETION_FINALIZE ;
out :
tasklet_schedule ( & host - > pio_tasklet ) ;
spin_unlock_irqrestore ( & host - > complete_lock , iflags ) ;
return ;
fail_request :
host - > mrq - > data - > error = - EINVAL ;
host - > complete_what = COMPLETION_FINALIZE ;
2009-10-02 02:44:16 +04:00
clear_imask ( host ) ;
2008-07-01 01:40:24 +04:00
2009-10-02 02:44:16 +04:00
goto out ;
2008-07-01 01:40:24 +04:00
}
static void finalize_request ( struct s3cmci_host * host )
{
struct mmc_request * mrq = host - > mrq ;
2009-12-15 05:01:20 +03:00
struct mmc_command * cmd ;
2008-07-01 01:40:24 +04:00
int debug_as_failure = 0 ;
if ( host - > complete_what ! = COMPLETION_FINALIZE )
return ;
if ( ! mrq )
return ;
2009-12-15 05:01:20 +03:00
cmd = host - > cmd_is_stop ? mrq - > stop : mrq - > cmd ;
2008-07-01 01:40:24 +04:00
if ( cmd - > data & & ( cmd - > error = = 0 ) & &
( cmd - > data - > error = = 0 ) ) {
2009-10-02 02:44:18 +04:00
if ( s3cmci_host_usedma ( host ) & & ( ! host - > dma_complete ) ) {
2009-10-02 02:44:19 +04:00
dbg ( host , dbg_dma , " DMA Missing (%d)! \n " ,
host - > dma_complete ) ;
2008-07-01 01:40:24 +04:00
return ;
}
}
/* Read response from controller. */
cmd - > resp [ 0 ] = readl ( host - > base + S3C2410_SDIRSP0 ) ;
cmd - > resp [ 1 ] = readl ( host - > base + S3C2410_SDIRSP1 ) ;
cmd - > resp [ 2 ] = readl ( host - > base + S3C2410_SDIRSP2 ) ;
cmd - > resp [ 3 ] = readl ( host - > base + S3C2410_SDIRSP3 ) ;
writel ( host - > prescaler , host - > base + S3C2410_SDIPRE ) ;
if ( cmd - > error )
debug_as_failure = 1 ;
if ( cmd - > data & & cmd - > data - > error )
debug_as_failure = 1 ;
dbg_dumpcmd ( host , cmd , debug_as_failure ) ;
/* Cleanup controller */
writel ( 0 , host - > base + S3C2410_SDICMDARG ) ;
2008-07-01 01:40:27 +04:00
writel ( S3C2410_SDIDCON_STOP , host - > base + S3C2410_SDIDCON ) ;
2008-07-01 01:40:24 +04:00
writel ( 0 , host - > base + S3C2410_SDICMDCON ) ;
2009-10-02 02:44:16 +04:00
clear_imask ( host ) ;
2008-07-01 01:40:24 +04:00
if ( cmd - > data & & cmd - > error )
cmd - > data - > error = cmd - > error ;
if ( cmd - > data & & cmd - > data - > stop & & ( ! host - > cmd_is_stop ) ) {
host - > cmd_is_stop = 1 ;
s3cmci_send_request ( host - > mmc ) ;
return ;
}
/* If we have no data transfer we are finished here */
if ( ! mrq - > data )
goto request_done ;
2011-03-31 05:57:33 +04:00
/* Calculate the amout of bytes transfer if there was no error */
2008-07-01 01:40:24 +04:00
if ( mrq - > data - > error = = 0 ) {
mrq - > data - > bytes_xfered =
( mrq - > data - > blocks * mrq - > data - > blksz ) ;
} else {
mrq - > data - > bytes_xfered = 0 ;
}
2011-03-31 05:57:33 +04:00
/* If we had an error while transferring data we flush the
2008-07-01 01:40:24 +04:00
* DMA channel and the fifo to clear out any garbage . */
if ( mrq - > data - > error ! = 0 ) {
2009-10-02 02:44:18 +04:00
if ( s3cmci_host_usedma ( host ) )
2008-07-01 01:40:24 +04:00
s3c2410_dma_ctrl ( host - > dma , S3C2410_DMAOP_FLUSH ) ;
if ( host - > is2440 ) {
/* Clear failure register and reset fifo. */
writel ( S3C2440_SDIFSTA_FIFORESET |
S3C2440_SDIFSTA_FIFOFAIL ,
host - > base + S3C2410_SDIFSTA ) ;
} else {
u32 mci_con ;
/* reset fifo */
mci_con = readl ( host - > base + S3C2410_SDICON ) ;
mci_con | = S3C2410_SDICON_FIFORESET ;
writel ( mci_con , host - > base + S3C2410_SDICON ) ;
}
}
request_done :
host - > complete_what = COMPLETION_NONE ;
host - > mrq = NULL ;
2009-10-02 02:44:18 +04:00
s3cmci_check_sdio_irq ( host ) ;
2008-07-01 01:40:24 +04:00
mmc_request_done ( host - > mmc , mrq ) ;
}
2008-08-08 13:55:41 +04:00
static void s3cmci_dma_setup ( struct s3cmci_host * host ,
2011-09-02 04:44:44 +04:00
enum dma_data_direction source )
2008-07-01 01:40:24 +04:00
{
2011-09-02 04:44:44 +04:00
static enum dma_data_direction last_source = - 1 ;
2008-07-01 01:40:24 +04:00
static int setup_ok ;
if ( last_source = = source )
return ;
last_source = source ;
2009-03-19 18:02:34 +03:00
s3c2410_dma_devconfig ( host - > dma , source ,
2008-07-01 01:40:24 +04:00
host - > mem - > start + host - > sdidata ) ;
if ( ! setup_ok ) {
2009-06-09 02:33:56 +04:00
s3c2410_dma_config ( host - > dma , 4 ) ;
2008-07-01 01:40:24 +04:00
s3c2410_dma_set_buffdone_fn ( host - > dma ,
s3cmci_dma_done_callback ) ;
s3c2410_dma_setflags ( host - > dma , S3C2410_DMAF_AUTOSTART ) ;
setup_ok = 1 ;
}
}
static void s3cmci_send_command ( struct s3cmci_host * host ,
struct mmc_command * cmd )
{
u32 ccon , imsk ;
imsk = S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_CMDTIMEOUT |
S3C2410_SDIIMSK_RESPONSEND | S3C2410_SDIIMSK_CMDSENT |
S3C2410_SDIIMSK_RESPONSECRC ;
enable_imask ( host , imsk ) ;
if ( cmd - > data )
host - > complete_what = COMPLETION_XFERFINISH_RSPFIN ;
else if ( cmd - > flags & MMC_RSP_PRESENT )
host - > complete_what = COMPLETION_RSPFIN ;
else
host - > complete_what = COMPLETION_CMDSENT ;
writel ( cmd - > arg , host - > base + S3C2410_SDICMDARG ) ;
ccon = cmd - > opcode & S3C2410_SDICMDCON_INDEX ;
ccon | = S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART ;
if ( cmd - > flags & MMC_RSP_PRESENT )
ccon | = S3C2410_SDICMDCON_WAITRSP ;
if ( cmd - > flags & MMC_RSP_136 )
ccon | = S3C2410_SDICMDCON_LONGRSP ;
writel ( ccon , host - > base + S3C2410_SDICMDCON ) ;
}
static int s3cmci_setup_data ( struct s3cmci_host * host , struct mmc_data * data )
{
u32 dcon , imsk , stoptries = 3 ;
/* write DCON register */
if ( ! data ) {
writel ( 0 , host - > base + S3C2410_SDIDCON ) ;
return 0 ;
}
2008-07-01 01:40:39 +04:00
if ( ( data - > blksz & 3 ) ! = 0 ) {
/* We cannot deal with unaligned blocks with more than
2011-03-31 05:57:33 +04:00
* one block being transferred . */
2008-07-01 01:40:39 +04:00
2008-10-15 03:17:17 +04:00
if ( data - > blocks > 1 ) {
pr_warning ( " %s: can't do non-word sized block transfers (blksz %d) \n " , __func__ , data - > blksz ) ;
2008-07-01 01:40:39 +04:00
return - EINVAL ;
2008-10-15 03:17:17 +04:00
}
2008-07-01 01:40:39 +04:00
}
2008-07-01 01:40:24 +04:00
while ( readl ( host - > base + S3C2410_SDIDSTA ) &
( S3C2410_SDIDSTA_TXDATAON | S3C2410_SDIDSTA_RXDATAON ) ) {
dbg ( host , dbg_err ,
" mci_setup_data() transfer stillin progress. \n " ) ;
2008-07-01 01:40:27 +04:00
writel ( S3C2410_SDIDCON_STOP , host - > base + S3C2410_SDIDCON ) ;
2008-07-01 01:40:24 +04:00
s3cmci_reset ( host ) ;
if ( ( stoptries - - ) = = 0 ) {
dbg_dumpregs ( host , " DRF " ) ;
return - EINVAL ;
}
}
dcon = data - > blocks & S3C2410_SDIDCON_BLKNUM_MASK ;
2009-10-02 02:44:18 +04:00
if ( s3cmci_host_usedma ( host ) )
2008-07-01 01:40:24 +04:00
dcon | = S3C2410_SDIDCON_DMAEN ;
if ( host - > bus_width = = MMC_BUS_WIDTH_4 )
dcon | = S3C2410_SDIDCON_WIDEBUS ;
if ( ! ( data - > flags & MMC_DATA_STREAM ) )
dcon | = S3C2410_SDIDCON_BLOCKMODE ;
if ( data - > flags & MMC_DATA_WRITE ) {
dcon | = S3C2410_SDIDCON_TXAFTERRESP ;
dcon | = S3C2410_SDIDCON_XFER_TXSTART ;
}
if ( data - > flags & MMC_DATA_READ ) {
dcon | = S3C2410_SDIDCON_RXAFTERCMD ;
dcon | = S3C2410_SDIDCON_XFER_RXSTART ;
}
if ( host - > is2440 ) {
dcon | = S3C2440_SDIDCON_DS_WORD ;
dcon | = S3C2440_SDIDCON_DATSTART ;
}
writel ( dcon , host - > base + S3C2410_SDIDCON ) ;
/* write BSIZE register */
writel ( data - > blksz , host - > base + S3C2410_SDIBSIZE ) ;
/* add to IMASK register */
imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH ;
enable_imask ( host , imsk ) ;
/* write TIMER register */
if ( host - > is2440 ) {
writel ( 0x007FFFFF , host - > base + S3C2410_SDITIMER ) ;
} else {
writel ( 0x0000FFFF , host - > base + S3C2410_SDITIMER ) ;
/* FIX: set slow clock to prevent timeouts on read */
if ( data - > flags & MMC_DATA_READ )
writel ( 0xFF , host - > base + S3C2410_SDIPRE ) ;
}
return 0 ;
}
# define BOTH_DIR (MMC_DATA_WRITE | MMC_DATA_READ)
static int s3cmci_prepare_pio ( struct s3cmci_host * host , struct mmc_data * data )
{
int rw = ( data - > flags & MMC_DATA_WRITE ) ? 1 : 0 ;
BUG_ON ( ( data - > flags & BOTH_DIR ) = = BOTH_DIR ) ;
host - > pio_sgptr = 0 ;
2008-10-15 03:17:17 +04:00
host - > pio_bytes = 0 ;
2008-07-01 01:40:24 +04:00
host - > pio_count = 0 ;
host - > pio_active = rw ? XFER_WRITE : XFER_READ ;
if ( rw ) {
do_pio_write ( host ) ;
enable_imask ( host , S3C2410_SDIIMSK_TXFIFOHALF ) ;
} else {
enable_imask ( host , S3C2410_SDIIMSK_RXFIFOHALF
| S3C2410_SDIIMSK_RXFIFOLAST ) ;
}
return 0 ;
}
static int s3cmci_prepare_dma ( struct s3cmci_host * host , struct mmc_data * data )
{
int dma_len , i ;
2009-10-02 02:44:19 +04:00
int rw = data - > flags & MMC_DATA_WRITE ;
2008-07-01 01:40:24 +04:00
BUG_ON ( ( data - > flags & BOTH_DIR ) = = BOTH_DIR ) ;
2011-09-02 04:44:44 +04:00
s3cmci_dma_setup ( host , rw ? DMA_TO_DEVICE : DMA_FROM_DEVICE ) ;
2008-07-01 01:40:24 +04:00
s3c2410_dma_ctrl ( host - > dma , S3C2410_DMAOP_FLUSH ) ;
dma_len = dma_map_sg ( mmc_dev ( host - > mmc ) , data - > sg , data - > sg_len ,
2009-10-02 02:44:19 +04:00
rw ? DMA_TO_DEVICE : DMA_FROM_DEVICE ) ;
2008-07-01 01:40:24 +04:00
if ( dma_len = = 0 )
return - ENOMEM ;
host - > dma_complete = 0 ;
host - > dmatogo = dma_len ;
for ( i = 0 ; i < dma_len ; i + + ) {
int res ;
2009-10-02 02:44:19 +04:00
dbg ( host , dbg_dma , " enqueue %i: %08x@%u \n " , i ,
sg_dma_address ( & data - > sg [ i ] ) ,
sg_dma_len ( & data - > sg [ i ] ) ) ;
2008-07-01 01:40:24 +04:00
2009-10-02 02:44:19 +04:00
res = s3c2410_dma_enqueue ( host - > dma , host ,
2008-07-01 01:40:24 +04:00
sg_dma_address ( & data - > sg [ i ] ) ,
sg_dma_len ( & data - > sg [ i ] ) ) ;
if ( res ) {
s3c2410_dma_ctrl ( host - > dma , S3C2410_DMAOP_FLUSH ) ;
return - EBUSY ;
}
}
s3c2410_dma_ctrl ( host - > dma , S3C2410_DMAOP_START ) ;
return 0 ;
}
static void s3cmci_send_request ( struct mmc_host * mmc )
{
struct s3cmci_host * host = mmc_priv ( mmc ) ;
struct mmc_request * mrq = host - > mrq ;
struct mmc_command * cmd = host - > cmd_is_stop ? mrq - > stop : mrq - > cmd ;
host - > ccnt + + ;
prepare_dbgmsg ( host , cmd , host - > cmd_is_stop ) ;
/* Clear command, data and fifo status registers
Fifo clear only necessary on 2440 , but doesn ' t hurt on 2410
*/
writel ( 0xFFFFFFFF , host - > base + S3C2410_SDICMDSTAT ) ;
writel ( 0xFFFFFFFF , host - > base + S3C2410_SDIDSTA ) ;
writel ( 0xFFFFFFFF , host - > base + S3C2410_SDIFSTA ) ;
if ( cmd - > data ) {
int res = s3cmci_setup_data ( host , cmd - > data ) ;
host - > dcnt + + ;
if ( res ) {
2008-07-01 01:40:37 +04:00
dbg ( host , dbg_err , " setup data error %d \n " , res ) ;
cmd - > error = res ;
cmd - > data - > error = res ;
2008-07-01 01:40:24 +04:00
mmc_request_done ( mmc , mrq ) ;
return ;
}
2009-10-02 02:44:18 +04:00
if ( s3cmci_host_usedma ( host ) )
2008-07-01 01:40:24 +04:00
res = s3cmci_prepare_dma ( host , cmd - > data ) ;
else
res = s3cmci_prepare_pio ( host , cmd - > data ) ;
if ( res ) {
2008-07-01 01:40:37 +04:00
dbg ( host , dbg_err , " data prepare error %d \n " , res ) ;
2008-07-01 01:40:24 +04:00
cmd - > error = res ;
cmd - > data - > error = res ;
mmc_request_done ( mmc , mrq ) ;
return ;
}
}
/* Send command */
s3cmci_send_command ( host , cmd ) ;
/* Enable Interrupt */
2009-10-02 02:44:18 +04:00
s3cmci_enable_irq ( host , true ) ;
2008-07-01 01:40:24 +04:00
}
2008-08-12 12:24:50 +04:00
static int s3cmci_card_present ( struct mmc_host * mmc )
2008-07-01 01:40:36 +04:00
{
2008-08-12 12:24:50 +04:00
struct s3cmci_host * host = mmc_priv ( mmc ) ;
2008-07-01 01:40:36 +04:00
struct s3c24xx_mci_pdata * pdata = host - > pdata ;
int ret ;
2010-03-06 00:43:37 +03:00
if ( pdata - > no_detect )
2008-07-01 01:40:36 +04:00
return - ENOSYS ;
2009-10-02 02:44:15 +04:00
ret = gpio_get_value ( pdata - > gpio_detect ) ? 0 : 1 ;
2008-07-01 01:40:36 +04:00
return ret ^ pdata - > detect_invert ;
}
2008-07-01 01:40:24 +04:00
static void s3cmci_request ( struct mmc_host * mmc , struct mmc_request * mrq )
{
struct s3cmci_host * host = mmc_priv ( mmc ) ;
host - > status = " mmc request " ;
host - > cmd_is_stop = 0 ;
host - > mrq = mrq ;
2008-08-12 12:24:50 +04:00
if ( s3cmci_card_present ( mmc ) = = 0 ) {
2008-07-01 01:40:36 +04:00
dbg ( host , dbg_err , " %s: no medium present \n " , __func__ ) ;
host - > mrq - > cmd - > error = - ENOMEDIUM ;
mmc_request_done ( mmc , mrq ) ;
} else
s3cmci_send_request ( mmc ) ;
2008-07-01 01:40:24 +04:00
}
2008-10-15 03:17:16 +04:00
static void s3cmci_set_clk ( struct s3cmci_host * host , struct mmc_ios * ios )
{
u32 mci_psc ;
/* Set clock */
for ( mci_psc = 0 ; mci_psc < 255 ; mci_psc + + ) {
host - > real_rate = host - > clk_rate / ( host - > clk_div * ( mci_psc + 1 ) ) ;
if ( host - > real_rate < = ios - > clock )
break ;
}
if ( mci_psc > 255 )
mci_psc = 255 ;
host - > prescaler = mci_psc ;
writel ( host - > prescaler , host - > base + S3C2410_SDIPRE ) ;
/* If requested clock is 0, real_rate will be 0, too */
if ( ios - > clock = = 0 )
host - > real_rate = 0 ;
}
2008-07-01 01:40:24 +04:00
static void s3cmci_set_ios ( struct mmc_host * mmc , struct mmc_ios * ios )
{
struct s3cmci_host * host = mmc_priv ( mmc ) ;
2008-10-15 03:17:16 +04:00
u32 mci_con ;
2008-07-01 01:40:24 +04:00
/* Set the power state */
mci_con = readl ( host - > base + S3C2410_SDICON ) ;
switch ( ios - > power_mode ) {
case MMC_POWER_ON :
case MMC_POWER_UP :
2012-06-30 16:24:22 +04:00
/* Configure GPE5...GPE10 pins in SD mode */
s3c_gpio_cfgall_range ( S3C2410_GPE ( 5 ) , 6 , S3C_GPIO_SFN ( 2 ) ,
S3C_GPIO_PULL_NONE ) ;
2008-07-01 01:40:24 +04:00
2008-07-01 01:40:29 +04:00
if ( host - > pdata - > set_power )
host - > pdata - > set_power ( ios - > power_mode , ios - > vdd ) ;
2008-07-01 01:40:24 +04:00
if ( ! host - > is2440 )
mci_con | = S3C2410_SDICON_FIFORESET ;
break ;
case MMC_POWER_OFF :
default :
2009-10-02 02:44:15 +04:00
gpio_direction_output ( S3C2410_GPE ( 5 ) , 0 ) ;
2008-07-01 01:40:24 +04:00
if ( host - > is2440 )
mci_con | = S3C2440_SDICON_SDRESET ;
2008-07-01 01:40:29 +04:00
if ( host - > pdata - > set_power )
host - > pdata - > set_power ( ios - > power_mode , ios - > vdd ) ;
2008-07-01 01:40:24 +04:00
break ;
}
2008-10-15 03:17:16 +04:00
s3cmci_set_clk ( host , ios ) ;
2008-07-01 01:40:24 +04:00
/* Set CLOCK_ENABLE */
if ( ios - > clock )
mci_con | = S3C2410_SDICON_CLOCKTYPE ;
else
mci_con & = ~ S3C2410_SDICON_CLOCKTYPE ;
writel ( mci_con , host - > base + S3C2410_SDICON ) ;
if ( ( ios - > power_mode = = MMC_POWER_ON ) | |
( ios - > power_mode = = MMC_POWER_UP ) ) {
dbg ( host , dbg_conf , " running at %lukHz (requested: %ukHz). \n " ,
host - > real_rate / 1000 , ios - > clock / 1000 ) ;
} else {
dbg ( host , dbg_conf , " powered down. \n " ) ;
}
host - > bus_width = ios - > bus_width ;
}
static void s3cmci_reset ( struct s3cmci_host * host )
{
u32 con = readl ( host - > base + S3C2410_SDICON ) ;
con | = S3C2440_SDICON_SDRESET ;
writel ( con , host - > base + S3C2410_SDICON ) ;
}
2008-07-01 01:40:29 +04:00
static int s3cmci_get_ro ( struct mmc_host * mmc )
{
struct s3cmci_host * host = mmc_priv ( mmc ) ;
2008-07-01 01:40:30 +04:00
struct s3c24xx_mci_pdata * pdata = host - > pdata ;
int ret ;
2008-07-01 01:40:29 +04:00
2009-10-02 02:44:21 +04:00
if ( pdata - > no_wprotect )
2008-07-01 01:40:29 +04:00
return 0 ;
2009-12-15 05:01:27 +03:00
ret = gpio_get_value ( pdata - > gpio_wprotect ) ? 1 : 0 ;
ret ^ = pdata - > wprotect_invert ;
2008-07-01 01:40:30 +04:00
return ret ;
2008-07-01 01:40:29 +04:00
}
2009-10-02 02:44:18 +04:00
static void s3cmci_enable_sdio_irq ( struct mmc_host * mmc , int enable )
{
struct s3cmci_host * host = mmc_priv ( mmc ) ;
unsigned long flags ;
u32 con ;
local_irq_save ( flags ) ;
con = readl ( host - > base + S3C2410_SDICON ) ;
host - > sdio_irqen = enable ;
if ( enable = = host - > sdio_irqen )
goto same_state ;
if ( enable ) {
con | = S3C2410_SDICON_SDIOIRQ ;
enable_imask ( host , S3C2410_SDIIMSK_SDIOIRQ ) ;
if ( ! host - > irq_state & & ! host - > irq_disabled ) {
host - > irq_state = true ;
enable_irq ( host - > irq ) ;
}
} else {
disable_imask ( host , S3C2410_SDIIMSK_SDIOIRQ ) ;
con & = ~ S3C2410_SDICON_SDIOIRQ ;
if ( ! host - > irq_enabled & & host - > irq_state ) {
disable_irq_nosync ( host - > irq ) ;
host - > irq_state = false ;
}
}
writel ( con , host - > base + S3C2410_SDICON ) ;
same_state :
local_irq_restore ( flags ) ;
s3cmci_check_sdio_irq ( host ) ;
}
2008-07-01 01:40:24 +04:00
static struct mmc_host_ops s3cmci_ops = {
. request = s3cmci_request ,
. set_ios = s3cmci_set_ios ,
2008-07-01 01:40:29 +04:00
. get_ro = s3cmci_get_ro ,
2008-08-12 12:24:50 +04:00
. get_cd = s3cmci_card_present ,
2009-10-02 02:44:18 +04:00
. enable_sdio_irq = s3cmci_enable_sdio_irq ,
2008-07-01 01:40:29 +04:00
} ;
static struct s3c24xx_mci_pdata s3cmci_def_pdata = {
/* This is currently here to avoid a number of if (host->pdata)
tree-wide: fix assorted typos all over the place
That is "success", "unknown", "through", "performance", "[re|un]mapping"
, "access", "default", "reasonable", "[con]currently", "temperature"
, "channel", "[un]used", "application", "example","hierarchy", "therefore"
, "[over|under]flow", "contiguous", "threshold", "enough" and others.
Signed-off-by: André Goddard Rosa <andre.goddard@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2009-11-14 18:09:05 +03:00
* checks . Any zero fields to ensure reasonable defaults are picked . */
2010-03-06 00:43:35 +03:00
. no_wprotect = 1 ,
. no_detect = 1 ,
2008-07-01 01:40:24 +04:00
} ;
2008-10-15 03:17:16 +04:00
# ifdef CONFIG_CPU_FREQ
static int s3cmci_cpufreq_transition ( struct notifier_block * nb ,
unsigned long val , void * data )
{
struct s3cmci_host * host ;
struct mmc_host * mmc ;
unsigned long newclk ;
unsigned long flags ;
host = container_of ( nb , struct s3cmci_host , freq_transition ) ;
newclk = clk_get_rate ( host - > clk ) ;
mmc = host - > mmc ;
if ( ( val = = CPUFREQ_PRECHANGE & & newclk > host - > clk_rate ) | |
( val = = CPUFREQ_POSTCHANGE & & newclk < host - > clk_rate ) ) {
spin_lock_irqsave ( & mmc - > lock , flags ) ;
host - > clk_rate = newclk ;
if ( mmc - > ios . power_mode ! = MMC_POWER_OFF & &
mmc - > ios . clock ! = 0 )
s3cmci_set_clk ( host , & mmc - > ios ) ;
spin_unlock_irqrestore ( & mmc - > lock , flags ) ;
}
return 0 ;
}
static inline int s3cmci_cpufreq_register ( struct s3cmci_host * host )
{
host - > freq_transition . notifier_call = s3cmci_cpufreq_transition ;
return cpufreq_register_notifier ( & host - > freq_transition ,
CPUFREQ_TRANSITION_NOTIFIER ) ;
}
static inline void s3cmci_cpufreq_deregister ( struct s3cmci_host * host )
{
cpufreq_unregister_notifier ( & host - > freq_transition ,
CPUFREQ_TRANSITION_NOTIFIER ) ;
}
# else
static inline int s3cmci_cpufreq_register ( struct s3cmci_host * host )
{
return 0 ;
}
static inline void s3cmci_cpufreq_deregister ( struct s3cmci_host * host )
{
}
# endif
2009-10-02 02:44:15 +04:00
2009-10-02 02:44:17 +04:00
# ifdef CONFIG_DEBUG_FS
static int s3cmci_state_show ( struct seq_file * seq , void * v )
{
struct s3cmci_host * host = seq - > private ;
seq_printf ( seq , " Register base = 0x%08x \n " , ( u32 ) host - > base ) ;
seq_printf ( seq , " Clock rate = %ld \n " , host - > clk_rate ) ;
seq_printf ( seq , " Prescale = %d \n " , host - > prescaler ) ;
seq_printf ( seq , " is2440 = %d \n " , host - > is2440 ) ;
seq_printf ( seq , " IRQ = %d \n " , host - > irq ) ;
2009-10-02 02:44:18 +04:00
seq_printf ( seq , " IRQ enabled = %d \n " , host - > irq_enabled ) ;
seq_printf ( seq , " IRQ disabled = %d \n " , host - > irq_disabled ) ;
seq_printf ( seq , " IRQ state = %d \n " , host - > irq_state ) ;
2009-10-02 02:44:17 +04:00
seq_printf ( seq , " CD IRQ = %d \n " , host - > irq_cd ) ;
2009-10-02 02:44:18 +04:00
seq_printf ( seq , " Do DMA = %d \n " , s3cmci_host_usedma ( host ) ) ;
2009-10-02 02:44:17 +04:00
seq_printf ( seq , " SDIIMSK at %d \n " , host - > sdiimsk ) ;
seq_printf ( seq , " SDIDATA at %d \n " , host - > sdidata ) ;
return 0 ;
}
static int s3cmci_state_open ( struct inode * inode , struct file * file )
{
return single_open ( file , s3cmci_state_show , inode - > i_private ) ;
}
static const struct file_operations s3cmci_fops_state = {
. owner = THIS_MODULE ,
. open = s3cmci_state_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release ,
} ;
# define DBG_REG(_r) { .addr = S3C2410_SDI##_r, .name = #_r }
struct s3cmci_reg {
unsigned short addr ;
unsigned char * name ;
} debug_regs [ ] = {
DBG_REG ( CON ) ,
DBG_REG ( PRE ) ,
DBG_REG ( CMDARG ) ,
DBG_REG ( CMDCON ) ,
DBG_REG ( CMDSTAT ) ,
DBG_REG ( RSP0 ) ,
DBG_REG ( RSP1 ) ,
DBG_REG ( RSP2 ) ,
DBG_REG ( RSP3 ) ,
DBG_REG ( TIMER ) ,
DBG_REG ( BSIZE ) ,
DBG_REG ( DCON ) ,
DBG_REG ( DCNT ) ,
DBG_REG ( DSTA ) ,
DBG_REG ( FSTA ) ,
{ }
} ;
static int s3cmci_regs_show ( struct seq_file * seq , void * v )
{
struct s3cmci_host * host = seq - > private ;
struct s3cmci_reg * rptr = debug_regs ;
for ( ; rptr - > name ; rptr + + )
seq_printf ( seq , " SDI%s \t =0x%08x \n " , rptr - > name ,
readl ( host - > base + rptr - > addr ) ) ;
seq_printf ( seq , " SDIIMSK \t =0x%08x \n " , readl ( host - > base + host - > sdiimsk ) ) ;
return 0 ;
}
static int s3cmci_regs_open ( struct inode * inode , struct file * file )
{
return single_open ( file , s3cmci_regs_show , inode - > i_private ) ;
}
static const struct file_operations s3cmci_fops_regs = {
. owner = THIS_MODULE ,
. open = s3cmci_regs_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release ,
} ;
static void s3cmci_debugfs_attach ( struct s3cmci_host * host )
{
struct device * dev = & host - > pdev - > dev ;
host - > debug_root = debugfs_create_dir ( dev_name ( dev ) , NULL ) ;
if ( IS_ERR ( host - > debug_root ) ) {
dev_err ( dev , " failed to create debugfs root \n " ) ;
return ;
}
host - > debug_state = debugfs_create_file ( " state " , 0444 ,
host - > debug_root , host ,
& s3cmci_fops_state ) ;
if ( IS_ERR ( host - > debug_state ) )
dev_err ( dev , " failed to create debug state file \n " ) ;
host - > debug_regs = debugfs_create_file ( " regs " , 0444 ,
host - > debug_root , host ,
& s3cmci_fops_regs ) ;
if ( IS_ERR ( host - > debug_regs ) )
dev_err ( dev , " failed to create debug regs file \n " ) ;
}
static void s3cmci_debugfs_remove ( struct s3cmci_host * host )
{
debugfs_remove ( host - > debug_regs ) ;
debugfs_remove ( host - > debug_state ) ;
debugfs_remove ( host - > debug_root ) ;
}
# else
static inline void s3cmci_debugfs_attach ( struct s3cmci_host * host ) { }
static inline void s3cmci_debugfs_remove ( struct s3cmci_host * host ) { }
# endif /* CONFIG_DEBUG_FS */
2012-11-19 22:23:06 +04:00
static int s3cmci_probe ( struct platform_device * pdev )
2008-07-01 01:40:24 +04:00
{
struct s3cmci_host * host ;
struct mmc_host * mmc ;
int ret ;
2009-10-02 02:44:14 +04:00
int is2440 ;
2009-10-02 02:44:15 +04:00
int i ;
2009-10-02 02:44:14 +04:00
is2440 = platform_get_device_id ( pdev ) - > driver_data ;
2008-07-01 01:40:24 +04:00
mmc = mmc_alloc_host ( sizeof ( struct s3cmci_host ) , & pdev - > dev ) ;
if ( ! mmc ) {
ret = - ENOMEM ;
goto probe_out ;
}
2009-10-02 02:44:15 +04:00
for ( i = S3C2410_GPE ( 5 ) ; i < = S3C2410_GPE ( 10 ) ; i + + ) {
ret = gpio_request ( i , dev_name ( & pdev - > dev ) ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to get gpio %d \n " , i ) ;
for ( i - - ; i > = S3C2410_GPE ( 5 ) ; i - - )
gpio_free ( i ) ;
goto probe_free_host ;
}
}
2008-07-01 01:40:24 +04:00
host = mmc_priv ( mmc ) ;
host - > mmc = mmc ;
host - > pdev = pdev ;
host - > is2440 = is2440 ;
2008-07-01 01:40:29 +04:00
host - > pdata = pdev - > dev . platform_data ;
if ( ! host - > pdata ) {
pdev - > dev . platform_data = & s3cmci_def_pdata ;
host - > pdata = & s3cmci_def_pdata ;
}
2008-07-01 01:40:24 +04:00
spin_lock_init ( & host - > complete_lock ) ;
tasklet_init ( & host - > pio_tasklet , pio_tasklet , ( unsigned long ) host ) ;
if ( is2440 ) {
host - > sdiimsk = S3C2440_SDIIMSK ;
host - > sdidata = S3C2440_SDIDATA ;
host - > clk_div = 1 ;
} else {
host - > sdiimsk = S3C2410_SDIIMSK ;
host - > sdidata = S3C2410_SDIDATA ;
host - > clk_div = 2 ;
}
host - > complete_what = COMPLETION_NONE ;
host - > pio_active = XFER_NONE ;
2009-10-02 02:44:18 +04:00
# ifdef CONFIG_MMC_S3C_PIODMA
2010-05-25 11:48:58 +04:00
host - > dodma = host - > pdata - > use_dma ;
2009-10-02 02:44:18 +04:00
# endif
2008-07-01 01:40:24 +04:00
host - > mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! host - > mem ) {
dev_err ( & pdev - > dev ,
2012-01-29 16:58:57 +04:00
" failed to get io memory region resource. \n " ) ;
2008-07-01 01:40:24 +04:00
ret = - ENOENT ;
2009-10-02 02:44:15 +04:00
goto probe_free_gpio ;
2008-07-01 01:40:24 +04:00
}
host - > mem = request_mem_region ( host - > mem - > start ,
2009-10-02 02:44:13 +04:00
resource_size ( host - > mem ) , pdev - > name ) ;
2008-07-01 01:40:24 +04:00
if ( ! host - > mem ) {
dev_err ( & pdev - > dev , " failed to request io memory region. \n " ) ;
ret = - ENOENT ;
2009-10-02 02:44:15 +04:00
goto probe_free_gpio ;
2008-07-01 01:40:24 +04:00
}
2009-10-02 02:44:13 +04:00
host - > base = ioremap ( host - > mem - > start , resource_size ( host - > mem ) ) ;
2008-08-08 13:55:41 +04:00
if ( ! host - > base ) {
2008-07-01 01:40:24 +04:00
dev_err ( & pdev - > dev , " failed to ioremap() io memory region. \n " ) ;
ret = - EINVAL ;
goto probe_free_mem_region ;
}
host - > irq = platform_get_irq ( pdev , 0 ) ;
if ( host - > irq = = 0 ) {
2012-01-29 16:58:57 +04:00
dev_err ( & pdev - > dev , " failed to get interrupt resource. \n " ) ;
2008-07-01 01:40:24 +04:00
ret = - EINVAL ;
goto probe_iounmap ;
}
if ( request_irq ( host - > irq , s3cmci_irq , 0 , DRIVER_NAME , host ) ) {
dev_err ( & pdev - > dev , " failed to request mci interrupt. \n " ) ;
ret = - ENOENT ;
goto probe_iounmap ;
}
/* We get spurious interrupts even when we have set the IMSK
* register to ignore everything , so use disable_irq ( ) to make
* ensure we don ' t lock the system with un - serviceable requests . */
disable_irq ( host - > irq ) ;
2009-10-02 02:44:18 +04:00
host - > irq_state = false ;
2008-07-01 01:40:24 +04:00
2009-10-02 02:44:21 +04:00
if ( ! host - > pdata - > no_detect ) {
2009-10-02 02:44:15 +04:00
ret = gpio_request ( host - > pdata - > gpio_detect , " s3cmci detect " ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to get detect gpio \n " ) ;
goto probe_free_irq ;
}
2008-07-01 01:40:24 +04:00
2009-12-15 05:01:27 +03:00
host - > irq_cd = gpio_to_irq ( host - > pdata - > gpio_detect ) ;
2009-10-02 02:44:21 +04:00
if ( host - > irq_cd > = 0 ) {
if ( request_irq ( host - > irq_cd , s3cmci_irq_cd ,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING ,
DRIVER_NAME , host ) ) {
dev_err ( & pdev - > dev ,
" can't get card detect irq. \n " ) ;
ret = - ENOENT ;
goto probe_free_gpio_cd ;
}
} else {
dev_warn ( & pdev - > dev ,
" host detect has no irq available \n " ) ;
gpio_direction_input ( host - > pdata - > gpio_detect ) ;
2008-07-01 01:40:32 +04:00
}
2009-10-02 02:44:21 +04:00
} else
host - > irq_cd = - 1 ;
2008-07-01 01:40:24 +04:00
2009-10-02 02:44:21 +04:00
if ( ! host - > pdata - > no_wprotect ) {
2009-10-02 02:44:15 +04:00
ret = gpio_request ( host - > pdata - > gpio_wprotect , " s3cmci wp " ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to get writeprotect \n " ) ;
goto probe_free_irq_cd ;
}
gpio_direction_input ( host - > pdata - > gpio_wprotect ) ;
}
2008-07-01 01:40:29 +04:00
2009-10-02 02:44:19 +04:00
/* depending on the dma state, get a dma channel to use. */
if ( s3cmci_host_usedma ( host ) ) {
host - > dma = s3c2410_dma_request ( DMACH_SDI , & s3cmci_dma_client ,
host ) ;
if ( host - > dma < 0 ) {
dev_err ( & pdev - > dev , " cannot get DMA channel. \n " ) ;
if ( ! s3cmci_host_canpio ( ) ) {
ret = - EBUSY ;
goto probe_free_gpio_wp ;
} else {
dev_warn ( & pdev - > dev , " falling back to PIO. \n " ) ;
host - > dodma = 0 ;
}
}
2008-07-01 01:40:24 +04:00
}
host - > clk = clk_get ( & pdev - > dev , " sdi " ) ;
if ( IS_ERR ( host - > clk ) ) {
dev_err ( & pdev - > dev , " failed to find clock source. \n " ) ;
ret = PTR_ERR ( host - > clk ) ;
host - > clk = NULL ;
2009-10-02 02:44:19 +04:00
goto probe_free_dma ;
2008-07-01 01:40:24 +04:00
}
ret = clk_enable ( host - > clk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to enable clock source. \n " ) ;
goto clk_free ;
}
host - > clk_rate = clk_get_rate ( host - > clk ) ;
mmc - > ops = & s3cmci_ops ;
2008-07-01 01:40:29 +04:00
mmc - > ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34 ;
2009-10-02 02:44:20 +04:00
# ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
2009-10-02 02:44:18 +04:00
mmc - > caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ ;
2009-10-02 02:44:20 +04:00
# else
mmc - > caps = MMC_CAP_4_BIT_DATA ;
# endif
2008-07-01 01:40:24 +04:00
mmc - > f_min = host - > clk_rate / ( host - > clk_div * 256 ) ;
mmc - > f_max = host - > clk_rate / host - > clk_div ;
2008-07-01 01:40:29 +04:00
if ( host - > pdata - > ocr_avail )
mmc - > ocr_avail = host - > pdata - > ocr_avail ;
2008-07-01 01:40:24 +04:00
mmc - > max_blk_count = 4095 ;
mmc - > max_blk_size = 4095 ;
mmc - > max_req_size = 4095 * 512 ;
mmc - > max_seg_size = mmc - > max_req_size ;
2010-09-10 09:33:59 +04:00
mmc - > max_segs = 128 ;
2008-07-01 01:40:24 +04:00
dbg ( host , dbg_debug ,
" probe: mode:%s mapped mci_base:%p irq:%u irq_cd:%u dma:%u. \n " ,
( host - > is2440 ? " 2440 " : " " ) ,
host - > base , host - > irq , host - > irq_cd , host - > dma ) ;
2008-10-15 03:17:16 +04:00
ret = s3cmci_cpufreq_register ( host ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to register cpufreq \n " ) ;
goto free_dmabuf ;
}
2008-07-01 01:40:24 +04:00
ret = mmc_add_host ( mmc ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to add mmc host. \n " ) ;
2008-10-15 03:17:16 +04:00
goto free_cpufreq ;
2008-07-01 01:40:24 +04:00
}
2009-10-02 02:44:17 +04:00
s3cmci_debugfs_attach ( host ) ;
2008-07-01 01:40:24 +04:00
platform_set_drvdata ( pdev , mmc ) ;
2009-10-02 02:44:20 +04:00
dev_info ( & pdev - > dev , " %s - using %s, %s SDIO IRQ \n " , mmc_hostname ( mmc ) ,
s3cmci_host_usedma ( host ) ? " dma " : " pio " ,
mmc - > caps & MMC_CAP_SDIO_IRQ ? " hw " : " sw " ) ;
2008-07-01 01:40:24 +04:00
return 0 ;
2008-10-15 03:17:16 +04:00
free_cpufreq :
s3cmci_cpufreq_deregister ( host ) ;
2008-07-01 01:40:24 +04:00
free_dmabuf :
clk_disable ( host - > clk ) ;
clk_free :
clk_put ( host - > clk ) ;
2009-10-02 02:44:19 +04:00
probe_free_dma :
if ( s3cmci_host_usedma ( host ) )
s3c2410_dma_free ( host - > dma , & s3cmci_dma_client ) ;
2009-10-02 02:44:15 +04:00
probe_free_gpio_wp :
2009-10-02 02:44:21 +04:00
if ( ! host - > pdata - > no_wprotect )
2009-10-02 02:44:15 +04:00
gpio_free ( host - > pdata - > gpio_wprotect ) ;
probe_free_gpio_cd :
2009-10-02 02:44:21 +04:00
if ( ! host - > pdata - > no_detect )
2009-10-02 02:44:15 +04:00
gpio_free ( host - > pdata - > gpio_detect ) ;
2008-07-01 01:40:24 +04:00
probe_free_irq_cd :
2008-07-01 01:40:32 +04:00
if ( host - > irq_cd > = 0 )
free_irq ( host - > irq_cd , host ) ;
2008-07-01 01:40:24 +04:00
probe_free_irq :
free_irq ( host - > irq , host ) ;
probe_iounmap :
iounmap ( host - > base ) ;
probe_free_mem_region :
2009-10-02 02:44:13 +04:00
release_mem_region ( host - > mem - > start , resource_size ( host - > mem ) ) ;
2008-07-01 01:40:24 +04:00
2009-10-02 02:44:15 +04:00
probe_free_gpio :
for ( i = S3C2410_GPE ( 5 ) ; i < = S3C2410_GPE ( 10 ) ; i + + )
gpio_free ( i ) ;
2008-07-01 01:40:24 +04:00
probe_free_host :
mmc_free_host ( mmc ) ;
2009-10-02 02:44:15 +04:00
2008-07-01 01:40:24 +04:00
probe_out :
return ret ;
}
2008-07-17 18:32:54 +04:00
static void s3cmci_shutdown ( struct platform_device * pdev )
{
struct mmc_host * mmc = platform_get_drvdata ( pdev ) ;
struct s3cmci_host * host = mmc_priv ( mmc ) ;
if ( host - > irq_cd > = 0 )
free_irq ( host - > irq_cd , host ) ;
2009-10-02 02:44:17 +04:00
s3cmci_debugfs_remove ( host ) ;
2008-10-15 03:17:16 +04:00
s3cmci_cpufreq_deregister ( host ) ;
2008-07-17 18:32:54 +04:00
mmc_remove_host ( mmc ) ;
clk_disable ( host - > clk ) ;
}
2012-11-19 22:26:03 +04:00
static int s3cmci_remove ( struct platform_device * pdev )
2008-07-01 01:40:24 +04:00
{
struct mmc_host * mmc = platform_get_drvdata ( pdev ) ;
struct s3cmci_host * host = mmc_priv ( mmc ) ;
2009-10-02 02:44:15 +04:00
struct s3c24xx_mci_pdata * pd = host - > pdata ;
int i ;
2008-07-01 01:40:24 +04:00
2008-07-17 18:32:54 +04:00
s3cmci_shutdown ( pdev ) ;
2008-07-01 01:40:24 +04:00
clk_put ( host - > clk ) ;
tasklet_disable ( & host - > pio_tasklet ) ;
2009-10-02 02:44:19 +04:00
if ( s3cmci_host_usedma ( host ) )
s3c2410_dma_free ( host - > dma , & s3cmci_dma_client ) ;
2008-07-01 01:40:24 +04:00
free_irq ( host - > irq , host ) ;
2009-10-02 02:44:21 +04:00
if ( ! pd - > no_wprotect )
2009-10-02 02:44:15 +04:00
gpio_free ( pd - > gpio_wprotect ) ;
2009-10-02 02:44:21 +04:00
if ( ! pd - > no_detect )
2009-10-02 02:44:15 +04:00
gpio_free ( pd - > gpio_detect ) ;
for ( i = S3C2410_GPE ( 5 ) ; i < = S3C2410_GPE ( 10 ) ; i + + )
gpio_free ( i ) ;
2008-07-01 01:40:24 +04:00
iounmap ( host - > base ) ;
2009-10-02 02:44:13 +04:00
release_mem_region ( host - > mem - > start , resource_size ( host - > mem ) ) ;
2008-07-01 01:40:24 +04:00
mmc_free_host ( mmc ) ;
return 0 ;
}
2009-10-02 02:44:14 +04:00
static struct platform_device_id s3cmci_driver_ids [ ] = {
{
. name = " s3c2410-sdi " ,
. driver_data = 0 ,
} , {
. name = " s3c2412-sdi " ,
. driver_data = 1 ,
} , {
. name = " s3c2440-sdi " ,
. driver_data = 1 ,
} ,
{ }
} ;
2008-07-01 01:40:24 +04:00
2009-10-02 02:44:14 +04:00
MODULE_DEVICE_TABLE ( platform , s3cmci_driver_ids ) ;
2008-07-01 01:40:24 +04:00
2009-10-02 02:44:14 +04:00
static struct platform_driver s3cmci_driver = {
2009-10-02 02:44:15 +04:00
. driver = {
. name = " s3c-sdi " ,
. owner = THIS_MODULE ,
} ,
2009-10-02 02:44:14 +04:00
. id_table = s3cmci_driver_ids ,
. probe = s3cmci_probe ,
2012-11-19 22:20:26 +04:00
. remove = s3cmci_remove ,
2008-07-17 18:32:54 +04:00
. shutdown = s3cmci_shutdown ,
2008-07-01 01:40:24 +04:00
} ;
2011-11-26 08:55:43 +04:00
module_platform_driver ( s3cmci_driver ) ;
2008-07-01 01:40:24 +04:00
MODULE_DESCRIPTION ( " Samsung S3C MMC/SD Card Interface driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
2008-10-15 03:17:19 +04:00
MODULE_AUTHOR ( " Thomas Kleffel <tk@maintech.de>, Ben Dooks <ben-linux@fluff.org> " ) ;