2007-06-30 16:29:41 +02:00
/*
* linux / drivers / mmc / core / sdio_irq . c
*
* Author : Nicolas Pitre
* Created : June 18 , 2007
* Copyright : MontaVista Software Inc .
*
2008-08-31 13:38:54 +02:00
* Copyright 2008 Pierre Ossman
*
2007-06-30 16:29:41 +02:00
* 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 .
*/
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/kthread.h>
2011-07-10 12:42:00 -04:00
# include <linux/export.h>
2007-06-30 16:29:41 +02:00
# include <linux/wait.h>
# include <linux/delay.h>
# include <linux/mmc/core.h>
# include <linux/mmc/host.h>
# include <linux/mmc/card.h>
# include <linux/mmc/sdio.h>
# include <linux/mmc/sdio_func.h>
# include "sdio_ops.h"
2012-04-16 19:16:54 -04:00
static int process_sdio_pending_irqs ( struct mmc_host * host )
2007-06-30 16:29:41 +02:00
{
2012-04-16 19:16:54 -04:00
struct mmc_card * card = host - > card ;
2007-09-27 10:48:29 +02:00
int i , ret , count ;
2007-06-30 16:29:41 +02:00
unsigned char pending ;
2011-05-11 17:48:05 +02:00
struct sdio_func * func ;
/*
* Optimization , if there is only 1 function interrupt registered
2012-04-16 19:16:54 -04:00
* and we know an IRQ was signaled then call irq handler directly .
* Otherwise do the full probe .
2011-05-11 17:48:05 +02:00
*/
func = card - > sdio_single_irq ;
2012-04-16 19:16:54 -04:00
if ( func & & host - > sdio_irq_pending ) {
2011-05-11 17:48:05 +02:00
func - > irq_handler ( func ) ;
return 1 ;
}
2007-06-30 16:29:41 +02:00
ret = mmc_io_rw_direct ( card , 0 , 0 , SDIO_CCCR_INTx , 0 , & pending ) ;
if ( ret ) {
2011-10-11 11:44:09 +05:30
pr_debug ( " %s: error %d reading SDIO_CCCR_INTx \n " ,
2007-06-30 16:29:41 +02:00
mmc_card_id ( card ) , ret ) ;
return ret ;
}
2013-11-26 15:39:20 -08:00
if ( pending & & mmc_card_broken_irq_polling ( card ) & &
! ( host - > caps & MMC_CAP_SDIO_IRQ ) ) {
unsigned char dummy ;
/* A fake interrupt could be created when we poll SDIO_CCCR_INTx
* register with a Marvell SD8797 card . A dummy CMD52 read to
* function 0 register 0xff can avoid this .
*/
mmc_io_rw_direct ( card , 0 , 0 , 0xff , 0 , & dummy ) ;
}
2007-09-27 10:48:29 +02:00
count = 0 ;
2007-06-30 16:29:41 +02:00
for ( i = 1 ; i < = 7 ; i + + ) {
if ( pending & ( 1 < < i ) ) {
2011-05-11 17:48:05 +02:00
func = card - > sdio_func [ i - 1 ] ;
2007-06-30 16:29:41 +02:00
if ( ! func ) {
2014-09-12 14:56:56 -07:00
pr_warn ( " %s: pending IRQ for non-existent function \n " ,
2007-10-03 15:32:10 -04:00
mmc_card_id ( card ) ) ;
2007-10-03 15:32:57 -04:00
ret = - EINVAL ;
2007-06-30 16:29:41 +02:00
} else if ( func - > irq_handler ) {
func - > irq_handler ( func ) ;
2007-09-27 10:48:29 +02:00
count + + ;
2007-10-03 15:32:57 -04:00
} else {
2014-09-12 14:56:56 -07:00
pr_warn ( " %s: pending IRQ with no handler \n " ,
sdio_func_id ( func ) ) ;
2007-10-03 15:32:57 -04:00
ret = - EINVAL ;
}
2007-06-30 16:29:41 +02:00
}
}
2007-10-03 15:32:57 -04:00
if ( count )
return count ;
return ret ;
2007-06-30 16:29:41 +02:00
}
2014-04-25 12:55:30 +01:00
void sdio_run_irqs ( struct mmc_host * host )
{
mmc_claim_host ( host ) ;
host - > sdio_irq_pending = true ;
process_sdio_pending_irqs ( host ) ;
mmc_release_host ( host ) ;
}
EXPORT_SYMBOL_GPL ( sdio_run_irqs ) ;
2007-06-30 16:29:41 +02:00
static int sdio_irq_thread ( void * _host )
{
struct mmc_host * host = _host ;
struct sched_param param = { . sched_priority = 1 } ;
2007-09-27 10:48:29 +02:00
unsigned long period , idle_period ;
2007-06-30 16:29:41 +02:00
int ret ;
sched_setscheduler ( current , SCHED_FIFO , & param ) ;
/*
* We want to allow for SDIO cards to work even on non SDIO
* aware hosts . One thing that non SDIO host cannot do is
* asynchronous notification of pending SDIO card interrupts
* hence we poll for them in that case .
*/
2007-09-27 10:48:29 +02:00
idle_period = msecs_to_jiffies ( 10 ) ;
2007-07-24 02:09:39 -04:00
period = ( host - > caps & MMC_CAP_SDIO_IRQ ) ?
2007-09-27 10:48:29 +02:00
MAX_SCHEDULE_TIMEOUT : idle_period ;
2007-06-30 16:29:41 +02:00
pr_debug ( " %s: IRQ thread started (poll period = %lu jiffies) \n " ,
mmc_hostname ( host ) , period ) ;
do {
/*
* We claim the host here on drivers behalf for a couple
* reasons :
*
* 1 ) it is already needed to retrieve the CCCR_INTx ;
* 2 ) we want the driver ( s ) to clear the IRQ condition ASAP ;
* 3 ) we need to control the abort condition locally .
*
* Just like traditional hard IRQ handlers , we expect SDIO
* IRQ handlers to be quick and to the point , so that the
* holding of the host lock does not cover too much work
* that doesn ' t require that lock to be held .
*/
ret = __mmc_claim_host ( host , & host - > sdio_irq_thread_abort ) ;
if ( ret )
break ;
2012-04-16 19:16:54 -04:00
ret = process_sdio_pending_irqs ( host ) ;
host - > sdio_irq_pending = false ;
2007-06-30 16:29:41 +02:00
mmc_release_host ( host ) ;
/*
* Give other threads a chance to run in the presence of
2008-08-31 13:38:54 +02:00
* errors .
2007-06-30 16:29:41 +02:00
*/
2008-08-31 13:38:54 +02:00
if ( ret < 0 ) {
set_current_state ( TASK_INTERRUPTIBLE ) ;
if ( ! kthread_should_stop ( ) )
schedule_timeout ( HZ ) ;
set_current_state ( TASK_RUNNING ) ;
}
2007-06-30 16:29:41 +02:00
2007-09-27 10:48:29 +02:00
/*
* Adaptive polling frequency based on the assumption
* that an interrupt will be closely followed by more .
* This has a substantial benefit for network devices .
*/
if ( ! ( host - > caps & MMC_CAP_SDIO_IRQ ) ) {
if ( ret > 0 )
period / = 2 ;
else {
period + + ;
if ( period > idle_period )
period = idle_period ;
}
}
2008-03-28 14:34:47 -07:00
set_current_state ( TASK_INTERRUPTIBLE ) ;
2015-10-02 10:56:11 +02:00
if ( host - > caps & MMC_CAP_SDIO_IRQ )
2007-07-24 02:09:39 -04:00
host - > ops - > enable_sdio_irq ( host , 1 ) ;
2007-06-30 16:29:41 +02:00
if ( ! kthread_should_stop ( ) )
schedule_timeout ( period ) ;
2008-03-28 14:34:47 -07:00
set_current_state ( TASK_RUNNING ) ;
2007-06-30 16:29:41 +02:00
} while ( ! kthread_should_stop ( ) ) ;
2015-10-02 10:56:11 +02:00
if ( host - > caps & MMC_CAP_SDIO_IRQ )
2007-07-24 02:09:39 -04:00
host - > ops - > enable_sdio_irq ( host , 0 ) ;
2007-06-30 16:29:41 +02:00
pr_debug ( " %s: IRQ thread exiting with code %d \n " ,
mmc_hostname ( host ) , ret ) ;
return ret ;
}
static int sdio_card_irq_get ( struct mmc_card * card )
{
struct mmc_host * host = card - > host ;
2007-08-09 13:23:56 +02:00
WARN_ON ( ! host - > claimed ) ;
2007-06-30 16:29:41 +02:00
if ( ! host - > sdio_irqs + + ) {
2014-04-25 12:55:30 +01:00
if ( ! ( host - > caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD ) ) {
atomic_set ( & host - > sdio_irq_thread_abort , 0 ) ;
host - > sdio_irq_thread =
kthread_run ( sdio_irq_thread , host ,
" ksdioirqd/%s " , mmc_hostname ( host ) ) ;
if ( IS_ERR ( host - > sdio_irq_thread ) ) {
int err = PTR_ERR ( host - > sdio_irq_thread ) ;
host - > sdio_irqs - - ;
return err ;
}
2014-08-18 10:48:14 +08:00
} else if ( host - > caps & MMC_CAP_SDIO_IRQ ) {
2014-04-25 12:55:30 +01:00
host - > ops - > enable_sdio_irq ( host , 1 ) ;
2007-06-30 16:29:41 +02:00
}
}
return 0 ;
}
static int sdio_card_irq_put ( struct mmc_card * card )
{
struct mmc_host * host = card - > host ;
2007-08-09 13:23:56 +02:00
WARN_ON ( ! host - > claimed ) ;
2007-06-30 16:29:41 +02:00
BUG_ON ( host - > sdio_irqs < 1 ) ;
if ( ! - - host - > sdio_irqs ) {
2014-04-25 12:55:30 +01:00
if ( ! ( host - > caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD ) ) {
atomic_set ( & host - > sdio_irq_thread_abort , 1 ) ;
kthread_stop ( host - > sdio_irq_thread ) ;
2014-08-18 10:48:14 +08:00
} else if ( host - > caps & MMC_CAP_SDIO_IRQ ) {
2014-04-25 12:55:30 +01:00
host - > ops - > enable_sdio_irq ( host , 0 ) ;
}
2007-06-30 16:29:41 +02:00
}
return 0 ;
}
2011-05-11 17:48:05 +02:00
/* If there is only 1 function registered set sdio_single_irq */
static void sdio_single_irq_set ( struct mmc_card * card )
{
struct sdio_func * func ;
int i ;
card - > sdio_single_irq = NULL ;
if ( ( card - > host - > caps & MMC_CAP_SDIO_IRQ ) & &
card - > host - > sdio_irqs = = 1 )
for ( i = 0 ; i < card - > sdio_funcs ; i + + ) {
func = card - > sdio_func [ i ] ;
if ( func & & func - > irq_handler ) {
card - > sdio_single_irq = func ;
break ;
}
}
}
2007-06-30 16:29:41 +02:00
/**
* sdio_claim_irq - claim the IRQ for a SDIO function
* @ func : SDIO function
* @ handler : IRQ handler callback
*
* Claim and activate the IRQ for the given SDIO function . The provided
* handler will be called when that IRQ is asserted . The host is always
* claimed already when the handler is called so the handler must not
* call sdio_claim_host ( ) nor sdio_release_host ( ) .
*/
int sdio_claim_irq ( struct sdio_func * func , sdio_irq_handler_t * handler )
{
int ret ;
unsigned char reg ;
BUG_ON ( ! func ) ;
BUG_ON ( ! func - > card ) ;
pr_debug ( " SDIO: Enabling IRQ for %s... \n " , sdio_func_id ( func ) ) ;
if ( func - > irq_handler ) {
pr_debug ( " SDIO: IRQ for %s already in use. \n " , sdio_func_id ( func ) ) ;
return - EBUSY ;
}
ret = mmc_io_rw_direct ( func - > card , 0 , 0 , SDIO_CCCR_IENx , 0 , & reg ) ;
if ( ret )
return ret ;
reg | = 1 < < func - > num ;
reg | = 1 ; /* Master interrupt enable */
ret = mmc_io_rw_direct ( func - > card , 1 , 0 , SDIO_CCCR_IENx , reg , NULL ) ;
if ( ret )
return ret ;
func - > irq_handler = handler ;
ret = sdio_card_irq_get ( func - > card ) ;
if ( ret )
func - > irq_handler = NULL ;
2011-05-11 17:48:05 +02:00
sdio_single_irq_set ( func - > card ) ;
2007-06-30 16:29:41 +02:00
return ret ;
}
EXPORT_SYMBOL_GPL ( sdio_claim_irq ) ;
/**
* sdio_release_irq - release the IRQ for a SDIO function
* @ func : SDIO function
*
* Disable and release the IRQ for the given SDIO function .
*/
int sdio_release_irq ( struct sdio_func * func )
{
int ret ;
unsigned char reg ;
BUG_ON ( ! func ) ;
BUG_ON ( ! func - > card ) ;
pr_debug ( " SDIO: Disabling IRQ for %s... \n " , sdio_func_id ( func ) ) ;
if ( func - > irq_handler ) {
func - > irq_handler = NULL ;
sdio_card_irq_put ( func - > card ) ;
2011-05-11 17:48:05 +02:00
sdio_single_irq_set ( func - > card ) ;
2007-06-30 16:29:41 +02:00
}
ret = mmc_io_rw_direct ( func - > card , 0 , 0 , SDIO_CCCR_IENx , 0 , & reg ) ;
if ( ret )
return ret ;
reg & = ~ ( 1 < < func - > num ) ;
/* Disable master interrupt with the last function interrupt */
if ( ! ( reg & 0xFE ) )
reg = 0 ;
ret = mmc_io_rw_direct ( func - > card , 1 , 0 , SDIO_CCCR_IENx , reg , NULL ) ;
if ( ret )
return ret ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( sdio_release_irq ) ;