2010-10-05 22:10:30 +09:00
/*
* Shared interrupt handling code for IPR and INTC2 types of IRQs .
*
* Copyright ( C ) 2007 , 2008 Magnus Damm
* Copyright ( C ) 2009 , 2010 Paul Mundt
*
* Based on intc2 . c and ipr . c
*
* Copyright ( C ) 1999 Niibe Yutaka & Takeshi Yaegashi
* Copyright ( C ) 2000 Kazumoto Kojima
* Copyright ( C ) 2001 David J . Mckay ( david . mckay @ st . com )
* Copyright ( C ) 2003 Takashi Kusuda < kusuda - takashi @ hitachi - ul . co . jp >
* Copyright ( C ) 2005 , 2006 Paul Mundt
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*/
# define pr_fmt(fmt) "intc: " fmt
# include <linux/init.h>
# include <linux/irq.h>
# include <linux/io.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/sh_intc.h>
# include <linux/sysdev.h>
# include <linux/list.h>
# include <linux/spinlock.h>
# include <linux/radix-tree.h>
# include "internals.h"
LIST_HEAD ( intc_list ) ;
DEFINE_RAW_SPINLOCK ( intc_big_lock ) ;
unsigned int nr_intc_controllers ;
/*
* Default priority level
* - this needs to be at least 2 for 5 - bit priorities on 7780
*/
static unsigned int default_prio_level = 2 ; /* 2 - 16 */
static unsigned int intc_prio_level [ NR_IRQS ] ; /* for now */
unsigned int intc_get_dfl_prio_level ( void )
{
return default_prio_level ;
}
unsigned int intc_get_prio_level ( unsigned int irq )
{
return intc_prio_level [ irq ] ;
}
void intc_set_prio_level ( unsigned int irq , unsigned int level )
{
unsigned long flags ;
raw_spin_lock_irqsave ( & intc_big_lock , flags ) ;
intc_prio_level [ irq ] = level ;
raw_spin_unlock_irqrestore ( & intc_big_lock , flags ) ;
}
static void intc_redirect_irq ( unsigned int irq , struct irq_desc * desc )
{
generic_handle_irq ( ( unsigned int ) get_irq_data ( irq ) ) ;
}
static void __init intc_register_irq ( struct intc_desc * desc ,
struct intc_desc_int * d ,
intc_enum enum_id ,
unsigned int irq )
{
struct intc_handle_int * hp ;
2010-10-27 15:42:10 +09:00
struct irq_data * irq_data ;
2010-10-05 22:10:30 +09:00
unsigned int data [ 2 ] , primary ;
unsigned long flags ;
/*
* Register the IRQ position with the global IRQ map , then insert
* it in to the radix tree .
*/
2010-10-26 16:05:08 +09:00
irq_reserve_irqs ( irq , 1 ) ;
2010-10-05 22:10:30 +09:00
raw_spin_lock_irqsave ( & intc_big_lock , flags ) ;
radix_tree_insert ( & d - > tree , enum_id , intc_irq_xlate_get ( irq ) ) ;
raw_spin_unlock_irqrestore ( & intc_big_lock , flags ) ;
/*
* Prefer single interrupt source bitmap over other combinations :
*
* 1. bitmap , single interrupt source
* 2. priority , single interrupt source
* 3. bitmap , multiple interrupt sources ( groups )
* 4. priority , multiple interrupt sources ( groups )
*/
data [ 0 ] = intc_get_mask_handle ( desc , d , enum_id , 0 ) ;
data [ 1 ] = intc_get_prio_handle ( desc , d , enum_id , 0 ) ;
primary = 0 ;
if ( ! data [ 0 ] & & data [ 1 ] )
primary = 1 ;
if ( ! data [ 0 ] & & ! data [ 1 ] )
pr_warning ( " missing unique irq mask for irq %d (vect 0x%04x) \n " ,
irq , irq2evt ( irq ) ) ;
data [ 0 ] = data [ 0 ] ? data [ 0 ] : intc_get_mask_handle ( desc , d , enum_id , 1 ) ;
data [ 1 ] = data [ 1 ] ? data [ 1 ] : intc_get_prio_handle ( desc , d , enum_id , 1 ) ;
if ( ! data [ primary ] )
primary ^ = 1 ;
BUG_ON ( ! data [ primary ] ) ; /* must have primary masking method */
2010-10-27 15:42:10 +09:00
irq_data = irq_get_irq_data ( irq ) ;
2010-10-05 22:10:30 +09:00
disable_irq_nosync ( irq ) ;
set_irq_chip_and_handler_name ( irq , & d - > chip ,
handle_level_irq , " level " ) ;
set_irq_chip_data ( irq , ( void * ) data [ primary ] ) ;
/*
* set priority level
*/
intc_set_prio_level ( irq , intc_get_dfl_prio_level ( ) ) ;
/* enable secondary masking method if present */
if ( data [ ! primary ] )
2010-10-27 15:42:10 +09:00
_intc_enable ( irq_data , data [ ! primary ] ) ;
2010-10-05 22:10:30 +09:00
/* add irq to d->prio list if priority is available */
if ( data [ 1 ] ) {
hp = d - > prio + d - > nr_prio ;
hp - > irq = irq ;
hp - > handle = data [ 1 ] ;
if ( primary ) {
/*
* only secondary priority should access registers , so
* set _INTC_FN ( h ) = REG_FN_ERR for intc_set_priority ( )
*/
hp - > handle & = ~ _INTC_MK ( 0x0f , 0 , 0 , 0 , 0 , 0 ) ;
hp - > handle | = _INTC_MK ( REG_FN_ERR , 0 , 0 , 0 , 0 , 0 ) ;
}
d - > nr_prio + + ;
}
/* add irq to d->sense list if sense is available */
data [ 0 ] = intc_get_sense_handle ( desc , d , enum_id ) ;
if ( data [ 0 ] ) {
( d - > sense + d - > nr_sense ) - > irq = irq ;
( d - > sense + d - > nr_sense ) - > handle = data [ 0 ] ;
d - > nr_sense + + ;
}
/* irq should be disabled by default */
2010-10-27 15:42:10 +09:00
d - > chip . irq_mask ( irq_data ) ;
2010-10-05 22:10:30 +09:00
intc_set_ack_handle ( irq , desc , d , enum_id ) ;
intc_set_dist_handle ( irq , desc , d , enum_id ) ;
activate_irq ( irq ) ;
}
static unsigned int __init save_reg ( struct intc_desc_int * d ,
unsigned int cnt ,
unsigned long value ,
unsigned int smp )
{
if ( value ) {
value = intc_phys_to_virt ( d , value ) ;
d - > reg [ cnt ] = value ;
# ifdef CONFIG_SMP
d - > smp [ cnt ] = smp ;
# endif
return 1 ;
}
return 0 ;
}
int __init register_intc_controller ( struct intc_desc * desc )
{
unsigned int i , k , smp ;
struct intc_hw_desc * hw = & desc - > hw ;
struct intc_desc_int * d ;
struct resource * res ;
pr_info ( " Registered controller '%s' with %u IRQs \n " ,
desc - > name , hw - > nr_vectors ) ;
d = kzalloc ( sizeof ( * d ) , GFP_NOWAIT ) ;
if ( ! d )
goto err0 ;
INIT_LIST_HEAD ( & d - > list ) ;
list_add_tail ( & d - > list , & intc_list ) ;
raw_spin_lock_init ( & d - > lock ) ;
d - > index = nr_intc_controllers ;
if ( desc - > num_resources ) {
d - > nr_windows = desc - > num_resources ;
d - > window = kzalloc ( d - > nr_windows * sizeof ( * d - > window ) ,
GFP_NOWAIT ) ;
if ( ! d - > window )
goto err1 ;
for ( k = 0 ; k < d - > nr_windows ; k + + ) {
res = desc - > resource + k ;
WARN_ON ( resource_type ( res ) ! = IORESOURCE_MEM ) ;
d - > window [ k ] . phys = res - > start ;
d - > window [ k ] . size = resource_size ( res ) ;
d - > window [ k ] . virt = ioremap_nocache ( res - > start ,
resource_size ( res ) ) ;
if ( ! d - > window [ k ] . virt )
goto err2 ;
}
}
d - > nr_reg = hw - > mask_regs ? hw - > nr_mask_regs * 2 : 0 ;
# ifdef CONFIG_INTC_BALANCING
if ( d - > nr_reg )
d - > nr_reg + = hw - > nr_mask_regs ;
# endif
d - > nr_reg + = hw - > prio_regs ? hw - > nr_prio_regs * 2 : 0 ;
d - > nr_reg + = hw - > sense_regs ? hw - > nr_sense_regs : 0 ;
d - > nr_reg + = hw - > ack_regs ? hw - > nr_ack_regs : 0 ;
d - > nr_reg + = hw - > subgroups ? hw - > nr_subgroups : 0 ;
d - > reg = kzalloc ( d - > nr_reg * sizeof ( * d - > reg ) , GFP_NOWAIT ) ;
if ( ! d - > reg )
goto err2 ;
# ifdef CONFIG_SMP
d - > smp = kzalloc ( d - > nr_reg * sizeof ( * d - > smp ) , GFP_NOWAIT ) ;
if ( ! d - > smp )
goto err3 ;
# endif
k = 0 ;
if ( hw - > mask_regs ) {
for ( i = 0 ; i < hw - > nr_mask_regs ; i + + ) {
smp = IS_SMP ( hw - > mask_regs [ i ] ) ;
k + = save_reg ( d , k , hw - > mask_regs [ i ] . set_reg , smp ) ;
k + = save_reg ( d , k , hw - > mask_regs [ i ] . clr_reg , smp ) ;
# ifdef CONFIG_INTC_BALANCING
k + = save_reg ( d , k , hw - > mask_regs [ i ] . dist_reg , 0 ) ;
# endif
}
}
if ( hw - > prio_regs ) {
d - > prio = kzalloc ( hw - > nr_vectors * sizeof ( * d - > prio ) ,
GFP_NOWAIT ) ;
if ( ! d - > prio )
goto err4 ;
for ( i = 0 ; i < hw - > nr_prio_regs ; i + + ) {
smp = IS_SMP ( hw - > prio_regs [ i ] ) ;
k + = save_reg ( d , k , hw - > prio_regs [ i ] . set_reg , smp ) ;
k + = save_reg ( d , k , hw - > prio_regs [ i ] . clr_reg , smp ) ;
}
}
if ( hw - > sense_regs ) {
d - > sense = kzalloc ( hw - > nr_vectors * sizeof ( * d - > sense ) ,
GFP_NOWAIT ) ;
if ( ! d - > sense )
goto err5 ;
for ( i = 0 ; i < hw - > nr_sense_regs ; i + + )
k + = save_reg ( d , k , hw - > sense_regs [ i ] . reg , 0 ) ;
}
if ( hw - > subgroups )
for ( i = 0 ; i < hw - > nr_subgroups ; i + + )
if ( hw - > subgroups [ i ] . reg )
k + = save_reg ( d , k , hw - > subgroups [ i ] . reg , 0 ) ;
memcpy ( & d - > chip , & intc_irq_chip , sizeof ( struct irq_chip ) ) ;
d - > chip . name = desc - > name ;
if ( hw - > ack_regs )
for ( i = 0 ; i < hw - > nr_ack_regs ; i + + )
k + = save_reg ( d , k , hw - > ack_regs [ i ] . set_reg , 0 ) ;
else
2010-10-27 15:42:10 +09:00
d - > chip . irq_mask_ack = d - > chip . irq_disable ;
2010-10-05 22:10:30 +09:00
/* disable bits matching force_disable before registering irqs */
if ( desc - > force_disable )
intc_enable_disable_enum ( desc , d , desc - > force_disable , 0 ) ;
/* disable bits matching force_enable before registering irqs */
if ( desc - > force_enable )
intc_enable_disable_enum ( desc , d , desc - > force_enable , 0 ) ;
BUG_ON ( k > 256 ) ; /* _INTC_ADDR_E() and _INTC_ADDR_D() are 8 bits */
/* register the vectors one by one */
for ( i = 0 ; i < hw - > nr_vectors ; i + + ) {
struct intc_vect * vect = hw - > vectors + i ;
unsigned int irq = evt2irq ( vect - > vect ) ;
2010-10-12 02:03:09 +09:00
int res ;
2010-10-05 22:10:30 +09:00
if ( ! vect - > enum_id )
continue ;
2010-10-12 02:03:09 +09:00
res = irq_alloc_desc_at ( irq , numa_node_id ( ) ) ;
if ( res ! = irq & & res ! = - EEXIST ) {
2010-10-05 22:10:30 +09:00
pr_err ( " can't get irq_desc for %d \n " , irq ) ;
continue ;
}
intc_irq_xlate_set ( irq , vect - > enum_id , d ) ;
intc_register_irq ( desc , d , vect - > enum_id , irq ) ;
for ( k = i + 1 ; k < hw - > nr_vectors ; k + + ) {
struct intc_vect * vect2 = hw - > vectors + k ;
unsigned int irq2 = evt2irq ( vect2 - > vect ) ;
if ( vect - > enum_id ! = vect2 - > enum_id )
continue ;
/*
* In the case of multi - evt handling and sparse
* IRQ support , each vector still needs to have
* its own backing irq_desc .
*/
2010-10-12 02:03:09 +09:00
res = irq_alloc_desc_at ( irq2 , numa_node_id ( ) ) ;
if ( res ! = irq2 & & res ! = - EEXIST ) {
2010-10-05 22:10:30 +09:00
pr_err ( " can't get irq_desc for %d \n " , irq2 ) ;
continue ;
}
vect2 - > enum_id = 0 ;
/* redirect this interrupts to the first one */
set_irq_chip ( irq2 , & dummy_irq_chip ) ;
set_irq_chained_handler ( irq2 , intc_redirect_irq ) ;
set_irq_data ( irq2 , ( void * ) irq ) ;
}
}
intc_subgroup_init ( desc , d ) ;
/* enable bits matching force_enable after registering irqs */
if ( desc - > force_enable )
intc_enable_disable_enum ( desc , d , desc - > force_enable , 1 ) ;
nr_intc_controllers + + ;
return 0 ;
err5 :
kfree ( d - > prio ) ;
err4 :
# ifdef CONFIG_SMP
kfree ( d - > smp ) ;
err3 :
# endif
kfree ( d - > reg ) ;
err2 :
for ( k = 0 ; k < d - > nr_windows ; k + + )
if ( d - > window [ k ] . virt )
iounmap ( d - > window [ k ] . virt ) ;
kfree ( d - > window ) ;
err1 :
kfree ( d ) ;
err0 :
pr_err ( " unable to allocate INTC memory \n " ) ;
return - ENOMEM ;
}
static ssize_t
show_intc_name ( struct sys_device * dev , struct sysdev_attribute * attr , char * buf )
{
struct intc_desc_int * d ;
d = container_of ( dev , struct intc_desc_int , sysdev ) ;
return sprintf ( buf , " %s \n " , d - > chip . name ) ;
}
static SYSDEV_ATTR ( name , S_IRUGO , show_intc_name , NULL ) ;
static int intc_suspend ( struct sys_device * dev , pm_message_t state )
{
struct intc_desc_int * d ;
2010-10-27 15:42:10 +09:00
struct irq_data * data ;
2010-10-05 22:10:30 +09:00
struct irq_desc * desc ;
2010-10-27 15:42:10 +09:00
struct irq_chip * chip ;
2010-10-05 22:10:30 +09:00
int irq ;
/* get intc controller associated with this sysdev */
d = container_of ( dev , struct intc_desc_int , sysdev ) ;
switch ( state . event ) {
case PM_EVENT_ON :
if ( d - > state . event ! = PM_EVENT_FREEZE )
break ;
2010-10-27 15:42:10 +09:00
for_each_irq_nr ( irq ) {
desc = irq_to_desc ( irq ) ;
if ( ! desc )
continue ;
data = irq_get_irq_data ( irq ) ;
chip = irq_data_get_irq_chip ( data ) ;
2010-10-05 22:10:30 +09:00
/*
* This will catch the redirect and VIRQ cases
* due to the dummy_irq_chip being inserted .
*/
2010-10-27 15:42:10 +09:00
if ( chip ! = & d - > chip )
2010-10-05 22:10:30 +09:00
continue ;
if ( desc - > status & IRQ_DISABLED )
2010-10-27 15:42:10 +09:00
chip - > irq_disable ( data ) ;
2010-10-05 22:10:30 +09:00
else
2010-10-27 15:42:10 +09:00
chip - > irq_enable ( data ) ;
2010-10-05 22:10:30 +09:00
}
break ;
case PM_EVENT_FREEZE :
/* nothing has to be done */
break ;
case PM_EVENT_SUSPEND :
/* enable wakeup irqs belonging to this intc controller */
2010-10-27 15:42:10 +09:00
for_each_irq_nr ( irq ) {
desc = irq_to_desc ( irq ) ;
if ( ! desc )
continue ;
data = irq_get_irq_data ( irq ) ;
chip = irq_data_get_irq_chip ( data ) ;
if ( chip ! = & d - > chip )
2010-10-05 22:10:30 +09:00
continue ;
if ( ( desc - > status & IRQ_WAKEUP ) )
2010-10-27 15:42:10 +09:00
chip - > irq_enable ( data ) ;
2010-10-05 22:10:30 +09:00
}
break ;
}
d - > state = state ;
return 0 ;
}
static int intc_resume ( struct sys_device * dev )
{
return intc_suspend ( dev , PMSG_ON ) ;
}
struct sysdev_class intc_sysdev_class = {
. name = " intc " ,
. suspend = intc_suspend ,
. resume = intc_resume ,
} ;
/* register this intc as sysdev to allow suspend/resume */
static int __init register_intc_sysdevs ( void )
{
struct intc_desc_int * d ;
int error ;
error = sysdev_class_register ( & intc_sysdev_class ) ;
if ( ! error ) {
list_for_each_entry ( d , & intc_list , list ) {
d - > sysdev . id = d - > index ;
d - > sysdev . cls = & intc_sysdev_class ;
error = sysdev_register ( & d - > sysdev ) ;
if ( error = = 0 )
error = sysdev_create_file ( & d - > sysdev ,
& attr_name ) ;
if ( error )
break ;
}
}
if ( error )
pr_err ( " sysdev registration error \n " ) ;
return error ;
}
device_initcall ( register_intc_sysdevs ) ;