]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/irqchip/irq-crossbar.c
Merge tag 'dt-for-3.17' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
[karo-tx-linux.git] / drivers / irqchip / irq-crossbar.c
index 51d4b878e5d62e8308018cf5301ad81668be0ebf..85c2985d8bcb5040aeae9c343cacc1352fafe125 100644 (file)
 #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;
@@ -55,7 +63,7 @@ static inline int get_prev_map_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] == cb_no)
                        return i;
 
@@ -66,7 +74,7 @@ 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;
@@ -76,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,
@@ -97,15 +133,33 @@ 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;
+                       }
+               }
 
-       ret = get_prev_map_irq(intspec[1]);
-       if (!IS_ERR_VALUE(ret))
+               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(intspec[1]);
+       ret = allocate_free_irq(req_num);
 
-       if (IS_ERR_VALUE(ret))
+       if (ret < 0)
                return ret;
 
 found:
@@ -113,7 +167,7 @@ found:
        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
@@ -121,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;
 
@@ -154,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;
                }
        }
 
-       cb->register_offsets = kzalloc(max * sizeof(int), GFP_KERNEL);
+       /* 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] = IRQ_SKIP;
+               }
+       }
+
+
+       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);
 
@@ -178,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;
        }
 
@@ -194,18 +283,30 @@ static int __init crossbar_of_init(struct device_node *node)
                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 = {