2013-02-13 02:04:52 +04:00
/*
* Copyright ( c ) 2010 - 2011 Samsung Electronics Co . , Ltd .
* http : //www.samsung.com
*
* Combiner irqchip for EXYNOS
*
* 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 .
*/
# include <linux/err.h>
# include <linux/export.h>
# include <linux/init.h>
# include <linux/io.h>
2013-04-10 17:31:11 +04:00
# include <linux/slab.h>
2015-06-12 08:43:15 +03:00
# include <linux/syscore_ops.h>
2013-02-13 02:04:52 +04:00
# include <linux/irqdomain.h>
2015-07-08 00:11:46 +03:00
# include <linux/irqchip.h>
2013-01-18 19:31:37 +04:00
# include <linux/irqchip/chained_irq.h>
2014-09-03 09:32:09 +04:00
# include <linux/interrupt.h>
2013-02-13 02:04:52 +04:00
# include <linux/of_address.h>
# include <linux/of_irq.h>
# define COMBINER_ENABLE_SET 0x0
# define COMBINER_ENABLE_CLEAR 0x4
# define COMBINER_INT_STATUS 0xC
2013-04-10 17:17:47 +04:00
# define IRQ_IN_COMBINER 8
2013-02-13 02:04:52 +04:00
static DEFINE_SPINLOCK ( irq_controller_lock ) ;
struct combiner_chip_data {
2013-04-19 01:57:26 +04:00
unsigned int hwirq_offset ;
2013-02-13 02:04:52 +04:00
unsigned int irq_mask ;
void __iomem * base ;
2012-12-12 09:02:45 +04:00
unsigned int parent_irq ;
2015-06-12 08:43:15 +03:00
# ifdef CONFIG_PM
u32 pm_save ;
# endif
2013-02-13 02:04:52 +04:00
} ;
2015-06-12 08:43:15 +03:00
static struct combiner_chip_data * combiner_data ;
2013-02-13 02:04:52 +04:00
static struct irq_domain * combiner_irq_domain ;
2015-06-12 08:43:15 +03:00
static unsigned int max_nr = 20 ;
2013-02-13 02:04:52 +04:00
static inline void __iomem * combiner_base ( struct irq_data * data )
{
struct combiner_chip_data * combiner_data =
irq_data_get_irq_chip_data ( data ) ;
return combiner_data - > base ;
}
static void combiner_mask_irq ( struct irq_data * data )
{
u32 mask = 1 < < ( data - > hwirq % 32 ) ;
__raw_writel ( mask , combiner_base ( data ) + COMBINER_ENABLE_CLEAR ) ;
}
static void combiner_unmask_irq ( struct irq_data * data )
{
u32 mask = 1 < < ( data - > hwirq % 32 ) ;
__raw_writel ( mask , combiner_base ( data ) + COMBINER_ENABLE_SET ) ;
}
static void combiner_handle_cascade_irq ( unsigned int irq , struct irq_desc * desc )
{
2015-06-04 07:13:20 +03:00
struct combiner_chip_data * chip_data = irq_desc_get_handler_data ( desc ) ;
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
2013-02-13 02:04:52 +04:00
unsigned int cascade_irq , combiner_irq ;
unsigned long status ;
chained_irq_enter ( chip , desc ) ;
spin_lock ( & irq_controller_lock ) ;
status = __raw_readl ( chip_data - > base + COMBINER_INT_STATUS ) ;
spin_unlock ( & irq_controller_lock ) ;
status & = chip_data - > irq_mask ;
if ( status = = 0 )
goto out ;
2013-04-19 01:57:26 +04:00
combiner_irq = chip_data - > hwirq_offset + __ffs ( status ) ;
cascade_irq = irq_find_mapping ( combiner_irq_domain , combiner_irq ) ;
2013-02-13 02:04:52 +04:00
2013-04-19 01:57:26 +04:00
if ( unlikely ( ! cascade_irq ) )
2014-02-14 02:27:40 +04:00
handle_bad_irq ( irq , desc ) ;
2013-02-13 02:04:52 +04:00
else
generic_handle_irq ( cascade_irq ) ;
out :
chained_irq_exit ( chip , desc ) ;
}
2012-12-12 09:02:45 +04:00
# ifdef CONFIG_SMP
static int combiner_set_affinity ( struct irq_data * d ,
const struct cpumask * mask_val , bool force )
{
struct combiner_chip_data * chip_data = irq_data_get_irq_chip_data ( d ) ;
struct irq_chip * chip = irq_get_chip ( chip_data - > parent_irq ) ;
struct irq_data * data = irq_get_irq_data ( chip_data - > parent_irq ) ;
if ( chip & & chip - > irq_set_affinity )
return chip - > irq_set_affinity ( data , mask_val , force ) ;
else
return - EINVAL ;
}
# endif
2013-02-13 02:04:52 +04:00
static struct irq_chip combiner_chip = {
2012-12-12 09:02:45 +04:00
. name = " COMBINER " ,
. irq_mask = combiner_mask_irq ,
. irq_unmask = combiner_unmask_irq ,
# ifdef CONFIG_SMP
. irq_set_affinity = combiner_set_affinity ,
# endif
2013-02-13 02:04:52 +04:00
} ;
2013-04-10 17:31:11 +04:00
static void __init combiner_cascade_irq ( struct combiner_chip_data * combiner_data ,
2012-12-12 09:02:49 +04:00
unsigned int irq )
{
irqchip/exynos-combiner: 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: Kukjin Kim <kgene@kernel.org>
Cc: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-samsung-soc@vger.kernel.org
2015-06-21 22:10:49 +03:00
irq_set_chained_handler_and_data ( irq , combiner_handle_cascade_irq ,
combiner_data ) ;
2013-02-13 02:04:52 +04:00
}
2013-04-10 17:31:11 +04:00
static void __init combiner_init_one ( struct combiner_chip_data * combiner_data ,
unsigned int combiner_nr ,
2012-12-12 09:02:45 +04:00
void __iomem * base , unsigned int irq )
2013-02-13 02:04:52 +04:00
{
2013-04-10 17:31:11 +04:00
combiner_data - > base = base ;
2013-04-19 01:57:26 +04:00
combiner_data - > hwirq_offset = ( combiner_nr & ~ 3 ) * IRQ_IN_COMBINER ;
2013-04-10 17:31:11 +04:00
combiner_data - > irq_mask = 0xff < < ( ( combiner_nr % 4 ) < < 3 ) ;
combiner_data - > parent_irq = irq ;
2013-02-13 02:04:52 +04:00
/* Disable all interrupts */
2013-04-10 17:31:11 +04:00
__raw_writel ( combiner_data - > irq_mask , base + COMBINER_ENABLE_CLEAR ) ;
2013-02-13 02:04:52 +04:00
}
static int combiner_irq_domain_xlate ( struct irq_domain * d ,
struct device_node * controller ,
const u32 * intspec , unsigned int intsize ,
unsigned long * out_hwirq ,
unsigned int * out_type )
{
if ( d - > of_node ! = controller )
return - EINVAL ;
if ( intsize < 2 )
return - EINVAL ;
2013-04-10 17:17:47 +04:00
* out_hwirq = intspec [ 0 ] * IRQ_IN_COMBINER + intspec [ 1 ] ;
2013-02-13 02:04:52 +04:00
* out_type = 0 ;
return 0 ;
}
static int combiner_irq_domain_map ( struct irq_domain * d , unsigned int irq ,
irq_hw_number_t hw )
{
2013-04-10 17:31:11 +04:00
struct combiner_chip_data * combiner_data = d - > host_data ;
2013-02-13 02:04:52 +04:00
irq_set_chip_and_handler ( irq , & combiner_chip , handle_level_irq ) ;
irq_set_chip_data ( irq , & combiner_data [ hw > > 3 ] ) ;
set_irq_flags ( irq , IRQF_VALID | IRQF_PROBE ) ;
return 0 ;
}
2015-04-27 15:54:24 +03:00
static const struct irq_domain_ops combiner_irq_domain_ops = {
2013-02-13 02:04:52 +04:00
. xlate = combiner_irq_domain_xlate ,
. map = combiner_irq_domain_map ,
} ;
2013-06-26 15:36:37 +04:00
static void __init combiner_init ( void __iomem * combiner_base ,
2015-06-12 08:43:15 +03:00
struct device_node * np )
2013-02-13 02:04:52 +04:00
{
2013-04-12 17:27:09 +04:00
int i , irq ;
2013-04-10 17:17:47 +04:00
unsigned int nr_irq ;
2013-02-13 02:04:52 +04:00
2013-04-10 17:17:47 +04:00
nr_irq = max_nr * IRQ_IN_COMBINER ;
2012-12-12 09:02:49 +04:00
2013-04-10 17:31:11 +04:00
combiner_data = kcalloc ( max_nr , sizeof ( * combiner_data ) , GFP_KERNEL ) ;
if ( ! combiner_data ) {
pr_warning ( " %s: could not allocate combiner data \n " , __func__ ) ;
return ;
2013-02-13 02:04:52 +04:00
}
2013-10-21 01:01:40 +04:00
combiner_irq_domain = irq_domain_add_linear ( np , nr_irq ,
2013-04-10 17:31:11 +04:00
& combiner_irq_domain_ops , combiner_data ) ;
2013-02-13 02:04:52 +04:00
if ( WARN_ON ( ! combiner_irq_domain ) ) {
pr_warning ( " %s: irq domain init failed \n " , __func__ ) ;
return ;
}
for ( i = 0 ; i < max_nr ; i + + ) {
2013-07-16 07:18:19 +04:00
irq = irq_of_parse_and_map ( np , i ) ;
2013-04-10 17:59:58 +04:00
2013-04-10 17:31:11 +04:00
combiner_init_one ( & combiner_data [ i ] , i ,
combiner_base + ( i > > 2 ) * 0x10 , irq ) ;
combiner_cascade_irq ( & combiner_data [ i ] , irq ) ;
2013-02-13 02:04:52 +04:00
}
}
2015-06-12 08:43:15 +03:00
# ifdef CONFIG_PM
/**
* combiner_suspend - save interrupt combiner state before suspend
*
* Save the interrupt enable set register for all combiner groups since
* the state is lost when the system enters into a sleep state .
*
*/
static int combiner_suspend ( void )
{
int i ;
for ( i = 0 ; i < max_nr ; i + + )
combiner_data [ i ] . pm_save =
__raw_readl ( combiner_data [ i ] . base + COMBINER_ENABLE_SET ) ;
return 0 ;
}
/**
* combiner_resume - restore interrupt combiner state after resume
*
* Restore the interrupt enable set register for all combiner groups since
* the state is lost when the system enters into a sleep state on suspend .
*
*/
static void combiner_resume ( void )
{
int i ;
for ( i = 0 ; i < max_nr ; i + + ) {
__raw_writel ( combiner_data [ i ] . irq_mask ,
combiner_data [ i ] . base + COMBINER_ENABLE_CLEAR ) ;
__raw_writel ( combiner_data [ i ] . pm_save ,
combiner_data [ i ] . base + COMBINER_ENABLE_SET ) ;
}
}
# else
# define combiner_suspend NULL
# define combiner_resume NULL
# endif
static struct syscore_ops combiner_syscore_ops = {
. suspend = combiner_suspend ,
. resume = combiner_resume ,
} ;
2013-02-13 02:04:52 +04:00
static int __init combiner_of_init ( struct device_node * np ,
struct device_node * parent )
{
void __iomem * combiner_base ;
combiner_base = of_iomap ( np , 0 ) ;
if ( ! combiner_base ) {
pr_err ( " %s: failed to map combiner registers \n " , __func__ ) ;
return - ENXIO ;
}
2013-04-10 17:17:47 +04:00
if ( of_property_read_u32 ( np , " samsung,combiner-nr " , & max_nr ) ) {
pr_info ( " %s: number of combiners not specified, "
" setting default as %d. \n " ,
__func__ , max_nr ) ;
}
2015-06-12 08:43:15 +03:00
combiner_init ( combiner_base , np ) ;
register_syscore_ops ( & combiner_syscore_ops ) ;
2013-02-13 02:04:52 +04:00
return 0 ;
}
IRQCHIP_DECLARE ( exynos4210_combiner , " samsung,exynos4210-combiner " ,
combiner_of_init ) ;