| /* | 
 |  * Interrupt controller driver for Xilinx Virtex FPGAs | 
 |  * | 
 |  * Copyright (C) 2007 Secret Lab Technologies Ltd. | 
 |  * | 
 |  * 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. | 
 |  * | 
 |  */ | 
 |  | 
 | /* | 
 |  * This is a driver for the interrupt controller typically found in | 
 |  * Xilinx Virtex FPGA designs. | 
 |  * | 
 |  * The interrupt sense levels are hard coded into the FPGA design with | 
 |  * typically a 1:1 relationship between irq lines and devices (no shared | 
 |  * irq lines).  Therefore, this driver does not attempt to handle edge | 
 |  * and level interrupts differently. | 
 |  */ | 
 | #undef DEBUG | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/irq.h> | 
 | #include <linux/of.h> | 
 | #include <asm/io.h> | 
 | #include <asm/processor.h> | 
 | #include <asm/irq.h> | 
 |  | 
 | /* | 
 |  * INTC Registers | 
 |  */ | 
 | #define XINTC_ISR	0	/* Interrupt Status */ | 
 | #define XINTC_IPR	4	/* Interrupt Pending */ | 
 | #define XINTC_IER	8	/* Interrupt Enable */ | 
 | #define XINTC_IAR	12	/* Interrupt Acknowledge */ | 
 | #define XINTC_SIE	16	/* Set Interrupt Enable bits */ | 
 | #define XINTC_CIE	20	/* Clear Interrupt Enable bits */ | 
 | #define XINTC_IVR	24	/* Interrupt Vector */ | 
 | #define XINTC_MER	28	/* Master Enable */ | 
 |  | 
 | static struct irq_host *master_irqhost; | 
 |  | 
 | /* | 
 |  * IRQ Chip operations | 
 |  */ | 
 | static void xilinx_intc_mask(unsigned int virq) | 
 | { | 
 | 	int irq = virq_to_hw(virq); | 
 | 	void * regs = get_irq_chip_data(virq); | 
 | 	pr_debug("mask: %d\n", irq); | 
 | 	out_be32(regs + XINTC_CIE, 1 << irq); | 
 | } | 
 |  | 
 | static void xilinx_intc_unmask(unsigned int virq) | 
 | { | 
 | 	int irq = virq_to_hw(virq); | 
 | 	void * regs = get_irq_chip_data(virq); | 
 | 	pr_debug("unmask: %d\n", irq); | 
 | 	out_be32(regs + XINTC_SIE, 1 << irq); | 
 | } | 
 |  | 
 | static void xilinx_intc_ack(unsigned int virq) | 
 | { | 
 | 	int irq = virq_to_hw(virq); | 
 | 	void * regs = get_irq_chip_data(virq); | 
 | 	pr_debug("ack: %d\n", irq); | 
 | 	out_be32(regs + XINTC_IAR, 1 << irq); | 
 | } | 
 |  | 
 | static struct irq_chip xilinx_intc_irqchip = { | 
 | 	.typename = "Xilinx INTC", | 
 | 	.mask = xilinx_intc_mask, | 
 | 	.unmask = xilinx_intc_unmask, | 
 | 	.ack = xilinx_intc_ack, | 
 | }; | 
 |  | 
 | /* | 
 |  * IRQ Host operations | 
 |  */ | 
 | static int xilinx_intc_map(struct irq_host *h, unsigned int virq, | 
 | 				  irq_hw_number_t irq) | 
 | { | 
 | 	set_irq_chip_data(virq, h->host_data); | 
 | 	set_irq_chip_and_handler(virq, &xilinx_intc_irqchip, handle_level_irq); | 
 | 	set_irq_type(virq, IRQ_TYPE_NONE); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct irq_host_ops xilinx_intc_ops = { | 
 | 	.map = xilinx_intc_map, | 
 | }; | 
 |  | 
 | struct irq_host * __init | 
 | xilinx_intc_init(struct device_node *np) | 
 | { | 
 | 	struct irq_host * irq; | 
 | 	struct resource res; | 
 | 	void * regs; | 
 | 	int rc; | 
 |  | 
 | 	/* Find and map the intc registers */ | 
 | 	rc = of_address_to_resource(np, 0, &res); | 
 | 	if (rc) { | 
 | 		printk(KERN_ERR __FILE__ ": of_address_to_resource() failed\n"); | 
 | 		return NULL; | 
 | 	} | 
 | 	regs = ioremap(res.start, 32); | 
 |  | 
 | 	printk(KERN_INFO "Xilinx intc at 0x%08X mapped to 0x%p\n", | 
 | 		res.start, regs); | 
 |  | 
 | 	/* Setup interrupt controller */ | 
 | 	out_be32(regs + XINTC_IER, 0); /* disable all irqs */ | 
 | 	out_be32(regs + XINTC_IAR, ~(u32) 0); /* Acknowledge pending irqs */ | 
 | 	out_be32(regs + XINTC_MER, 0x3UL); /* Turn on the Master Enable. */ | 
 |  | 
 | 	/* Allocate and initialize an irq_host structure. */ | 
 | 	irq = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, 32, &xilinx_intc_ops, -1); | 
 | 	if (!irq) | 
 | 		panic(__FILE__ ": Cannot allocate IRQ host\n"); | 
 | 	irq->host_data = regs; | 
 | 	return irq; | 
 | } | 
 |  | 
 | int xilinx_intc_get_irq(void) | 
 | { | 
 | 	void * regs = master_irqhost->host_data; | 
 | 	pr_debug("get_irq:\n"); | 
 | 	return irq_linear_revmap(master_irqhost, in_be32(regs + XINTC_IVR)); | 
 | } | 
 |  | 
 | void __init xilinx_intc_init_tree(void) | 
 | { | 
 | 	struct device_node *np; | 
 |  | 
 | 	/* find top level interrupt controller */ | 
 | 	for_each_compatible_node(np, NULL, "xilinx,intc") { | 
 | 		if (!of_get_property(np, "interrupts", NULL)) | 
 | 			break; | 
 | 	} | 
 |  | 
 | 	/* xilinx interrupt controller needs to be top level */ | 
 | 	BUG_ON(!np); | 
 |  | 
 | 	master_irqhost = xilinx_intc_init(np); | 
 | 	BUG_ON(!master_irqhost); | 
 |  | 
 | 	irq_set_default_host(master_irqhost); | 
 | 	of_node_put(np); | 
 | } |