2019-05-27 09:55:08 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
irqchip: Add LPC32xx interrupt controller driver
The change adds improved support of NXP LPC32xx MIC, SIC1 and SIC2
interrupt controllers.
This is a list of new features in comparison to the legacy driver:
* irq types are taken from device tree settings, no more need to
hardcode them,
* old driver is based on irq_domain_add_legacy, which causes problems
with handling MIC hardware interrupt 0 produced by SIC1,
* there is one driver for MIC, SIC1 and SIC2, no more need to handle
them separately, e.g. have two separate handlers for SIC1 and SIC2,
* the driver does not have any dependencies on hardcoded register
offsets,
* the driver is much simpler for maintenance,
* SPARSE_IRQS option is supported.
Legacy LPC32xx interrupt controller driver was broken since commit
76ba59f8366f ("genirq: Add irq_domain-aware core IRQ handler"), which
requires a private interrupt handler, otherwise any SIC1 generated
interrupt (mapped to MIC hwirq 0) breaks the kernel with the message
"unexpected IRQ trap at vector 00".
The change disables compilation of a legacy driver found at
arch/arm/mach-lpc32xx/irq.c, the file will be removed in a separate
commit.
Fixes: 76ba59f8366f ("genirq: Add irq_domain-aware core IRQ handler")
Tested-by: Sylvain Lemieux <slemieux.tyco@gmail.com>
Signed-off-by: Vladimir Zapolskiy <vz@mleia.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
2016-04-25 04:00:38 +03:00
/*
* Copyright 2015 - 2016 Vladimir Zapolskiy < vz @ mleia . com >
*/
# define pr_fmt(fmt) "%s: " fmt, __func__
# include <linux/io.h>
# include <linux/irqchip.h>
# include <linux/irqchip/chained_irq.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/of_platform.h>
# include <linux/slab.h>
# include <asm/exception.h>
# define LPC32XX_INTC_MASK 0x00
# define LPC32XX_INTC_RAW 0x04
# define LPC32XX_INTC_STAT 0x08
# define LPC32XX_INTC_POL 0x0C
# define LPC32XX_INTC_TYPE 0x10
# define LPC32XX_INTC_FIQ 0x14
# define NR_LPC32XX_IC_IRQS 32
struct lpc32xx_irq_chip {
void __iomem * base ;
struct irq_domain * domain ;
struct irq_chip chip ;
} ;
static struct lpc32xx_irq_chip * lpc32xx_mic_irqc ;
static inline u32 lpc32xx_ic_read ( struct lpc32xx_irq_chip * ic , u32 reg )
{
return readl_relaxed ( ic - > base + reg ) ;
}
static inline void lpc32xx_ic_write ( struct lpc32xx_irq_chip * ic ,
u32 reg , u32 val )
{
writel_relaxed ( val , ic - > base + reg ) ;
}
static void lpc32xx_irq_mask ( struct irq_data * d )
{
struct lpc32xx_irq_chip * ic = irq_data_get_irq_chip_data ( d ) ;
u32 val , mask = BIT ( d - > hwirq ) ;
val = lpc32xx_ic_read ( ic , LPC32XX_INTC_MASK ) & ~ mask ;
lpc32xx_ic_write ( ic , LPC32XX_INTC_MASK , val ) ;
}
static void lpc32xx_irq_unmask ( struct irq_data * d )
{
struct lpc32xx_irq_chip * ic = irq_data_get_irq_chip_data ( d ) ;
u32 val , mask = BIT ( d - > hwirq ) ;
val = lpc32xx_ic_read ( ic , LPC32XX_INTC_MASK ) | mask ;
lpc32xx_ic_write ( ic , LPC32XX_INTC_MASK , val ) ;
}
static void lpc32xx_irq_ack ( struct irq_data * d )
{
struct lpc32xx_irq_chip * ic = irq_data_get_irq_chip_data ( d ) ;
u32 mask = BIT ( d - > hwirq ) ;
lpc32xx_ic_write ( ic , LPC32XX_INTC_RAW , mask ) ;
}
static int lpc32xx_irq_set_type ( struct irq_data * d , unsigned int type )
{
struct lpc32xx_irq_chip * ic = irq_data_get_irq_chip_data ( d ) ;
u32 val , mask = BIT ( d - > hwirq ) ;
bool high , edge ;
switch ( type ) {
case IRQ_TYPE_EDGE_RISING :
edge = true ;
high = true ;
break ;
case IRQ_TYPE_EDGE_FALLING :
edge = true ;
high = false ;
break ;
case IRQ_TYPE_LEVEL_HIGH :
edge = false ;
high = true ;
break ;
case IRQ_TYPE_LEVEL_LOW :
edge = false ;
high = false ;
break ;
default :
pr_info ( " unsupported irq type %d \n " , type ) ;
return - EINVAL ;
}
irqd_set_trigger_type ( d , type ) ;
val = lpc32xx_ic_read ( ic , LPC32XX_INTC_POL ) ;
if ( high )
val | = mask ;
else
val & = ~ mask ;
lpc32xx_ic_write ( ic , LPC32XX_INTC_POL , val ) ;
val = lpc32xx_ic_read ( ic , LPC32XX_INTC_TYPE ) ;
if ( edge ) {
val | = mask ;
irq_set_handler_locked ( d , handle_edge_irq ) ;
} else {
val & = ~ mask ;
irq_set_handler_locked ( d , handle_level_irq ) ;
}
lpc32xx_ic_write ( ic , LPC32XX_INTC_TYPE , val ) ;
return 0 ;
}
static void __exception_irq_entry lpc32xx_handle_irq ( struct pt_regs * regs )
{
struct lpc32xx_irq_chip * ic = lpc32xx_mic_irqc ;
u32 hwirq = lpc32xx_ic_read ( ic , LPC32XX_INTC_STAT ) , irq ;
while ( hwirq ) {
irq = __ffs ( hwirq ) ;
hwirq & = ~ BIT ( irq ) ;
2021-10-20 22:23:09 +03:00
generic_handle_domain_irq ( lpc32xx_mic_irqc - > domain , irq ) ;
irqchip: Add LPC32xx interrupt controller driver
The change adds improved support of NXP LPC32xx MIC, SIC1 and SIC2
interrupt controllers.
This is a list of new features in comparison to the legacy driver:
* irq types are taken from device tree settings, no more need to
hardcode them,
* old driver is based on irq_domain_add_legacy, which causes problems
with handling MIC hardware interrupt 0 produced by SIC1,
* there is one driver for MIC, SIC1 and SIC2, no more need to handle
them separately, e.g. have two separate handlers for SIC1 and SIC2,
* the driver does not have any dependencies on hardcoded register
offsets,
* the driver is much simpler for maintenance,
* SPARSE_IRQS option is supported.
Legacy LPC32xx interrupt controller driver was broken since commit
76ba59f8366f ("genirq: Add irq_domain-aware core IRQ handler"), which
requires a private interrupt handler, otherwise any SIC1 generated
interrupt (mapped to MIC hwirq 0) breaks the kernel with the message
"unexpected IRQ trap at vector 00".
The change disables compilation of a legacy driver found at
arch/arm/mach-lpc32xx/irq.c, the file will be removed in a separate
commit.
Fixes: 76ba59f8366f ("genirq: Add irq_domain-aware core IRQ handler")
Tested-by: Sylvain Lemieux <slemieux.tyco@gmail.com>
Signed-off-by: Vladimir Zapolskiy <vz@mleia.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
2016-04-25 04:00:38 +03:00
}
}
static void lpc32xx_sic_handler ( struct irq_desc * desc )
{
struct lpc32xx_irq_chip * ic = irq_desc_get_handler_data ( desc ) ;
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
u32 hwirq = lpc32xx_ic_read ( ic , LPC32XX_INTC_STAT ) , irq ;
chained_irq_enter ( chip , desc ) ;
while ( hwirq ) {
irq = __ffs ( hwirq ) ;
hwirq & = ~ BIT ( irq ) ;
2021-05-04 19:42:18 +03:00
generic_handle_domain_irq ( ic - > domain , irq ) ;
irqchip: Add LPC32xx interrupt controller driver
The change adds improved support of NXP LPC32xx MIC, SIC1 and SIC2
interrupt controllers.
This is a list of new features in comparison to the legacy driver:
* irq types are taken from device tree settings, no more need to
hardcode them,
* old driver is based on irq_domain_add_legacy, which causes problems
with handling MIC hardware interrupt 0 produced by SIC1,
* there is one driver for MIC, SIC1 and SIC2, no more need to handle
them separately, e.g. have two separate handlers for SIC1 and SIC2,
* the driver does not have any dependencies on hardcoded register
offsets,
* the driver is much simpler for maintenance,
* SPARSE_IRQS option is supported.
Legacy LPC32xx interrupt controller driver was broken since commit
76ba59f8366f ("genirq: Add irq_domain-aware core IRQ handler"), which
requires a private interrupt handler, otherwise any SIC1 generated
interrupt (mapped to MIC hwirq 0) breaks the kernel with the message
"unexpected IRQ trap at vector 00".
The change disables compilation of a legacy driver found at
arch/arm/mach-lpc32xx/irq.c, the file will be removed in a separate
commit.
Fixes: 76ba59f8366f ("genirq: Add irq_domain-aware core IRQ handler")
Tested-by: Sylvain Lemieux <slemieux.tyco@gmail.com>
Signed-off-by: Vladimir Zapolskiy <vz@mleia.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
2016-04-25 04:00:38 +03:00
}
chained_irq_exit ( chip , desc ) ;
}
static int lpc32xx_irq_domain_map ( struct irq_domain * id , unsigned int virq ,
irq_hw_number_t hw )
{
struct lpc32xx_irq_chip * ic = id - > host_data ;
irq_set_chip_data ( virq , ic ) ;
irq_set_chip_and_handler ( virq , & ic - > chip , handle_level_irq ) ;
irq_set_status_flags ( virq , IRQ_LEVEL ) ;
irq_set_noprobe ( virq ) ;
return 0 ;
}
static void lpc32xx_irq_domain_unmap ( struct irq_domain * id , unsigned int virq )
{
irq_set_chip_and_handler ( virq , NULL , NULL ) ;
}
static const struct irq_domain_ops lpc32xx_irq_domain_ops = {
. map = lpc32xx_irq_domain_map ,
. unmap = lpc32xx_irq_domain_unmap ,
. xlate = irq_domain_xlate_twocell ,
} ;
static int __init lpc32xx_of_ic_init ( struct device_node * node ,
struct device_node * parent )
{
struct lpc32xx_irq_chip * irqc ;
bool is_mic = of_device_is_compatible ( node , " nxp,lpc3220-mic " ) ;
const __be32 * reg = of_get_property ( node , " reg " , NULL ) ;
u32 parent_irq , i , addr = reg ? be32_to_cpu ( * reg ) : 0 ;
irqc = kzalloc ( sizeof ( * irqc ) , GFP_KERNEL ) ;
if ( ! irqc )
return - ENOMEM ;
irqc - > base = of_iomap ( node , 0 ) ;
if ( ! irqc - > base ) {
2017-07-19 00:43:10 +03:00
pr_err ( " %pOF: unable to map registers \n " , node ) ;
irqchip: Add LPC32xx interrupt controller driver
The change adds improved support of NXP LPC32xx MIC, SIC1 and SIC2
interrupt controllers.
This is a list of new features in comparison to the legacy driver:
* irq types are taken from device tree settings, no more need to
hardcode them,
* old driver is based on irq_domain_add_legacy, which causes problems
with handling MIC hardware interrupt 0 produced by SIC1,
* there is one driver for MIC, SIC1 and SIC2, no more need to handle
them separately, e.g. have two separate handlers for SIC1 and SIC2,
* the driver does not have any dependencies on hardcoded register
offsets,
* the driver is much simpler for maintenance,
* SPARSE_IRQS option is supported.
Legacy LPC32xx interrupt controller driver was broken since commit
76ba59f8366f ("genirq: Add irq_domain-aware core IRQ handler"), which
requires a private interrupt handler, otherwise any SIC1 generated
interrupt (mapped to MIC hwirq 0) breaks the kernel with the message
"unexpected IRQ trap at vector 00".
The change disables compilation of a legacy driver found at
arch/arm/mach-lpc32xx/irq.c, the file will be removed in a separate
commit.
Fixes: 76ba59f8366f ("genirq: Add irq_domain-aware core IRQ handler")
Tested-by: Sylvain Lemieux <slemieux.tyco@gmail.com>
Signed-off-by: Vladimir Zapolskiy <vz@mleia.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
2016-04-25 04:00:38 +03:00
kfree ( irqc ) ;
return - EINVAL ;
}
irqc - > chip . irq_ack = lpc32xx_irq_ack ;
irqc - > chip . irq_mask = lpc32xx_irq_mask ;
irqc - > chip . irq_unmask = lpc32xx_irq_unmask ;
irqc - > chip . irq_set_type = lpc32xx_irq_set_type ;
if ( is_mic )
irqc - > chip . name = kasprintf ( GFP_KERNEL , " %08x.mic " , addr ) ;
else
irqc - > chip . name = kasprintf ( GFP_KERNEL , " %08x.sic " , addr ) ;
irqc - > domain = irq_domain_add_linear ( node , NR_LPC32XX_IC_IRQS ,
& lpc32xx_irq_domain_ops , irqc ) ;
if ( ! irqc - > domain ) {
pr_err ( " unable to add irq domain \n " ) ;
iounmap ( irqc - > base ) ;
kfree ( irqc - > chip . name ) ;
kfree ( irqc ) ;
return - ENODEV ;
}
if ( is_mic ) {
lpc32xx_mic_irqc = irqc ;
set_handle_irq ( lpc32xx_handle_irq ) ;
} else {
for ( i = 0 ; i < of_irq_count ( node ) ; i + + ) {
parent_irq = irq_of_parse_and_map ( node , i ) ;
if ( parent_irq )
irq_set_chained_handler_and_data ( parent_irq ,
lpc32xx_sic_handler , irqc ) ;
}
}
lpc32xx_ic_write ( irqc , LPC32XX_INTC_MASK , 0x00 ) ;
lpc32xx_ic_write ( irqc , LPC32XX_INTC_POL , 0x00 ) ;
lpc32xx_ic_write ( irqc , LPC32XX_INTC_TYPE , 0x00 ) ;
return 0 ;
}
IRQCHIP_DECLARE ( nxp_lpc32xx_mic , " nxp,lpc3220-mic " , lpc32xx_of_ic_init ) ;
IRQCHIP_DECLARE ( nxp_lpc32xx_sic , " nxp,lpc3220-sic " , lpc32xx_of_ic_init ) ;