2013-03-24 13:10:04 +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>
2015-07-08 00:11:46 +03:00
# include <linux/irqchip.h>
2013-03-24 13:10:04 +04:00
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <asm/exception.h>
# define SUN4I_IRQ_VECTOR_REG 0x00
# define SUN4I_IRQ_PROTECTION_REG 0x08
# define SUN4I_IRQ_NMI_CTRL_REG 0x0c
# define SUN4I_IRQ_PENDING_REG(x) (0x10 + 0x4 * x)
# define SUN4I_IRQ_FIQ_PENDING_REG(x) (0x20 + 0x4 * x)
2018-12-02 23:23:40 +03:00
# define SUN4I_IRQ_ENABLE_REG(data, x) ((data)->enable_reg_offset + 0x4 * x)
# define SUN4I_IRQ_MASK_REG(data, x) ((data)->mask_reg_offset + 0x4 * x)
# define SUN4I_IRQ_ENABLE_REG_OFFSET 0x40
# define SUN4I_IRQ_MASK_REG_OFFSET 0x50
2018-12-02 23:23:41 +03:00
# define SUNIV_IRQ_ENABLE_REG_OFFSET 0x20
# define SUNIV_IRQ_MASK_REG_OFFSET 0x30
2013-03-24 13:10:04 +04:00
2018-12-02 23:23:39 +03:00
struct sun4i_irq_chip_data {
void __iomem * irq_base ;
struct irq_domain * irq_domain ;
2018-12-02 23:23:40 +03:00
u32 enable_reg_offset ;
u32 mask_reg_offset ;
2018-12-02 23:23:39 +03:00
} ;
static struct sun4i_irq_chip_data * irq_ic_data ;
2013-03-24 13:10:04 +04:00
2014-03-05 04:40:30 +04:00
static void __exception_irq_entry sun4i_handle_irq ( struct pt_regs * regs ) ;
2013-03-24 13:10:04 +04:00
2013-07-05 11:41:10 +04:00
static void sun4i_irq_ack ( struct irq_data * irqd )
2013-03-24 13:10:04 +04:00
{
unsigned int irq = irqd_to_hwirq ( irqd ) ;
2014-03-15 19:04:53 +04:00
if ( irq ! = 0 )
return ; /* Only IRQ 0 / the ENMI needs to be acked */
2018-12-02 23:23:39 +03:00
writel ( BIT ( 0 ) , irq_ic_data - > irq_base + SUN4I_IRQ_PENDING_REG ( 0 ) ) ;
2013-03-24 13:10:04 +04:00
}
static void sun4i_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 ;
2018-12-02 23:23:40 +03:00
val = readl ( irq_ic_data - > irq_base +
SUN4I_IRQ_ENABLE_REG ( irq_ic_data , reg ) ) ;
2013-03-24 13:10:04 +04:00
writel ( val & ~ ( 1 < < irq_off ) ,
2018-12-02 23:23:40 +03:00
irq_ic_data - > irq_base + SUN4I_IRQ_ENABLE_REG ( irq_ic_data , reg ) ) ;
2013-03-24 13:10:04 +04:00
}
static void sun4i_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 ;
2018-12-02 23:23:40 +03:00
val = readl ( irq_ic_data - > irq_base +
SUN4I_IRQ_ENABLE_REG ( irq_ic_data , reg ) ) ;
2013-03-24 13:10:04 +04:00
writel ( val | ( 1 < < irq_off ) ,
2018-12-02 23:23:40 +03:00
irq_ic_data - > irq_base + SUN4I_IRQ_ENABLE_REG ( irq_ic_data , reg ) ) ;
2013-03-24 13:10:04 +04:00
}
static struct irq_chip sun4i_irq_chip = {
2014-03-13 22:03:54 +04:00
. name = " sun4i_irq " ,
. irq_eoi = sun4i_irq_ack ,
. irq_mask = sun4i_irq_mask ,
. irq_unmask = sun4i_irq_unmask ,
. flags = IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED ,
} ;
2013-03-24 13:10:04 +04:00
static int sun4i_irq_map ( struct irq_domain * d , unsigned int virq ,
irq_hw_number_t hw )
{
2014-03-15 19:04:53 +04:00
irq_set_chip_and_handler ( virq , & sun4i_irq_chip , handle_fasteoi_irq ) ;
2015-08-30 02:01:22 +03:00
irq_set_probe ( virq ) ;
2013-03-24 13:10:04 +04:00
return 0 ;
}
2015-04-27 15:54:24 +03:00
static const struct irq_domain_ops sun4i_irq_ops = {
2013-03-24 13:10:04 +04:00
. map = sun4i_irq_map ,
. xlate = irq_domain_xlate_onecell ,
} ;
static int __init sun4i_of_init ( struct device_node * node ,
struct device_node * parent )
{
2018-12-02 23:23:39 +03:00
irq_ic_data - > irq_base = of_iomap ( node , 0 ) ;
if ( ! irq_ic_data - > irq_base )
2017-07-19 00:43:10 +03:00
panic ( " %pOF: unable to map IC registers \n " ,
node ) ;
2013-03-24 13:10:04 +04:00
/* Disable all interrupts */
2018-12-02 23:23:40 +03:00
writel ( 0 , irq_ic_data - > irq_base + SUN4I_IRQ_ENABLE_REG ( irq_ic_data , 0 ) ) ;
writel ( 0 , irq_ic_data - > irq_base + SUN4I_IRQ_ENABLE_REG ( irq_ic_data , 1 ) ) ;
writel ( 0 , irq_ic_data - > irq_base + SUN4I_IRQ_ENABLE_REG ( irq_ic_data , 2 ) ) ;
2013-03-24 13:10:04 +04:00
2014-03-13 22:03:53 +04:00
/* Unmask all the interrupts, ENABLE_REG(x) is used for masking */
2018-12-02 23:23:40 +03:00
writel ( 0 , irq_ic_data - > irq_base + SUN4I_IRQ_MASK_REG ( irq_ic_data , 0 ) ) ;
writel ( 0 , irq_ic_data - > irq_base + SUN4I_IRQ_MASK_REG ( irq_ic_data , 1 ) ) ;
writel ( 0 , irq_ic_data - > irq_base + SUN4I_IRQ_MASK_REG ( irq_ic_data , 2 ) ) ;
2013-03-24 13:10:04 +04:00
/* Clear all the pending interrupts */
2018-12-02 23:23:39 +03:00
writel ( 0xffffffff , irq_ic_data - > irq_base + SUN4I_IRQ_PENDING_REG ( 0 ) ) ;
writel ( 0xffffffff , irq_ic_data - > irq_base + SUN4I_IRQ_PENDING_REG ( 1 ) ) ;
writel ( 0xffffffff , irq_ic_data - > irq_base + SUN4I_IRQ_PENDING_REG ( 2 ) ) ;
2013-03-24 13:10:04 +04:00
/* Enable protection mode */
2018-12-02 23:23:39 +03:00
writel ( 0x01 , irq_ic_data - > irq_base + SUN4I_IRQ_PROTECTION_REG ) ;
2013-03-24 13:10:04 +04:00
/* Configure the external interrupt source type */
2018-12-02 23:23:39 +03:00
writel ( 0x00 , irq_ic_data - > irq_base + SUN4I_IRQ_NMI_CTRL_REG ) ;
2013-03-24 13:10:04 +04:00
2018-12-02 23:23:39 +03:00
irq_ic_data - > irq_domain = irq_domain_add_linear ( node , 3 * 32 ,
2013-03-24 13:10:04 +04:00
& sun4i_irq_ops , NULL ) ;
2018-12-02 23:23:39 +03:00
if ( ! irq_ic_data - > irq_domain )
2017-07-19 00:43:10 +03:00
panic ( " %pOF: unable to create IRQ domain \n " , node ) ;
2013-03-24 13:10:04 +04:00
set_handle_irq ( sun4i_handle_irq ) ;
return 0 ;
}
2018-12-02 23:23:41 +03:00
static int __init sun4i_ic_of_init ( struct device_node * node ,
struct device_node * parent )
{
irq_ic_data = kzalloc ( sizeof ( struct sun4i_irq_chip_data ) , GFP_KERNEL ) ;
2021-06-09 17:14:28 +03:00
if ( ! irq_ic_data )
2018-12-02 23:23:41 +03:00
return - ENOMEM ;
irq_ic_data - > enable_reg_offset = SUN4I_IRQ_ENABLE_REG_OFFSET ;
irq_ic_data - > mask_reg_offset = SUN4I_IRQ_MASK_REG_OFFSET ;
return sun4i_of_init ( node , parent ) ;
}
IRQCHIP_DECLARE ( allwinner_sun4i_ic , " allwinner,sun4i-a10-ic " , sun4i_ic_of_init ) ;
static int __init suniv_ic_of_init ( struct device_node * node ,
struct device_node * parent )
{
irq_ic_data = kzalloc ( sizeof ( struct sun4i_irq_chip_data ) , GFP_KERNEL ) ;
2021-06-09 17:14:28 +03:00
if ( ! irq_ic_data )
2018-12-02 23:23:41 +03:00
return - ENOMEM ;
irq_ic_data - > enable_reg_offset = SUNIV_IRQ_ENABLE_REG_OFFSET ;
irq_ic_data - > mask_reg_offset = SUNIV_IRQ_MASK_REG_OFFSET ;
return sun4i_of_init ( node , parent ) ;
}
IRQCHIP_DECLARE ( allwinner_sunvi_ic , " allwinner,suniv-f1c100s-ic " ,
suniv_ic_of_init ) ;
2013-03-24 13:10:04 +04:00
2014-03-05 04:40:30 +04:00
static void __exception_irq_entry sun4i_handle_irq ( struct pt_regs * regs )
2013-03-24 13:10:04 +04:00
{
2014-08-26 14:03:28 +04:00
u32 hwirq ;
2013-03-24 13:10:04 +04:00
2014-03-13 22:03:52 +04:00
/*
* hwirq = = 0 can mean one of 3 things :
* 1 ) no more irqs pending
* 2 ) irq 0 pending
* 3 ) spurious irq
* So if we immediately get a reading of 0 , check the irq - pending reg
* to differentiate between 2 and 3. We only do this once to avoid
2021-03-22 06:21:30 +03:00
* the extra check in the common case of 1 happening after having
2014-03-13 22:03:52 +04:00
* read the vector - reg once .
*/
2018-12-02 23:23:39 +03:00
hwirq = readl ( irq_ic_data - > irq_base + SUN4I_IRQ_VECTOR_REG ) > > 2 ;
2014-03-13 22:03:52 +04:00
if ( hwirq = = 0 & &
2018-12-02 23:23:39 +03:00
! ( readl ( irq_ic_data - > irq_base + SUN4I_IRQ_PENDING_REG ( 0 ) ) &
BIT ( 0 ) ) )
2014-03-13 22:03:52 +04:00
return ;
do {
2021-10-20 22:23:09 +03:00
generic_handle_domain_irq ( irq_ic_data - > irq_domain , hwirq ) ;
2018-12-02 23:23:39 +03:00
hwirq = readl ( irq_ic_data - > irq_base +
SUN4I_IRQ_VECTOR_REG ) > > 2 ;
2014-03-13 22:03:52 +04:00
} while ( hwirq ! = 0 ) ;
2013-03-24 13:10:04 +04:00
}