2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-09-10 04:44:21 +04:00
/*
* Broadcom BCM7120 style Level 2 interrupt controller driver
*
* Copyright ( C ) 2014 Broadcom Corporation
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/module.h>
2014-12-25 20:49:05 +03:00
# include <linux/kernel.h>
2014-09-10 04:44:21 +04:00
# include <linux/platform_device.h>
# include <linux/of.h>
# include <linux/of_irq.h>
# include <linux/of_address.h>
# include <linux/of_platform.h>
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/io.h>
# include <linux/irqdomain.h>
# include <linux/reboot.h>
2014-11-07 09:44:26 +03:00
# include <linux/bitops.h>
2015-07-08 00:11:46 +03:00
# include <linux/irqchip.h>
2014-09-10 04:44:21 +04:00
# include <linux/irqchip/chained_irq.h>
/* Register offset in the L2 interrupt controller */
# define IRQEN 0x00
# define IRQSTAT 0x04
2014-11-07 09:44:26 +03:00
# define MAX_WORDS 4
2014-12-25 20:49:04 +03:00
# define MAX_MAPPINGS (MAX_WORDS * 2)
2014-11-07 09:44:26 +03:00
# define IRQS_PER_WORD 32
2015-07-24 01:52:21 +03:00
struct bcm7120_l1_intc_data {
struct bcm7120_l2_intc_data * b ;
u32 irq_map_mask [ MAX_WORDS ] ;
} ;
2014-09-10 04:44:21 +04:00
struct bcm7120_l2_intc_data {
2014-11-07 09:44:26 +03:00
unsigned int n_words ;
2014-12-25 20:49:03 +03:00
void __iomem * map_base [ MAX_MAPPINGS ] ;
void __iomem * pair_base [ MAX_WORDS ] ;
int en_offset [ MAX_WORDS ] ;
int stat_offset [ MAX_WORDS ] ;
2014-09-10 04:44:21 +04:00
struct irq_domain * domain ;
bool can_wake ;
2014-11-07 09:44:26 +03:00
u32 irq_fwd_mask [ MAX_WORDS ] ;
2015-07-24 01:52:21 +03:00
struct bcm7120_l1_intc_data * l1_data ;
2014-12-25 20:49:04 +03:00
int num_parent_irqs ;
const __be32 * map_mask_prop ;
2014-09-10 04:44:21 +04:00
} ;
2015-09-14 11:42:37 +03:00
static void bcm7120_l2_intc_irq_handle ( struct irq_desc * desc )
2014-09-10 04:44:21 +04:00
{
2015-07-24 01:52:21 +03:00
struct bcm7120_l1_intc_data * data = irq_desc_get_handler_data ( desc ) ;
struct bcm7120_l2_intc_data * b = data - > b ;
2014-09-10 04:44:21 +04:00
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
2014-11-07 09:44:26 +03:00
unsigned int idx ;
2014-09-10 04:44:21 +04:00
chained_irq_enter ( chip , desc ) ;
2014-11-07 09:44:26 +03:00
for ( idx = 0 ; idx < b - > n_words ; idx + + ) {
int base = idx * IRQS_PER_WORD ;
struct irq_chip_generic * gc =
irq_get_domain_generic_chip ( b - > domain , base ) ;
unsigned long pending ;
int hwirq ;
irq_gc_lock ( gc ) ;
2014-12-25 20:49:03 +03:00
pending = irq_reg_readl ( gc , b - > stat_offset [ idx ] ) &
2015-07-24 01:52:21 +03:00
gc - > mask_cache &
data - > irq_map_mask [ idx ] ;
2014-11-07 09:44:26 +03:00
irq_gc_unlock ( gc ) ;
2021-05-04 19:42:18 +03:00
for_each_set_bit ( hwirq , & pending , IRQS_PER_WORD )
generic_handle_domain_irq ( b - > domain , base + hwirq ) ;
2014-09-10 04:44:21 +04:00
}
chained_irq_exit ( chip , desc ) ;
}
2015-07-23 02:21:40 +03:00
static void bcm7120_l2_intc_suspend ( struct irq_chip_generic * gc )
2014-09-10 04:44:21 +04:00
{
struct bcm7120_l2_intc_data * b = gc - > private ;
2015-07-23 02:21:40 +03:00
struct irq_chip_type * ct = gc - > chip_types ;
2014-09-10 04:44:21 +04:00
irq_gc_lock ( gc ) ;
2014-11-07 09:44:28 +03:00
if ( b - > can_wake )
2014-12-25 20:49:03 +03:00
irq_reg_writel ( gc , gc - > mask_cache | gc - > wake_active ,
ct - > regs . mask ) ;
2014-09-10 04:44:21 +04:00
irq_gc_unlock ( gc ) ;
}
2015-07-23 02:21:40 +03:00
static void bcm7120_l2_intc_resume ( struct irq_chip_generic * gc )
2014-09-10 04:44:21 +04:00
{
2015-07-23 02:21:40 +03:00
struct irq_chip_type * ct = gc - > chip_types ;
2014-09-10 04:44:21 +04:00
/* Restore the saved mask */
irq_gc_lock ( gc ) ;
2014-12-25 20:49:03 +03:00
irq_reg_writel ( gc , gc - > mask_cache , ct - > regs . mask ) ;
2014-09-10 04:44:21 +04:00
irq_gc_unlock ( gc ) ;
}
static int bcm7120_l2_intc_init_one ( struct device_node * dn ,
struct bcm7120_l2_intc_data * data ,
2015-07-24 01:52:21 +03:00
int irq , u32 * valid_mask )
2014-09-10 04:44:21 +04:00
{
2015-07-24 01:52:21 +03:00
struct bcm7120_l1_intc_data * l1_data = & data - > l1_data [ irq ] ;
2014-09-10 04:44:21 +04:00
int parent_irq ;
2014-11-07 09:44:26 +03:00
unsigned int idx ;
2014-09-10 04:44:21 +04:00
parent_irq = irq_of_parse_and_map ( dn , irq ) ;
2014-11-15 01:16:14 +03:00
if ( ! parent_irq ) {
2014-09-10 04:44:21 +04:00
pr_err ( " failed to map interrupt %d \n " , irq ) ;
2014-11-15 01:16:14 +03:00
return - EINVAL ;
2014-09-10 04:44:21 +04:00
}
2014-11-07 09:44:26 +03:00
/* For multiple parent IRQs with multiple words, this looks like:
* < irq0_w0 irq0_w1 irq1_w0 irq1_w1 . . . >
2015-07-24 01:52:21 +03:00
*
* We need to associate a given parent interrupt with its corresponding
* map_mask in order to mask the status register with it because we
* have the same handler being called for multiple parent interrupts .
*
* This is typically something needed on BCM7xxx ( STB chips ) .
2014-11-07 09:44:26 +03:00
*/
2014-12-25 20:49:05 +03:00
for ( idx = 0 ; idx < data - > n_words ; idx + + ) {
if ( data - > map_mask_prop ) {
2015-07-24 01:52:21 +03:00
l1_data - > irq_map_mask [ idx ] | =
2014-12-25 20:49:05 +03:00
be32_to_cpup ( data - > map_mask_prop +
irq * data - > n_words + idx ) ;
} else {
2015-07-24 01:52:21 +03:00
l1_data - > irq_map_mask [ idx ] = 0xffffffff ;
2014-12-25 20:49:05 +03:00
}
2015-07-24 01:52:21 +03:00
valid_mask [ idx ] | = l1_data - > irq_map_mask [ idx ] ;
2014-12-25 20:49:05 +03:00
}
2014-09-10 04:44:21 +04:00
2015-07-24 01:52:21 +03:00
l1_data - > b = data ;
2014-09-10 04:44:21 +04:00
2015-07-24 01:52:21 +03:00
irq_set_chained_handler_and_data ( parent_irq ,
bcm7120_l2_intc_irq_handle , l1_data ) ;
2020-07-10 01:30:11 +03:00
if ( data - > can_wake )
enable_irq_wake ( parent_irq ) ;
2014-09-10 04:44:21 +04:00
return 0 ;
}
2014-12-25 20:49:04 +03:00
static int __init bcm7120_l2_intc_iomap_7120 ( struct device_node * dn ,
struct bcm7120_l2_intc_data * data )
{
int ret ;
data - > map_base [ 0 ] = of_iomap ( dn , 0 ) ;
if ( ! data - > map_base [ 0 ] ) {
pr_err ( " unable to map registers \n " ) ;
return - ENOMEM ;
}
data - > pair_base [ 0 ] = data - > map_base [ 0 ] ;
data - > en_offset [ 0 ] = IRQEN ;
data - > stat_offset [ 0 ] = IRQSTAT ;
data - > n_words = 1 ;
ret = of_property_read_u32_array ( dn , " brcm,int-fwd-mask " ,
data - > irq_fwd_mask , data - > n_words ) ;
if ( ret ! = 0 & & ret ! = - EINVAL ) {
/* property exists but has the wrong number of words */
pr_err ( " invalid brcm,int-fwd-mask property \n " ) ;
return - EINVAL ;
}
data - > map_mask_prop = of_get_property ( dn , " brcm,int-map-mask " , & ret ) ;
if ( ! data - > map_mask_prop | |
( ret ! = ( sizeof ( __be32 ) * data - > num_parent_irqs * data - > n_words ) ) ) {
pr_err ( " invalid brcm,int-map-mask property \n " ) ;
return - EINVAL ;
}
return 0 ;
}
2014-12-25 20:49:05 +03:00
static int __init bcm7120_l2_intc_iomap_3380 ( struct device_node * dn ,
struct bcm7120_l2_intc_data * data )
{
unsigned int gc_idx ;
for ( gc_idx = 0 ; gc_idx < MAX_WORDS ; gc_idx + + ) {
unsigned int map_idx = gc_idx * 2 ;
void __iomem * en = of_iomap ( dn , map_idx + 0 ) ;
void __iomem * stat = of_iomap ( dn , map_idx + 1 ) ;
void __iomem * base = min ( en , stat ) ;
data - > map_base [ map_idx + 0 ] = en ;
data - > map_base [ map_idx + 1 ] = stat ;
if ( ! base )
break ;
data - > pair_base [ gc_idx ] = base ;
data - > en_offset [ gc_idx ] = en - base ;
data - > stat_offset [ gc_idx ] = stat - base ;
}
if ( ! gc_idx ) {
pr_err ( " unable to map registers \n " ) ;
return - EINVAL ;
}
data - > n_words = gc_idx ;
return 0 ;
}
2016-06-08 20:59:58 +03:00
static int __init bcm7120_l2_intc_probe ( struct device_node * dn ,
2014-12-25 20:49:04 +03:00
struct device_node * parent ,
int ( * iomap_regs_fn ) ( struct device_node * ,
struct bcm7120_l2_intc_data * ) ,
const char * intc_name )
2014-09-10 04:44:21 +04:00
{
unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN ;
struct bcm7120_l2_intc_data * data ;
2021-10-20 21:48:56 +03:00
struct platform_device * pdev ;
2014-09-10 04:44:21 +04:00
struct irq_chip_generic * gc ;
struct irq_chip_type * ct ;
2014-12-25 20:49:04 +03:00
int ret = 0 ;
2014-11-07 09:44:28 +03:00
unsigned int idx , irq , flags ;
2015-07-24 01:52:21 +03:00
u32 valid_mask [ MAX_WORDS ] = { } ;
2014-09-10 04:44:21 +04:00
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
2021-10-20 21:48:56 +03:00
pdev = of_find_device_by_node ( dn ) ;
if ( ! pdev ) {
ret = - ENODEV ;
goto out_free_data ;
}
data - > num_parent_irqs = platform_irq_count ( pdev ) ;
2021-11-09 08:59:58 +03:00
put_device ( & pdev - > dev ) ;
2014-12-25 20:49:04 +03:00
if ( data - > num_parent_irqs < = 0 ) {
2014-09-10 04:44:21 +04:00
pr_err ( " invalid number of parent interrupts \n " ) ;
ret = - ENOMEM ;
goto out_unmap ;
}
2015-07-24 01:52:21 +03:00
data - > l1_data = kcalloc ( data - > num_parent_irqs , sizeof ( * data - > l1_data ) ,
GFP_KERNEL ) ;
if ( ! data - > l1_data ) {
ret = - ENOMEM ;
goto out_free_l1_data ;
}
2014-12-25 20:49:04 +03:00
ret = iomap_regs_fn ( dn , data ) ;
if ( ret < 0 )
2015-07-24 01:52:21 +03:00
goto out_free_l1_data ;
2014-12-25 20:49:04 +03:00
2020-07-10 01:30:11 +03:00
data - > can_wake = of_property_read_bool ( dn , " brcm,irq-can-wake " ) ;
2014-12-25 20:49:04 +03:00
for ( irq = 0 ; irq < data - > num_parent_irqs ; irq + + ) {
2015-07-24 01:52:21 +03:00
ret = bcm7120_l2_intc_init_one ( dn , data , irq , valid_mask ) ;
2014-09-10 04:44:21 +04:00
if ( ret )
2015-07-24 01:52:21 +03:00
goto out_free_l1_data ;
2014-09-10 04:44:21 +04:00
}
2014-11-07 09:44:26 +03:00
data - > domain = irq_domain_add_linear ( dn , IRQS_PER_WORD * data - > n_words ,
& irq_generic_chip_ops , NULL ) ;
2014-09-10 04:44:21 +04:00
if ( ! data - > domain ) {
ret = - ENOMEM ;
2015-07-24 01:52:21 +03:00
goto out_free_l1_data ;
2014-09-10 04:44:21 +04:00
}
2014-11-07 09:44:28 +03:00
/* MIPS chips strapped for BE will automagically configure the
* peripheral registers for CPU - native byte order .
*/
flags = IRQ_GC_INIT_MASK_CACHE ;
if ( IS_ENABLED ( CONFIG_MIPS ) & & IS_ENABLED ( CONFIG_CPU_BIG_ENDIAN ) )
flags | = IRQ_GC_BE_IO ;
2014-11-07 09:44:26 +03:00
ret = irq_alloc_domain_generic_chips ( data - > domain , IRQS_PER_WORD , 1 ,
2014-11-07 09:44:28 +03:00
dn - > full_name , handle_level_irq , clr , 0 , flags ) ;
2014-09-10 04:44:21 +04:00
if ( ret ) {
pr_err ( " failed to allocate generic irq chip \n " ) ;
goto out_free_domain ;
}
2014-11-07 09:44:26 +03:00
for ( idx = 0 ; idx < data - > n_words ; idx + + ) {
irq = idx * IRQS_PER_WORD ;
gc = irq_get_domain_generic_chip ( data - > domain , irq ) ;
2015-07-24 01:52:21 +03:00
gc - > unused = 0xffffffff & ~ valid_mask [ idx ] ;
2014-11-07 09:44:26 +03:00
gc - > private = data ;
ct = gc - > chip_types ;
2014-12-25 20:49:03 +03:00
gc - > reg_base = data - > pair_base [ idx ] ;
ct - > regs . mask = data - > en_offset [ idx ] ;
2017-08-31 03:29:16 +03:00
/* gc->reg_base is defined and so is gc->writel */
irq_reg_writel ( gc , data - > irq_fwd_mask [ idx ] ,
data - > en_offset [ idx ] ) ;
2014-11-07 09:44:26 +03:00
ct - > chip . irq_mask = irq_gc_mask_clr_bit ;
ct - > chip . irq_unmask = irq_gc_mask_set_bit ;
ct - > chip . irq_ack = irq_gc_noop ;
2015-07-23 02:21:40 +03:00
gc - > suspend = bcm7120_l2_intc_suspend ;
gc - > resume = bcm7120_l2_intc_resume ;
/*
* Initialize mask - cache , in case we need it for
* saving / restoring fwd mask even w / o any child interrupts
* installed
*/
gc - > mask_cache = irq_reg_readl ( gc , ct - > regs . mask ) ;
2014-11-07 09:44:26 +03:00
if ( data - > can_wake ) {
/* This IRQ chip can wake the system, set all
2021-03-22 06:21:30 +03:00
* relevant child interrupts in wake_enabled mask
2014-11-07 09:44:26 +03:00
*/
gc - > wake_enabled = 0xffffffff ;
gc - > wake_enabled & = ~ gc - > unused ;
ct - > chip . irq_set_wake = irq_gc_set_wake ;
}
2014-09-10 04:44:21 +04:00
}
2019-03-20 22:39:19 +03:00
pr_info ( " registered %s intc (%pOF, parent IRQ(s): %d) \n " ,
intc_name , dn , data - > num_parent_irqs ) ;
2014-09-10 04:44:21 +04:00
return 0 ;
out_free_domain :
irq_domain_remove ( data - > domain ) ;
2015-07-24 01:52:21 +03:00
out_free_l1_data :
kfree ( data - > l1_data ) ;
2014-09-10 04:44:21 +04:00
out_unmap :
2014-12-25 20:49:03 +03:00
for ( idx = 0 ; idx < MAX_MAPPINGS ; idx + + ) {
if ( data - > map_base [ idx ] )
iounmap ( data - > map_base [ idx ] ) ;
2014-11-07 09:44:26 +03:00
}
2021-10-20 21:48:56 +03:00
out_free_data :
2014-09-10 04:44:21 +04:00
kfree ( data ) ;
return ret ;
}
2014-12-25 20:49:04 +03:00
2016-06-08 20:59:58 +03:00
static int __init bcm7120_l2_intc_probe_7120 ( struct device_node * dn ,
struct device_node * parent )
2014-12-25 20:49:04 +03:00
{
return bcm7120_l2_intc_probe ( dn , parent , bcm7120_l2_intc_iomap_7120 ,
" BCM7120 L2 " ) ;
}
2016-06-08 20:59:58 +03:00
static int __init bcm7120_l2_intc_probe_3380 ( struct device_node * dn ,
struct device_node * parent )
2014-12-25 20:49:05 +03:00
{
return bcm7120_l2_intc_probe ( dn , parent , bcm7120_l2_intc_iomap_3380 ,
" BCM3380 L2 " ) ;
}
2021-10-20 21:48:56 +03:00
IRQCHIP_PLATFORM_DRIVER_BEGIN ( bcm7120_l2 )
IRQCHIP_MATCH ( " brcm,bcm7120-l2-intc " , bcm7120_l2_intc_probe_7120 )
IRQCHIP_MATCH ( " brcm,bcm3380-l2-intc " , bcm7120_l2_intc_probe_3380 )
IRQCHIP_PLATFORM_DRIVER_END ( bcm7120_l2 )
MODULE_DESCRIPTION ( " Broadcom STB 7120-style L2 interrupt controller driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;