summaryrefslogtreecommitdiff
path: root/drivers/irqchip/irq-crossbar.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/irqchip/irq-crossbar.c')
-rw-r--r--drivers/irqchip/irq-crossbar.c210
1 files changed, 123 insertions, 87 deletions
diff --git a/drivers/irqchip/irq-crossbar.c b/drivers/irqchip/irq-crossbar.c
index bbbaf5de65d2..692fe2bc8197 100644
--- a/drivers/irqchip/irq-crossbar.c
+++ b/drivers/irqchip/irq-crossbar.c
@@ -11,11 +11,12 @@
*/
#include <linux/err.h>
#include <linux/io.h>
+#include <linux/irqdomain.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
-#include <linux/irqchip/arm-gic.h>
-#include <linux/irqchip/irq-crossbar.h>
+
+#include "irqchip.h"
#define IRQ_FREE -1
#define IRQ_RESERVED -2
@@ -24,6 +25,7 @@
/**
* struct crossbar_device - crossbar device description
+ * @lock: spinlock serializing access to @irq_map
* @int_max: maximum number of supported interrupts
* @safe_map: safe default value to initialize the crossbar
* @max_crossbar_sources: Maximum number of crossbar sources
@@ -33,6 +35,7 @@
* @write: register write function pointer
*/
struct crossbar_device {
+ raw_spinlock_t lock;
uint int_max;
uint safe_map;
uint max_crossbar_sources;
@@ -44,72 +47,101 @@ struct crossbar_device {
static struct crossbar_device *cb;
-static inline void crossbar_writel(int irq_no, int cb_no)
+static 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)
+static 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)
+static 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 struct irq_chip crossbar_chip = {
+ .name = "CBAR",
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_retrigger = irq_chip_retrigger_hierarchy,
+ .irq_set_wake = irq_chip_set_wake_parent,
+#ifdef CONFIG_SMP
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+#endif
+};
-static inline int allocate_free_irq(int cb_no)
+static int allocate_gic_irq(struct irq_domain *domain, unsigned virq,
+ irq_hw_number_t hwirq)
{
+ struct of_phandle_args args;
int i;
+ int err;
+ raw_spin_lock(&cb->lock);
for (i = cb->int_max - 1; i >= 0; i--) {
if (cb->irq_map[i] == IRQ_FREE) {
- cb->irq_map[i] = cb_no;
- return i;
+ cb->irq_map[i] = hwirq;
+ break;
}
}
+ raw_spin_unlock(&cb->lock);
- return -ENODEV;
-}
+ if (i < 0)
+ return -ENODEV;
-static inline bool needs_crossbar_write(irq_hw_number_t hw)
-{
- int cb_no;
+ args.np = domain->parent->of_node;
+ args.args_count = 3;
+ args.args[0] = 0; /* SPI */
+ args.args[1] = i;
+ args.args[2] = IRQ_TYPE_LEVEL_HIGH;
- 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;
- }
+ err = irq_domain_alloc_irqs_parent(domain, virq, 1, &args);
+ if (err)
+ cb->irq_map[i] = IRQ_FREE;
+ else
+ cb->write(i, hwirq);
- return false;
+ return err;
}
-static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
- irq_hw_number_t hw)
+static int crossbar_domain_alloc(struct irq_domain *d, unsigned int virq,
+ unsigned int nr_irqs, void *data)
{
- if (needs_crossbar_write(hw))
- cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]);
+ struct of_phandle_args *args = data;
+ irq_hw_number_t hwirq;
+ int i;
+
+ if (args->args_count != 3)
+ return -EINVAL; /* Not GIC compliant */
+ if (args->args[0] != 0)
+ return -EINVAL; /* No PPI should point to this domain */
+
+ hwirq = args->args[1];
+ if ((hwirq + nr_irqs) > cb->max_crossbar_sources)
+ return -EINVAL; /* Can't deal with this */
+
+ for (i = 0; i < nr_irqs; i++) {
+ int err = allocate_gic_irq(d, virq + i, hwirq + i);
+
+ if (err)
+ return err;
+
+ irq_domain_set_hwirq_and_chip(d, virq + i, hwirq + i,
+ &crossbar_chip, NULL);
+ }
return 0;
}
/**
- * crossbar_domain_unmap - unmap a crossbar<->irq connection
- * @d: domain of irq to unmap
- * @irq: virq number
+ * crossbar_domain_free - unmap/free a crossbar<->irq connection
+ * @domain: domain of irq to unmap
+ * @virq: virq number
+ * @nr_irqs: number of irqs to free
*
* 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
@@ -117,14 +149,20 @@ static int crossbar_domain_map(struct irq_domain *d, unsigned int 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)
+static void crossbar_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
{
- irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
+ int i;
- if (needs_crossbar_write(hw)) {
- cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE;
- cb->write(hw - GIC_IRQ_START, cb->safe_map);
+ raw_spin_lock(&cb->lock);
+ for (i = 0; i < nr_irqs; i++) {
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
+
+ irq_domain_reset_irq_data(d);
+ cb->irq_map[d->hwirq] = IRQ_FREE;
+ cb->write(d->hwirq, cb->safe_map);
}
+ raw_spin_unlock(&cb->lock);
}
static int crossbar_domain_xlate(struct irq_domain *d,
@@ -133,44 +171,22 @@ static int crossbar_domain_xlate(struct irq_domain *d,
unsigned long *out_hwirq,
unsigned int *out_type)
{
- 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 = get_prev_map_irq(req_num);
- if (ret >= 0)
- goto found;
-
- ret = allocate_free_irq(req_num);
-
- if (ret < 0)
- return ret;
-
-found:
- *out_hwirq = ret + GIC_IRQ_START;
+ if (d->of_node != controller)
+ return -EINVAL; /* Shouldn't happen, really... */
+ if (intsize != 3)
+ return -EINVAL; /* Not GIC compliant */
+ if (intspec[0] != 0)
+ return -EINVAL; /* No PPI should point to this domain */
+
+ *out_hwirq = intspec[1];
+ *out_type = intspec[2];
return 0;
}
-static const struct irq_domain_ops routable_irq_domain_ops = {
- .map = crossbar_domain_map,
- .unmap = crossbar_domain_unmap,
- .xlate = crossbar_domain_xlate
+static const struct irq_domain_ops crossbar_domain_ops = {
+ .alloc = crossbar_domain_alloc,
+ .free = crossbar_domain_free,
+ .xlate = crossbar_domain_xlate,
};
static int __init crossbar_of_init(struct device_node *node)
@@ -293,7 +309,8 @@ static int __init crossbar_of_init(struct device_node *node)
cb->write(i, cb->safe_map);
}
- register_routable_domain_ops(&routable_irq_domain_ops);
+ raw_spin_lock_init(&cb->lock);
+
return 0;
err_reg_offset:
@@ -309,18 +326,37 @@ err_cb:
return ret;
}
-static const struct of_device_id crossbar_match[] __initconst = {
- { .compatible = "ti,irq-crossbar" },
- {}
-};
-
-int __init irqcrossbar_init(void)
+static int __init irqcrossbar_init(struct device_node *node,
+ struct device_node *parent)
{
- struct device_node *np;
- np = of_find_matching_node(NULL, crossbar_match);
- if (!np)
+ struct irq_domain *parent_domain, *domain;
+ int err;
+
+ if (!parent) {
+ pr_err("%s: no parent, giving up\n", node->full_name);
return -ENODEV;
+ }
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ pr_err("%s: unable to obtain parent domain\n", node->full_name);
+ return -ENXIO;
+ }
+
+ err = crossbar_of_init(node);
+ if (err)
+ return err;
+
+ domain = irq_domain_add_hierarchy(parent_domain, 0,
+ cb->max_crossbar_sources,
+ node, &crossbar_domain_ops,
+ NULL);
+ if (!domain) {
+ pr_err("%s: failed to allocated domain\n", node->full_name);
+ return -ENOMEM;
+ }
- crossbar_of_init(np);
return 0;
}
+
+IRQCHIP_DECLARE(ti_irqcrossbar, "ti,irq-crossbar", irqcrossbar_init);