2014-05-24 04:40:53 +04:00
/*
* Generic Broadcom Set Top Box Level 2 Interrupt controller driver
*
2017-09-19 03:59:58 +03:00
* Copyright ( C ) 2014 - 2017 Broadcom
2014-05-24 04:40:53 +04:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/module.h>
# include <linux/platform_device.h>
2014-11-07 09:44:20 +03:00
# include <linux/spinlock.h>
2014-05-24 04:40:53 +04:00
# 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/irqchip.h>
# include <linux/irqchip/chained_irq.h>
/* Register offsets in the L2 interrupt controller */
# define CPU_STATUS 0x00
# define CPU_SET 0x04
# define CPU_CLEAR 0x08
# define CPU_MASK_STATUS 0x0c
# define CPU_MASK_SET 0x10
# define CPU_MASK_CLEAR 0x14
/* L2 intc private data structure */
struct brcmstb_l2_intc_data {
struct irq_domain * domain ;
2017-09-19 03:59:58 +03:00
struct irq_chip_generic * gc ;
2017-09-19 03:59:59 +03:00
int status_offset ;
int mask_offset ;
2014-05-24 04:40:53 +04:00
bool can_wake ;
u32 saved_mask ; /* for suspend/resume */
} ;
2017-09-19 03:59:58 +03:00
/**
* brcmstb_l2_mask_and_ack - Mask and ack pending interrupt
* @ d : irq_data
*
* Chip has separate enable / disable registers instead of a single mask
* register and pending interrupt is acknowledged by setting a bit .
*
* Note : This function is generic and could easily be added to the
* generic irqchip implementation if there ever becomes a will to do so .
* Perhaps with a name like irq_gc_mask_disable_and_ack_set ( ) .
*
* e . g . : https : //patchwork.kernel.org/patch/9831047/
*/
static void brcmstb_l2_mask_and_ack ( struct irq_data * d )
{
struct irq_chip_generic * gc = irq_data_get_irq_chip_data ( d ) ;
struct irq_chip_type * ct = irq_data_get_chip_type ( d ) ;
u32 mask = d - > mask ;
irq_gc_lock ( gc ) ;
irq_reg_writel ( gc , mask , ct - > regs . disable ) ;
* ct - > mask_cache & = ~ mask ;
irq_reg_writel ( gc , mask , ct - > regs . ack ) ;
irq_gc_unlock ( gc ) ;
}
2015-09-14 11:42:37 +03:00
static void brcmstb_l2_intc_irq_handle ( struct irq_desc * desc )
2014-05-24 04:40:53 +04:00
{
struct brcmstb_l2_intc_data * b = irq_desc_get_handler_data ( desc ) ;
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
2015-09-14 11:42:37 +03:00
unsigned int irq ;
2014-05-24 04:40:53 +04:00
u32 status ;
chained_irq_enter ( chip , desc ) ;
2017-09-19 03:59:59 +03:00
status = irq_reg_readl ( b - > gc , b - > status_offset ) &
~ ( irq_reg_readl ( b - > gc , b - > mask_offset ) ) ;
2014-05-24 04:40:53 +04:00
if ( status = = 0 ) {
2014-11-07 09:44:20 +03:00
raw_spin_lock ( & desc - > lock ) ;
2015-09-14 11:42:37 +03:00
handle_bad_irq ( desc ) ;
2014-11-07 09:44:20 +03:00
raw_spin_unlock ( & desc - > lock ) ;
2014-05-24 04:40:53 +04:00
goto out ;
}
do {
irq = ffs ( status ) - 1 ;
status & = ~ ( 1 < < irq ) ;
2017-09-19 03:59:58 +03:00
generic_handle_irq ( irq_linear_revmap ( b - > domain , irq ) ) ;
2014-05-24 04:40:53 +04:00
} while ( status ) ;
out :
chained_irq_exit ( chip , desc ) ;
}
static void brcmstb_l2_intc_suspend ( struct irq_data * d )
{
struct irq_chip_generic * gc = irq_data_get_irq_chip_data ( d ) ;
2017-09-19 03:59:59 +03:00
struct irq_chip_type * ct = irq_data_get_chip_type ( d ) ;
2014-05-24 04:40:53 +04:00
struct brcmstb_l2_intc_data * b = gc - > private ;
irq_gc_lock ( gc ) ;
/* Save the current mask */
2017-09-19 03:59:59 +03:00
b - > saved_mask = irq_reg_readl ( gc , ct - > regs . mask ) ;
2014-05-24 04:40:53 +04:00
if ( b - > can_wake ) {
/* Program the wakeup mask */
2017-09-19 03:59:59 +03:00
irq_reg_writel ( gc , ~ gc - > wake_active , ct - > regs . disable ) ;
irq_reg_writel ( gc , gc - > wake_active , ct - > regs . enable ) ;
2014-05-24 04:40:53 +04:00
}
irq_gc_unlock ( gc ) ;
}
static void brcmstb_l2_intc_resume ( struct irq_data * d )
{
struct irq_chip_generic * gc = irq_data_get_irq_chip_data ( d ) ;
2017-09-19 03:59:59 +03:00
struct irq_chip_type * ct = irq_data_get_chip_type ( d ) ;
2014-05-24 04:40:53 +04:00
struct brcmstb_l2_intc_data * b = gc - > private ;
irq_gc_lock ( gc ) ;
2017-09-19 03:59:59 +03:00
if ( ct - > chip . irq_ack ! = irq_gc_noop ) {
/* Clear unmasked non-wakeup interrupts */
irq_reg_writel ( gc , ~ b - > saved_mask & ~ gc - > wake_active ,
ct - > regs . ack ) ;
}
2014-05-24 04:40:53 +04:00
/* Restore the saved mask */
2017-09-19 03:59:59 +03:00
irq_reg_writel ( gc , b - > saved_mask , ct - > regs . disable ) ;
irq_reg_writel ( gc , ~ b - > saved_mask , ct - > regs . enable ) ;
2014-05-24 04:40:53 +04:00
irq_gc_unlock ( gc ) ;
}
2016-06-08 21:02:20 +03:00
static int __init brcmstb_l2_intc_of_init ( struct device_node * np ,
struct device_node * parent )
2014-05-24 04:40:53 +04:00
{
unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN ;
struct brcmstb_l2_intc_data * data ;
struct irq_chip_type * ct ;
int ret ;
2014-11-07 09:44:29 +03:00
unsigned int flags ;
2017-09-19 03:59:58 +03:00
int parent_irq ;
void __iomem * base ;
2014-05-24 04:40:53 +04:00
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
2017-09-19 03:59:58 +03:00
base = of_iomap ( np , 0 ) ;
if ( ! base ) {
2014-05-24 04:40:53 +04:00
pr_err ( " failed to remap intc L2 registers \n " ) ;
ret = - ENOMEM ;
goto out_free ;
}
/* Disable all interrupts by default */
2017-09-19 03:59:58 +03:00
writel ( 0xffffffff , base + CPU_MASK_SET ) ;
2014-12-25 20:49:02 +03:00
/* Wakeup interrupts may be retained from S5 (cold boot) */
data - > can_wake = of_property_read_bool ( np , " brcm,irq-can-wake " ) ;
if ( ! data - > can_wake )
2017-09-19 03:59:58 +03:00
writel ( 0xffffffff , base + CPU_CLEAR ) ;
2014-05-24 04:40:53 +04:00
2017-09-19 03:59:58 +03:00
parent_irq = irq_of_parse_and_map ( np , 0 ) ;
if ( ! parent_irq ) {
2014-05-24 04:40:53 +04:00
pr_err ( " failed to find parent interrupt \n " ) ;
2014-11-15 01:16:42 +03:00
ret = - EINVAL ;
2014-05-24 04:40:53 +04:00
goto out_unmap ;
}
data - > domain = irq_domain_add_linear ( np , 32 ,
& irq_generic_chip_ops , NULL ) ;
if ( ! data - > domain ) {
ret = - ENOMEM ;
goto out_unmap ;
}
2014-11-07 09:44:29 +03:00
/* MIPS chips strapped for BE will automagically configure the
* peripheral registers for CPU - native byte order .
*/
flags = 0 ;
if ( IS_ENABLED ( CONFIG_MIPS ) & & IS_ENABLED ( CONFIG_CPU_BIG_ENDIAN ) )
flags | = IRQ_GC_BE_IO ;
2014-05-24 04:40:53 +04:00
/* Allocate a single Generic IRQ chip for this node */
ret = irq_alloc_domain_generic_chips ( data - > domain , 32 , 1 ,
2014-11-07 09:44:29 +03:00
np - > full_name , handle_edge_irq , clr , 0 , flags ) ;
2014-05-24 04:40:53 +04:00
if ( ret ) {
pr_err ( " failed to allocate generic irq chip \n " ) ;
goto out_free_domain ;
}
/* Set the IRQ chaining logic */
2017-09-19 03:59:58 +03:00
irq_set_chained_handler_and_data ( parent_irq ,
irqchip/brcmstb-l2: Consolidate chained IRQ handler install/remove
Chained irq handlers usually set up handler data as well. We now have
a function to set both under irq_desc->lock. Replace the two calls
with one.
Search and conversion was done with coccinelle:
@@
expression E1, E2, E3;
@@
(
-if (irq_set_handler_data(E1, E2) != 0)
- BUG();
|
-irq_set_handler_data(E1, E2);
)
-irq_set_chained_handler(E1, E3);
+irq_set_chained_handler_and_data(E1, E3, E2);
@@
expression E1, E2, E3;
@@
(
-if (irq_set_handler_data(E1, E2) != 0)
- BUG();
...
|
-irq_set_handler_data(E1, E2);
...
)
-irq_set_chained_handler(E1, E3);
+irq_set_chained_handler_and_data(E1, E3, E2);
Reported-by: Russell King <rmk+kernel@arm.linux.org.uk>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Julia Lawall <Julia.Lawall@lip6.fr>
Cc: Kevin Cernekee <cernekee@gmail.com>
Cc: Florian Fainelli <f.fainelli@gmail.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: linux-mips@linux-mips.org
2015-06-21 22:10:52 +03:00
brcmstb_l2_intc_irq_handle , data ) ;
2014-05-24 04:40:53 +04:00
2017-09-19 03:59:58 +03:00
data - > gc = irq_get_domain_generic_chip ( data - > domain , 0 ) ;
data - > gc - > reg_base = base ;
data - > gc - > private = data ;
2017-09-19 03:59:59 +03:00
data - > status_offset = CPU_STATUS ;
data - > mask_offset = CPU_MASK_STATUS ;
2017-09-19 03:59:58 +03:00
ct = data - > gc - > chip_types ;
2014-05-24 04:40:53 +04:00
ct - > chip . irq_ack = irq_gc_ack_set_bit ;
ct - > regs . ack = CPU_CLEAR ;
ct - > chip . irq_mask = irq_gc_mask_disable_reg ;
2017-09-19 03:59:58 +03:00
ct - > chip . irq_mask_ack = brcmstb_l2_mask_and_ack ;
2014-05-24 04:40:53 +04:00
ct - > regs . disable = CPU_MASK_SET ;
2017-09-19 03:59:59 +03:00
ct - > regs . mask = CPU_MASK_STATUS ;
2014-05-24 04:40:53 +04:00
ct - > chip . irq_unmask = irq_gc_unmask_enable_reg ;
ct - > regs . enable = CPU_MASK_CLEAR ;
ct - > chip . irq_suspend = brcmstb_l2_intc_suspend ;
ct - > chip . irq_resume = brcmstb_l2_intc_resume ;
2017-07-28 01:38:17 +03:00
ct - > chip . irq_pm_shutdown = brcmstb_l2_intc_suspend ;
2014-05-24 04:40:53 +04:00
2014-12-25 20:49:02 +03:00
if ( data - > can_wake ) {
2014-05-24 04:40:53 +04:00
/* This IRQ chip can wake the system, set all child interrupts
* in wake_enabled mask
*/
2017-09-19 03:59:58 +03:00
data - > gc - > wake_enabled = 0xffffffff ;
2014-05-24 04:40:53 +04:00
ct - > chip . irq_set_wake = irq_gc_set_wake ;
}
pr_info ( " registered L2 intc (mem: 0x%p, parent irq: %d) \n " ,
2017-09-19 03:59:58 +03:00
base , parent_irq ) ;
2014-05-24 04:40:53 +04:00
return 0 ;
out_free_domain :
irq_domain_remove ( data - > domain ) ;
out_unmap :
2017-09-19 03:59:58 +03:00
iounmap ( base ) ;
2014-05-24 04:40:53 +04:00
out_free :
kfree ( data ) ;
return ret ;
}
IRQCHIP_DECLARE ( brcmstb_l2_intc , " brcm,l2-intc " , brcmstb_l2_intc_of_init ) ;