2012-11-14 12:59:14 +04:00
/*
* Allwinner A1X SoCs IRQ chip driver .
*
* Copyright ( C ) 2012 Maxime Ripard
*
* Maxime Ripard < maxime . ripard @ free - electrons . com >
*
* Based on code from
* Allwinner Technology Co . , Ltd . < www . allwinnertech . com >
* Benn Huang < benn @ allwinnertech . 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 .
*/
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/irqchip/sunxi.h>
# define SUNXI_IRQ_VECTOR_REG 0x00
# define SUNXI_IRQ_PROTECTION_REG 0x08
# define SUNXI_IRQ_NMI_CTRL_REG 0x0c
# define SUNXI_IRQ_PENDING_REG(x) (0x10 + 0x4 * x)
# define SUNXI_IRQ_FIQ_PENDING_REG(x) (0x20 + 0x4 * x)
# define SUNXI_IRQ_ENABLE_REG(x) (0x40 + 0x4 * x)
# define SUNXI_IRQ_MASK_REG(x) (0x50 + 0x4 * x)
static void __iomem * sunxi_irq_base ;
static struct irq_domain * sunxi_irq_domain ;
void sunxi_irq_ack ( struct irq_data * irqd )
{
unsigned int irq = irqd_to_hwirq ( irqd ) ;
unsigned int irq_off = irq % 32 ;
int reg = irq / 32 ;
u32 val ;
val = readl ( sunxi_irq_base + SUNXI_IRQ_PENDING_REG ( reg ) ) ;
writel ( val | ( 1 < < irq_off ) ,
sunxi_irq_base + SUNXI_IRQ_PENDING_REG ( reg ) ) ;
}
static void sunxi_irq_mask ( struct irq_data * irqd )
{
unsigned int irq = irqd_to_hwirq ( irqd ) ;
unsigned int irq_off = irq % 32 ;
int reg = irq / 32 ;
u32 val ;
val = readl ( sunxi_irq_base + SUNXI_IRQ_ENABLE_REG ( reg ) ) ;
writel ( val & ~ ( 1 < < irq_off ) ,
sunxi_irq_base + SUNXI_IRQ_ENABLE_REG ( reg ) ) ;
}
static void sunxi_irq_unmask ( struct irq_data * irqd )
{
unsigned int irq = irqd_to_hwirq ( irqd ) ;
unsigned int irq_off = irq % 32 ;
int reg = irq / 32 ;
u32 val ;
val = readl ( sunxi_irq_base + SUNXI_IRQ_ENABLE_REG ( reg ) ) ;
writel ( val | ( 1 < < irq_off ) ,
sunxi_irq_base + SUNXI_IRQ_ENABLE_REG ( reg ) ) ;
}
static struct irq_chip sunxi_irq_chip = {
. name = " sunxi_irq " ,
. irq_ack = sunxi_irq_ack ,
. irq_mask = sunxi_irq_mask ,
. irq_unmask = sunxi_irq_unmask ,
} ;
static int sunxi_irq_map ( struct irq_domain * d , unsigned int virq ,
irq_hw_number_t hw )
{
irq_set_chip_and_handler ( virq , & sunxi_irq_chip ,
handle_level_irq ) ;
set_irq_flags ( virq , IRQF_VALID | IRQF_PROBE ) ;
return 0 ;
}
static struct irq_domain_ops sunxi_irq_ops = {
. map = sunxi_irq_map ,
. xlate = irq_domain_xlate_onecell ,
} ;
static int __init sunxi_of_init ( struct device_node * node ,
struct device_node * parent )
{
sunxi_irq_base = of_iomap ( node , 0 ) ;
if ( ! sunxi_irq_base )
panic ( " %s: unable to map IC registers \n " ,
node - > full_name ) ;
/* Disable all interrupts */
writel ( 0 , sunxi_irq_base + SUNXI_IRQ_ENABLE_REG ( 0 ) ) ;
writel ( 0 , sunxi_irq_base + SUNXI_IRQ_ENABLE_REG ( 1 ) ) ;
writel ( 0 , sunxi_irq_base + SUNXI_IRQ_ENABLE_REG ( 2 ) ) ;
/* Mask all the interrupts */
writel ( 0 , sunxi_irq_base + SUNXI_IRQ_MASK_REG ( 0 ) ) ;
writel ( 0 , sunxi_irq_base + SUNXI_IRQ_MASK_REG ( 1 ) ) ;
writel ( 0 , sunxi_irq_base + SUNXI_IRQ_MASK_REG ( 2 ) ) ;
/* Clear all the pending interrupts */
writel ( 0xffffffff , sunxi_irq_base + SUNXI_IRQ_PENDING_REG ( 0 ) ) ;
writel ( 0xffffffff , sunxi_irq_base + SUNXI_IRQ_PENDING_REG ( 1 ) ) ;
writel ( 0xffffffff , sunxi_irq_base + SUNXI_IRQ_PENDING_REG ( 2 ) ) ;
/* Enable protection mode */
writel ( 0x01 , sunxi_irq_base + SUNXI_IRQ_PROTECTION_REG ) ;
/* Configure the external interrupt source type */
writel ( 0x00 , sunxi_irq_base + SUNXI_IRQ_NMI_CTRL_REG ) ;
sunxi_irq_domain = irq_domain_add_linear ( node , 3 * 32 ,
& sunxi_irq_ops , NULL ) ;
if ( ! sunxi_irq_domain )
panic ( " %s: unable to create IRQ domain \n " , node - > full_name ) ;
return 0 ;
}
static struct of_device_id sunxi_irq_dt_ids [ ] __initconst = {
2012-12-02 18:40:24 +04:00
{ . compatible = " allwinner,sunxi-ic " , . data = sunxi_of_init } ,
{ }
2012-11-14 12:59:14 +04:00
} ;
void __init sunxi_init_irq ( void )
{
of_irq_init ( sunxi_irq_dt_ids ) ;
}
asmlinkage void __exception_irq_entry sunxi_handle_irq ( struct pt_regs * regs )
{
u32 irq , hwirq ;
hwirq = readl ( sunxi_irq_base + SUNXI_IRQ_VECTOR_REG ) > > 2 ;
while ( hwirq ! = 0 ) {
irq = irq_find_mapping ( sunxi_irq_domain , hwirq ) ;
handle_IRQ ( irq , regs ) ;
hwirq = readl ( sunxi_irq_base + SUNXI_IRQ_VECTOR_REG ) > > 2 ;
}
}