2007-04-18 16:36:26 +10:00
/*
* arch / powerpc / sysdev / uic . c
*
* IBM PowerPC 4 xx Universal Interrupt Controller
*
* Copyright 2007 David Gibson < dwg @ au1 . ibm . com > , IBM Corporation .
*
* 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/init.h>
# include <linux/errno.h>
# include <linux/reboot.h>
# include <linux/slab.h>
# include <linux/stddef.h>
# include <linux/sched.h>
# include <linux/signal.h>
# include <linux/device.h>
# include <linux/bootmem.h>
# include <linux/spinlock.h>
# include <linux/irq.h>
# include <linux/interrupt.h>
2007-08-14 13:52:42 +10:00
# include <linux/kernel_stat.h>
2007-04-18 16:36:26 +10:00
# include <asm/irq.h>
# include <asm/io.h>
# include <asm/prom.h>
# include <asm/dcr.h>
# define NR_UIC_INTS 32
# define UIC_SR 0x0
# define UIC_ER 0x2
# define UIC_CR 0x3
# define UIC_PR 0x4
# define UIC_TR 0x5
# define UIC_MSR 0x6
# define UIC_VR 0x7
# define UIC_VCR 0x8
struct uic * primary_uic ;
struct uic {
int index ;
int dcrbase ;
2010-04-06 09:44:10 +02:00
raw_spinlock_t lock ;
2007-04-18 16:36:26 +10:00
/* The remapper for this UIC */
2012-02-14 14:06:50 -07:00
struct irq_domain * irqhost ;
2007-04-18 16:36:26 +10:00
} ;
2011-03-08 22:27:02 +00:00
static void uic_unmask_irq ( struct irq_data * d )
2007-04-18 16:36:26 +10:00
{
2011-03-08 22:27:02 +00:00
struct uic * uic = irq_data_get_irq_chip_data ( d ) ;
2011-05-04 15:02:15 +10:00
unsigned int src = irqd_to_hwirq ( d ) ;
2007-04-18 16:36:26 +10:00
unsigned long flags ;
2007-11-15 01:00:52 +11:00
u32 er , sr ;
2007-04-18 16:36:26 +10:00
2007-11-15 01:00:52 +11:00
sr = 1 < < ( 31 - src ) ;
2010-04-06 09:44:10 +02:00
raw_spin_lock_irqsave ( & uic - > lock , flags ) ;
2007-11-15 01:00:52 +11:00
/* ack level-triggered interrupts here */
2011-03-25 16:23:57 +01:00
if ( irqd_is_level_type ( d ) )
2007-11-15 01:00:52 +11:00
mtdcr ( uic - > dcrbase + UIC_SR , sr ) ;
2007-04-18 16:36:26 +10:00
er = mfdcr ( uic - > dcrbase + UIC_ER ) ;
2007-11-15 01:00:52 +11:00
er | = sr ;
2007-04-18 16:36:26 +10:00
mtdcr ( uic - > dcrbase + UIC_ER , er ) ;
2010-04-06 09:44:10 +02:00
raw_spin_unlock_irqrestore ( & uic - > lock , flags ) ;
2007-04-18 16:36:26 +10:00
}
2011-03-08 22:27:02 +00:00
static void uic_mask_irq ( struct irq_data * d )
2007-04-18 16:36:26 +10:00
{
2011-03-08 22:27:02 +00:00
struct uic * uic = irq_data_get_irq_chip_data ( d ) ;
2011-05-04 15:02:15 +10:00
unsigned int src = irqd_to_hwirq ( d ) ;
2007-04-18 16:36:26 +10:00
unsigned long flags ;
u32 er ;
2010-04-06 09:44:10 +02:00
raw_spin_lock_irqsave ( & uic - > lock , flags ) ;
2007-04-18 16:36:26 +10:00
er = mfdcr ( uic - > dcrbase + UIC_ER ) ;
er & = ~ ( 1 < < ( 31 - src ) ) ;
mtdcr ( uic - > dcrbase + UIC_ER , er ) ;
2010-04-06 09:44:10 +02:00
raw_spin_unlock_irqrestore ( & uic - > lock , flags ) ;
2007-04-18 16:36:26 +10:00
}
2011-03-08 22:27:02 +00:00
static void uic_ack_irq ( struct irq_data * d )
2007-04-18 16:36:26 +10:00
{
2011-03-08 22:27:02 +00:00
struct uic * uic = irq_data_get_irq_chip_data ( d ) ;
2011-05-04 15:02:15 +10:00
unsigned int src = irqd_to_hwirq ( d ) ;
2007-04-18 16:36:26 +10:00
unsigned long flags ;
2010-04-06 09:44:10 +02:00
raw_spin_lock_irqsave ( & uic - > lock , flags ) ;
2007-04-18 16:36:26 +10:00
mtdcr ( uic - > dcrbase + UIC_SR , 1 < < ( 31 - src ) ) ;
2010-04-06 09:44:10 +02:00
raw_spin_unlock_irqrestore ( & uic - > lock , flags ) ;
2007-04-18 16:36:26 +10:00
}
2011-03-08 22:27:02 +00:00
static void uic_mask_ack_irq ( struct irq_data * d )
2007-11-14 07:25:21 +11:00
{
2011-03-08 22:27:02 +00:00
struct uic * uic = irq_data_get_irq_chip_data ( d ) ;
2011-05-04 15:02:15 +10:00
unsigned int src = irqd_to_hwirq ( d ) ;
2007-11-14 07:25:21 +11:00
unsigned long flags ;
u32 er , sr ;
sr = 1 < < ( 31 - src ) ;
2010-04-06 09:44:10 +02:00
raw_spin_lock_irqsave ( & uic - > lock , flags ) ;
2007-11-14 07:25:21 +11:00
er = mfdcr ( uic - > dcrbase + UIC_ER ) ;
er & = ~ sr ;
mtdcr ( uic - > dcrbase + UIC_ER , er ) ;
2007-11-15 01:00:52 +11:00
/* On the UIC, acking (i.e. clearing the SR bit)
* a level irq will have no effect if the interrupt
* is still asserted by the device , even if
* the interrupt is already masked . Therefore
* we only ack the egde interrupts here , while
* level interrupts are ack ' ed after the actual
* isr call in the uic_unmask_irq ( )
*/
2011-03-25 16:23:57 +01:00
if ( ! irqd_is_level_type ( d ) )
2007-11-15 01:00:52 +11:00
mtdcr ( uic - > dcrbase + UIC_SR , sr ) ;
2010-04-06 09:44:10 +02:00
raw_spin_unlock_irqrestore ( & uic - > lock , flags ) ;
2007-11-14 07:25:21 +11:00
}
2011-03-08 22:27:02 +00:00
static int uic_set_irq_type ( struct irq_data * d , unsigned int flow_type )
2007-04-18 16:36:26 +10:00
{
2011-03-08 22:27:02 +00:00
struct uic * uic = irq_data_get_irq_chip_data ( d ) ;
2011-05-04 15:02:15 +10:00
unsigned int src = irqd_to_hwirq ( d ) ;
2007-04-18 16:36:26 +10:00
unsigned long flags ;
int trigger , polarity ;
u32 tr , pr , mask ;
switch ( flow_type & IRQ_TYPE_SENSE_MASK ) {
case IRQ_TYPE_NONE :
2011-03-08 22:27:02 +00:00
uic_mask_irq ( d ) ;
2007-04-18 16:36:26 +10:00
return 0 ;
case IRQ_TYPE_EDGE_RISING :
trigger = 1 ; polarity = 1 ;
break ;
case IRQ_TYPE_EDGE_FALLING :
trigger = 1 ; polarity = 0 ;
break ;
case IRQ_TYPE_LEVEL_HIGH :
trigger = 0 ; polarity = 1 ;
break ;
case IRQ_TYPE_LEVEL_LOW :
trigger = 0 ; polarity = 0 ;
break ;
default :
return - EINVAL ;
}
mask = ~ ( 1 < < ( 31 - src ) ) ;
2010-04-06 09:44:10 +02:00
raw_spin_lock_irqsave ( & uic - > lock , flags ) ;
2007-04-18 16:36:26 +10:00
tr = mfdcr ( uic - > dcrbase + UIC_TR ) ;
pr = mfdcr ( uic - > dcrbase + UIC_PR ) ;
tr = ( tr & mask ) | ( trigger < < ( 31 - src ) ) ;
pr = ( pr & mask ) | ( polarity < < ( 31 - src ) ) ;
mtdcr ( uic - > dcrbase + UIC_PR , pr ) ;
mtdcr ( uic - > dcrbase + UIC_TR , tr ) ;
2010-04-06 09:44:10 +02:00
raw_spin_unlock_irqrestore ( & uic - > lock , flags ) ;
2007-04-18 16:36:26 +10:00
return 0 ;
}
static struct irq_chip uic_irq_chip = {
2010-01-31 20:33:41 +00:00
. name = " UIC " ,
2011-03-08 22:27:02 +00:00
. irq_unmask = uic_unmask_irq ,
. irq_mask = uic_mask_irq ,
. irq_mask_ack = uic_mask_ack_irq ,
. irq_ack = uic_ack_irq ,
. irq_set_type = uic_set_irq_type ,
2007-04-18 16:36:26 +10:00
} ;
2012-02-14 14:06:50 -07:00
static int uic_host_map ( struct irq_domain * h , unsigned int virq ,
2007-04-18 16:36:26 +10:00
irq_hw_number_t hw )
{
struct uic * uic = h - > host_data ;
2011-03-25 16:45:20 +01:00
irq_set_chip_data ( virq , uic ) ;
2007-04-18 16:36:26 +10:00
/* Despite the name, handle_level_irq() works for both level
* and edge irqs on UIC . FIXME : check this is correct */
2011-03-25 16:45:20 +01:00
irq_set_chip_and_handler ( virq , & uic_irq_chip , handle_level_irq ) ;
2007-04-18 16:36:26 +10:00
/* Set default irq type */
2011-03-25 16:45:20 +01:00
irq_set_irq_type ( virq , IRQ_TYPE_NONE ) ;
2007-04-18 16:36:26 +10:00
return 0 ;
}
2012-02-14 14:06:50 -07:00
static struct irq_domain_ops uic_host_ops = {
2007-04-18 16:36:26 +10:00
. map = uic_host_map ,
2012-01-24 17:09:13 -07:00
. xlate = irq_domain_xlate_twocell ,
2007-04-18 16:36:26 +10:00
} ;
2007-12-07 00:48:26 +11:00
void uic_irq_cascade ( unsigned int virq , struct irq_desc * desc )
2007-04-18 16:36:26 +10:00
{
2011-03-25 16:45:20 +01:00
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
2011-03-25 16:23:57 +01:00
struct irq_data * idata = irq_desc_get_irq_data ( desc ) ;
2011-03-25 16:45:20 +01:00
struct uic * uic = irq_get_handler_data ( virq ) ;
2007-04-18 16:36:26 +10:00
u32 msr ;
int src ;
int subvirq ;
2009-11-17 16:46:45 +01:00
raw_spin_lock ( & desc - > lock ) ;
2011-03-25 16:23:57 +01:00
if ( irqd_is_level_type ( idata ) )
chip - > irq_mask ( idata ) ;
2007-12-07 00:48:26 +11:00
else
2011-03-25 16:23:57 +01:00
chip - > irq_mask_ack ( idata ) ;
2009-11-17 16:46:45 +01:00
raw_spin_unlock ( & desc - > lock ) ;
2007-12-07 00:48:26 +11:00
2007-04-18 16:36:26 +10:00
msr = mfdcr ( uic - > dcrbase + UIC_MSR ) ;
2007-08-14 13:52:42 +10:00
if ( ! msr ) /* spurious interrupt */
2007-12-07 00:48:26 +11:00
goto uic_irq_ret ;
2007-08-14 13:52:42 +10:00
2007-04-18 16:36:26 +10:00
src = 32 - ffs ( msr ) ;
subvirq = irq_linear_revmap ( uic - > irqhost , src ) ;
generic_handle_irq ( subvirq ) ;
2007-12-07 00:48:26 +11:00
uic_irq_ret :
2009-11-17 16:46:45 +01:00
raw_spin_lock ( & desc - > lock ) ;
2011-03-25 16:23:57 +01:00
if ( irqd_is_level_type ( idata ) )
chip - > irq_ack ( idata ) ;
if ( ! irqd_irq_disabled ( idata ) & & chip - > irq_unmask )
chip - > irq_unmask ( idata ) ;
2009-11-17 16:46:45 +01:00
raw_spin_unlock ( & desc - > lock ) ;
2007-04-18 16:36:26 +10:00
}
static struct uic * __init uic_init_one ( struct device_node * node )
{
struct uic * uic ;
const u32 * indexp , * dcrreg ;
int len ;
2007-05-03 17:26:52 +10:00
BUG_ON ( ! of_device_is_compatible ( node , " ibm,uic " ) ) ;
2007-04-18 16:36:26 +10:00
2009-07-01 10:59:57 +00:00
uic = kzalloc ( sizeof ( * uic ) , GFP_KERNEL ) ;
2007-04-18 16:36:26 +10:00
if ( ! uic )
return NULL ; /* FIXME: panic? */
2010-04-06 09:44:10 +02:00
raw_spin_lock_init ( & uic - > lock ) ;
2007-04-29 16:29:08 +10:00
indexp = of_get_property ( node , " cell-index " , & len ) ;
2007-04-18 16:36:26 +10:00
if ( ! indexp | | ( len ! = sizeof ( u32 ) ) ) {
printk ( KERN_ERR " uic: Device node %s has missing or invalid "
" cell-index property \n " , node - > full_name ) ;
return NULL ;
}
uic - > index = * indexp ;
2007-04-29 16:29:08 +10:00
dcrreg = of_get_property ( node , " dcr-reg " , & len ) ;
2007-04-18 16:36:26 +10:00
if ( ! dcrreg | | ( len ! = 2 * sizeof ( u32 ) ) ) {
printk ( KERN_ERR " uic: Device node %s has missing or invalid "
" dcr-reg property \n " , node - > full_name ) ;
return NULL ;
}
uic - > dcrbase = * dcrreg ;
2012-02-14 14:06:54 -07:00
uic - > irqhost = irq_domain_add_linear ( node , NR_UIC_INTS , & uic_host_ops ,
uic ) ;
2008-05-26 12:12:32 +10:00
if ( ! uic - > irqhost )
2007-04-18 16:36:26 +10:00
return NULL ; /* FIXME: panic? */
/* Start with all interrupts disabled, level and non-critical */
mtdcr ( uic - > dcrbase + UIC_ER , 0 ) ;
mtdcr ( uic - > dcrbase + UIC_CR , 0 ) ;
mtdcr ( uic - > dcrbase + UIC_TR , 0 ) ;
/* Clear any pending interrupts, in case the firmware left some */
mtdcr ( uic - > dcrbase + UIC_SR , 0xffffffff ) ;
printk ( " UIC%d (%d IRQ sources) at DCR 0x%x \n " , uic - > index ,
NR_UIC_INTS , uic - > dcrbase ) ;
return uic ;
}
void __init uic_init_tree ( void )
{
struct device_node * np ;
struct uic * uic ;
const u32 * interrupts ;
/* First locate and initialize the top-level UIC */
2007-11-30 06:44:36 +11:00
for_each_compatible_node ( np , NULL , " ibm,uic " ) {
2007-04-29 16:29:08 +10:00
interrupts = of_get_property ( np , " interrupts " , NULL ) ;
2007-11-30 06:44:36 +11:00
if ( ! interrupts )
2007-04-18 16:36:26 +10:00
break ;
}
BUG_ON ( ! np ) ; /* uic_init_tree() assumes there's a UIC as the
* top - level interrupt controller */
primary_uic = uic_init_one ( np ) ;
2007-11-30 06:44:36 +11:00
if ( ! primary_uic )
2007-04-18 16:36:26 +10:00
panic ( " Unable to initialize primary UIC %s \n " , np - > full_name ) ;
irq_set_default_host ( primary_uic - > irqhost ) ;
of_node_put ( np ) ;
/* The scan again for cascaded UICs */
2007-11-30 06:44:36 +11:00
for_each_compatible_node ( np , NULL , " ibm,uic " ) {
2007-04-29 16:29:08 +10:00
interrupts = of_get_property ( np , " interrupts " , NULL ) ;
2007-04-18 16:36:26 +10:00
if ( interrupts ) {
/* Secondary UIC */
int cascade_virq ;
uic = uic_init_one ( np ) ;
if ( ! uic )
panic ( " Unable to initialize a secondary UIC %s \n " ,
np - > full_name ) ;
cascade_virq = irq_of_parse_and_map ( np , 0 ) ;
2011-03-25 16:45:20 +01:00
irq_set_handler_data ( cascade_virq , uic ) ;
irq_set_chained_handler ( cascade_virq , uic_irq_cascade ) ;
2007-04-18 16:36:26 +10:00
/* FIXME: setup critical cascade?? */
}
}
}
/* Return an interrupt vector or NO_IRQ if no interrupt is pending. */
unsigned int uic_get_irq ( void )
{
u32 msr ;
int src ;
BUG_ON ( ! primary_uic ) ;
msr = mfdcr ( primary_uic - > dcrbase + UIC_MSR ) ;
src = 32 - ffs ( msr ) ;
return irq_linear_revmap ( primary_uic - > irqhost , src ) ;
}