2007-10-02 12:15:23 +10:00
/*
* Interrupt controller driver for Xilinx Virtex FPGAs
*
* Copyright ( C ) 2007 Secret Lab Technologies Ltd .
*
* 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 .
*
*/
/*
* This is a driver for the interrupt controller typically found in
* Xilinx Virtex FPGA designs .
*
* The interrupt sense levels are hard coded into the FPGA design with
* typically a 1 : 1 relationship between irq lines and devices ( no shared
* irq lines ) . Therefore , this driver does not attempt to handle edge
* and level interrupts differently .
*/
# undef DEBUG
# include <linux/kernel.h>
# include <linux/irq.h>
# include <linux/of.h>
# include <asm/io.h>
# include <asm/processor.h>
# include <asm/irq.h>
/*
* INTC Registers
*/
# define XINTC_ISR 0 /* Interrupt Status */
# define XINTC_IPR 4 /* Interrupt Pending */
# define XINTC_IER 8 /* Interrupt Enable */
# define XINTC_IAR 12 /* Interrupt Acknowledge */
# define XINTC_SIE 16 /* Set Interrupt Enable bits */
# define XINTC_CIE 20 /* Clear Interrupt Enable bits */
# define XINTC_IVR 24 /* Interrupt Vector */
# define XINTC_MER 28 /* Master Enable */
static struct irq_host * master_irqhost ;
/*
* IRQ Chip operations
*/
static void xilinx_intc_mask ( unsigned int virq )
{
int irq = virq_to_hw ( virq ) ;
void * regs = get_irq_chip_data ( virq ) ;
pr_debug ( " mask: %d \n " , irq ) ;
out_be32 ( regs + XINTC_CIE , 1 < < irq ) ;
}
static void xilinx_intc_unmask ( unsigned int virq )
{
int irq = virq_to_hw ( virq ) ;
void * regs = get_irq_chip_data ( virq ) ;
pr_debug ( " unmask: %d \n " , irq ) ;
out_be32 ( regs + XINTC_SIE , 1 < < irq ) ;
}
static void xilinx_intc_ack ( unsigned int virq )
{
int irq = virq_to_hw ( virq ) ;
void * regs = get_irq_chip_data ( virq ) ;
pr_debug ( " ack: %d \n " , irq ) ;
out_be32 ( regs + XINTC_IAR , 1 < < irq ) ;
}
static struct irq_chip xilinx_intc_irqchip = {
. typename = " Xilinx INTC " ,
. mask = xilinx_intc_mask ,
. unmask = xilinx_intc_unmask ,
. ack = xilinx_intc_ack ,
} ;
/*
* IRQ Host operations
*/
static int xilinx_intc_map ( struct irq_host * h , unsigned int virq ,
irq_hw_number_t irq )
{
set_irq_chip_data ( virq , h - > host_data ) ;
set_irq_chip_and_handler ( virq , & xilinx_intc_irqchip , handle_level_irq ) ;
set_irq_type ( virq , IRQ_TYPE_NONE ) ;
return 0 ;
}
static struct irq_host_ops xilinx_intc_ops = {
. map = xilinx_intc_map ,
} ;
struct irq_host * __init
xilinx_intc_init ( struct device_node * np )
{
struct irq_host * irq ;
struct resource res ;
void * regs ;
int rc ;
/* Find and map the intc registers */
rc = of_address_to_resource ( np , 0 , & res ) ;
if ( rc ) {
printk ( KERN_ERR __FILE__ " : of_address_to_resource() failed \n " ) ;
return NULL ;
}
regs = ioremap ( res . start , 32 ) ;
2008-04-30 10:24:44 -05:00
printk ( KERN_INFO " Xilinx intc at 0x%08LX mapped to 0x%p \n " ,
2007-10-02 12:15:23 +10:00
res . start , regs ) ;
/* Setup interrupt controller */
out_be32 ( regs + XINTC_IER , 0 ) ; /* disable all irqs */
out_be32 ( regs + XINTC_IAR , ~ ( u32 ) 0 ) ; /* Acknowledge pending irqs */
out_be32 ( regs + XINTC_MER , 0x3UL ) ; /* Turn on the Master Enable. */
/* Allocate and initialize an irq_host structure. */
irq = irq_alloc_host ( np , IRQ_HOST_MAP_LINEAR , 32 , & xilinx_intc_ops , - 1 ) ;
if ( ! irq )
panic ( __FILE__ " : Cannot allocate IRQ host \n " ) ;
irq - > host_data = regs ;
return irq ;
}
int xilinx_intc_get_irq ( void )
{
void * regs = master_irqhost - > host_data ;
pr_debug ( " get_irq: \n " ) ;
return irq_linear_revmap ( master_irqhost , in_be32 ( regs + XINTC_IVR ) ) ;
}
void __init xilinx_intc_init_tree ( void )
{
struct device_node * np ;
/* find top level interrupt controller */
2008-01-09 06:35:04 +11:00
for_each_compatible_node ( np , NULL , " xlnx,opb-intc-1.00.c " ) {
2007-10-02 12:15:23 +10:00
if ( ! of_get_property ( np , " interrupts " , NULL ) )
break ;
}
2008-01-09 06:35:04 +11:00
if ( ! np ) {
for_each_compatible_node ( np , NULL , " xlnx,xps-intc-1.00.a " ) {
if ( ! of_get_property ( np , " interrupts " , NULL ) )
break ;
}
}
2007-10-02 12:15:23 +10:00
/* xilinx interrupt controller needs to be top level */
BUG_ON ( ! np ) ;
master_irqhost = xilinx_intc_init ( np ) ;
BUG_ON ( ! master_irqhost ) ;
irq_set_default_host ( master_irqhost ) ;
of_node_put ( np ) ;
}