2012-10-09 13:54:47 +04:00
/*
* Meta External interrupt code .
*
* Copyright ( C ) 2005 - 2012 Imagination Technologies Ltd .
*
* External interrupts on Meta are configured at two - levels , in the CPU core and
* in the external trigger block . Interrupts from SoC peripherals are
* multiplexed onto a single Meta CPU " trigger " - traditionally it has always
* been trigger 2 ( TR2 ) . For info on how de - multiplexing happens check out
* meta_intc_irq_demux ( ) .
*/
# include <linux/interrupt.h>
# include <linux/irqchip/metag-ext.h>
# include <linux/irqdomain.h>
# include <linux/io.h>
# include <linux/of.h>
# include <linux/slab.h>
# include <linux/syscore_ops.h>
# include <asm/irq.h>
# include <asm/hwthread.h>
# define HWSTAT_STRIDE 8
# define HWVEC_BLK_STRIDE 0x1000
/**
* struct meta_intc_priv - private meta external interrupt data
* @ nr_banks : Number of interrupt banks
* @ domain : IRQ domain for all banks of external IRQs
* @ unmasked : Record of unmasked IRQs
* @ levels_altered : Record of altered level bits
*/
struct meta_intc_priv {
unsigned int nr_banks ;
struct irq_domain * domain ;
unsigned long unmasked [ 4 ] ;
# ifdef CONFIG_METAG_SUSPEND_MEM
unsigned long levels_altered [ 4 ] ;
# endif
} ;
/* Private data for the one and only external interrupt controller */
static struct meta_intc_priv meta_intc_priv ;
/**
* meta_intc_offset ( ) - Get the offset into the bank of a hardware IRQ number
* @ hw : Hardware IRQ number ( within external trigger block )
*
* Returns : Bit offset into the IRQ ' s bank registers
*/
static unsigned int meta_intc_offset ( irq_hw_number_t hw )
{
return hw & 0x1f ;
}
/**
* meta_intc_bank ( ) - Get the bank number of a hardware IRQ number
* @ hw : Hardware IRQ number ( within external trigger block )
*
* Returns : Bank number indicating which register the IRQ ' s bits are
*/
static unsigned int meta_intc_bank ( irq_hw_number_t hw )
{
return hw > > 5 ;
}
/**
* meta_intc_stat_addr ( ) - Get the address of a HWSTATEXT register
* @ hw : Hardware IRQ number ( within external trigger block )
*
* Returns : Address of a HWSTATEXT register containing the status bit for
* the specified hardware IRQ number
*/
static void __iomem * meta_intc_stat_addr ( irq_hw_number_t hw )
{
return ( void __iomem * ) ( HWSTATEXT +
HWSTAT_STRIDE * meta_intc_bank ( hw ) ) ;
}
/**
* meta_intc_level_addr ( ) - Get the address of a HWLEVELEXT register
* @ hw : Hardware IRQ number ( within external trigger block )
*
* Returns : Address of a HWLEVELEXT register containing the sense bit for
* the specified hardware IRQ number
*/
static void __iomem * meta_intc_level_addr ( irq_hw_number_t hw )
{
return ( void __iomem * ) ( HWLEVELEXT +
HWSTAT_STRIDE * meta_intc_bank ( hw ) ) ;
}
/**
* meta_intc_mask_addr ( ) - Get the address of a HWMASKEXT register
* @ hw : Hardware IRQ number ( within external trigger block )
*
* Returns : Address of a HWMASKEXT register containing the mask bit for the
* specified hardware IRQ number
*/
static void __iomem * meta_intc_mask_addr ( irq_hw_number_t hw )
{
return ( void __iomem * ) ( HWMASKEXT +
HWSTAT_STRIDE * meta_intc_bank ( hw ) ) ;
}
/**
* meta_intc_vec_addr ( ) - Get the vector address of a hardware interrupt
* @ hw : Hardware IRQ number ( within external trigger block )
*
* Returns : Address of a HWVECEXT register controlling the core trigger to
* vector the IRQ onto
*/
static inline void __iomem * meta_intc_vec_addr ( irq_hw_number_t hw )
{
return ( void __iomem * ) ( HWVEC0EXT +
HWVEC_BLK_STRIDE * meta_intc_bank ( hw ) +
HWVECnEXT_STRIDE * meta_intc_offset ( hw ) ) ;
}
/**
* meta_intc_startup_irq ( ) - set up an external irq
* @ data : data for the external irq to start up
*
* Multiplex interrupts for irq onto TR2 . Clear any pending interrupts and
* unmask irq , both using the appropriate callbacks .
*/
static unsigned int meta_intc_startup_irq ( struct irq_data * data )
{
irq_hw_number_t hw = data - > hwirq ;
void __iomem * vec_addr = meta_intc_vec_addr ( hw ) ;
int thread = hard_processor_id ( ) ;
/* Perform any necessary acking. */
if ( data - > chip - > irq_ack )
data - > chip - > irq_ack ( data ) ;
/* Wire up this interrupt to the core with HWVECxEXT. */
metag_out32 ( TBI_TRIG_VEC ( TBID_SIGNUM_TR2 ( thread ) ) , vec_addr ) ;
/* Perform any necessary unmasking. */
data - > chip - > irq_unmask ( data ) ;
return 0 ;
}
/**
* meta_intc_shutdown_irq ( ) - turn off an external irq
* @ data : data for the external irq to turn off
*
* Mask irq using the appropriate callback and stop muxing it onto TR2 .
*/
static void meta_intc_shutdown_irq ( struct irq_data * data )
{
irq_hw_number_t hw = data - > hwirq ;
void __iomem * vec_addr = meta_intc_vec_addr ( hw ) ;
/* Mask the IRQ */
data - > chip - > irq_mask ( data ) ;
/*
* Disable the IRQ at the core by removing the interrupt from
* the HW vector mapping .
*/
metag_out32 ( 0 , vec_addr ) ;
}
/**
* meta_intc_ack_irq ( ) - acknowledge an external irq
* @ data : data for the external irq to ack
*
* Clear down an edge interrupt in the status register .
*/
static void meta_intc_ack_irq ( struct irq_data * data )
{
irq_hw_number_t hw = data - > hwirq ;
unsigned int bit = 1 < < meta_intc_offset ( hw ) ;
void __iomem * stat_addr = meta_intc_stat_addr ( hw ) ;
/* Ack the int, if it is still 'on'.
* NOTE - this only works for edge triggered interrupts .
*/
if ( metag_in32 ( stat_addr ) & bit )
metag_out32 ( bit , stat_addr ) ;
}
/**
* record_irq_is_masked ( ) - record the IRQ masked so it doesn ' t get handled
* @ data : data for the external irq to record
*
* This should get called whenever an external IRQ is masked ( by whichever
* callback is used ) . It records the IRQ masked so that it doesn ' t get handled
* if it still shows up in the status register .
*/
static void record_irq_is_masked ( struct irq_data * data )
{
struct meta_intc_priv * priv = & meta_intc_priv ;
irq_hw_number_t hw = data - > hwirq ;
clear_bit ( meta_intc_offset ( hw ) , & priv - > unmasked [ meta_intc_bank ( hw ) ] ) ;
}
/**
* record_irq_is_unmasked ( ) - record the IRQ unmasked so it can be handled
* @ data : data for the external irq to record
*
* This should get called whenever an external IRQ is unmasked ( by whichever
* callback is used ) . It records the IRQ unmasked so that it gets handled if it
* shows up in the status register .
*/
static void record_irq_is_unmasked ( struct irq_data * data )
{
struct meta_intc_priv * priv = & meta_intc_priv ;
irq_hw_number_t hw = data - > hwirq ;
set_bit ( meta_intc_offset ( hw ) , & priv - > unmasked [ meta_intc_bank ( hw ) ] ) ;
}
/*
* For use by wrapper IRQ drivers
*/
/**
* meta_intc_mask_irq_simple ( ) - minimal mask used by wrapper IRQ drivers
* @ data : data for the external irq being masked
*
* This should be called by any wrapper IRQ driver mask functions . it doesn ' t do
* any masking but records the IRQ as masked so that the core code knows the
* mask has taken place . It is the callers responsibility to ensure that the IRQ
* won ' t trigger an interrupt to the core .
*/
void meta_intc_mask_irq_simple ( struct irq_data * data )
{
record_irq_is_masked ( data ) ;
}
/**
* meta_intc_unmask_irq_simple ( ) - minimal unmask used by wrapper IRQ drivers
* @ data : data for the external irq being unmasked
*
* This should be called by any wrapper IRQ driver unmask functions . it doesn ' t
* do any unmasking but records the IRQ as unmasked so that the core code knows
* the unmask has taken place . It is the callers responsibility to ensure that
* the IRQ can now trigger an interrupt to the core .
*/
void meta_intc_unmask_irq_simple ( struct irq_data * data )
{
record_irq_is_unmasked ( data ) ;
}
/**
* meta_intc_mask_irq ( ) - mask an external irq using HWMASKEXT
* @ data : data for the external irq to mask
*
* This is a default implementation of a mask function which makes use of the
* HWMASKEXT registers available in newer versions .
*
* Earlier versions without these registers should use SoC level IRQ masking
* which call the meta_intc_ * _simple ( ) functions above , or if that isn ' t
* available should use the fallback meta_intc_ * _nomask ( ) functions below .
*/
static void meta_intc_mask_irq ( struct irq_data * data )
{
irq_hw_number_t hw = data - > hwirq ;
unsigned int bit = 1 < < meta_intc_offset ( hw ) ;
void __iomem * mask_addr = meta_intc_mask_addr ( hw ) ;
unsigned long flags ;
record_irq_is_masked ( data ) ;
/* update the interrupt mask */
__global_lock2 ( flags ) ;
metag_out32 ( metag_in32 ( mask_addr ) & ~ bit , mask_addr ) ;
__global_unlock2 ( flags ) ;
}
/**
* meta_intc_unmask_irq ( ) - unmask an external irq using HWMASKEXT
* @ data : data for the external irq to unmask
*
* This is a default implementation of an unmask function which makes use of the
* HWMASKEXT registers available on new versions . It should be paired with
* meta_intc_mask_irq ( ) above .
*/
static void meta_intc_unmask_irq ( struct irq_data * data )
{
irq_hw_number_t hw = data - > hwirq ;
unsigned int bit = 1 < < meta_intc_offset ( hw ) ;
void __iomem * mask_addr = meta_intc_mask_addr ( hw ) ;
unsigned long flags ;
record_irq_is_unmasked ( data ) ;
/* update the interrupt mask */
__global_lock2 ( flags ) ;
metag_out32 ( metag_in32 ( mask_addr ) | bit , mask_addr ) ;
__global_unlock2 ( flags ) ;
}
/**
* meta_intc_mask_irq_nomask ( ) - mask an external irq by unvectoring
* @ data : data for the external irq to mask
*
* This is the version of the mask function for older versions which don ' t have
* HWMASKEXT registers , or a SoC level means of masking IRQs . Instead the IRQ is
* unvectored from the core and retriggered if necessary later .
*/
static void meta_intc_mask_irq_nomask ( struct irq_data * data )
{
irq_hw_number_t hw = data - > hwirq ;
void __iomem * vec_addr = meta_intc_vec_addr ( hw ) ;
record_irq_is_masked ( data ) ;
/* there is no interrupt mask, so unvector the interrupt */
metag_out32 ( 0 , vec_addr ) ;
}
/**
* meta_intc_unmask_edge_irq_nomask ( ) - unmask an edge irq by revectoring
* @ data : data for the external irq to unmask
*
* This is the version of the unmask function for older versions which don ' t
* have HWMASKEXT registers , or a SoC level means of masking IRQs . Instead the
* IRQ is revectored back to the core and retriggered if necessary .
*
* The retriggering done by this function is specific to edge interrupts .
*/
static void meta_intc_unmask_edge_irq_nomask ( struct irq_data * data )
{
irq_hw_number_t hw = data - > hwirq ;
unsigned int bit = 1 < < meta_intc_offset ( hw ) ;
void __iomem * stat_addr = meta_intc_stat_addr ( hw ) ;
void __iomem * vec_addr = meta_intc_vec_addr ( hw ) ;
unsigned int thread = hard_processor_id ( ) ;
record_irq_is_unmasked ( data ) ;
/* there is no interrupt mask, so revector the interrupt */
metag_out32 ( TBI_TRIG_VEC ( TBID_SIGNUM_TR2 ( thread ) ) , vec_addr ) ;
/*
* Re - trigger interrupt
*
* Writing a 1 toggles , and a 0 - > 1 transition triggers . We only
* retrigger if the status bit is already set , which means we
* need to clear it first . Retriggering is fundamentally racy
* because if the interrupt fires again after we clear it we
* could end up clearing it again and the interrupt handler
* thinking it hasn ' t fired . Therefore we need to keep trying to
* retrigger until the bit is set .
*/
if ( metag_in32 ( stat_addr ) & bit ) {
metag_out32 ( bit , stat_addr ) ;
while ( ! ( metag_in32 ( stat_addr ) & bit ) )
metag_out32 ( bit , stat_addr ) ;
}
}
/**
* meta_intc_unmask_level_irq_nomask ( ) - unmask a level irq by revectoring
* @ data : data for the external irq to unmask
*
* This is the version of the unmask function for older versions which don ' t
* have HWMASKEXT registers , or a SoC level means of masking IRQs . Instead the
* IRQ is revectored back to the core and retriggered if necessary .
*
* The retriggering done by this function is specific to level interrupts .
*/
static void meta_intc_unmask_level_irq_nomask ( struct irq_data * data )
{
irq_hw_number_t hw = data - > hwirq ;
unsigned int bit = 1 < < meta_intc_offset ( hw ) ;
void __iomem * stat_addr = meta_intc_stat_addr ( hw ) ;
void __iomem * vec_addr = meta_intc_vec_addr ( hw ) ;
unsigned int thread = hard_processor_id ( ) ;
record_irq_is_unmasked ( data ) ;
/* there is no interrupt mask, so revector the interrupt */
metag_out32 ( TBI_TRIG_VEC ( TBID_SIGNUM_TR2 ( thread ) ) , vec_addr ) ;
/* Re-trigger interrupt */
/* Writing a 1 triggers interrupt */
if ( metag_in32 ( stat_addr ) & bit )
metag_out32 ( bit , stat_addr ) ;
}
/**
* meta_intc_irq_set_type ( ) - set the type of an external irq
* @ data : data for the external irq to set the type of
* @ flow_type : new irq flow type
*
* Set the flow type of an external interrupt . This updates the irq chip and irq
* handler depending on whether the irq is edge or level sensitive ( the polarity
* is ignored ) , and also sets up the bit in HWLEVELEXT so the hardware knows
* when to trigger .
*/
static int meta_intc_irq_set_type ( struct irq_data * data , unsigned int flow_type )
{
# ifdef CONFIG_METAG_SUSPEND_MEM
struct meta_intc_priv * priv = & meta_intc_priv ;
# endif
irq_hw_number_t hw = data - > hwirq ;
unsigned int bit = 1 < < meta_intc_offset ( hw ) ;
void __iomem * level_addr = meta_intc_level_addr ( hw ) ;
unsigned long flags ;
unsigned int level ;
/* update the chip/handler */
if ( flow_type & IRQ_TYPE_LEVEL_MASK )
2015-06-23 15:39:45 +03:00
irq_set_chip_handler_name_locked ( data , & meta_intc_level_chip ,
handle_level_irq , NULL ) ;
2012-10-09 13:54:47 +04:00
else
2015-06-23 15:39:45 +03:00
irq_set_chip_handler_name_locked ( data , & meta_intc_edge_chip ,
handle_edge_irq , NULL ) ;
2012-10-09 13:54:47 +04:00
/* and clear/set the bit in HWLEVELEXT */
__global_lock2 ( flags ) ;
level = metag_in32 ( level_addr ) ;
if ( flow_type & IRQ_TYPE_LEVEL_MASK )
level | = bit ;
else
level & = ~ bit ;
metag_out32 ( level , level_addr ) ;
# ifdef CONFIG_METAG_SUSPEND_MEM
priv - > levels_altered [ meta_intc_bank ( hw ) ] | = bit ;
# endif
__global_unlock2 ( flags ) ;
return 0 ;
}
/**
* meta_intc_irq_demux ( ) - external irq de - multiplexer
* @ desc : the interrupt description structure for this irq
*
* The cpu receives an interrupt on TR2 when a SoC interrupt has occurred . It is
* this function ' s job to demux this irq and figure out exactly which external
* irq needs servicing .
*
* Whilst using TR2 to detect external interrupts is a software convention it is
* ( hopefully ) unlikely to change .
*/
2015-09-14 11:42:37 +03:00
static void meta_intc_irq_demux ( struct irq_desc * desc )
2012-10-09 13:54:47 +04:00
{
struct meta_intc_priv * priv = & meta_intc_priv ;
irq_hw_number_t hw ;
unsigned int bank , irq_no , status ;
void __iomem * stat_addr = meta_intc_stat_addr ( 0 ) ;
/*
* Locate which interrupt has caused our handler to run .
*/
for ( bank = 0 ; bank < priv - > nr_banks ; + + bank ) {
/* Which interrupts are currently pending in this bank? */
recalculate :
status = metag_in32 ( stat_addr ) & priv - > unmasked [ bank ] ;
for ( hw = bank * 32 ; status ; status > > = 1 , + + hw ) {
if ( status & 0x1 ) {
/*
* Map the hardware IRQ number to a virtual
* Linux IRQ number .
*/
irq_no = irq_linear_revmap ( priv - > domain , hw ) ;
/*
* Only fire off external interrupts that are
* registered to be handled by the kernel .
* Other external interrupts are probably being
* handled by other Meta hardware threads .
*/
generic_handle_irq ( irq_no ) ;
/*
* The handler may have re - enabled interrupts
* which could have caused a nested invocation
* of this code and make the copy of the
* status register we are using invalid .
*/
goto recalculate ;
}
}
stat_addr + = HWSTAT_STRIDE ;
}
}
# ifdef CONFIG_SMP
/**
* meta_intc_set_affinity ( ) - set the affinity for an interrupt
* @ data : data for the external irq to set the affinity of
* @ cpumask : cpu mask representing cpus which can handle the interrupt
* @ force : whether to force ( ignored )
*
* Revector the specified external irq onto a specific cpu ' s TR2 trigger , so
* that that cpu tends to be the one who handles it .
*/
static int meta_intc_set_affinity ( struct irq_data * data ,
const struct cpumask * cpumask , bool force )
{
irq_hw_number_t hw = data - > hwirq ;
void __iomem * vec_addr = meta_intc_vec_addr ( hw ) ;
unsigned int cpu , thread ;
/*
* Wire up this interrupt from HWVECxEXT to the Meta core .
*
* Note that we can ' t wire up HWVECxEXT to interrupt more than
* one cpu ( the interrupt code doesn ' t support it ) , so we just
* pick the first cpu we find in ' cpumask ' .
*/
2014-02-26 02:05:35 +04:00
cpu = cpumask_any_and ( cpumask , cpu_online_mask ) ;
2012-10-09 13:54:47 +04:00
thread = cpu_2_hwthread_id [ cpu ] ;
metag_out32 ( TBI_TRIG_VEC ( TBID_SIGNUM_TR2 ( thread ) ) , vec_addr ) ;
return 0 ;
}
# else
# define meta_intc_set_affinity NULL
# endif
# ifdef CONFIG_PM_SLEEP
# define META_INTC_CHIP_FLAGS (IRQCHIP_MASK_ON_SUSPEND \
| IRQCHIP_SKIP_SET_WAKE )
# else
# define META_INTC_CHIP_FLAGS 0
# endif
/* public edge/level irq chips which SoCs can override */
struct irq_chip meta_intc_edge_chip = {
. irq_startup = meta_intc_startup_irq ,
. irq_shutdown = meta_intc_shutdown_irq ,
. irq_ack = meta_intc_ack_irq ,
. irq_mask = meta_intc_mask_irq ,
. irq_unmask = meta_intc_unmask_irq ,
. irq_set_type = meta_intc_irq_set_type ,
. irq_set_affinity = meta_intc_set_affinity ,
. flags = META_INTC_CHIP_FLAGS ,
} ;
struct irq_chip meta_intc_level_chip = {
. irq_startup = meta_intc_startup_irq ,
. irq_shutdown = meta_intc_shutdown_irq ,
. irq_set_type = meta_intc_irq_set_type ,
. irq_mask = meta_intc_mask_irq ,
. irq_unmask = meta_intc_unmask_irq ,
. irq_set_affinity = meta_intc_set_affinity ,
. flags = META_INTC_CHIP_FLAGS ,
} ;
/**
* meta_intc_map ( ) - map an external irq
* @ d : irq domain of external trigger block
* @ irq : virtual irq number
* @ hw : hardware irq number within external trigger block
*
* This sets up a virtual irq for a specified hardware interrupt . The irq chip
* and handler is configured , using the HWLEVELEXT registers to determine
* edge / level flow type . These registers will have been set when the irq type is
* set ( or set to a default at init time ) .
*/
static int meta_intc_map ( struct irq_domain * d , unsigned int irq ,
irq_hw_number_t hw )
{
unsigned int bit = 1 < < meta_intc_offset ( hw ) ;
void __iomem * level_addr = meta_intc_level_addr ( hw ) ;
/* Go by the current sense in the HWLEVELEXT register */
if ( metag_in32 ( level_addr ) & bit )
irq_set_chip_and_handler ( irq , & meta_intc_level_chip ,
handle_level_irq ) ;
else
irq_set_chip_and_handler ( irq , & meta_intc_edge_chip ,
handle_edge_irq ) ;
return 0 ;
}
static const struct irq_domain_ops meta_intc_domain_ops = {
. map = meta_intc_map ,
. xlate = irq_domain_xlate_twocell ,
} ;
# ifdef CONFIG_METAG_SUSPEND_MEM
/**
* struct meta_intc_context - suspend context
* @ levels : State of HWLEVELEXT registers
* @ masks : State of HWMASKEXT registers
* @ vectors : State of HWVECEXT registers
* @ txvecint : State of TxVECINT registers
*
* This structure stores the IRQ state across suspend .
*/
struct meta_intc_context {
u32 levels [ 4 ] ;
u32 masks [ 4 ] ;
u8 vectors [ 4 * 32 ] ;
u8 txvecint [ 4 ] [ 4 ] ;
} ;
/* suspend context */
static struct meta_intc_context * meta_intc_context ;
/**
* meta_intc_suspend ( ) - store irq state
*
* To avoid interfering with other threads we only save the IRQ state of IRQs in
* use by Linux .
*/
static int meta_intc_suspend ( void )
{
struct meta_intc_priv * priv = & meta_intc_priv ;
int i , j ;
irq_hw_number_t hw ;
unsigned int bank ;
unsigned long flags ;
struct meta_intc_context * context ;
void __iomem * level_addr , * mask_addr , * vec_addr ;
u32 mask , bit ;
context = kzalloc ( sizeof ( * context ) , GFP_ATOMIC ) ;
if ( ! context )
return - ENOMEM ;
hw = 0 ;
level_addr = meta_intc_level_addr ( 0 ) ;
mask_addr = meta_intc_mask_addr ( 0 ) ;
for ( bank = 0 ; bank < priv - > nr_banks ; + + bank ) {
vec_addr = meta_intc_vec_addr ( hw ) ;
/* create mask of interrupts in use */
mask = 0 ;
for ( bit = 1 ; bit ; bit < < = 1 ) {
i = irq_linear_revmap ( priv - > domain , hw ) ;
/* save mapped irqs which are enabled or have actions */
if ( i & & ( ! irqd_irq_disabled ( irq_get_irq_data ( i ) ) | |
irq_has_action ( i ) ) ) {
mask | = bit ;
/* save trigger vector */
context - > vectors [ hw ] = metag_in32 ( vec_addr ) ;
}
+ + hw ;
vec_addr + = HWVECnEXT_STRIDE ;
}
/* save level state if any IRQ levels altered */
if ( priv - > levels_altered [ bank ] )
context - > levels [ bank ] = metag_in32 ( level_addr ) ;
/* save mask state if any IRQs in use */
if ( mask )
context - > masks [ bank ] = metag_in32 ( mask_addr ) ;
level_addr + = HWSTAT_STRIDE ;
mask_addr + = HWSTAT_STRIDE ;
}
/* save trigger matrixing */
__global_lock2 ( flags ) ;
for ( i = 0 ; i < 4 ; + + i )
for ( j = 0 ; j < 4 ; + + j )
context - > txvecint [ i ] [ j ] = metag_in32 ( T0VECINT_BHALT +
TnVECINT_STRIDE * i +
8 * j ) ;
__global_unlock2 ( flags ) ;
meta_intc_context = context ;
return 0 ;
}
/**
* meta_intc_resume ( ) - restore saved irq state
*
* Restore the saved IRQ state and drop it .
*/
static void meta_intc_resume ( void )
{
struct meta_intc_priv * priv = & meta_intc_priv ;
int i , j ;
irq_hw_number_t hw ;
unsigned int bank ;
unsigned long flags ;
struct meta_intc_context * context = meta_intc_context ;
void __iomem * level_addr , * mask_addr , * vec_addr ;
u32 mask , bit , tmp ;
meta_intc_context = NULL ;
hw = 0 ;
level_addr = meta_intc_level_addr ( 0 ) ;
mask_addr = meta_intc_mask_addr ( 0 ) ;
for ( bank = 0 ; bank < priv - > nr_banks ; + + bank ) {
vec_addr = meta_intc_vec_addr ( hw ) ;
/* create mask of interrupts in use */
mask = 0 ;
for ( bit = 1 ; bit ; bit < < = 1 ) {
i = irq_linear_revmap ( priv - > domain , hw ) ;
/* restore mapped irqs, enabled or with actions */
if ( i & & ( ! irqd_irq_disabled ( irq_get_irq_data ( i ) ) | |
irq_has_action ( i ) ) ) {
mask | = bit ;
/* restore trigger vector */
metag_out32 ( context - > vectors [ hw ] , vec_addr ) ;
}
+ + hw ;
vec_addr + = HWVECnEXT_STRIDE ;
}
if ( mask ) {
/* restore mask state */
__global_lock2 ( flags ) ;
tmp = metag_in32 ( mask_addr ) ;
tmp = ( tmp & ~ mask ) | ( context - > masks [ bank ] & mask ) ;
metag_out32 ( tmp , mask_addr ) ;
__global_unlock2 ( flags ) ;
}
mask = priv - > levels_altered [ bank ] ;
if ( mask ) {
/* restore level state */
__global_lock2 ( flags ) ;
tmp = metag_in32 ( level_addr ) ;
tmp = ( tmp & ~ mask ) | ( context - > levels [ bank ] & mask ) ;
metag_out32 ( tmp , level_addr ) ;
__global_unlock2 ( flags ) ;
}
level_addr + = HWSTAT_STRIDE ;
mask_addr + = HWSTAT_STRIDE ;
}
/* restore trigger matrixing */
__global_lock2 ( flags ) ;
for ( i = 0 ; i < 4 ; + + i ) {
for ( j = 0 ; j < 4 ; + + j ) {
metag_out32 ( context - > txvecint [ i ] [ j ] ,
T0VECINT_BHALT +
TnVECINT_STRIDE * i +
8 * j ) ;
}
}
__global_unlock2 ( flags ) ;
kfree ( context ) ;
}
static struct syscore_ops meta_intc_syscore_ops = {
. suspend = meta_intc_suspend ,
. resume = meta_intc_resume ,
} ;
static void __init meta_intc_init_syscore_ops ( struct meta_intc_priv * priv )
{
register_syscore_ops ( & meta_intc_syscore_ops ) ;
}
# else
# define meta_intc_init_syscore_ops(priv) do {} while (0)
# endif
/**
* meta_intc_init_cpu ( ) - register with a Meta cpu
* @ priv : private interrupt controller data
* @ cpu : the CPU to register on
*
* Configure @ cpu ' s TR2 irq so that we can demux external irqs .
*/
static void __init meta_intc_init_cpu ( struct meta_intc_priv * priv , int cpu )
{
unsigned int thread = cpu_2_hwthread_id [ cpu ] ;
unsigned int signum = TBID_SIGNUM_TR2 ( thread ) ;
int irq = tbisig_map ( signum ) ;
/* Register the multiplexed IRQ handler */
irq_set_chained_handler ( irq , meta_intc_irq_demux ) ;
irq_set_irq_type ( irq , IRQ_TYPE_LEVEL_LOW ) ;
}
/**
* meta_intc_no_mask ( ) - indicate lack of HWMASKEXT registers
*
* Called from SoC code ( or init code below ) to dynamically indicate the lack of
* HWMASKEXT registers ( for example depending on some SoC revision register ) .
* This alters the irq mask and unmask callbacks to use the fallback
* unvectoring / retriggering technique instead of using HWMASKEXT registers .
*/
void __init meta_intc_no_mask ( void )
{
meta_intc_edge_chip . irq_mask = meta_intc_mask_irq_nomask ;
meta_intc_edge_chip . irq_unmask = meta_intc_unmask_edge_irq_nomask ;
meta_intc_level_chip . irq_mask = meta_intc_mask_irq_nomask ;
meta_intc_level_chip . irq_unmask = meta_intc_unmask_level_irq_nomask ;
}
/**
* init_external_IRQ ( ) - initialise the external irq controller
*
* Set up the external irq controller using device tree properties . This is
* called from init_IRQ ( ) .
*/
int __init init_external_IRQ ( void )
{
struct meta_intc_priv * priv = & meta_intc_priv ;
struct device_node * node ;
int ret , cpu ;
u32 val ;
bool no_masks = false ;
node = of_find_compatible_node ( NULL , NULL , " img,meta-intc " ) ;
if ( ! node )
return - ENOENT ;
/* Get number of banks */
ret = of_property_read_u32 ( node , " num-banks " , & val ) ;
if ( ret ) {
pr_err ( " meta-intc: No num-banks property found \n " ) ;
return ret ;
}
if ( val < 1 | | val > 4 ) {
pr_err ( " meta-intc: num-banks (%u) out of range \n " , val ) ;
return - EINVAL ;
}
priv - > nr_banks = val ;
/* Are any mask registers present? */
if ( of_get_property ( node , " no-mask " , NULL ) )
no_masks = true ;
/* No HWMASKEXT registers present? */
if ( no_masks )
meta_intc_no_mask ( ) ;
/* Set up an IRQ domain */
/*
* This is a legacy IRQ domain for now until all the platform setup code
* has been converted to devicetree .
*/
priv - > domain = irq_domain_add_linear ( node , priv - > nr_banks * 32 ,
& meta_intc_domain_ops , priv ) ;
if ( unlikely ( ! priv - > domain ) ) {
pr_err ( " meta-intc: cannot add IRQ domain \n " ) ;
return - ENOMEM ;
}
/* Setup TR2 for all cpus. */
for_each_possible_cpu ( cpu )
meta_intc_init_cpu ( priv , cpu ) ;
/* Set up system suspend/resume callbacks */
meta_intc_init_syscore_ops ( priv ) ;
pr_info ( " meta-intc: External IRQ controller initialised (%u IRQs) \n " ,
priv - > nr_banks * 32 ) ;
return 0 ;
}