2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2013-12-03 14:27:23 +04:00
/*
* drivers / irqchip / irq - crossbar . c
*
* Copyright ( C ) 2013 Texas Instruments Incorporated - http : //www.ti.com
* Author : Sricharan R < r . sricharan @ ti . com >
*/
# include <linux/err.h>
# include <linux/io.h>
2015-07-08 00:11:46 +03:00
# include <linux/irqchip.h>
2015-03-11 18:43:44 +03:00
# include <linux/irqdomain.h>
2013-12-03 14:27:23 +04:00
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/slab.h>
2015-03-11 18:43:44 +03:00
2013-12-03 14:27:23 +04:00
# define IRQ_FREE -1
2014-06-26 11:10:19 +04:00
# define IRQ_RESERVED -2
2014-06-26 11:10:21 +04:00
# define IRQ_SKIP -3
2013-12-03 14:27:23 +04:00
# define GIC_IRQ_START 32
2014-06-26 11:10:26 +04:00
/**
* struct crossbar_device - crossbar device description
2015-03-11 18:43:44 +03:00
* @ lock : spinlock serializing access to @ irq_map
2013-12-03 14:27:23 +04:00
* @ int_max : maximum number of supported interrupts
2014-06-26 11:10:22 +04:00
* @ safe_map : safe default value to initialize the crossbar
2014-06-26 11:10:31 +04:00
* @ max_crossbar_sources : Maximum number of crossbar sources
2013-12-03 14:27:23 +04:00
* @ irq_map : array of interrupts to crossbar number mapping
* @ crossbar_base : crossbar base address
* @ register_offsets : offsets for each irq number
2014-06-26 11:10:26 +04:00
* @ write : register write function pointer
2013-12-03 14:27:23 +04:00
*/
struct crossbar_device {
2015-03-11 18:43:44 +03:00
raw_spinlock_t lock ;
2013-12-03 14:27:23 +04:00
uint int_max ;
2014-06-26 11:10:22 +04:00
uint safe_map ;
2014-06-26 11:10:31 +04:00
uint max_crossbar_sources ;
2013-12-03 14:27:23 +04:00
uint * irq_map ;
void __iomem * crossbar_base ;
int * register_offsets ;
2014-06-26 11:10:22 +04:00
void ( * write ) ( int , int ) ;
2013-12-03 14:27:23 +04:00
} ;
static struct crossbar_device * cb ;
2015-03-11 18:43:44 +03:00
static void crossbar_writel ( int irq_no , int cb_no )
2013-12-03 14:27:23 +04:00
{
writel ( cb_no , cb - > crossbar_base + cb - > register_offsets [ irq_no ] ) ;
}
2015-03-11 18:43:44 +03:00
static void crossbar_writew ( int irq_no , int cb_no )
2013-12-03 14:27:23 +04:00
{
writew ( cb_no , cb - > crossbar_base + cb - > register_offsets [ irq_no ] ) ;
}
2015-03-11 18:43:44 +03:00
static void crossbar_writeb ( int irq_no , int cb_no )
2013-12-03 14:27:23 +04:00
{
writeb ( cb_no , cb - > crossbar_base + cb - > register_offsets [ irq_no ] ) ;
}
2015-03-11 18:43:44 +03:00
static struct irq_chip crossbar_chip = {
. name = " CBAR " ,
. irq_eoi = irq_chip_eoi_parent ,
. irq_mask = irq_chip_mask_parent ,
. irq_unmask = irq_chip_unmask_parent ,
. irq_retrigger = irq_chip_retrigger_hierarchy ,
2015-08-14 15:20:27 +03:00
. irq_set_type = irq_chip_set_type_parent ,
2015-08-14 15:20:30 +03:00
. flags = IRQCHIP_MASK_ON_SUSPEND |
IRQCHIP_SKIP_SET_WAKE ,
2015-03-11 18:43:44 +03:00
# ifdef CONFIG_SMP
. irq_set_affinity = irq_chip_set_affinity_parent ,
# endif
} ;
2014-06-26 11:10:20 +04:00
2015-03-11 18:43:44 +03:00
static int allocate_gic_irq ( struct irq_domain * domain , unsigned virq ,
irq_hw_number_t hwirq )
2013-12-03 14:27:23 +04:00
{
2015-10-13 14:51:33 +03:00
struct irq_fwspec fwspec ;
2013-12-03 14:27:23 +04:00
int i ;
2015-03-11 18:43:44 +03:00
int err ;
2013-12-03 14:27:23 +04:00
2015-10-13 14:51:33 +03:00
if ( ! irq_domain_get_of_node ( domain - > parent ) )
return - EINVAL ;
2015-03-11 18:43:44 +03:00
raw_spin_lock ( & cb - > lock ) ;
2014-06-26 11:10:23 +04:00
for ( i = cb - > int_max - 1 ; i > = 0 ; i - - ) {
2013-12-03 14:27:23 +04:00
if ( cb - > irq_map [ i ] = = IRQ_FREE ) {
2015-03-11 18:43:44 +03:00
cb - > irq_map [ i ] = hwirq ;
break ;
2013-12-03 14:27:23 +04:00
}
}
2015-03-11 18:43:44 +03:00
raw_spin_unlock ( & cb - > lock ) ;
2013-12-03 14:27:23 +04:00
2015-03-11 18:43:44 +03:00
if ( i < 0 )
return - ENODEV ;
2013-12-03 14:27:23 +04:00
2015-10-13 14:51:33 +03:00
fwspec . fwnode = domain - > parent - > fwnode ;
fwspec . param_count = 3 ;
fwspec . param [ 0 ] = 0 ; /* SPI */
fwspec . param [ 1 ] = i ;
fwspec . param [ 2 ] = IRQ_TYPE_LEVEL_HIGH ;
irqchip: crossbar: Allow for quirky hardware with direct hardwiring of GIC
On certain platforms such as DRA7, SPIs 0, 1, 2, 3, 5, 6, 10, 131,
132, 133 are direct wired to hardware blocks bypassing crossbar.
This quirky implementation is *NOT* supposed to be the expectation
of crossbar hardware usage. However, these are already marked in our
description of the hardware with SKIP and RESERVED where appropriate.
Unfortunately, we need to be able to refer to these hardwired IRQs.
So, to request these, crossbar driver can use the existing information
from it's table that these SKIP/RESERVED maps are direct wired sources
and generic allocation/programming of crossbar should be avoided.
Signed-off-by: Nishanth Menon <nm@ti.com>
Signed-off-by: Sricharan R <r.sricharan@ti.com>
Acked-by: Santosh Shilimkar <santosh.shilimkar@ti.com>
Link: https://lkml.kernel.org/r/1403766634-18543-17-git-send-email-r.sricharan@ti.com
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
2014-06-26 11:10:34 +04:00
2015-10-13 14:51:33 +03:00
err = irq_domain_alloc_irqs_parent ( domain , virq , 1 , & fwspec ) ;
2015-03-11 18:43:44 +03:00
if ( err )
cb - > irq_map [ i ] = IRQ_FREE ;
else
cb - > write ( i , hwirq ) ;
2014-06-26 11:10:32 +04:00
2015-03-11 18:43:44 +03:00
return err ;
2014-06-26 11:10:32 +04:00
}
2015-03-11 18:43:44 +03:00
static int crossbar_domain_alloc ( struct irq_domain * d , unsigned int virq ,
unsigned int nr_irqs , void * data )
2013-12-03 14:27:23 +04:00
{
2015-10-13 14:51:33 +03:00
struct irq_fwspec * fwspec = data ;
2015-03-11 18:43:44 +03:00
irq_hw_number_t hwirq ;
int i ;
2015-10-13 14:51:33 +03:00
if ( fwspec - > param_count ! = 3 )
2015-03-11 18:43:44 +03:00
return - EINVAL ; /* Not GIC compliant */
2015-10-13 14:51:33 +03:00
if ( fwspec - > param [ 0 ] ! = 0 )
2015-03-11 18:43:44 +03:00
return - EINVAL ; /* No PPI should point to this domain */
2015-10-13 14:51:33 +03:00
hwirq = fwspec - > param [ 1 ] ;
2015-03-11 18:43:44 +03:00
if ( ( hwirq + nr_irqs ) > cb - > max_crossbar_sources )
return - EINVAL ; /* Can't deal with this */
for ( i = 0 ; i < nr_irqs ; i + + ) {
int err = allocate_gic_irq ( d , virq + i , hwirq + i ) ;
if ( err )
return err ;
irq_domain_set_hwirq_and_chip ( d , virq + i , hwirq + i ,
& crossbar_chip , NULL ) ;
}
2014-06-26 11:10:32 +04:00
2013-12-03 14:27:23 +04:00
return 0 ;
}
2014-06-26 11:10:30 +04:00
/**
2015-03-11 18:43:44 +03:00
* crossbar_domain_free - unmap / free a crossbar < - > irq connection
* @ domain : domain of irq to unmap
* @ virq : virq number
* @ nr_irqs : number of irqs to free
2014-06-26 11:10:30 +04:00
*
* We do not maintain a use count of total number of map / unmap
* calls for a particular irq to find out if a irq can be really
* unmapped . This is because unmap is called during irq_dispose_mapping ( irq ) ,
* after which irq is anyways unusable . So an explicit map has to be called
* after that .
*/
2015-03-11 18:43:44 +03:00
static void crossbar_domain_free ( struct irq_domain * domain , unsigned int virq ,
unsigned int nr_irqs )
2013-12-03 14:27:23 +04:00
{
2015-03-11 18:43:44 +03:00
int i ;
2013-12-03 14:27:23 +04:00
2015-03-11 18:43:44 +03:00
raw_spin_lock ( & cb - > lock ) ;
for ( i = 0 ; i < nr_irqs ; i + + ) {
struct irq_data * d = irq_domain_get_irq_data ( domain , virq + i ) ;
irq_domain_reset_irq_data ( d ) ;
cb - > irq_map [ d - > hwirq ] = IRQ_FREE ;
cb - > write ( d - > hwirq , cb - > safe_map ) ;
2014-06-26 11:10:22 +04:00
}
2015-03-11 18:43:44 +03:00
raw_spin_unlock ( & cb - > lock ) ;
2013-12-03 14:27:23 +04:00
}
2015-10-13 14:51:33 +03:00
static int crossbar_domain_translate ( struct irq_domain * d ,
struct irq_fwspec * fwspec ,
unsigned long * hwirq ,
unsigned int * type )
2013-12-03 14:27:23 +04:00
{
2015-10-13 14:51:33 +03:00
if ( is_of_node ( fwspec - > fwnode ) ) {
if ( fwspec - > param_count ! = 3 )
return - EINVAL ;
2015-03-11 18:43:44 +03:00
2015-10-13 14:51:33 +03:00
/* No PPI should point to this domain */
if ( fwspec - > param [ 0 ] ! = 0 )
return - EINVAL ;
* hwirq = fwspec - > param [ 1 ] ;
2016-05-10 18:14:37 +03:00
* type = fwspec - > param [ 2 ] & IRQ_TYPE_SENSE_MASK ;
2015-10-13 14:51:33 +03:00
return 0 ;
}
return - EINVAL ;
2013-12-03 14:27:23 +04:00
}
2015-03-11 18:43:44 +03:00
static const struct irq_domain_ops crossbar_domain_ops = {
2015-10-13 14:51:33 +03:00
. alloc = crossbar_domain_alloc ,
. free = crossbar_domain_free ,
. translate = crossbar_domain_translate ,
2013-12-03 14:27:23 +04:00
} ;
static int __init crossbar_of_init ( struct device_node * node )
{
2017-03-06 16:41:06 +03:00
u32 max = 0 , entry , reg_size ;
2017-02-23 12:48:55 +03:00
int i , size , reserved = 0 ;
2013-12-03 14:27:23 +04:00
const __be32 * irqsr ;
2014-06-26 11:10:27 +04:00
int ret = - ENOMEM ;
2013-12-03 14:27:23 +04:00
2014-04-03 11:21:34 +04:00
cb = kzalloc ( sizeof ( * cb ) , GFP_KERNEL ) ;
2013-12-03 14:27:23 +04:00
if ( ! cb )
2014-06-26 11:10:27 +04:00
return ret ;
2013-12-03 14:27:23 +04:00
cb - > crossbar_base = of_iomap ( node , 0 ) ;
if ( ! cb - > crossbar_base )
2014-06-26 11:10:28 +04:00
goto err_cb ;
2013-12-03 14:27:23 +04:00
2014-06-26 11:10:31 +04:00
of_property_read_u32 ( node , " ti,max-crossbar-sources " ,
& cb - > max_crossbar_sources ) ;
if ( ! cb - > max_crossbar_sources ) {
pr_err ( " missing 'ti,max-crossbar-sources' property \n " ) ;
ret = - EINVAL ;
goto err_base ;
}
2013-12-03 14:27:23 +04:00
of_property_read_u32 ( node , " ti,max-irqs " , & max ) ;
2014-06-26 11:10:27 +04:00
if ( ! max ) {
pr_err ( " missing 'ti,max-irqs' property \n " ) ;
ret = - EINVAL ;
2014-06-26 11:10:28 +04:00
goto err_base ;
2014-06-26 11:10:27 +04:00
}
2014-06-26 11:10:25 +04:00
cb - > irq_map = kcalloc ( max , sizeof ( int ) , GFP_KERNEL ) ;
2013-12-03 14:27:23 +04:00
if ( ! cb - > irq_map )
2014-06-26 11:10:28 +04:00
goto err_base ;
2013-12-03 14:27:23 +04:00
cb - > int_max = max ;
for ( i = 0 ; i < max ; i + + )
cb - > irq_map [ i ] = IRQ_FREE ;
/* Get and mark reserved irqs */
irqsr = of_get_property ( node , " ti,irqs-reserved " , & size ) ;
if ( irqsr ) {
size / = sizeof ( __be32 ) ;
for ( i = 0 ; i < size ; i + + ) {
of_property_read_u32_index ( node ,
" ti,irqs-reserved " ,
i , & entry ) ;
2014-08-07 19:28:21 +04:00
if ( entry > = max ) {
2013-12-03 14:27:23 +04:00
pr_err ( " Invalid reserved entry \n " ) ;
2014-06-26 11:10:27 +04:00
ret = - EINVAL ;
2014-06-26 11:10:28 +04:00
goto err_irq_map ;
2013-12-03 14:27:23 +04:00
}
2014-06-26 11:10:19 +04:00
cb - > irq_map [ entry ] = IRQ_RESERVED ;
2013-12-03 14:27:23 +04:00
}
}
2014-06-26 11:10:21 +04:00
/* Skip irqs hardwired to bypass the crossbar */
irqsr = of_get_property ( node , " ti,irqs-skip " , & size ) ;
if ( irqsr ) {
size / = sizeof ( __be32 ) ;
for ( i = 0 ; i < size ; i + + ) {
of_property_read_u32_index ( node ,
" ti,irqs-skip " ,
i , & entry ) ;
2014-08-07 19:28:21 +04:00
if ( entry > = max ) {
2014-06-26 11:10:21 +04:00
pr_err ( " Invalid skip entry \n " ) ;
ret = - EINVAL ;
2014-06-26 11:10:28 +04:00
goto err_irq_map ;
2014-06-26 11:10:21 +04:00
}
cb - > irq_map [ entry ] = IRQ_SKIP ;
}
}
2014-06-26 11:10:25 +04:00
cb - > register_offsets = kcalloc ( max , sizeof ( int ) , GFP_KERNEL ) ;
2013-12-03 14:27:23 +04:00
if ( ! cb - > register_offsets )
2014-06-26 11:10:28 +04:00
goto err_irq_map ;
2013-12-03 14:27:23 +04:00
2017-03-06 16:41:06 +03:00
of_property_read_u32 ( node , " ti,reg-size " , & reg_size ) ;
2013-12-03 14:27:23 +04:00
2017-03-06 16:41:06 +03:00
switch ( reg_size ) {
2013-12-03 14:27:23 +04:00
case 1 :
cb - > write = crossbar_writeb ;
break ;
case 2 :
cb - > write = crossbar_writew ;
break ;
case 4 :
cb - > write = crossbar_writel ;
break ;
default :
pr_err ( " Invalid reg-size property \n " ) ;
2014-06-26 11:10:27 +04:00
ret = - EINVAL ;
2014-06-26 11:10:28 +04:00
goto err_reg_offset ;
2013-12-03 14:27:23 +04:00
break ;
}
/*
* Register offsets are not linear because of the
* reserved irqs . so find and store the offsets once .
*/
for ( i = 0 ; i < max ; i + + ) {
2014-06-26 11:10:19 +04:00
if ( cb - > irq_map [ i ] = = IRQ_RESERVED )
2013-12-03 14:27:23 +04:00
continue ;
cb - > register_offsets [ i ] = reserved ;
2017-03-06 16:41:06 +03:00
reserved + = reg_size ;
2013-12-03 14:27:23 +04:00
}
2014-06-26 11:10:22 +04:00
of_property_read_u32 ( node , " ti,irqs-safe-map " , & cb - > safe_map ) ;
/* Initialize the crossbar with safe map to start with */
for ( i = 0 ; i < max ; i + + ) {
if ( cb - > irq_map [ i ] = = IRQ_RESERVED | |
cb - > irq_map [ i ] = = IRQ_SKIP )
continue ;
cb - > write ( i , cb - > safe_map ) ;
}
2015-03-11 18:43:44 +03:00
raw_spin_lock_init ( & cb - > lock ) ;
2013-12-03 14:27:23 +04:00
return 0 ;
2014-06-26 11:10:28 +04:00
err_reg_offset :
2013-12-03 14:27:23 +04:00
kfree ( cb - > register_offsets ) ;
2014-06-26 11:10:28 +04:00
err_irq_map :
2013-12-03 14:27:23 +04:00
kfree ( cb - > irq_map ) ;
2014-06-26 11:10:28 +04:00
err_base :
2013-12-03 14:27:23 +04:00
iounmap ( cb - > crossbar_base ) ;
2014-06-26 11:10:28 +04:00
err_cb :
2013-12-03 14:27:23 +04:00
kfree ( cb ) ;
2014-06-26 11:10:29 +04:00
cb = NULL ;
2014-06-26 11:10:27 +04:00
return ret ;
2013-12-03 14:27:23 +04:00
}
2015-03-11 18:43:44 +03:00
static int __init irqcrossbar_init ( struct device_node * node ,
struct device_node * parent )
2013-12-03 14:27:23 +04:00
{
2015-03-11 18:43:44 +03:00
struct irq_domain * parent_domain , * domain ;
int err ;
if ( ! parent ) {
2017-07-19 00:43:10 +03:00
pr_err ( " %pOF: no parent, giving up \n " , node ) ;
2013-12-03 14:27:23 +04:00
return - ENODEV ;
2015-03-11 18:43:44 +03:00
}
parent_domain = irq_find_host ( parent ) ;
if ( ! parent_domain ) {
2017-07-19 00:43:10 +03:00
pr_err ( " %pOF: unable to obtain parent domain \n " , node ) ;
2015-03-11 18:43:44 +03:00
return - ENXIO ;
}
err = crossbar_of_init ( node ) ;
if ( err )
return err ;
domain = irq_domain_add_hierarchy ( parent_domain , 0 ,
cb - > max_crossbar_sources ,
node , & crossbar_domain_ops ,
NULL ) ;
if ( ! domain ) {
2017-07-19 00:43:10 +03:00
pr_err ( " %pOF: failed to allocated domain \n " , node ) ;
2015-03-11 18:43:44 +03:00
return - ENOMEM ;
}
2013-12-03 14:27:23 +04:00
return 0 ;
}
2015-03-11 18:43:44 +03:00
IRQCHIP_DECLARE ( ti_irqcrossbar , " ti,irq-crossbar " , irqcrossbar_init ) ;