| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 1 | /* | 
 | 2 |  * Copyright (C) 2007-2009 Michal Simek <monstr@monstr.eu> | 
 | 3 |  * Copyright (C) 2007-2009 PetaLogix | 
 | 4 |  * Copyright (C) 2006 Atmark Techno, Inc. | 
 | 5 |  * | 
 | 6 |  * This file is subject to the terms and conditions of the GNU General Public | 
 | 7 |  * License. See the file "COPYING" in the main directory of this archive | 
 | 8 |  * for more details. | 
 | 9 |  */ | 
 | 10 |  | 
 | 11 | #include <linux/init.h> | 
| Grant Likely | 2462bac | 2012-01-26 14:10:13 -0700 | [diff] [blame] | 12 | #include <linux/irqdomain.h> | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 13 | #include <linux/irq.h> | 
 | 14 | #include <asm/page.h> | 
 | 15 | #include <linux/io.h> | 
| John Williams | 892ee92 | 2009-07-29 22:08:40 +1000 | [diff] [blame] | 16 | #include <linux/bug.h> | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 17 |  | 
 | 18 | #include <asm/prom.h> | 
 | 19 | #include <asm/irq.h> | 
 | 20 |  | 
 | 21 | #ifdef CONFIG_SELFMOD_INTC | 
 | 22 | #include <asm/selfmod.h> | 
 | 23 | #define INTC_BASE	BARRIER_BASE_ADDR | 
 | 24 | #else | 
 | 25 | static unsigned int intc_baseaddr; | 
 | 26 | #define INTC_BASE	intc_baseaddr | 
 | 27 | #endif | 
 | 28 |  | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 29 | /* No one else should require these constants, so define them locally here. */ | 
 | 30 | #define ISR 0x00			/* Interrupt Status Register */ | 
 | 31 | #define IPR 0x04			/* Interrupt Pending Register */ | 
 | 32 | #define IER 0x08			/* Interrupt Enable Register */ | 
 | 33 | #define IAR 0x0c			/* Interrupt Acknowledge Register */ | 
 | 34 | #define SIE 0x10			/* Set Interrupt Enable bits */ | 
 | 35 | #define CIE 0x14			/* Clear Interrupt Enable bits */ | 
 | 36 | #define IVR 0x18			/* Interrupt Vector Register */ | 
 | 37 | #define MER 0x1c			/* Master Enable Register */ | 
 | 38 |  | 
 | 39 | #define MER_ME (1<<0) | 
 | 40 | #define MER_HIE (1<<1) | 
 | 41 |  | 
| Thomas Gleixner | 6f205a4 | 2011-02-06 19:36:30 +0000 | [diff] [blame] | 42 | static void intc_enable_or_unmask(struct irq_data *d) | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 43 | { | 
| Michal Simek | 6c7a267 | 2011-12-09 10:45:20 +0100 | [diff] [blame] | 44 | 	unsigned long mask = 1 << d->hwirq; | 
 | 45 |  | 
 | 46 | 	pr_debug("enable_or_unmask: %ld\n", d->hwirq); | 
| steve@digidescorp.com | 33d9ff5 | 2009-11-17 08:43:39 -0600 | [diff] [blame] | 47 |  | 
 | 48 | 	/* ack level irqs because they can't be acked during | 
 | 49 | 	 * ack function since the handle_level_irq function | 
 | 50 | 	 * acks the irq before calling the interrupt handler | 
 | 51 | 	 */ | 
| Thomas Gleixner | 4adc192 | 2011-03-24 14:52:04 +0100 | [diff] [blame] | 52 | 	if (irqd_is_level_type(d)) | 
| steve@digidescorp.com | 33d9ff5 | 2009-11-17 08:43:39 -0600 | [diff] [blame] | 53 | 		out_be32(INTC_BASE + IAR, mask); | 
| Michal Simek | 7958a68 | 2012-11-05 11:51:13 +0100 | [diff] [blame] | 54 |  | 
 | 55 | 	out_be32(INTC_BASE + SIE, mask); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 56 | } | 
 | 57 |  | 
| Thomas Gleixner | 6f205a4 | 2011-02-06 19:36:30 +0000 | [diff] [blame] | 58 | static void intc_disable_or_mask(struct irq_data *d) | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 59 | { | 
| Michal Simek | 6c7a267 | 2011-12-09 10:45:20 +0100 | [diff] [blame] | 60 | 	pr_debug("disable: %ld\n", d->hwirq); | 
 | 61 | 	out_be32(INTC_BASE + CIE, 1 << d->hwirq); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 62 | } | 
 | 63 |  | 
