2009-03-11 18:49:05 +03:00
/*
* TI Common Platform Interrupt Controller ( cp_intc ) driver
*
* Author : Steve Chen < schen @ mvista . com >
* Copyright ( C ) 2008 - 2009 , MontaVista Software , Inc . < source @ mvista . com >
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed " as is " without any warranty of any
* kind , whether express or implied .
*/
2012-05-30 14:18:57 +04:00
# include <linux/export.h>
2009-03-11 18:49:05 +03:00
# include <linux/init.h>
# include <linux/irq.h>
2012-05-30 14:18:57 +04:00
# include <linux/irqdomain.h>
2009-03-11 18:49:05 +03:00
# include <linux/io.h>
2012-05-30 14:18:58 +04:00
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
2009-03-11 18:49:05 +03:00
2010-05-08 01:06:37 +04:00
# include <mach/common.h>
2009-03-11 18:49:05 +03:00
# include <mach/cp_intc.h>
static inline unsigned int cp_intc_read ( unsigned offset )
{
2010-05-08 01:06:37 +04:00
return __raw_readl ( davinci_intc_base + offset ) ;
2009-03-11 18:49:05 +03:00
}
static inline void cp_intc_write ( unsigned long value , unsigned offset )
{
2010-05-08 01:06:37 +04:00
__raw_writel ( value , davinci_intc_base + offset ) ;
2009-03-11 18:49:05 +03:00
}
2010-11-29 12:27:27 +03:00
static void cp_intc_ack_irq ( struct irq_data * d )
2009-03-11 18:49:05 +03:00
{
2012-05-30 14:18:57 +04:00
cp_intc_write ( d - > hwirq , CP_INTC_SYS_STAT_IDX_CLR ) ;
2009-03-11 18:49:05 +03:00
}
/* Disable interrupt */
2010-11-29 12:27:27 +03:00
static void cp_intc_mask_irq ( struct irq_data * d )
2009-03-11 18:49:05 +03:00
{
/* XXX don't know why we need to disable nIRQ here... */
cp_intc_write ( 1 , CP_INTC_HOST_ENABLE_IDX_CLR ) ;
2012-05-30 14:18:57 +04:00
cp_intc_write ( d - > hwirq , CP_INTC_SYS_ENABLE_IDX_CLR ) ;
2009-03-11 18:49:05 +03:00
cp_intc_write ( 1 , CP_INTC_HOST_ENABLE_IDX_SET ) ;
}
/* Enable interrupt */
2010-11-29 12:27:27 +03:00
static void cp_intc_unmask_irq ( struct irq_data * d )
2009-03-11 18:49:05 +03:00
{
2012-05-30 14:18:57 +04:00
cp_intc_write ( d - > hwirq , CP_INTC_SYS_ENABLE_IDX_SET ) ;
2009-03-11 18:49:05 +03:00
}
2010-11-29 12:27:27 +03:00
static int cp_intc_set_irq_type ( struct irq_data * d , unsigned int flow_type )
2009-03-11 18:49:05 +03:00
{
2012-05-30 14:18:57 +04:00
unsigned reg = BIT_WORD ( d - > hwirq ) ;
unsigned mask = BIT_MASK ( d - > hwirq ) ;
2009-03-11 18:49:05 +03:00
unsigned polarity = cp_intc_read ( CP_INTC_SYS_POLARITY ( reg ) ) ;
unsigned type = cp_intc_read ( CP_INTC_SYS_TYPE ( reg ) ) ;
switch ( flow_type ) {
case IRQ_TYPE_EDGE_RISING :
polarity | = mask ;
type | = mask ;
break ;
case IRQ_TYPE_EDGE_FALLING :
polarity & = ~ mask ;
type | = mask ;
break ;
case IRQ_TYPE_LEVEL_HIGH :
polarity | = mask ;
type & = ~ mask ;
break ;
case IRQ_TYPE_LEVEL_LOW :
polarity & = ~ mask ;
type & = ~ mask ;
break ;
default :
return - EINVAL ;
}
cp_intc_write ( polarity , CP_INTC_SYS_POLARITY ( reg ) ) ;
cp_intc_write ( type , CP_INTC_SYS_TYPE ( reg ) ) ;
return 0 ;
}
2009-11-16 14:51:30 +03:00
/*
* Faking this allows us to to work with suspend functions of
* generic drivers which call { enable | disable } _irq_wake for
* wake up interrupt sources ( eg RTC on DA850 ) .
*/
2010-11-29 12:27:27 +03:00
static int cp_intc_set_wake ( struct irq_data * d , unsigned int on )
2009-11-16 14:51:30 +03:00
{
return 0 ;
}
2009-03-11 18:49:05 +03:00
static struct irq_chip cp_intc_irq_chip = {
. name = " cp_intc " ,
2010-11-29 12:27:27 +03:00
. irq_ack = cp_intc_ack_irq ,
. irq_mask = cp_intc_mask_irq ,
. irq_unmask = cp_intc_unmask_irq ,
. irq_set_type = cp_intc_set_irq_type ,
. irq_set_wake = cp_intc_set_wake ,
2009-03-11 18:49:05 +03:00
} ;
2012-05-30 14:18:57 +04:00
static struct irq_domain * cp_intc_domain ;
static int cp_intc_host_map ( struct irq_domain * h , unsigned int virq ,
irq_hw_number_t hw )
{
pr_debug ( " cp_intc_host_map(%d, 0x%lx) \n " , virq , hw ) ;
irq_set_chip ( virq , & cp_intc_irq_chip ) ;
set_irq_flags ( virq , IRQF_VALID | IRQF_PROBE ) ;
irq_set_handler ( virq , handle_edge_irq ) ;
return 0 ;
}
static const struct irq_domain_ops cp_intc_host_ops = {
. map = cp_intc_host_map ,
. xlate = irq_domain_xlate_onetwocell ,
} ;
2012-05-30 14:18:58 +04:00
int __init cp_intc_of_init ( struct device_node * node , struct device_node * parent )
2009-03-11 18:49:05 +03:00
{
2012-05-30 14:18:57 +04:00
u32 num_irq = davinci_soc_info . intc_irq_num ;
2010-05-08 01:06:37 +04:00
u8 * irq_prio = davinci_soc_info . intc_irq_prios ;
u32 * host_map = davinci_soc_info . intc_host_map ;
2009-03-11 18:49:05 +03:00
unsigned num_reg = BITS_TO_LONGS ( num_irq ) ;
2012-05-30 14:18:57 +04:00
int i , irq_base ;
2009-03-11 18:49:05 +03:00
2010-05-08 01:06:37 +04:00
davinci_intc_type = DAVINCI_INTC_TYPE_CP_INTC ;
2012-05-30 14:18:58 +04:00
if ( node ) {
davinci_intc_base = of_iomap ( node , 0 ) ;
if ( of_property_read_u32 ( node , " ti,intc-size " , & num_irq ) )
pr_warn ( " unable to get intc-size, default to %d \n " ,
num_irq ) ;
} else {
davinci_intc_base = ioremap ( davinci_soc_info . intc_base , SZ_8K ) ;
}
2010-05-08 01:06:37 +04:00
if ( WARN_ON ( ! davinci_intc_base ) )
2012-05-30 14:18:57 +04:00
return - EINVAL ;
2009-03-11 18:49:05 +03:00
cp_intc_write ( 0 , CP_INTC_GLOBAL_ENABLE ) ;
/* Disable all host interrupts */
cp_intc_write ( 0 , CP_INTC_HOST_ENABLE ( 0 ) ) ;
/* Disable system interrupts */
for ( i = 0 ; i < num_reg ; i + + )
cp_intc_write ( ~ 0 , CP_INTC_SYS_ENABLE_CLR ( i ) ) ;
/* Set to normal mode, no nesting, no priority hold */
cp_intc_write ( 0 , CP_INTC_CTRL ) ;
cp_intc_write ( 0 , CP_INTC_HOST_CTRL ) ;
/* Clear system interrupt status */
for ( i = 0 ; i < num_reg ; i + + )
cp_intc_write ( ~ 0 , CP_INTC_SYS_STAT_CLR ( i ) ) ;
/* Enable nIRQ (what about nFIQ?) */
cp_intc_write ( 1 , CP_INTC_HOST_ENABLE_IDX_SET ) ;
/*
* Priority is determined by host channel : lower channel number has
* higher priority i . e . channel 0 has highest priority and channel 31
* had the lowest priority .
*/
num_reg = ( num_irq + 3 ) > > 2 ; /* 4 channels per register */
if ( irq_prio ) {
unsigned j , k ;
u32 val ;
for ( k = i = 0 ; i < num_reg ; i + + ) {
for ( val = j = 0 ; j < 4 ; j + + , k + + ) {
val > > = 8 ;
if ( k < num_irq )
val | = irq_prio [ k ] < < 24 ;
}
cp_intc_write ( val , CP_INTC_CHAN_MAP ( i ) ) ;
}
} else {
/*
* Default everything to channel 15 if priority not specified .
* Note that channel 0 - 1 are mapped to nFIQ and channels 2 - 31
* are mapped to nIRQ .
*/
for ( i = 0 ; i < num_reg ; i + + )
cp_intc_write ( 0x0f0f0f0f , CP_INTC_CHAN_MAP ( i ) ) ;
}
2010-03-26 00:43:46 +03:00
if ( host_map )
for ( i = 0 ; host_map [ i ] ! = - 1 ; i + + )
cp_intc_write ( host_map [ i ] , CP_INTC_HOST_MAP ( i ) ) ;
2012-05-30 14:18:57 +04:00
irq_base = irq_alloc_descs ( - 1 , 0 , num_irq , 0 ) ;
if ( irq_base < 0 ) {
pr_warn ( " Couldn't allocate IRQ numbers \n " ) ;
irq_base = 0 ;
}
/* create a legacy host */
cp_intc_domain = irq_domain_add_legacy ( node , num_irq ,
irq_base , 0 , & cp_intc_host_ops , NULL ) ;
if ( ! cp_intc_domain ) {
pr_err ( " cp_intc: failed to allocate irq host! \n " ) ;
return - EINVAL ;
2009-03-11 18:49:05 +03:00
}
/* Enable global interrupt */
cp_intc_write ( 1 , CP_INTC_GLOBAL_ENABLE ) ;
2012-05-30 14:18:57 +04:00
return 0 ;
}
void __init cp_intc_init ( void )
{
2012-05-30 14:18:58 +04:00
cp_intc_of_init ( NULL , NULL ) ;
2009-03-11 18:49:05 +03:00
}