2016-02-19 16:34:43 +03:00
/*
* Copyright ( C ) 2016 Marvell
*
* Thomas Petazzoni < thomas . petazzoni @ free - electrons . 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 .
*/
# define pr_fmt(fmt) "GIC-ODMI: " fmt
# include <linux/irq.h>
# include <linux/irqchip.h>
# include <linux/irqdomain.h>
# include <linux/kernel.h>
# include <linux/msi.h>
# include <linux/of_address.h>
# include <linux/slab.h>
# include <dt-bindings/interrupt-controller/arm-gic.h>
# define GICP_ODMIN_SET 0x40
# define GICP_ODMI_INT_NUM_SHIFT 12
# define GICP_ODMIN_GM_EP_R0 0x110
# define GICP_ODMIN_GM_EP_R1 0x114
# define GICP_ODMIN_GM_EA_R0 0x108
# define GICP_ODMIN_GM_EA_R1 0x118
/*
* We don ' t support the group events , so we simply have 8 interrupts
* per frame .
*/
# define NODMIS_SHIFT 3
# define NODMIS_PER_FRAME (1 << NODMIS_SHIFT)
# define NODMIS_MASK (NODMIS_PER_FRAME - 1)
struct odmi_data {
struct resource res ;
void __iomem * base ;
unsigned int spi_base ;
} ;
static struct odmi_data * odmis ;
static unsigned long * odmis_bm ;
static unsigned int odmis_count ;
/* Protects odmis_bm */
static DEFINE_SPINLOCK ( odmis_bm_lock ) ;
static void odmi_compose_msi_msg ( struct irq_data * d , struct msi_msg * msg )
{
struct odmi_data * odmi ;
phys_addr_t addr ;
unsigned int odmin ;
if ( WARN_ON ( d - > hwirq > = odmis_count * NODMIS_PER_FRAME ) )
return ;
odmi = & odmis [ d - > hwirq > > NODMIS_SHIFT ] ;
odmin = d - > hwirq & NODMIS_MASK ;
addr = odmi - > res . start + GICP_ODMIN_SET ;
msg - > address_hi = upper_32_bits ( addr ) ;
msg - > address_lo = lower_32_bits ( addr ) ;
msg - > data = odmin < < GICP_ODMI_INT_NUM_SHIFT ;
}
static struct irq_chip odmi_irq_chip = {
. name = " ODMI " ,
. irq_mask = irq_chip_mask_parent ,
. irq_unmask = irq_chip_unmask_parent ,
. irq_eoi = irq_chip_eoi_parent ,
2016-02-19 18:00:29 +03:00
. irq_set_affinity = irq_chip_set_affinity_parent ,
2016-02-19 16:34:43 +03:00
. irq_compose_msi_msg = odmi_compose_msi_msg ,
} ;
static int odmi_irq_domain_alloc ( struct irq_domain * domain , unsigned int virq ,
unsigned int nr_irqs , void * args )
{
struct odmi_data * odmi = NULL ;
struct irq_fwspec fwspec ;
struct irq_data * d ;
unsigned int hwirq , odmin ;
int ret ;
spin_lock ( & odmis_bm_lock ) ;
hwirq = find_first_zero_bit ( odmis_bm , NODMIS_PER_FRAME * odmis_count ) ;
if ( hwirq > = NODMIS_PER_FRAME * odmis_count ) {
spin_unlock ( & odmis_bm_lock ) ;
return - ENOSPC ;
}
__set_bit ( hwirq , odmis_bm ) ;
spin_unlock ( & odmis_bm_lock ) ;
odmi = & odmis [ hwirq > > NODMIS_SHIFT ] ;
odmin = hwirq & NODMIS_MASK ;
fwspec . fwnode = domain - > parent - > fwnode ;
fwspec . param_count = 3 ;
fwspec . param [ 0 ] = GIC_SPI ;
fwspec . param [ 1 ] = odmi - > spi_base - 32 + odmin ;
fwspec . param [ 2 ] = IRQ_TYPE_EDGE_RISING ;
ret = irq_domain_alloc_irqs_parent ( domain , virq , 1 , & fwspec ) ;
if ( ret ) {
pr_err ( " Cannot allocate parent IRQ \n " ) ;
spin_lock ( & odmis_bm_lock ) ;
__clear_bit ( odmin , odmis_bm ) ;
spin_unlock ( & odmis_bm_lock ) ;
return ret ;
}
/* Configure the interrupt line to be edge */
d = irq_domain_get_irq_data ( domain - > parent , virq ) ;
d - > chip - > irq_set_type ( d , IRQ_TYPE_EDGE_RISING ) ;
irq_domain_set_hwirq_and_chip ( domain , virq , hwirq ,
& odmi_irq_chip , NULL ) ;
return 0 ;
}
static void odmi_irq_domain_free ( struct irq_domain * domain ,
unsigned int virq , unsigned int nr_irqs )
{
struct irq_data * d = irq_domain_get_irq_data ( domain , virq ) ;
if ( d - > hwirq > = odmis_count * NODMIS_PER_FRAME ) {
pr_err ( " Failed to teardown msi. Invalid hwirq %lu \n " , d - > hwirq ) ;
return ;
}
irq_domain_free_irqs_parent ( domain , virq , nr_irqs ) ;
/* Actually free the MSI */
spin_lock ( & odmis_bm_lock ) ;
__clear_bit ( d - > hwirq , odmis_bm ) ;
spin_unlock ( & odmis_bm_lock ) ;
}
static const struct irq_domain_ops odmi_domain_ops = {
. alloc = odmi_irq_domain_alloc ,
. free = odmi_irq_domain_free ,
} ;
static struct irq_chip odmi_msi_irq_chip = {
. name = " ODMI " ,
} ;
static struct msi_domain_ops odmi_msi_ops = {
} ;
static struct msi_domain_info odmi_msi_domain_info = {
. flags = ( MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS ) ,
. ops = & odmi_msi_ops ,
. chip = & odmi_msi_irq_chip ,
} ;
static int __init mvebu_odmi_init ( struct device_node * node ,
struct device_node * parent )
{
struct irq_domain * inner_domain , * plat_domain ;
int ret , i ;
if ( of_property_read_u32 ( node , " marvell,odmi-frames " , & odmis_count ) )
return - EINVAL ;
odmis = kcalloc ( odmis_count , sizeof ( struct odmi_data ) , GFP_KERNEL ) ;
if ( ! odmis )
return - ENOMEM ;
odmis_bm = kcalloc ( BITS_TO_LONGS ( odmis_count * NODMIS_PER_FRAME ) ,
sizeof ( long ) , GFP_KERNEL ) ;
if ( ! odmis_bm ) {
ret = - ENOMEM ;
goto err_alloc ;
}
for ( i = 0 ; i < odmis_count ; i + + ) {
struct odmi_data * odmi = & odmis [ i ] ;
ret = of_address_to_resource ( node , i , & odmi - > res ) ;
if ( ret )
goto err_unmap ;
odmi - > base = of_io_request_and_map ( node , i , " odmi " ) ;
if ( IS_ERR ( odmi - > base ) ) {
ret = PTR_ERR ( odmi - > base ) ;
goto err_unmap ;
}
if ( of_property_read_u32_index ( node , " marvell,spi-base " ,
i , & odmi - > spi_base ) ) {
ret = - EINVAL ;
goto err_unmap ;
}
}
inner_domain = irq_domain_create_linear ( of_node_to_fwnode ( node ) ,
odmis_count * NODMIS_PER_FRAME ,
& odmi_domain_ops , NULL ) ;
if ( ! inner_domain ) {
ret = - ENOMEM ;
goto err_unmap ;
}
inner_domain - > parent = irq_find_host ( parent ) ;
plat_domain = platform_msi_create_irq_domain ( of_node_to_fwnode ( node ) ,
& odmi_msi_domain_info ,
inner_domain ) ;
if ( ! plat_domain ) {
ret = - ENOMEM ;
goto err_remove_inner ;
}
return 0 ;
err_remove_inner :
irq_domain_remove ( inner_domain ) ;
err_unmap :
for ( i = 0 ; i < odmis_count ; i + + ) {
struct odmi_data * odmi = & odmis [ i ] ;
if ( odmi - > base & & ! IS_ERR ( odmi - > base ) )
iounmap ( odmis [ i ] . base ) ;
}
kfree ( odmis_bm ) ;
err_alloc :
kfree ( odmis ) ;
return ret ;
}
IRQCHIP_DECLARE ( mvebu_odmi , " marvell,odmi-controller " , mvebu_odmi_init ) ;