2014-05-24 04:40:53 +04:00
/*
* Generic Broadcom Set Top Box Level 2 Interrupt controller driver
*
* Copyright ( C ) 2014 Broadcom Corporation
*
* 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 {
int parent_irq ;
void __iomem * base ;
struct irq_domain * domain ;
bool can_wake ;
u32 saved_mask ; /* for suspend/resume */
} ;
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 ) ;
2014-11-07 09:44:29 +03:00
struct irq_chip_generic * gc = irq_get_domain_generic_chip ( b - > domain , 0 ) ;
2014-05-24 04:40:53 +04:00
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 ) ;
2014-11-07 09:44:29 +03:00
status = irq_reg_readl ( gc , CPU_STATUS ) &
~ ( irq_reg_readl ( gc , CPU_MASK_STATUS ) ) ;
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 ;
/* ack at our level */
2014-11-07 09:44:29 +03:00
irq_reg_writel ( gc , 1 < < irq , CPU_CLEAR ) ;
2014-05-24 04:40:53 +04:00
status & = ~ ( 1 < < irq ) ;
generic_handle_irq ( irq_find_mapping ( b - > domain , irq ) ) ;
} 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 ) ;
struct brcmstb_l2_intc_data * b = gc - > private ;
irq_gc_lock ( gc ) ;
/* Save the current mask */
2014-11-07 09:44:29 +03:00
b - > saved_mask = irq_reg_readl ( gc , CPU_MASK_STATUS ) ;
2014-05-24 04:40:53 +04:00
if ( b - > can_wake ) {
/* Program the wakeup mask */
2014-11-07 09:44:29 +03:00
irq_reg_writel ( gc , ~ gc - > wake_active , CPU_MASK_SET ) ;
irq_reg_writel ( gc , gc - > wake_active , CPU_MASK_CLEAR ) ;
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 ) ;
struct brcmstb_l2_intc_data * b = gc - > private ;
irq_gc_lock ( gc ) ;
/* Clear unmasked non-wakeup interrupts */
2014-11-07 09:44:29 +03:00
irq_reg_writel ( gc , ~ b - > saved_mask & ~ gc - > wake_active , CPU_CLEAR ) ;
2014-05-24 04:40:53 +04:00
/* Restore the saved mask */
2014-11-07 09:44:29 +03:00
irq_reg_writel ( gc , b - > saved_mask , CPU_MASK_SET ) ;
irq_reg_writel ( gc , ~ b - > saved_mask , CPU_MASK_CLEAR ) ;
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_generic * gc ;
struct irq_chip_type * ct ;
int ret ;
2014-11-07 09:44:29 +03:00
unsigned int flags ;
2014-05-24 04:40:53 +04:00
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > base = of_iomap ( np , 0 ) ;
if ( ! data - > base ) {
pr_err ( " failed to remap intc L2 registers \n " ) ;
ret = - ENOMEM ;
goto out_free ;
}
/* Disable all interrupts by default */
2014-11-07 09:44:29 +03:00
writel ( 0xffffffff , data - > 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 )
writel ( 0xffffffff , data - > base + CPU_CLEAR ) ;
2014-05-24 04:40:53 +04:00
data - > parent_irq = irq_of_parse_and_map ( np , 0 ) ;
2014-11-15 01:16:42 +03:00
if ( ! data - > 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 */
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
irq_set_chained_handler_and_data ( data - > parent_irq ,
brcmstb_l2_intc_irq_handle , data ) ;
2014-05-24 04:40:53 +04:00
gc = irq_get_domain_generic_chip ( data - > domain , 0 ) ;
gc - > reg_base = data - > base ;
gc - > private = data ;
ct = gc - > chip_types ;
ct - > chip . irq_ack = irq_gc_ack_set_bit ;
ct - > regs . ack = CPU_CLEAR ;
ct - > chip . irq_mask = irq_gc_mask_disable_reg ;
ct - > regs . disable = CPU_MASK_SET ;
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 ;
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
*/
gc - > wake_enabled = 0xffffffff ;
ct - > chip . irq_set_wake = irq_gc_set_wake ;
}
pr_info ( " registered L2 intc (mem: 0x%p, parent irq: %d) \n " ,
data - > base , data - > parent_irq ) ;
return 0 ;
out_free_domain :
irq_domain_remove ( data - > domain ) ;
out_unmap :
iounmap ( data - > base ) ;
out_free :
kfree ( data ) ;
return ret ;
}
IRQCHIP_DECLARE ( brcmstb_l2_intc , " brcm,l2-intc " , brcmstb_l2_intc_of_init ) ;