diff options
Diffstat (limited to 'drivers/irqchip/irq-crossbar.c')
-rw-r--r-- | drivers/irqchip/irq-crossbar.c | 168 |
1 files changed, 143 insertions, 25 deletions
diff --git a/drivers/irqchip/irq-crossbar.c b/drivers/irqchip/irq-crossbar.c index 3d15d16a7088..85c2985d8bcb 100644 --- a/drivers/irqchip/irq-crossbar.c +++ b/drivers/irqchip/irq-crossbar.c @@ -15,22 +15,31 @@ #include <linux/of_irq.h> #include <linux/slab.h> #include <linux/irqchip/arm-gic.h> +#include <linux/irqchip/irq-crossbar.h> #define IRQ_FREE -1 +#define IRQ_RESERVED -2 +#define IRQ_SKIP -3 #define GIC_IRQ_START 32 -/* +/** + * struct crossbar_device - crossbar device description * @int_max: maximum number of supported interrupts + * @safe_map: safe default value to initialize the crossbar + * @max_crossbar_sources: Maximum number of crossbar sources * @irq_map: array of interrupts to crossbar number mapping * @crossbar_base: crossbar base address * @register_offsets: offsets for each irq number + * @write: register write function pointer */ struct crossbar_device { uint int_max; + uint safe_map; + uint max_crossbar_sources; uint *irq_map; void __iomem *crossbar_base; int *register_offsets; - void (*write) (int, int); + void (*write)(int, int); }; static struct crossbar_device *cb; @@ -50,11 +59,22 @@ static inline void crossbar_writeb(int irq_no, int cb_no) writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); } +static inline int get_prev_map_irq(int cb_no) +{ + int i; + + for (i = cb->int_max - 1; i >= 0; i--) + if (cb->irq_map[i] == cb_no) + return i; + + return -ENODEV; +} + static inline int allocate_free_irq(int cb_no) { int i; - for (i = 0; i < cb->int_max; i++) { + for (i = cb->int_max - 1; i >= 0; i--) { if (cb->irq_map[i] == IRQ_FREE) { cb->irq_map[i] = cb_no; return i; @@ -64,19 +84,47 @@ static inline int allocate_free_irq(int cb_no) return -ENODEV; } +static inline bool needs_crossbar_write(irq_hw_number_t hw) +{ + int cb_no; + + if (hw > GIC_IRQ_START) { + cb_no = cb->irq_map[hw - GIC_IRQ_START]; + if (cb_no != IRQ_RESERVED && cb_no != IRQ_SKIP) + return true; + } + + return false; +} + static int crossbar_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) { - cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]); + if (needs_crossbar_write(hw)) + cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]); + return 0; } +/** + * crossbar_domain_unmap - unmap a crossbar<->irq connection + * @d: domain of irq to unmap + * @irq: virq number + * + * We do not maintain a use count of total number of map/unmap + * calls for a particular irq to find out if a irq can be really + * unmapped. This is because unmap is called during irq_dispose_mapping(irq), + * after which irq is anyways unusable. So an explicit map has to be called + * after that. + */ static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq) { irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq; - if (hw > GIC_IRQ_START) + if (needs_crossbar_write(hw)) { cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE; + cb->write(hw - GIC_IRQ_START, cb->safe_map); + } } static int crossbar_domain_xlate(struct irq_domain *d, @@ -85,18 +133,41 @@ static int crossbar_domain_xlate(struct irq_domain *d, unsigned long *out_hwirq, unsigned int *out_type) { - unsigned long ret; + int ret; + int req_num = intspec[1]; + int direct_map_num; + + if (req_num >= cb->max_crossbar_sources) { + direct_map_num = req_num - cb->max_crossbar_sources; + if (direct_map_num < cb->int_max) { + ret = cb->irq_map[direct_map_num]; + if (ret == IRQ_RESERVED || ret == IRQ_SKIP) { + /* We use the interrupt num as h/w irq num */ + ret = direct_map_num; + goto found; + } + } + + pr_err("%s: requested crossbar number %d > max %d\n", + __func__, req_num, cb->max_crossbar_sources); + return -EINVAL; + } - ret = allocate_free_irq(intspec[1]); + ret = get_prev_map_irq(req_num); + if (ret >= 0) + goto found; - if (IS_ERR_VALUE(ret)) + ret = allocate_free_irq(req_num); + + if (ret < 0) return ret; +found: *out_hwirq = ret + GIC_IRQ_START; return 0; } -const struct irq_domain_ops routable_irq_domain_ops = { +static const struct irq_domain_ops routable_irq_domain_ops = { .map = crossbar_domain_map, .unmap = crossbar_domain_unmap, .xlate = crossbar_domain_xlate @@ -104,22 +175,36 @@ const struct irq_domain_ops routable_irq_domain_ops = { static int __init crossbar_of_init(struct device_node *node) { - int i, size, max, reserved = 0, entry; + int i, size, max = 0, reserved = 0, entry; const __be32 *irqsr; + int ret = -ENOMEM; cb = kzalloc(sizeof(*cb), GFP_KERNEL); if (!cb) - return -ENOMEM; + return ret; cb->crossbar_base = of_iomap(node, 0); if (!cb->crossbar_base) - goto err1; + goto err_cb; + + of_property_read_u32(node, "ti,max-crossbar-sources", + &cb->max_crossbar_sources); + if (!cb->max_crossbar_sources) { + pr_err("missing 'ti,max-crossbar-sources' property\n"); + ret = -EINVAL; + goto err_base; + } of_property_read_u32(node, "ti,max-irqs", &max); - cb->irq_map = kzalloc(max * sizeof(int), GFP_KERNEL); + if (!max) { + pr_err("missing 'ti,max-irqs' property\n"); + ret = -EINVAL; + goto err_base; + } + cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL); if (!cb->irq_map) - goto err2; + goto err_base; cb->int_max = max; @@ -137,15 +222,35 @@ static int __init crossbar_of_init(struct device_node *node) i, &entry); if (entry > max) { pr_err("Invalid reserved entry\n"); - goto err3; + ret = -EINVAL; + goto err_irq_map; + } + cb->irq_map[entry] = IRQ_RESERVED; + } + } + + /* Skip irqs hardwired to bypass the crossbar */ + irqsr = of_get_property(node, "ti,irqs-skip", &size); + if (irqsr) { + size /= sizeof(__be32); + + for (i = 0; i < size; i++) { + of_property_read_u32_index(node, + "ti,irqs-skip", + i, &entry); + if (entry > max) { + pr_err("Invalid skip entry\n"); + ret = -EINVAL; + goto err_irq_map; } - cb->irq_map[entry] = 0; + cb->irq_map[entry] = IRQ_SKIP; } } - cb->register_offsets = kzalloc(max * sizeof(int), GFP_KERNEL); + + cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL); if (!cb->register_offsets) - goto err3; + goto err_irq_map; of_property_read_u32(node, "ti,reg-size", &size); @@ -161,7 +266,8 @@ static int __init crossbar_of_init(struct device_node *node) break; default: pr_err("Invalid reg-size property\n"); - goto err4; + ret = -EINVAL; + goto err_reg_offset; break; } @@ -170,25 +276,37 @@ static int __init crossbar_of_init(struct device_node *node) * reserved irqs. so find and store the offsets once. */ for (i = 0; i < max; i++) { - if (!cb->irq_map[i]) + if (cb->irq_map[i] == IRQ_RESERVED) continue; cb->register_offsets[i] = reserved; reserved += size; } + of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map); + /* Initialize the crossbar with safe map to start with */ + for (i = 0; i < max; i++) { + if (cb->irq_map[i] == IRQ_RESERVED || + cb->irq_map[i] == IRQ_SKIP) + continue; + + cb->write(i, cb->safe_map); + } + register_routable_domain_ops(&routable_irq_domain_ops); return 0; -err4: +err_reg_offset: kfree(cb->register_offsets); -err3: +err_irq_map: kfree(cb->irq_map); -err2: +err_base: iounmap(cb->crossbar_base); -err1: +err_cb: kfree(cb); - return -ENOMEM; + + cb = NULL; + return ret; } static const struct of_device_id crossbar_match[] __initconst = { |