2019-07-24 20:16:08 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* JZ47xx SoCs TCU IRQ driver
* Copyright ( C ) 2019 Paul Cercueil < paul @ crapouillou . net >
*/
# include <linux/clk.h>
# include <linux/interrupt.h>
# include <linux/irqchip.h>
# include <linux/irqchip/chained_irq.h>
# include <linux/mfd/ingenic-tcu.h>
# include <linux/mfd/syscon.h>
# include <linux/of_irq.h>
# include <linux/regmap.h>
struct ingenic_tcu {
struct regmap * map ;
struct clk * clk ;
struct irq_domain * domain ;
unsigned int nb_parent_irqs ;
u32 parent_irqs [ 3 ] ;
} ;
static void ingenic_tcu_intc_cascade ( struct irq_desc * desc )
{
struct irq_chip * irq_chip = irq_data_get_irq_chip ( & desc - > irq_data ) ;
struct irq_domain * domain = irq_desc_get_handler_data ( desc ) ;
struct irq_chip_generic * gc = irq_get_domain_generic_chip ( domain , 0 ) ;
struct regmap * map = gc - > private ;
uint32_t irq_reg , irq_mask ;
irqchip/ingenic-tcu: Use correctly sized arguments for bit field
The find.h APIs are designed to be used only on unsigned long arguments.
This can technically result in a over-read, but it is harmless in this
case. Regardless, fix it to avoid the warning seen under -Warray-bounds,
which we'd like to enable globally:
In file included from ./include/linux/bitmap.h:9,
from ./include/linux/cpumask.h:12,
from ./include/linux/smp.h:13,
from ./include/linux/lockdep.h:14,
from ./include/linux/mutex.h:17,
from ./include/linux/notifier.h:14,
from ./include/linux/clk.h:14,
from drivers/irqchip/irq-ingenic-tcu.c:7:
drivers/irqchip/irq-ingenic-tcu.c: In function 'ingenic_tcu_intc_cascade':
./include/linux/find.h:40:23: warning: array subscript 'long unsigned int[0]' is partly outside array bounds of 'uint32_t[1]' {aka 'unsigned int[1]'} [-Warray-bounds]
40 | val = *addr & GENMASK(size - 1, offset);
| ^~~~~
drivers/irqchip/irq-ingenic-tcu.c:30:18: note: while referencing 'irq_reg'
30 | uint32_t irq_reg, irq_mask;
| ^~~~~~~
Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20211215232457.2069969-1-keescook@chromium.org
2021-12-16 02:24:57 +03:00
unsigned long bits ;
2019-07-24 20:16:08 +03:00
unsigned int i ;
regmap_read ( map , TCU_REG_TFR , & irq_reg ) ;
regmap_read ( map , TCU_REG_TMR , & irq_mask ) ;
chained_irq_enter ( irq_chip , desc ) ;
irq_reg & = ~ irq_mask ;
irqchip/ingenic-tcu: Use correctly sized arguments for bit field
The find.h APIs are designed to be used only on unsigned long arguments.
This can technically result in a over-read, but it is harmless in this
case. Regardless, fix it to avoid the warning seen under -Warray-bounds,
which we'd like to enable globally:
In file included from ./include/linux/bitmap.h:9,
from ./include/linux/cpumask.h:12,
from ./include/linux/smp.h:13,
from ./include/linux/lockdep.h:14,
from ./include/linux/mutex.h:17,
from ./include/linux/notifier.h:14,
from ./include/linux/clk.h:14,
from drivers/irqchip/irq-ingenic-tcu.c:7:
drivers/irqchip/irq-ingenic-tcu.c: In function 'ingenic_tcu_intc_cascade':
./include/linux/find.h:40:23: warning: array subscript 'long unsigned int[0]' is partly outside array bounds of 'uint32_t[1]' {aka 'unsigned int[1]'} [-Warray-bounds]
40 | val = *addr & GENMASK(size - 1, offset);
| ^~~~~
drivers/irqchip/irq-ingenic-tcu.c:30:18: note: while referencing 'irq_reg'
30 | uint32_t irq_reg, irq_mask;
| ^~~~~~~
Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20211215232457.2069969-1-keescook@chromium.org
2021-12-16 02:24:57 +03:00
bits = irq_reg ;
2019-07-24 20:16:08 +03:00
irqchip/ingenic-tcu: Use correctly sized arguments for bit field
The find.h APIs are designed to be used only on unsigned long arguments.
This can technically result in a over-read, but it is harmless in this
case. Regardless, fix it to avoid the warning seen under -Warray-bounds,
which we'd like to enable globally:
In file included from ./include/linux/bitmap.h:9,
from ./include/linux/cpumask.h:12,
from ./include/linux/smp.h:13,
from ./include/linux/lockdep.h:14,
from ./include/linux/mutex.h:17,
from ./include/linux/notifier.h:14,
from ./include/linux/clk.h:14,
from drivers/irqchip/irq-ingenic-tcu.c:7:
drivers/irqchip/irq-ingenic-tcu.c: In function 'ingenic_tcu_intc_cascade':
./include/linux/find.h:40:23: warning: array subscript 'long unsigned int[0]' is partly outside array bounds of 'uint32_t[1]' {aka 'unsigned int[1]'} [-Warray-bounds]
40 | val = *addr & GENMASK(size - 1, offset);
| ^~~~~
drivers/irqchip/irq-ingenic-tcu.c:30:18: note: while referencing 'irq_reg'
30 | uint32_t irq_reg, irq_mask;
| ^~~~~~~
Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20211215232457.2069969-1-keescook@chromium.org
2021-12-16 02:24:57 +03:00
for_each_set_bit ( i , & bits , 32 )
2021-05-04 19:42:18 +03:00
generic_handle_domain_irq ( domain , i ) ;
2019-07-24 20:16:08 +03:00
chained_irq_exit ( irq_chip , desc ) ;
}
static void ingenic_tcu_gc_unmask_enable_reg ( 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 ) ;
struct regmap * map = gc - > private ;
u32 mask = d - > mask ;
irq_gc_lock ( gc ) ;
regmap_write ( map , ct - > regs . ack , mask ) ;
regmap_write ( map , ct - > regs . enable , mask ) ;
* ct - > mask_cache | = mask ;
irq_gc_unlock ( gc ) ;
}
static void ingenic_tcu_gc_mask_disable_reg ( 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 ) ;
struct regmap * map = gc - > private ;
u32 mask = d - > mask ;
irq_gc_lock ( gc ) ;
regmap_write ( map , ct - > regs . disable , mask ) ;
* ct - > mask_cache & = ~ mask ;
irq_gc_unlock ( gc ) ;
}
static void ingenic_tcu_gc_mask_disable_reg_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 ) ;
struct regmap * map = gc - > private ;
u32 mask = d - > mask ;
irq_gc_lock ( gc ) ;
regmap_write ( map , ct - > regs . ack , mask ) ;
regmap_write ( map , ct - > regs . disable , mask ) ;
irq_gc_unlock ( gc ) ;
}
static int __init ingenic_tcu_irq_init ( struct device_node * np ,
struct device_node * parent )
{
struct irq_chip_generic * gc ;
struct irq_chip_type * ct ;
struct ingenic_tcu * tcu ;
struct regmap * map ;
unsigned int i ;
int ret , irqs ;
map = device_node_to_regmap ( np ) ;
if ( IS_ERR ( map ) )
return PTR_ERR ( map ) ;
tcu = kzalloc ( sizeof ( * tcu ) , GFP_KERNEL ) ;
if ( ! tcu )
return - ENOMEM ;
tcu - > map = map ;
irqs = of_property_count_elems_of_size ( np , " interrupts " , sizeof ( u32 ) ) ;
if ( irqs < 0 | | irqs > ARRAY_SIZE ( tcu - > parent_irqs ) ) {
pr_crit ( " %s: Invalid 'interrupts' property \n " , __func__ ) ;
ret = - EINVAL ;
goto err_free_tcu ;
}
tcu - > nb_parent_irqs = irqs ;
tcu - > domain = irq_domain_add_linear ( np , 32 , & irq_generic_chip_ops ,
NULL ) ;
if ( ! tcu - > domain ) {
ret = - ENOMEM ;
goto err_free_tcu ;
}
ret = irq_alloc_domain_generic_chips ( tcu - > domain , 32 , 1 , " TCU " ,
handle_level_irq , 0 ,
IRQ_NOPROBE | IRQ_LEVEL , 0 ) ;
if ( ret ) {
pr_crit ( " %s: Invalid 'interrupts' property \n " , __func__ ) ;
goto out_domain_remove ;
}
gc = irq_get_domain_generic_chip ( tcu - > domain , 0 ) ;
ct = gc - > chip_types ;
gc - > wake_enabled = IRQ_MSK ( 32 ) ;
gc - > private = tcu - > map ;
ct - > regs . disable = TCU_REG_TMSR ;
ct - > regs . enable = TCU_REG_TMCR ;
ct - > regs . ack = TCU_REG_TFCR ;
ct - > chip . irq_unmask = ingenic_tcu_gc_unmask_enable_reg ;
ct - > chip . irq_mask = ingenic_tcu_gc_mask_disable_reg ;
ct - > chip . irq_mask_ack = ingenic_tcu_gc_mask_disable_reg_and_ack ;
ct - > chip . flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE ;
/* Mask all IRQs by default */
regmap_write ( tcu - > map , TCU_REG_TMSR , IRQ_MSK ( 32 ) ) ;
/*
* On JZ4740 , timer 0 and timer 1 have their own interrupt line ;
* timers 2 - 7 share one interrupt .
* On SoCs > = JZ4770 , timer 5 has its own interrupt line ;
* timers 0 - 4 and 6 - 7 share one single interrupt .
*
* To keep things simple , we just register the same handler to
* all parent interrupts . The handler will properly detect which
* channel fired the interrupt .
*/
for ( i = 0 ; i < irqs ; i + + ) {
tcu - > parent_irqs [ i ] = irq_of_parse_and_map ( np , i ) ;
if ( ! tcu - > parent_irqs [ i ] ) {
ret = - EINVAL ;
goto out_unmap_irqs ;
}
irq_set_chained_handler_and_data ( tcu - > parent_irqs [ i ] ,
ingenic_tcu_intc_cascade ,
tcu - > domain ) ;
}
return 0 ;
out_unmap_irqs :
for ( ; i > 0 ; i - - )
irq_dispose_mapping ( tcu - > parent_irqs [ i - 1 ] ) ;
out_domain_remove :
irq_domain_remove ( tcu - > domain ) ;
err_free_tcu :
kfree ( tcu ) ;
return ret ;
}
IRQCHIP_DECLARE ( jz4740_tcu_irq , " ingenic,jz4740-tcu " , ingenic_tcu_irq_init ) ;
IRQCHIP_DECLARE ( jz4725b_tcu_irq , " ingenic,jz4725b-tcu " , ingenic_tcu_irq_init ) ;
2021-03-07 20:20:14 +03:00
IRQCHIP_DECLARE ( jz4760_tcu_irq , " ingenic,jz4760-tcu " , ingenic_tcu_irq_init ) ;
2019-07-24 20:16:08 +03:00
IRQCHIP_DECLARE ( jz4770_tcu_irq , " ingenic,jz4770-tcu " , ingenic_tcu_irq_init ) ;
2020-03-17 17:42:40 +03:00
IRQCHIP_DECLARE ( x1000_tcu_irq , " ingenic,x1000-tcu " , ingenic_tcu_irq_init ) ;