| Thomas Gleixner | 6f205a4 | 2011-02-06 19:36:30 +0000 | [diff] [blame] | 64 | static void intc_ack(struct irq_data *d) | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 65 | { | 
| Michal Simek | 6c7a267 | 2011-12-09 10:45:20 +0100 | [diff] [blame] | 66 | 	pr_debug("ack: %ld\n", d->hwirq); | 
 | 67 | 	out_be32(INTC_BASE + IAR, 1 << d->hwirq); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 68 | } | 
 | 69 |  | 
| Thomas Gleixner | 6f205a4 | 2011-02-06 19:36:30 +0000 | [diff] [blame] | 70 | static void intc_mask_ack(struct irq_data *d) | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 71 | { | 
| Michal Simek | 6c7a267 | 2011-12-09 10:45:20 +0100 | [diff] [blame] | 72 | 	unsigned long mask = 1 << d->hwirq; | 
 | 73 |  | 
 | 74 | 	pr_debug("disable_and_ack: %ld\n", d->hwirq); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 75 | 	out_be32(INTC_BASE + CIE, mask); | 
 | 76 | 	out_be32(INTC_BASE + IAR, mask); | 
 | 77 | } | 
 | 78 |  | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 79 | static struct irq_chip intc_dev = { | 
 | 80 | 	.name = "Xilinx INTC", | 
| Thomas Gleixner | 6f205a4 | 2011-02-06 19:36:30 +0000 | [diff] [blame] | 81 | 	.irq_unmask = intc_enable_or_unmask, | 
 | 82 | 	.irq_mask = intc_disable_or_mask, | 
 | 83 | 	.irq_ack = intc_ack, | 
 | 84 | 	.irq_mask_ack = intc_mask_ack, | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 85 | }; | 
 | 86 |  | 
| Grant Likely | 2462bac | 2012-01-26 14:10:13 -0700 | [diff] [blame] | 87 | static struct irq_domain *root_domain; | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 88 |  | 
| Grant Likely | 2462bac | 2012-01-26 14:10:13 -0700 | [diff] [blame] | 89 | unsigned int get_irq(void) | 
 | 90 | { | 
 | 91 | 	unsigned int hwirq, irq = -1; | 
 | 92 |  | 
 | 93 | 	hwirq = in_be32(INTC_BASE + IVR); | 
 | 94 | 	if (hwirq != -1U) | 
 | 95 | 		irq = irq_find_mapping(root_domain, hwirq); | 
 | 96 |  | 
 | 97 | 	pr_debug("get_irq: hwirq=%d, irq=%d\n", hwirq, irq); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 98 |  | 
 | 99 | 	return irq; | 
 | 100 | } | 
 | 101 |  | 
