2014-03-19 23:21:17 +04:00
/*
* Allwinner A20 / A31 SoCs NMI IRQ chip driver .
*
* Carlo Caione < carlo . caione @ gmail . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
2015-10-05 19:42:13 +03:00
# define DRV_NAME "sunxi-nmi"
# define pr_fmt(fmt) DRV_NAME ": " fmt
2014-03-19 23:21:17 +04:00
# include <linux/bitops.h>
# include <linux/device.h>
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/interrupt.h>
# include <linux/irqdomain.h>
# include <linux/of_irq.h>
# include <linux/of_address.h>
# include <linux/of_platform.h>
2015-07-08 00:11:46 +03:00
# include <linux/irqchip.h>
2014-03-19 23:21:17 +04:00
# include <linux/irqchip/chained_irq.h>
# define SUNXI_NMI_SRC_TYPE_MASK 0x00000003
enum {
SUNXI_SRC_TYPE_LEVEL_LOW = 0 ,
SUNXI_SRC_TYPE_EDGE_FALLING ,
SUNXI_SRC_TYPE_LEVEL_HIGH ,
SUNXI_SRC_TYPE_EDGE_RISING ,
} ;
struct sunxi_sc_nmi_reg_offs {
u32 ctrl ;
u32 pend ;
u32 enable ;
} ;
static struct sunxi_sc_nmi_reg_offs sun7i_reg_offs = {
. ctrl = 0x00 ,
. pend = 0x04 ,
. enable = 0x08 ,
} ;
static struct sunxi_sc_nmi_reg_offs sun6i_reg_offs = {
. ctrl = 0x00 ,
. pend = 0x04 ,
. enable = 0x34 ,
} ;
static inline void sunxi_sc_nmi_write ( struct irq_chip_generic * gc , u32 off ,
u32 val )
{
2014-11-07 09:44:17 +03:00
irq_reg_writel ( gc , val , off ) ;
2014-03-19 23:21:17 +04:00
}
static inline u32 sunxi_sc_nmi_read ( struct irq_chip_generic * gc , u32 off )
{
2014-11-07 09:44:17 +03:00
return irq_reg_readl ( gc , off ) ;
2014-03-19 23:21:17 +04:00
}
2015-09-14 11:42:37 +03:00
static void sunxi_sc_nmi_handle_irq ( struct irq_desc * desc )
2014-03-19 23:21:17 +04:00
{
struct irq_domain * domain = irq_desc_get_handler_data ( desc ) ;
2015-06-04 07:13:20 +03:00
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
2014-03-19 23:21:17 +04:00
unsigned int virq = irq_find_mapping ( domain , 0 ) ;
chained_irq_enter ( chip , desc ) ;
generic_handle_irq ( virq ) ;
chained_irq_exit ( chip , desc ) ;
}
static int sunxi_sc_nmi_set_type ( struct irq_data * data , unsigned int flow_type )
{
struct irq_chip_generic * gc = irq_data_get_irq_chip_data ( data ) ;
struct irq_chip_type * ct = gc - > chip_types ;
u32 src_type_reg ;
u32 ctrl_off = ct - > regs . type ;
unsigned int src_type ;
unsigned int i ;
irq_gc_lock ( gc ) ;
switch ( flow_type & IRQF_TRIGGER_MASK ) {
case IRQ_TYPE_EDGE_FALLING :
src_type = SUNXI_SRC_TYPE_EDGE_FALLING ;
break ;
case IRQ_TYPE_EDGE_RISING :
src_type = SUNXI_SRC_TYPE_EDGE_RISING ;
break ;
case IRQ_TYPE_LEVEL_HIGH :
src_type = SUNXI_SRC_TYPE_LEVEL_HIGH ;
break ;
case IRQ_TYPE_NONE :
case IRQ_TYPE_LEVEL_LOW :
src_type = SUNXI_SRC_TYPE_LEVEL_LOW ;
break ;
default :
irq_gc_unlock ( gc ) ;
2015-10-05 19:42:13 +03:00
pr_err ( " Cannot assign multiple trigger modes to IRQ %d. \n " ,
data - > irq ) ;
2014-03-19 23:21:17 +04:00
return - EBADR ;
}
irqd_set_trigger_type ( data , flow_type ) ;
irq_setup_alt_chip ( data , flow_type ) ;
2015-06-07 16:33:29 +03:00
for ( i = 0 ; i < gc - > num_ct ; i + + , ct + + )
2014-03-19 23:21:17 +04:00
if ( ct - > type & flow_type )
ctrl_off = ct - > regs . type ;
src_type_reg = sunxi_sc_nmi_read ( gc , ctrl_off ) ;
src_type_reg & = ~ SUNXI_NMI_SRC_TYPE_MASK ;
src_type_reg | = src_type ;
sunxi_sc_nmi_write ( gc , ctrl_off , src_type_reg ) ;
irq_gc_unlock ( gc ) ;
return IRQ_SET_MASK_OK ;
}
static int __init sunxi_sc_nmi_irq_init ( struct device_node * node ,
struct sunxi_sc_nmi_reg_offs * reg_offs )
{
struct irq_domain * domain ;
struct irq_chip_generic * gc ;
unsigned int irq ;
unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN ;
int ret ;
domain = irq_domain_add_linear ( node , 1 , & irq_generic_chip_ops , NULL ) ;
if ( ! domain ) {
2015-10-05 19:42:13 +03:00
pr_err ( " Could not register interrupt domain. \n " ) ;
2014-03-19 23:21:17 +04:00
return - ENOMEM ;
}
2015-10-05 19:42:13 +03:00
ret = irq_alloc_domain_generic_chips ( domain , 1 , 2 , DRV_NAME ,
2014-03-19 23:21:17 +04:00
handle_fasteoi_irq , clr , 0 ,
IRQ_GC_INIT_MASK_CACHE ) ;
if ( ret ) {
2015-10-05 19:42:13 +03:00
pr_err ( " Could not allocate generic interrupt chip. \n " ) ;
goto fail_irqd_remove ;
2014-03-19 23:21:17 +04:00
}
irq = irq_of_parse_and_map ( node , 0 ) ;
if ( irq < = 0 ) {
2015-10-05 19:42:13 +03:00
pr_err ( " unable to parse irq \n " ) ;
2014-03-19 23:21:17 +04:00
ret = - EINVAL ;
goto fail_irqd_remove ;
}
gc = irq_get_domain_generic_chip ( domain , 0 ) ;
2015-10-05 19:42:14 +03:00
gc - > reg_base = of_io_request_and_map ( node , 0 , of_node_full_name ( node ) ) ;
2014-03-19 23:21:17 +04:00
if ( ! gc - > reg_base ) {
2015-10-05 19:42:13 +03:00
pr_err ( " unable to map resource \n " ) ;
2014-03-19 23:21:17 +04:00
ret = - ENOMEM ;
goto fail_irqd_remove ;
}
gc - > chip_types [ 0 ] . type = IRQ_TYPE_LEVEL_MASK ;
gc - > chip_types [ 0 ] . chip . irq_mask = irq_gc_mask_clr_bit ;
gc - > chip_types [ 0 ] . chip . irq_unmask = irq_gc_mask_set_bit ;
gc - > chip_types [ 0 ] . chip . irq_eoi = irq_gc_ack_set_bit ;
gc - > chip_types [ 0 ] . chip . irq_set_type = sunxi_sc_nmi_set_type ;
gc - > chip_types [ 0 ] . chip . flags = IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED ;
gc - > chip_types [ 0 ] . regs . ack = reg_offs - > pend ;
gc - > chip_types [ 0 ] . regs . mask = reg_offs - > enable ;
gc - > chip_types [ 0 ] . regs . type = reg_offs - > ctrl ;
gc - > chip_types [ 1 ] . type = IRQ_TYPE_EDGE_BOTH ;
gc - > chip_types [ 1 ] . chip . name = gc - > chip_types [ 0 ] . chip . name ;
gc - > chip_types [ 1 ] . chip . irq_ack = irq_gc_ack_set_bit ;
gc - > chip_types [ 1 ] . chip . irq_mask = irq_gc_mask_clr_bit ;
gc - > chip_types [ 1 ] . chip . irq_unmask = irq_gc_mask_set_bit ;
gc - > chip_types [ 1 ] . chip . irq_set_type = sunxi_sc_nmi_set_type ;
gc - > chip_types [ 1 ] . regs . ack = reg_offs - > pend ;
gc - > chip_types [ 1 ] . regs . mask = reg_offs - > enable ;
gc - > chip_types [ 1 ] . regs . type = reg_offs - > ctrl ;
gc - > chip_types [ 1 ] . handler = handle_edge_irq ;
sunxi_sc_nmi_write ( gc , reg_offs - > enable , 0 ) ;
sunxi_sc_nmi_write ( gc , reg_offs - > pend , 0x1 ) ;
irqchip/sunxi-nmi: 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: Thomas Gleixner <tglx@linutronix.de>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: Maxime Ripard <maxime.ripard@free-electrons.com>
Cc: linux-arm-kernel@lists.infradead.org
2015-06-21 22:10:58 +03:00
irq_set_chained_handler_and_data ( irq , sunxi_sc_nmi_handle_irq , domain ) ;
2014-03-27 21:02:39 +04:00
2014-03-19 23:21:17 +04:00
return 0 ;
fail_irqd_remove :
irq_domain_remove ( domain ) ;
return ret ;
}
static int __init sun6i_sc_nmi_irq_init ( struct device_node * node ,
struct device_node * parent )
{
return sunxi_sc_nmi_irq_init ( node , & sun6i_reg_offs ) ;
}
IRQCHIP_DECLARE ( sun6i_sc_nmi , " allwinner,sun6i-a31-sc-nmi " , sun6i_sc_nmi_irq_init ) ;
static int __init sun7i_sc_nmi_irq_init ( struct device_node * node ,
struct device_node * parent )
{
return sunxi_sc_nmi_irq_init ( node , & sun7i_reg_offs ) ;
}
IRQCHIP_DECLARE ( sun7i_sc_nmi , " allwinner,sun7i-a20-sc-nmi " , sun7i_sc_nmi_irq_init ) ;