1 /* 2 * drivers/irqchip/irq-crossbar.c 3 * 4 * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com 5 * Author: Sricharan R <r.sricharan@ti.com> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 * 11 */ 12 #include <linux/err.h> 13 #include <linux/io.h> 14 #include <linux/of_address.h> 15 #include <linux/of_irq.h> 16 #include <linux/slab.h> 17 #include <linux/irqchip/arm-gic.h> 18 19 #define IRQ_FREE -1 20 #define GIC_IRQ_START 32 21 22 /* 23 * @int_max: maximum number of supported interrupts 24 * @irq_map: array of interrupts to crossbar number mapping 25 * @crossbar_base: crossbar base address 26 * @register_offsets: offsets for each irq number 27 */ 28 struct crossbar_device { 29 uint int_max; 30 uint *irq_map; 31 void __iomem *crossbar_base; 32 int *register_offsets; 33 void (*write) (int, int); 34 }; 35 36 static struct crossbar_device *cb; 37 38 static inline void crossbar_writel(int irq_no, int cb_no) 39 { 40 writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); 41 } 42 43 static inline void crossbar_writew(int irq_no, int cb_no) 44 { 45 writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); 46 } 47 48 static inline void crossbar_writeb(int irq_no, int cb_no) 49 { 50 writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); 51 } 52 53 static inline int allocate_free_irq(int cb_no) 54 { 55 int i; 56 57 for (i = 0; i < cb->int_max; i++) { 58 if (cb->irq_map[i] == IRQ_FREE) { 59 cb->irq_map[i] = cb_no; 60 return i; 61 } 62 } 63 64 return -ENODEV; 65 } 66 67 static int crossbar_domain_map(struct irq_domain *d, unsigned int irq, 68 irq_hw_number_t hw) 69 { 70 cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]); 71 return 0; 72 } 73 74 static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq) 75 { 76 irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq; 77 78 if (hw > GIC_IRQ_START) 79 cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE; 80 } 81 82 static int crossbar_domain_xlate(struct irq_domain *d, 83 struct device_node *controller, 84 const u32 *intspec, unsigned int intsize, 85 unsigned long *out_hwirq, 86 unsigned int *out_type) 87 { 88 unsigned long ret; 89 90 ret = allocate_free_irq(intspec[1]); 91 92 if (IS_ERR_VALUE(ret)) 93 return ret; 94 95 *out_hwirq = ret + GIC_IRQ_START; 96 return 0; 97 } 98 99 const struct irq_domain_ops routable_irq_domain_ops = { 100 .map = crossbar_domain_map, 101 .unmap = crossbar_domain_unmap, 102 .xlate = crossbar_domain_xlate 103 }; 104 105 static int __init crossbar_of_init(struct device_node *node) 106 { 107 int i, size, max, reserved = 0, entry; 108 const __be32 *irqsr; 109 110 cb = kzalloc(sizeof(*cb), GFP_KERNEL); 111 112 if (!cb) 113 return -ENOMEM; 114 115 cb->crossbar_base = of_iomap(node, 0); 116 if (!cb->crossbar_base) 117 goto err1; 118 119 of_property_read_u32(node, "ti,max-irqs", &max); 120 cb->irq_map = kzalloc(max * sizeof(int), GFP_KERNEL); 121 if (!cb->irq_map) 122 goto err2; 123 124 cb->int_max = max; 125 126 for (i = 0; i < max; i++) 127 cb->irq_map[i] = IRQ_FREE; 128 129 /* Get and mark reserved irqs */ 130 irqsr = of_get_property(node, "ti,irqs-reserved", &size); 131 if (irqsr) { 132 size /= sizeof(__be32); 133 134 for (i = 0; i < size; i++) { 135 of_property_read_u32_index(node, 136 "ti,irqs-reserved", 137 i, &entry); 138 if (entry > max) { 139 pr_err("Invalid reserved entry\n"); 140 goto err3; 141 } 142 cb->irq_map[entry] = 0; 143 } 144 } 145 146 cb->register_offsets = kzalloc(max * sizeof(int), GFP_KERNEL); 147 if (!cb->register_offsets) 148 goto err3; 149 150 of_property_read_u32(node, "ti,reg-size", &size); 151 152 switch (size) { 153 case 1: 154 cb->write = crossbar_writeb; 155 break; 156 case 2: 157 cb->write = crossbar_writew; 158 break; 159 case 4: 160 cb->write = crossbar_writel; 161 break; 162 default: 163 pr_err("Invalid reg-size property\n"); 164 goto err4; 165 break; 166 } 167 168 /* 169 * Register offsets are not linear because of the 170 * reserved irqs. so find and store the offsets once. 171 */ 172 for (i = 0; i < max; i++) { 173 if (!cb->irq_map[i]) 174 continue; 175 176 cb->register_offsets[i] = reserved; 177 reserved += size; 178 } 179 180 register_routable_domain_ops(&routable_irq_domain_ops); 181 return 0; 182 183 err4: 184 kfree(cb->register_offsets); 185 err3: 186 kfree(cb->irq_map); 187 err2: 188 iounmap(cb->crossbar_base); 189 err1: 190 kfree(cb); 191 return -ENOMEM; 192 } 193 194 static const struct of_device_id crossbar_match[] __initconst = { 195 { .compatible = "ti,irq-crossbar" }, 196 {} 197 }; 198 199 int __init irqcrossbar_init(void) 200 { 201 struct device_node *np; 202 np = of_find_matching_node(NULL, crossbar_match); 203 if (!np) 204 return -ENODEV; 205 206 crossbar_of_init(np); 207 return 0; 208 } 209