123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- /*
- * drivers/irqchip/irq-crossbar.c
- *
- * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
- * Author: Sricharan R <r.sricharan@ti.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- */
- #include <linux/err.h>
- #include <linux/io.h>
- #include <linux/of_address.h>
- #include <linux/of_irq.h>
- #include <linux/slab.h>
- #include <linux/irqchip/arm-gic.h>
- #define IRQ_FREE -1
- #define GIC_IRQ_START 32
- /*
- * @int_max: maximum number of supported interrupts
- * @irq_map: array of interrupts to crossbar number mapping
- * @crossbar_base: crossbar base address
- * @register_offsets: offsets for each irq number
- */
- struct crossbar_device {
- uint int_max;
- uint *irq_map;
- void __iomem *crossbar_base;
- int *register_offsets;
- void (*write) (int, int);
- };
- static struct crossbar_device *cb;
- static inline void crossbar_writel(int irq_no, int cb_no)
- {
- writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
- }
- static inline void crossbar_writew(int irq_no, int cb_no)
- {
- writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
- }
- 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 allocate_free_irq(int cb_no)
- {
- int i;
- for (i = 0; i < cb->int_max; i++) {
- if (cb->irq_map[i] == IRQ_FREE) {
- cb->irq_map[i] = cb_no;
- return i;
- }
- }
- return -ENODEV;
- }
- 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]);
- return 0;
- }
- 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)
- cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE;
- }
- static int crossbar_domain_xlate(struct irq_domain *d,
- struct device_node *controller,
- const u32 *intspec, unsigned int intsize,
- unsigned long *out_hwirq,
- unsigned int *out_type)
- {
- unsigned long ret;
- ret = allocate_free_irq(intspec[1]);
- if (IS_ERR_VALUE(ret))
- return ret;
- *out_hwirq = ret + GIC_IRQ_START;
- return 0;
- }
- const struct irq_domain_ops routable_irq_domain_ops = {
- .map = crossbar_domain_map,
- .unmap = crossbar_domain_unmap,
- .xlate = crossbar_domain_xlate
- };
- static int __init crossbar_of_init(struct device_node *node)
- {
- int i, size, max, reserved = 0, entry;
- const __be32 *irqsr;
- cb = kzalloc(sizeof(*cb), GFP_KERNEL);
- if (!cb)
- return -ENOMEM;
- cb->crossbar_base = of_iomap(node, 0);
- if (!cb->crossbar_base)
- goto err1;
- of_property_read_u32(node, "ti,max-irqs", &max);
- cb->irq_map = kzalloc(max * sizeof(int), GFP_KERNEL);
- if (!cb->irq_map)
- goto err2;
- cb->int_max = max;
- for (i = 0; i < max; i++)
- cb->irq_map[i] = IRQ_FREE;
- /* Get and mark reserved irqs */
- irqsr = of_get_property(node, "ti,irqs-reserved", &size);
- if (irqsr) {
- size /= sizeof(__be32);
- for (i = 0; i < size; i++) {
- of_property_read_u32_index(node,
- "ti,irqs-reserved",
- i, &entry);
- if (entry > max) {
- pr_err("Invalid reserved entry\n");
- goto err3;
- }
- cb->irq_map[entry] = 0;
- }
- }
- cb->register_offsets = kzalloc(max * sizeof(int), GFP_KERNEL);
- if (!cb->register_offsets)
- goto err3;
- of_property_read_u32(node, "ti,reg-size", &size);
- switch (size) {
- case 1:
- cb->write = crossbar_writeb;
- break;
- case 2:
- cb->write = crossbar_writew;
- break;
- case 4:
- cb->write = crossbar_writel;
- break;
- default:
- pr_err("Invalid reg-size property\n");
- goto err4;
- break;
- }
- /*
- * Register offsets are not linear because of the
- * reserved irqs. so find and store the offsets once.
- */
- for (i = 0; i < max; i++) {
- if (!cb->irq_map[i])
- continue;
- cb->register_offsets[i] = reserved;
- reserved += size;
- }
- register_routable_domain_ops(&routable_irq_domain_ops);
- return 0;
- err4:
- kfree(cb->register_offsets);
- err3:
- kfree(cb->irq_map);
- err2:
- iounmap(cb->crossbar_base);
- err1:
- kfree(cb);
- return -ENOMEM;
- }
- static const struct of_device_id crossbar_match[] __initconst = {
- { .compatible = "ti,irq-crossbar" },
- {}
- };
- int __init irqcrossbar_init(void)
- {
- struct device_node *np;
- np = of_find_matching_node(NULL, crossbar_match);
- if (!np)
- return -ENODEV;
- crossbar_of_init(np);
- return 0;
- }
|