| Michal Simek | c0d997f | 2012-12-13 17:30:05 +0100 | [diff] [blame] | 102 | static int xintc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) | 
| Grant Likely | 2462bac | 2012-01-26 14:10:13 -0700 | [diff] [blame] | 103 | { | 
 | 104 | 	u32 intr_mask = (u32)d->host_data; | 
 | 105 |  | 
 | 106 | 	if (intr_mask & (1 << hw)) { | 
 | 107 | 		irq_set_chip_and_handler_name(irq, &intc_dev, | 
 | 108 | 						handle_edge_irq, "edge"); | 
 | 109 | 		irq_clear_status_flags(irq, IRQ_LEVEL); | 
 | 110 | 	} else { | 
 | 111 | 		irq_set_chip_and_handler_name(irq, &intc_dev, | 
 | 112 | 						handle_level_irq, "level"); | 
 | 113 | 		irq_set_status_flags(irq, IRQ_LEVEL); | 
 | 114 | 	} | 
 | 115 | 	return 0; | 
 | 116 | } | 
 | 117 |  | 
 | 118 | static const struct irq_domain_ops xintc_irq_domain_ops = { | 
 | 119 | 	.xlate = irq_domain_xlate_onetwocell, | 
 | 120 | 	.map = xintc_map, | 
 | 121 | }; | 
 | 122 |  | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 123 | void __init init_IRQ(void) | 
 | 124 | { | 
| Grant Likely | 2462bac | 2012-01-26 14:10:13 -0700 | [diff] [blame] | 125 | 	u32 nr_irq, intr_mask; | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 126 | 	struct device_node *intc = NULL; | 
 | 127 | #ifdef CONFIG_SELFMOD_INTC | 
 | 128 | 	unsigned int intc_baseaddr = 0; | 
 | 129 | 	static int arr_func[] = { | 
 | 130 | 				(int)&get_irq, | 
 | 131 | 				(int)&intc_enable_or_unmask, | 
 | 132 | 				(int)&intc_disable_or_mask, | 
 | 133 | 				(int)&intc_mask_ack, | 
 | 134 | 				(int)&intc_ack, | 
 | 135 | 				(int)&intc_end, | 
 | 136 | 				0 | 
 | 137 | 			}; | 
 | 138 | #endif | 
| Michal Simek | 5a26cd6 | 2011-12-09 12:26:16 +0100 | [diff] [blame] | 139 | 	intc = of_find_compatible_node(NULL, NULL, "xlnx,xps-intc-1.00.a"); | 
| John Williams | 892ee92 | 2009-07-29 22:08:40 +1000 | [diff] [blame] | 140 | 	BUG_ON(!intc); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 141 |  | 
| Michal Simek | 6c7a267 | 2011-12-09 10:45:20 +0100 | [diff] [blame] | 142 | 	intc_baseaddr = be32_to_cpup(of_get_property(intc, "reg", NULL)); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 143 | 	intc_baseaddr = (unsigned long) ioremap(intc_baseaddr, PAGE_SIZE); | 
| Michal Simek | 02b0804 | 2010-09-28 16:04:14 +1000 | [diff] [blame] | 144 | 	nr_irq = be32_to_cpup(of_get_property(intc, | 
 | 145 | 						"xlnx,num-intr-inputs", NULL)); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 146 |  | 
| Michal Simek | 2ecb899 | 2011-12-09 12:26:55 +0100 | [diff] [blame] | 147 | 	intr_mask = | 
 | 148 | 		be32_to_cpup(of_get_property(intc, "xlnx,kind-of-intr", NULL)); | 
 | 149 | 	if (intr_mask > (u32)((1ULL << nr_irq) - 1)) | 
| Michal Simek | 6bd55f0 | 2012-12-27 10:40:38 +0100 | [diff] [blame] | 150 | 		pr_info(" ERROR: Mismatch in kind-of-intr param\n"); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 151 |  | 
 | 152 | #ifdef CONFIG_SELFMOD_INTC | 
 | 153 | 	selfmod_function((int *) arr_func, intc_baseaddr); | 
 | 154 | #endif | 
| Michal Simek | 6bd55f0 | 2012-12-27 10:40:38 +0100 | [diff] [blame] | 155 | 	pr_info("%s #0 at 0x%08x, num_irq=%d, edge=0x%x\n", | 
| Michal Simek | cc5647a | 2011-11-07 13:42:12 +0100 | [diff] [blame] | 156 | 		intc->name, intc_baseaddr, nr_irq, intr_mask); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 157 |  | 
 | 158 | 	/* | 
 | 159 | 	 * Disable all external interrupts until they are | 
 | 160 | 	 * explicity requested. | 
 | 161 | 	 */ | 
 | 162 | 	out_be32(intc_baseaddr + IER, 0); | 
 | 163 |  | 
 | 164 | 	/* Acknowledge any pending interrupts just in case. */ | 
 | 165 | 	out_be32(intc_baseaddr + IAR, 0xffffffff); | 
 | 166 |  | 
 | 167 | 	/* Turn on the Master Enable. */ | 
 | 168 | 	out_be32(intc_baseaddr + MER, MER_HIE | MER_ME); | 
 | 169 |  | 
| Grant Likely | 2462bac | 2012-01-26 14:10:13 -0700 | [diff] [blame] | 170 | 	/* Yeah, okay, casting the intr_mask to a void* is butt-ugly, but I'm | 
 | 171 | 	 * lazy and Michal can clean it up to something nicer when he tests | 
 | 172 | 	 * and commits this patch.  ~~gcl */ | 
 | 173 | 	root_domain = irq_domain_add_linear(intc, nr_irq, &xintc_irq_domain_ops, | 
 | 174 | 							(void *)intr_mask); | 
| Dan Christensen | 7c2c851 | 2013-03-17 04:48:56 -0500 | [diff] [blame] | 175 |  | 
 | 176 | 	irq_set_default_host(root_domain); | 
| Michal Simek | eedbdab | 2009-03-27 14:25:49 +0100 | [diff] [blame] | 177 | } |