2011-05-19 17:54:28 +04:00
/*
* Driver for ePAPR Embedded Hypervisor PIC
*
* Copyright 2008 - 2011 Freescale Semiconductor , Inc .
*
* Author : Ashish Kalra < ashish . kalra @ freescale . 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 .
*/
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/irq.h>
# include <linux/smp.h>
# include <linux/interrupt.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/of.h>
2013-11-11 09:19:08 +04:00
# include <linux/of_address.h>
2011-05-19 17:54:28 +04:00
# include <asm/io.h>
# include <asm/irq.h>
# include <asm/smp.h>
# include <asm/machdep.h>
# include <asm/ehv_pic.h>
# include <asm/fsl_hcalls.h>
static struct ehv_pic * global_ehv_pic ;
static DEFINE_SPINLOCK ( ehv_pic_lock ) ;
static u32 hwirq_intspec [ NR_EHV_PIC_INTS ] ;
static u32 __iomem * mpic_percpu_base_vaddr ;
# define IRQ_TYPE_MPIC_DIRECT 4
# define MPIC_EOI 0x00B0
/*
* Linux descriptor level callbacks
*/
void ehv_pic_unmask_irq ( struct irq_data * d )
{
unsigned int src = virq_to_hw ( d - > irq ) ;
ev_int_set_mask ( src , 0 ) ;
}
void ehv_pic_mask_irq ( struct irq_data * d )
{
unsigned int src = virq_to_hw ( d - > irq ) ;
ev_int_set_mask ( src , 1 ) ;
}
void ehv_pic_end_irq ( struct irq_data * d )
{
unsigned int src = virq_to_hw ( d - > irq ) ;
ev_int_eoi ( src ) ;
}
void ehv_pic_direct_end_irq ( struct irq_data * d )
{
out_be32 ( mpic_percpu_base_vaddr + MPIC_EOI / 4 , 0 ) ;
}
int ehv_pic_set_affinity ( struct irq_data * d , const struct cpumask * dest ,
bool force )
{
unsigned int src = virq_to_hw ( d - > irq ) ;
unsigned int config , prio , cpu_dest ;
int cpuid = irq_choose_cpu ( dest ) ;
unsigned long flags ;
spin_lock_irqsave ( & ehv_pic_lock , flags ) ;
ev_int_get_config ( src , & config , & prio , & cpu_dest ) ;
ev_int_set_config ( src , config , prio , cpuid ) ;
spin_unlock_irqrestore ( & ehv_pic_lock , flags ) ;
2013-05-13 04:57:49 +04:00
return IRQ_SET_MASK_OK ;
2011-05-19 17:54:28 +04:00
}
static unsigned int ehv_pic_type_to_vecpri ( unsigned int type )
{
/* Now convert sense value */
switch ( type & IRQ_TYPE_SENSE_MASK ) {
case IRQ_TYPE_EDGE_RISING :
return EHV_PIC_INFO ( VECPRI_SENSE_EDGE ) |
EHV_PIC_INFO ( VECPRI_POLARITY_POSITIVE ) ;
case IRQ_TYPE_EDGE_FALLING :
case IRQ_TYPE_EDGE_BOTH :
return EHV_PIC_INFO ( VECPRI_SENSE_EDGE ) |
EHV_PIC_INFO ( VECPRI_POLARITY_NEGATIVE ) ;
case IRQ_TYPE_LEVEL_HIGH :
return EHV_PIC_INFO ( VECPRI_SENSE_LEVEL ) |
EHV_PIC_INFO ( VECPRI_POLARITY_POSITIVE ) ;
case IRQ_TYPE_LEVEL_LOW :
default :
return EHV_PIC_INFO ( VECPRI_SENSE_LEVEL ) |
EHV_PIC_INFO ( VECPRI_POLARITY_NEGATIVE ) ;
}
}
int ehv_pic_set_irq_type ( struct irq_data * d , unsigned int flow_type )
{
unsigned int src = virq_to_hw ( d - > irq ) ;
unsigned int vecpri , vold , vnew , prio , cpu_dest ;
unsigned long flags ;
if ( flow_type = = IRQ_TYPE_NONE )
flow_type = IRQ_TYPE_LEVEL_LOW ;
2014-02-24 01:40:08 +04:00
irqd_set_trigger_type ( d , flow_type ) ;
2011-05-19 17:54:28 +04:00
vecpri = ehv_pic_type_to_vecpri ( flow_type ) ;
spin_lock_irqsave ( & ehv_pic_lock , flags ) ;
ev_int_get_config ( src , & vold , & prio , & cpu_dest ) ;
vnew = vold & ~ ( EHV_PIC_INFO ( VECPRI_POLARITY_MASK ) |
EHV_PIC_INFO ( VECPRI_SENSE_MASK ) ) ;
vnew | = vecpri ;
/*
* TODO : Add specific interface call for platform to set
* individual interrupt priorities .
* platform currently using static / default priority for all ints
*/
prio = 8 ;
ev_int_set_config ( src , vecpri , prio , cpu_dest ) ;
spin_unlock_irqrestore ( & ehv_pic_lock , flags ) ;
2014-02-24 01:40:08 +04:00
return IRQ_SET_MASK_OK_NOCOPY ;
2011-05-19 17:54:28 +04:00
}
static struct irq_chip ehv_pic_irq_chip = {
. irq_mask = ehv_pic_mask_irq ,
. irq_unmask = ehv_pic_unmask_irq ,
. irq_eoi = ehv_pic_end_irq ,
. irq_set_type = ehv_pic_set_irq_type ,
} ;
static struct irq_chip ehv_pic_direct_eoi_irq_chip = {
. irq_mask = ehv_pic_mask_irq ,
. irq_unmask = ehv_pic_unmask_irq ,
. irq_eoi = ehv_pic_direct_end_irq ,
. irq_set_type = ehv_pic_set_irq_type ,
} ;
2016-09-06 14:53:24 +03:00
/* Return an interrupt vector or 0 if no interrupt is pending. */
2011-05-19 17:54:28 +04:00
unsigned int ehv_pic_get_irq ( void )
{
int irq ;
BUG_ON ( global_ehv_pic = = NULL ) ;
if ( global_ehv_pic - > coreint_flag )
irq = mfspr ( SPRN_EPR ) ; /* if core int mode */
else
ev_int_iack ( 0 , & irq ) ; /* legacy mode */
if ( irq = = 0xFFFF ) /* 0xFFFF --> no irq is pending */
2016-09-06 14:53:24 +03:00
return 0 ;
2011-05-19 17:54:28 +04:00
/*
* this will also setup revmap [ ] in the slow path for the first
* time , next calls will always use fast path by indexing revmap
*/
return irq_linear_revmap ( global_ehv_pic - > irqhost , irq ) ;
}
genirq/irqdomain: Allow irq domain aliasing
It is not uncommon (at least with the ARM stuff) to have a piece
of hardware that implements different flavours of "interrupts".
A typical example of this is the GICv3 ITS, which implements
standard PCI/MSI support, but also some form of "generic MSI".
So far, the PCI/MSI domain is registered using the ITS device_node,
so that irq_find_host can return it. On the contrary, the raw MSI
domain is not registered with an device_node, making it impossible
to be looked up by another subsystem (obviously, using the same
device_node twice would only result in confusion, as it is not
defined which one irq_find_host would return).
A solution to this is to "type" domains that may be aliasing, and
to be able to lookup an device_node that matches a given type.
For this, we introduce irq_find_matching_host() as a superset
of irq_find_host:
struct irq_domain *irq_find_matching_host(struct device_node *node,
enum irq_domain_bus_token bus_token);
where bus_token is the "type" we want to match the domain against
(so far, only DOMAIN_BUS_ANY is defined). This result in some
moderately invasive changes on the PPC side (which is the only
user of the .match method).
This has otherwise no functionnal change.
Reviewed-by: Hanjun Guo <hanjun.guo@linaro.org>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Cc: <linux-arm-kernel@lists.infradead.org>
Cc: Yijing Wang <wangyijing@huawei.com>
Cc: Ma Jun <majun258@huawei.com>
Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Cc: Duc Dang <dhdang@apm.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Jiang Liu <jiang.liu@linux.intel.com>
Cc: Jason Cooper <jason@lakedaemon.net>
Link: http://lkml.kernel.org/r/1438091186-10244-2-git-send-email-marc.zyngier@arm.com
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
2015-07-28 16:46:08 +03:00
static int ehv_pic_host_match ( struct irq_domain * h , struct device_node * node ,
enum irq_domain_bus_token bus_token )
2011-05-19 17:54:28 +04:00
{
/* Exact match, unless ehv_pic node is NULL */
2015-10-13 14:51:29 +03:00
struct device_node * of_node = irq_domain_get_of_node ( h ) ;
return of_node = = NULL | | of_node = = node ;
2011-05-19 17:54:28 +04:00
}
2012-02-15 01:06:50 +04:00
static int ehv_pic_host_map ( struct irq_domain * h , unsigned int virq ,
2011-05-19 17:54:28 +04:00
irq_hw_number_t hw )
{
struct ehv_pic * ehv_pic = h - > host_data ;
struct irq_chip * chip ;
/* Default chip */
chip = & ehv_pic - > hc_irq ;
if ( mpic_percpu_base_vaddr )
if ( hwirq_intspec [ hw ] & IRQ_TYPE_MPIC_DIRECT )
chip = & ehv_pic_direct_eoi_irq_chip ;
irq_set_chip_data ( virq , chip ) ;
/*
* using handle_fasteoi_irq as our irq handler , this will
* only call the eoi callback and suitable for the MPIC
* controller which set ISR / IPR automatically and clear the
* highest priority active interrupt in ISR / IPR when we do
* a specific eoi
*/
irq_set_chip_and_handler ( virq , chip , handle_fasteoi_irq ) ;
/* Set default irq type */
irq_set_irq_type ( virq , IRQ_TYPE_NONE ) ;
return 0 ;
}
2012-02-15 01:06:50 +04:00
static int ehv_pic_host_xlate ( struct irq_domain * h , struct device_node * ct ,
2011-05-19 17:54:28 +04:00
const u32 * intspec , unsigned int intsize ,
irq_hw_number_t * out_hwirq , unsigned int * out_flags )
{
/*
* interrupt sense values coming from the guest device tree
* interrupt specifiers can have four possible sense and
* level encoding information and they need to
* be translated between firmware type & linux type .
*/
static unsigned char map_of_senses_to_linux_irqtype [ 4 ] = {
IRQ_TYPE_EDGE_FALLING ,
IRQ_TYPE_EDGE_RISING ,
IRQ_TYPE_LEVEL_LOW ,
IRQ_TYPE_LEVEL_HIGH ,
} ;
* out_hwirq = intspec [ 0 ] ;
if ( intsize > 1 ) {
hwirq_intspec [ intspec [ 0 ] ] = intspec [ 1 ] ;
* out_flags = map_of_senses_to_linux_irqtype [ intspec [ 1 ] &
~ IRQ_TYPE_MPIC_DIRECT ] ;
} else {
* out_flags = IRQ_TYPE_NONE ;
}
return 0 ;
}
2012-01-26 23:24:34 +04:00
static const struct irq_domain_ops ehv_pic_host_ops = {
2011-05-19 17:54:28 +04:00
. match = ehv_pic_host_match ,
. map = ehv_pic_host_map ,
. xlate = ehv_pic_host_xlate ,
} ;
void __init ehv_pic_init ( void )
{
struct device_node * np , * np2 ;
struct ehv_pic * ehv_pic ;
int coreint_flag = 1 ;
np = of_find_compatible_node ( NULL , NULL , " epapr,hv-pic " ) ;
if ( ! np ) {
pr_err ( " ehv_pic_init: could not find epapr,hv-pic node \n " ) ;
return ;
}
if ( ! of_find_property ( np , " has-external-proxy " , NULL ) )
coreint_flag = 0 ;
ehv_pic = kzalloc ( sizeof ( struct ehv_pic ) , GFP_KERNEL ) ;
if ( ! ehv_pic ) {
of_node_put ( np ) ;
return ;
}
2012-02-15 01:06:54 +04:00
ehv_pic - > irqhost = irq_domain_add_linear ( np , NR_EHV_PIC_INTS ,
& ehv_pic_host_ops , ehv_pic ) ;
2011-05-19 17:54:28 +04:00
if ( ! ehv_pic - > irqhost ) {
of_node_put ( np ) ;
2011-08-08 15:18:02 +04:00
kfree ( ehv_pic ) ;
2011-05-19 17:54:28 +04:00
return ;
}
np2 = of_find_compatible_node ( NULL , NULL , " fsl,hv-mpic-per-cpu " ) ;
if ( np2 ) {
mpic_percpu_base_vaddr = of_iomap ( np2 , 0 ) ;
if ( ! mpic_percpu_base_vaddr )
pr_err ( " ehv_pic_init: of_iomap failed \n " ) ;
of_node_put ( np2 ) ;
}
ehv_pic - > hc_irq = ehv_pic_irq_chip ;
ehv_pic - > hc_irq . irq_set_affinity = ehv_pic_set_affinity ;
ehv_pic - > coreint_flag = coreint_flag ;
global_ehv_pic = ehv_pic ;
irq_set_default_host ( global_ehv_pic - > irqhost ) ;
}