xref: /openbmc/linux/arch/powerpc/sysdev/ehv_pic.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
13a93261fSAshish Kalra /*
23a93261fSAshish Kalra  *  Driver for ePAPR Embedded Hypervisor PIC
33a93261fSAshish Kalra  *
43a93261fSAshish Kalra  *  Copyright 2008-2011 Freescale Semiconductor, Inc.
53a93261fSAshish Kalra  *
63a93261fSAshish Kalra  *  Author: Ashish Kalra <ashish.kalra@freescale.com>
73a93261fSAshish Kalra  *
83a93261fSAshish Kalra  * This file is licensed under the terms of the GNU General Public License
93a93261fSAshish Kalra  * version 2.  This program is licensed "as is" without any warranty of any
103a93261fSAshish Kalra  * kind, whether express or implied.
113a93261fSAshish Kalra  */
123a93261fSAshish Kalra 
133a93261fSAshish Kalra #include <linux/types.h>
143a93261fSAshish Kalra #include <linux/kernel.h>
153a93261fSAshish Kalra #include <linux/init.h>
163a93261fSAshish Kalra #include <linux/irq.h>
1713a9a5d1SMarc Zyngier #include <linux/irqdomain.h>
183a93261fSAshish Kalra #include <linux/smp.h>
193a93261fSAshish Kalra #include <linux/interrupt.h>
203a93261fSAshish Kalra #include <linux/slab.h>
213a93261fSAshish Kalra #include <linux/spinlock.h>
223a93261fSAshish Kalra #include <linux/of.h>
23c11eede6SRob Herring #include <linux/of_address.h>
243a93261fSAshish Kalra 
253a93261fSAshish Kalra #include <asm/io.h>
263a93261fSAshish Kalra #include <asm/irq.h>
273a93261fSAshish Kalra #include <asm/smp.h>
283a93261fSAshish Kalra #include <asm/machdep.h>
293a93261fSAshish Kalra #include <asm/ehv_pic.h>
303a93261fSAshish Kalra #include <asm/fsl_hcalls.h>
313a93261fSAshish Kalra 
323a93261fSAshish Kalra static struct ehv_pic *global_ehv_pic;
333a93261fSAshish Kalra static DEFINE_SPINLOCK(ehv_pic_lock);
343a93261fSAshish Kalra 
353a93261fSAshish Kalra static u32 hwirq_intspec[NR_EHV_PIC_INTS];
363a93261fSAshish Kalra static u32 __iomem *mpic_percpu_base_vaddr;
373a93261fSAshish Kalra 
383a93261fSAshish Kalra #define IRQ_TYPE_MPIC_DIRECT 4
393a93261fSAshish Kalra #define MPIC_EOI  0x00B0
403a93261fSAshish Kalra 
413a93261fSAshish Kalra /*
423a93261fSAshish Kalra  * Linux descriptor level callbacks
433a93261fSAshish Kalra  */
443a93261fSAshish Kalra 
ehv_pic_unmask_irq(struct irq_data * d)45*c265735fSChristophe Leroy static void ehv_pic_unmask_irq(struct irq_data *d)
463a93261fSAshish Kalra {
473a93261fSAshish Kalra 	unsigned int src = virq_to_hw(d->irq);
483a93261fSAshish Kalra 
493a93261fSAshish Kalra 	ev_int_set_mask(src, 0);
503a93261fSAshish Kalra }
513a93261fSAshish Kalra 
ehv_pic_mask_irq(struct irq_data * d)52*c265735fSChristophe Leroy static void ehv_pic_mask_irq(struct irq_data *d)
533a93261fSAshish Kalra {
543a93261fSAshish Kalra 	unsigned int src = virq_to_hw(d->irq);
553a93261fSAshish Kalra 
563a93261fSAshish Kalra 	ev_int_set_mask(src, 1);
573a93261fSAshish Kalra }
583a93261fSAshish Kalra 
ehv_pic_end_irq(struct irq_data * d)59*c265735fSChristophe Leroy static void ehv_pic_end_irq(struct irq_data *d)
603a93261fSAshish Kalra {
613a93261fSAshish Kalra 	unsigned int src = virq_to_hw(d->irq);
623a93261fSAshish Kalra 
633a93261fSAshish Kalra 	ev_int_eoi(src);
643a93261fSAshish Kalra }
653a93261fSAshish Kalra 
ehv_pic_direct_end_irq(struct irq_data * d)66*c265735fSChristophe Leroy static void ehv_pic_direct_end_irq(struct irq_data *d)
673a93261fSAshish Kalra {
683a93261fSAshish Kalra 	out_be32(mpic_percpu_base_vaddr + MPIC_EOI / 4, 0);
693a93261fSAshish Kalra }
703a93261fSAshish Kalra 
ehv_pic_set_affinity(struct irq_data * d,const struct cpumask * dest,bool force)71*c265735fSChristophe Leroy static int ehv_pic_set_affinity(struct irq_data *d, const struct cpumask *dest,
723a93261fSAshish Kalra 			 bool force)
733a93261fSAshish Kalra {
743a93261fSAshish Kalra 	unsigned int src = virq_to_hw(d->irq);
753a93261fSAshish Kalra 	unsigned int config, prio, cpu_dest;
763a93261fSAshish Kalra 	int cpuid = irq_choose_cpu(dest);
773a93261fSAshish Kalra 	unsigned long flags;
783a93261fSAshish Kalra 
793a93261fSAshish Kalra 	spin_lock_irqsave(&ehv_pic_lock, flags);
803a93261fSAshish Kalra 	ev_int_get_config(src, &config, &prio, &cpu_dest);
813a93261fSAshish Kalra 	ev_int_set_config(src, config, prio, cpuid);
823a93261fSAshish Kalra 	spin_unlock_irqrestore(&ehv_pic_lock, flags);
833a93261fSAshish Kalra 
84dcb615aeSAlexander Gordeev 	return IRQ_SET_MASK_OK;
853a93261fSAshish Kalra }
863a93261fSAshish Kalra 
ehv_pic_type_to_vecpri(unsigned int type)873a93261fSAshish Kalra static unsigned int ehv_pic_type_to_vecpri(unsigned int type)
883a93261fSAshish Kalra {
893a93261fSAshish Kalra 	/* Now convert sense value */
903a93261fSAshish Kalra 
913a93261fSAshish Kalra 	switch (type & IRQ_TYPE_SENSE_MASK) {
923a93261fSAshish Kalra 	case IRQ_TYPE_EDGE_RISING:
933a93261fSAshish Kalra 		return EHV_PIC_INFO(VECPRI_SENSE_EDGE) |
943a93261fSAshish Kalra 		       EHV_PIC_INFO(VECPRI_POLARITY_POSITIVE);
953a93261fSAshish Kalra 
963a93261fSAshish Kalra 	case IRQ_TYPE_EDGE_FALLING:
973a93261fSAshish Kalra 	case IRQ_TYPE_EDGE_BOTH:
983a93261fSAshish Kalra 		return EHV_PIC_INFO(VECPRI_SENSE_EDGE) |
993a93261fSAshish Kalra 		       EHV_PIC_INFO(VECPRI_POLARITY_NEGATIVE);
1003a93261fSAshish Kalra 
1013a93261fSAshish Kalra 	case IRQ_TYPE_LEVEL_HIGH:
1023a93261fSAshish Kalra 		return EHV_PIC_INFO(VECPRI_SENSE_LEVEL) |
1033a93261fSAshish Kalra 		       EHV_PIC_INFO(VECPRI_POLARITY_POSITIVE);
1043a93261fSAshish Kalra 
1053a93261fSAshish Kalra 	case IRQ_TYPE_LEVEL_LOW:
1063a93261fSAshish Kalra 	default:
1073a93261fSAshish Kalra 		return EHV_PIC_INFO(VECPRI_SENSE_LEVEL) |
1083a93261fSAshish Kalra 		       EHV_PIC_INFO(VECPRI_POLARITY_NEGATIVE);
1093a93261fSAshish Kalra 	}
1103a93261fSAshish Kalra }
1113a93261fSAshish Kalra 
ehv_pic_set_irq_type(struct irq_data * d,unsigned int flow_type)112*c265735fSChristophe Leroy static int ehv_pic_set_irq_type(struct irq_data *d, unsigned int flow_type)
1133a93261fSAshish Kalra {
1143a93261fSAshish Kalra 	unsigned int src = virq_to_hw(d->irq);
1153a93261fSAshish Kalra 	unsigned int vecpri, vold, vnew, prio, cpu_dest;
1163a93261fSAshish Kalra 	unsigned long flags;
1173a93261fSAshish Kalra 
1183a93261fSAshish Kalra 	if (flow_type == IRQ_TYPE_NONE)
1193a93261fSAshish Kalra 		flow_type = IRQ_TYPE_LEVEL_LOW;
1203a93261fSAshish Kalra 
121c866cda4SThomas Gleixner 	irqd_set_trigger_type(d, flow_type);
1223a93261fSAshish Kalra 
1233a93261fSAshish Kalra 	vecpri = ehv_pic_type_to_vecpri(flow_type);
1243a93261fSAshish Kalra 
1253a93261fSAshish Kalra 	spin_lock_irqsave(&ehv_pic_lock, flags);
1263a93261fSAshish Kalra 	ev_int_get_config(src, &vold, &prio, &cpu_dest);
1273a93261fSAshish Kalra 	vnew = vold & ~(EHV_PIC_INFO(VECPRI_POLARITY_MASK) |
1283a93261fSAshish Kalra 			EHV_PIC_INFO(VECPRI_SENSE_MASK));
1293a93261fSAshish Kalra 	vnew |= vecpri;
1303a93261fSAshish Kalra 
1313a93261fSAshish Kalra 	/*
1323a93261fSAshish Kalra 	 * TODO : Add specific interface call for platform to set
1333a93261fSAshish Kalra 	 * individual interrupt priorities.
1343a93261fSAshish Kalra 	 * platform currently using static/default priority for all ints
1353a93261fSAshish Kalra 	 */
1363a93261fSAshish Kalra 
1373a93261fSAshish Kalra 	prio = 8;
1383a93261fSAshish Kalra 
1393a93261fSAshish Kalra 	ev_int_set_config(src, vecpri, prio, cpu_dest);
1403a93261fSAshish Kalra 
1413a93261fSAshish Kalra 	spin_unlock_irqrestore(&ehv_pic_lock, flags);
142c866cda4SThomas Gleixner 	return IRQ_SET_MASK_OK_NOCOPY;
1433a93261fSAshish Kalra }
1443a93261fSAshish Kalra 
1453a93261fSAshish Kalra static struct irq_chip ehv_pic_irq_chip = {
1463a93261fSAshish Kalra 	.irq_mask	= ehv_pic_mask_irq,
1473a93261fSAshish Kalra 	.irq_unmask	= ehv_pic_unmask_irq,
1483a93261fSAshish Kalra 	.irq_eoi	= ehv_pic_end_irq,
1493a93261fSAshish Kalra 	.irq_set_type	= ehv_pic_set_irq_type,
1503a93261fSAshish Kalra };
1513a93261fSAshish Kalra 
1523a93261fSAshish Kalra static struct irq_chip ehv_pic_direct_eoi_irq_chip = {
1533a93261fSAshish Kalra 	.irq_mask	= ehv_pic_mask_irq,
1543a93261fSAshish Kalra 	.irq_unmask	= ehv_pic_unmask_irq,
1553a93261fSAshish Kalra 	.irq_eoi	= ehv_pic_direct_end_irq,
1563a93261fSAshish Kalra 	.irq_set_type	= ehv_pic_set_irq_type,
1573a93261fSAshish Kalra };
1583a93261fSAshish Kalra 
159ef24ba70SMichael Ellerman /* Return an interrupt vector or 0 if no interrupt is pending. */
ehv_pic_get_irq(void)1603a93261fSAshish Kalra unsigned int ehv_pic_get_irq(void)
1613a93261fSAshish Kalra {
1623a93261fSAshish Kalra 	int irq;
1633a93261fSAshish Kalra 
1643a93261fSAshish Kalra 	BUG_ON(global_ehv_pic == NULL);
1653a93261fSAshish Kalra 
1663a93261fSAshish Kalra 	if (global_ehv_pic->coreint_flag)
1673a93261fSAshish Kalra 		irq = mfspr(SPRN_EPR); /* if core int mode */
1683a93261fSAshish Kalra 	else
1693a93261fSAshish Kalra 		ev_int_iack(0, &irq); /* legacy mode */
1703a93261fSAshish Kalra 
1713a93261fSAshish Kalra 	if (irq == 0xFFFF)    /* 0xFFFF --> no irq is pending */
172ef24ba70SMichael Ellerman 		return 0;
1733a93261fSAshish Kalra 
1743a93261fSAshish Kalra 	/*
1753a93261fSAshish Kalra 	 * this will also setup revmap[] in the slow path for the first
1763a93261fSAshish Kalra 	 * time, next calls will always use fast path by indexing revmap
1773a93261fSAshish Kalra 	 */
1783a93261fSAshish Kalra 	return irq_linear_revmap(global_ehv_pic->irqhost, irq);
1793a93261fSAshish Kalra }
1803a93261fSAshish Kalra 
ehv_pic_host_match(struct irq_domain * h,struct device_node * node,enum irq_domain_bus_token bus_token)181ad3aedfbSMarc Zyngier static int ehv_pic_host_match(struct irq_domain *h, struct device_node *node,
182ad3aedfbSMarc Zyngier 			      enum irq_domain_bus_token bus_token)
1833a93261fSAshish Kalra {
1843a93261fSAshish Kalra 	/* Exact match, unless ehv_pic node is NULL */
1855d4c9bc7SMarc Zyngier 	struct device_node *of_node = irq_domain_get_of_node(h);
1865d4c9bc7SMarc Zyngier 	return of_node == NULL || of_node == node;
1873a93261fSAshish Kalra }
1883a93261fSAshish Kalra 
ehv_pic_host_map(struct irq_domain * h,unsigned int virq,irq_hw_number_t hw)189bae1d8f1SGrant Likely static int ehv_pic_host_map(struct irq_domain *h, unsigned int virq,
1903a93261fSAshish Kalra 			 irq_hw_number_t hw)
1913a93261fSAshish Kalra {
1923a93261fSAshish Kalra 	struct ehv_pic *ehv_pic = h->host_data;
1933a93261fSAshish Kalra 	struct irq_chip *chip;
1943a93261fSAshish Kalra 
1953a93261fSAshish Kalra 	/* Default chip */
1963a93261fSAshish Kalra 	chip = &ehv_pic->hc_irq;
1973a93261fSAshish Kalra 
1983a93261fSAshish Kalra 	if (mpic_percpu_base_vaddr)
1993a93261fSAshish Kalra 		if (hwirq_intspec[hw] & IRQ_TYPE_MPIC_DIRECT)
2003a93261fSAshish Kalra 			chip = &ehv_pic_direct_eoi_irq_chip;
2013a93261fSAshish Kalra 
2023a93261fSAshish Kalra 	irq_set_chip_data(virq, chip);
2033a93261fSAshish Kalra 	/*
2043a93261fSAshish Kalra 	 * using handle_fasteoi_irq as our irq handler, this will
2053a93261fSAshish Kalra 	 * only call the eoi callback and suitable for the MPIC
2063a93261fSAshish Kalra 	 * controller which set ISR/IPR automatically and clear the
2073a93261fSAshish Kalra 	 * highest priority active interrupt in ISR/IPR when we do
2083a93261fSAshish Kalra 	 * a specific eoi
2093a93261fSAshish Kalra 	 */
2103a93261fSAshish Kalra 	irq_set_chip_and_handler(virq, chip, handle_fasteoi_irq);
2113a93261fSAshish Kalra 
2123a93261fSAshish Kalra 	/* Set default irq type */
2133a93261fSAshish Kalra 	irq_set_irq_type(virq, IRQ_TYPE_NONE);
2143a93261fSAshish Kalra 
2153a93261fSAshish Kalra 	return 0;
2163a93261fSAshish Kalra }
2173a93261fSAshish Kalra 
ehv_pic_host_xlate(struct irq_domain * h,struct device_node * ct,const u32 * intspec,unsigned int intsize,irq_hw_number_t * out_hwirq,unsigned int * out_flags)218bae1d8f1SGrant Likely static int ehv_pic_host_xlate(struct irq_domain *h, struct device_node *ct,
2193a93261fSAshish Kalra 			   const u32 *intspec, unsigned int intsize,
2203a93261fSAshish Kalra 			   irq_hw_number_t *out_hwirq, unsigned int *out_flags)
2213a93261fSAshish Kalra 
2223a93261fSAshish Kalra {
2233a93261fSAshish Kalra 	/*
2243a93261fSAshish Kalra 	 * interrupt sense values coming from the guest device tree
2253a93261fSAshish Kalra 	 * interrupt specifiers can have four possible sense and
2263a93261fSAshish Kalra 	 * level encoding information and they need to
2273a93261fSAshish Kalra 	 * be translated between firmware type & linux type.
2283a93261fSAshish Kalra 	 */
2293a93261fSAshish Kalra 
2303a93261fSAshish Kalra 	static unsigned char map_of_senses_to_linux_irqtype[4] = {
2313a93261fSAshish Kalra 		IRQ_TYPE_EDGE_FALLING,
2323a93261fSAshish Kalra 		IRQ_TYPE_EDGE_RISING,
2333a93261fSAshish Kalra 		IRQ_TYPE_LEVEL_LOW,
2343a93261fSAshish Kalra 		IRQ_TYPE_LEVEL_HIGH,
2353a93261fSAshish Kalra 	};
2363a93261fSAshish Kalra 
2373a93261fSAshish Kalra 	*out_hwirq = intspec[0];
2383a93261fSAshish Kalra 	if (intsize > 1) {
2393a93261fSAshish Kalra 		hwirq_intspec[intspec[0]] = intspec[1];
2403a93261fSAshish Kalra 		*out_flags = map_of_senses_to_linux_irqtype[intspec[1] &
2413a93261fSAshish Kalra 							~IRQ_TYPE_MPIC_DIRECT];
2423a93261fSAshish Kalra 	} else {
2433a93261fSAshish Kalra 		*out_flags = IRQ_TYPE_NONE;
2443a93261fSAshish Kalra 	}
2453a93261fSAshish Kalra 
2463a93261fSAshish Kalra 	return 0;
2473a93261fSAshish Kalra }
2483a93261fSAshish Kalra 
2499f70b8ebSGrant Likely static const struct irq_domain_ops ehv_pic_host_ops = {
2503a93261fSAshish Kalra 	.match = ehv_pic_host_match,
2513a93261fSAshish Kalra 	.map = ehv_pic_host_map,
2523a93261fSAshish Kalra 	.xlate = ehv_pic_host_xlate,
2533a93261fSAshish Kalra };
2543a93261fSAshish Kalra 
ehv_pic_init(void)2553a93261fSAshish Kalra void __init ehv_pic_init(void)
2563a93261fSAshish Kalra {
2573a93261fSAshish Kalra 	struct device_node *np, *np2;
2583a93261fSAshish Kalra 	struct ehv_pic *ehv_pic;
2593a93261fSAshish Kalra 
2603a93261fSAshish Kalra 	np = of_find_compatible_node(NULL, NULL, "epapr,hv-pic");
2613a93261fSAshish Kalra 	if (!np) {
2623a93261fSAshish Kalra 		pr_err("ehv_pic_init: could not find epapr,hv-pic node\n");
2633a93261fSAshish Kalra 		return;
2643a93261fSAshish Kalra 	}
2653a93261fSAshish Kalra 
2663a93261fSAshish Kalra 	ehv_pic = kzalloc(sizeof(struct ehv_pic), GFP_KERNEL);
2673a93261fSAshish Kalra 	if (!ehv_pic) {
2683a93261fSAshish Kalra 		of_node_put(np);
2693a93261fSAshish Kalra 		return;
2703a93261fSAshish Kalra 	}
2713a93261fSAshish Kalra 
272a8db8cf0SGrant Likely 	ehv_pic->irqhost = irq_domain_add_linear(np, NR_EHV_PIC_INTS,
273a8db8cf0SGrant Likely 						 &ehv_pic_host_ops, ehv_pic);
2743a93261fSAshish Kalra 	if (!ehv_pic->irqhost) {
2753a93261fSAshish Kalra 		of_node_put(np);
276e3854b6eSJulia Lawall 		kfree(ehv_pic);
2773a93261fSAshish Kalra 		return;
2783a93261fSAshish Kalra 	}
2793a93261fSAshish Kalra 
2803a93261fSAshish Kalra 	np2 = of_find_compatible_node(NULL, NULL, "fsl,hv-mpic-per-cpu");
2813a93261fSAshish Kalra 	if (np2) {
2823a93261fSAshish Kalra 		mpic_percpu_base_vaddr = of_iomap(np2, 0);
2833a93261fSAshish Kalra 		if (!mpic_percpu_base_vaddr)
2843a93261fSAshish Kalra 			pr_err("ehv_pic_init: of_iomap failed\n");
2853a93261fSAshish Kalra 
2863a93261fSAshish Kalra 		of_node_put(np2);
2873a93261fSAshish Kalra 	}
2883a93261fSAshish Kalra 
2893a93261fSAshish Kalra 	ehv_pic->hc_irq = ehv_pic_irq_chip;
2903a93261fSAshish Kalra 	ehv_pic->hc_irq.irq_set_affinity = ehv_pic_set_affinity;
2914d57e351SRob Herring 	ehv_pic->coreint_flag = of_property_read_bool(np, "has-external-proxy");
2923a93261fSAshish Kalra 
2933a93261fSAshish Kalra 	global_ehv_pic = ehv_pic;
2943a93261fSAshish Kalra 	irq_set_default_host(global_ehv_pic->irqhost);
2953a93261fSAshish Kalra }
296