152 lines
		
	
	
	
		
			3.8 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			152 lines
		
	
	
	
		
			3.8 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| 
								 | 
							
								/*
							 | 
						||
| 
								 | 
							
								 * 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);
							 | 
						||
| 
								 | 
							
								}
							 |