2009-03-16 22:33:49 +01:00
/*
* linux / kernel / irq / pm . c
*
* Copyright ( C ) 2009 Rafael J . Wysocki < rjw @ sisk . pl > , Novell Inc .
*
* This file contains power management functions related to interrupts .
*/
# include <linux/irq.h>
# include <linux/module.h>
# include <linux/interrupt.h>
2014-08-29 14:00:16 +02:00
# include <linux/suspend.h>
2011-10-03 15:37:00 +01:00
# include <linux/syscore_ops.h>
2009-03-16 22:33:49 +01:00
# include "internals.h"
2014-08-29 14:00:16 +02:00
bool irq_pm_check_wakeup ( struct irq_desc * desc )
{
if ( irqd_is_wakeup_armed ( & desc - > irq_data ) ) {
irqd_clear ( & desc - > irq_data , IRQD_WAKEUP_ARMED ) ;
desc - > istate | = IRQS_SUSPENDED | IRQS_PENDING ;
desc - > depth + + ;
irq_disable ( desc ) ;
pm_system_wakeup ( ) ;
return true ;
}
return false ;
}
2014-08-28 11:44:31 +02:00
/*
* Called from __setup_irq ( ) with desc - > lock held after @ action has
* been installed in the action chain .
*/
void irq_pm_install_action ( struct irq_desc * desc , struct irqaction * action )
{
desc - > nr_actions + + ;
if ( action - > flags & IRQF_FORCE_RESUME )
desc - > force_resume_depth + + ;
WARN_ON_ONCE ( desc - > force_resume_depth & &
desc - > force_resume_depth ! = desc - > nr_actions ) ;
if ( action - > flags & IRQF_NO_SUSPEND )
desc - > no_suspend_depth + + ;
WARN_ON_ONCE ( desc - > no_suspend_depth & &
desc - > no_suspend_depth ! = desc - > nr_actions ) ;
}
/*
* Called from __free_irq ( ) with desc - > lock held after @ action has
* been removed from the action chain .
*/
void irq_pm_remove_action ( struct irq_desc * desc , struct irqaction * action )
{
desc - > nr_actions - - ;
if ( action - > flags & IRQF_FORCE_RESUME )
desc - > force_resume_depth - - ;
if ( action - > flags & IRQF_NO_SUSPEND )
desc - > no_suspend_depth - - ;
}
2014-08-28 22:50:43 +02:00
static bool suspend_device_irq ( struct irq_desc * desc , int irq )
2014-08-28 11:49:28 +02:00
{
2014-08-28 15:48:59 +02:00
if ( ! desc - > action | | desc - > no_suspend_depth )
2014-08-28 22:50:43 +02:00
return false ;
2014-08-28 11:49:28 +02:00
2014-08-29 14:00:16 +02:00
if ( irqd_is_wakeup_set ( & desc - > irq_data ) ) {
2014-08-29 13:54:09 +02:00
irqd_set ( & desc - > irq_data , IRQD_WAKEUP_ARMED ) ;
2014-08-29 14:00:16 +02:00
/*
* We return true here to force the caller to issue
* synchronize_irq ( ) . We need to make sure that the
* IRQD_WAKEUP_ARMED is visible before we return from
* suspend_device_irqs ( ) .
*/
return true ;
}
2014-08-29 13:54:09 +02:00
2014-08-28 11:49:28 +02:00
desc - > istate | = IRQS_SUSPENDED ;
__disable_irq ( desc , irq ) ;
2014-08-28 16:49:43 +02:00
/*
* Hardware which has no wakeup source configuration facility
* requires that the non wakeup interrupts are masked at the
* chip level . The chip implementation indicates that with
* IRQCHIP_MASK_ON_SUSPEND .
*/
if ( irq_desc_get_chip ( desc ) - > flags & IRQCHIP_MASK_ON_SUSPEND )
mask_irq ( desc ) ;
2014-08-28 22:50:43 +02:00
return true ;
2014-08-28 11:49:28 +02:00
}
2009-03-16 22:33:49 +01:00
/**
* suspend_device_irqs - disable all currently enabled interrupt lines
*
2014-08-28 11:49:28 +02:00
* During system - wide suspend or hibernation device drivers need to be
* prevented from receiving interrupts and this function is provided
* for this purpose .
*
* So we disable all interrupts and mark them IRQS_SUSPENDED except
2014-08-29 14:00:16 +02:00
* for those which are unused , those which are marked as not
2014-08-28 11:49:28 +02:00
* suspendable via an interrupt request with the flag IRQF_NO_SUSPEND
2014-08-29 14:00:16 +02:00
* set and those which are marked as active wakeup sources .
*
* The active wakeup sources are handled by the flow handler entry
* code which checks for the IRQD_WAKEUP_ARMED flag , suspends the
* interrupt and notifies the pm core about the wakeup .
2009-03-16 22:33:49 +01:00
*/
void suspend_device_irqs ( void )
{
struct irq_desc * desc ;
int irq ;
for_each_irq_desc ( irq , desc ) {
unsigned long flags ;
2014-08-28 22:50:43 +02:00
bool sync ;
2009-03-16 22:33:49 +01:00
2009-11-17 16:46:45 +01:00
raw_spin_lock_irqsave ( & desc - > lock , flags ) ;
2014-08-28 22:50:43 +02:00
sync = suspend_device_irq ( desc , irq ) ;
2009-11-17 16:46:45 +01:00
raw_spin_unlock_irqrestore ( & desc - > lock , flags ) ;
2009-03-16 22:33:49 +01:00
2014-08-28 22:50:43 +02:00
if ( sync )
2009-03-16 22:33:49 +01:00
synchronize_irq ( irq ) ;
2014-08-28 22:50:43 +02:00
}
2009-03-16 22:33:49 +01:00
}
EXPORT_SYMBOL_GPL ( suspend_device_irqs ) ;
2014-08-28 11:49:28 +02:00
static void resume_irq ( struct irq_desc * desc , int irq )
{
2014-08-29 13:54:09 +02:00
irqd_clear ( & desc - > irq_data , IRQD_WAKEUP_ARMED ) ;
2014-08-28 11:49:28 +02:00
if ( desc - > istate & IRQS_SUSPENDED )
goto resume ;
2014-08-28 15:48:59 +02:00
/* Force resume the interrupt? */
if ( ! desc - > force_resume_depth )
2014-08-28 11:49:28 +02:00
return ;
/* Pretend that it got disabled ! */
desc - > depth + + ;
resume :
desc - > istate & = ~ IRQS_SUSPENDED ;
__enable_irq ( desc , irq ) ;
}
2011-10-03 15:37:00 +01:00
static void resume_irqs ( bool want_early )
2009-03-16 22:33:49 +01:00
{
struct irq_desc * desc ;
int irq ;
for_each_irq_desc ( irq , desc ) {
unsigned long flags ;
2011-10-03 15:37:00 +01:00
bool is_early = desc - > action & &
desc - > action - > flags & IRQF_EARLY_RESUME ;
2013-11-25 19:39:47 +05:30
if ( ! is_early & & want_early )
2011-10-03 15:37:00 +01:00
continue ;
2009-03-16 22:33:49 +01:00
2009-11-17 16:46:45 +01:00
raw_spin_lock_irqsave ( & desc - > lock , flags ) ;
2014-08-28 11:49:28 +02:00
resume_irq ( desc , irq ) ;
2009-11-17 16:46:45 +01:00
raw_spin_unlock_irqrestore ( & desc - > lock , flags ) ;
2009-03-16 22:33:49 +01:00
}
}
2011-10-03 15:37:00 +01:00
/**
* irq_pm_syscore_ops - enable interrupt lines early
*
* Enable all interrupt lines with % IRQF_EARLY_RESUME set .
*/
static void irq_pm_syscore_resume ( void )
{
resume_irqs ( true ) ;
}
static struct syscore_ops irq_pm_syscore_ops = {
. resume = irq_pm_syscore_resume ,
} ;
static int __init irq_pm_init_ops ( void )
{
register_syscore_ops ( & irq_pm_syscore_ops ) ;
return 0 ;
}
device_initcall ( irq_pm_init_ops ) ;
/**
* resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs ( )
*
* Enable all non - % IRQF_EARLY_RESUME interrupt lines previously
* disabled by suspend_device_irqs ( ) that have the IRQS_SUSPENDED flag
* set as well as those with % IRQF_FORCE_RESUME .
*/
void resume_device_irqs ( void )
{
resume_irqs ( false ) ;
}
2009-03-16 22:33:49 +01:00
EXPORT_SYMBOL_GPL ( resume_device_irqs ) ;