[RFC PATCH v1] irqchip: add support for SMP irq router

From: Sebastian Frias
Date: Thu Jun 30 2016 - 12:11:26 EST


This adds support for a second-gen irq router/controller present
on some Sigma Designs chips.

Signed-off-by: Sebastian Frias <sf84@xxxxxxxxxxx>
---

This is a RFC because I have a few doubts:
1) I had to unroll irq_of_parse_and_map() in order to get the HW
IRQ declared in the device tree so that I can associate it with
a given domain.

2) I'm not sure about the DT specification, in particular, the use
of child nodes to declare different domains, but it works

3) I'm calling this an irq router to somehow highlight the fact
that it is not a simple interrupt controller. Indeed it does
not latch the IRQ lines by itself, and does not supports edge
detection.

4) Do I have to do something more to handle the affinity stuff?

5) checkpatch.pl reports warnings, etc. but I guess it's ok for
now since it is a RFC :-)

Please feel free to comment and suggest improvements.
---
.../sigma,smp87xx-irqrouter.txt | 69 +++
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-tango_v2.c | 594 +++++++++++++++++++++
3 files changed, 664 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/sigma,smp87xx-irqrouter.txt
create mode 100644 drivers/irqchip/irq-tango_v2.c

diff --git a/Documentation/devicetree/bindings/interrupt-controller/sigma,smp87xx-irqrouter.txt b/Documentation/devicetree/bindings/interrupt-controller/sigma,smp87xx-irqrouter.txt
new file mode 100644
index 0000000..0e404f0
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/sigma,smp87xx-irqrouter.txt
@@ -0,0 +1,69 @@
+* Sigma Designs Interrupt Router
+
+This module can route N IRQ inputs into M IRQ outputs, with N>M.
+For instance N=128, M=24.
+
+Note however that the HW does not latches the IRQ lines, so devices
+connecting to the router are expected to latch their IRQ line by themselves.
+
+A single node in the device tree is used to describe the interrupt router.
+Child nodes (up to a maximum of 'outputs') describe irqdomains for the outputs
+of the interrupt router.
+These child nodes specify, via their 'interrupts' property, how the
+interrupt router is connected to its parent interrupt controller (usually the
+GIC), and define irqdomains that can be used in other nodes' 'interrupts'
+property.
+
+Required properties:
+- compatible: Should be "sigma,smp87xx-irqrouter".
+- interrupt-controller: Identifies the node as an interrupt controller.
+- inputs: The number of IRQ lines entering the router
+- outputs: The number of IRQ lines exiting the router
+- reg: Base address and size of interrupt router registers.
+- #interrupt-cells: Should be <2>. Defines how other nodes will be able to
+interact with this node. The meaning of the cells are
+ * First Cell: HW IRQ number.
+ * Second Cell: IRQ polarity (level high or low).
+
+Required properties of child nodes:
+- interrupt-controller: Identifies the node as an interrupt controller.
+- interrupts: Defines the hwirq associated with a domain and connected to
+the parent interrupt controller. The format of the interrupt specifier
+depends on the interrupt parent controller.
+
+Optional properties:
+- interrupt-parent: pHandle of the parent interrupt controller, if not
+ inherited from the parent node.
+
+
+Example:
+
+See Documentation/devicetree/bindings/interrupt-controller/interrupts.txt and
+Documentation/devicetree/bindings/arm/gic.txt for further details.
+
+The following example declares a irqrouter with 128 inputs and 24 outputs,
+with registers @ 0x6F800 and connected to the GIC.
+The two child nodes define two irqdomains, one connected to GIC input 2
+(hwirq=2, level=high), and ther other connected to GIC input 3 (hwirq=3,
+level=low)
+
+ irq_router: irq_router@6f800 {
+ compatible = "sigma,smp87xx-irqrouter";
+ reg = <0x6f800 0x800>;
+ interrupt-controller;
+ interrupt-parent = <&gic>;
+ inputs = <128>;
+ outputs = <24>;
+
+ irq0: irqdomain0@parentirq2 {
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ irq1: irqdomain1@parentirq3 {
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_LOW>;
+ };
+ };
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 7451245..703a2b5 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_ARCH_NSPIRE) += irq-zevio.o
obj-$(CONFIG_ARCH_VT8500) += irq-vt8500.o
obj-$(CONFIG_ST_IRQCHIP) += irq-st.o
obj-$(CONFIG_TANGO_IRQ) += irq-tango.o
+obj-$(CONFIG_TANGO_IRQ) += irq-tango_v2.o
obj-$(CONFIG_TB10X_IRQC) += irq-tb10x.o
obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o
obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
diff --git a/drivers/irqchip/irq-tango_v2.c b/drivers/irqchip/irq-tango_v2.c
new file mode 100644
index 0000000..f6cf747
--- /dev/null
+++ b/drivers/irqchip/irq-tango_v2.c
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) 2014 Sebastian Frias <sf84@xxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+
+
+#define DBGERR(__format, ...) \
+ do { \
+ pr_err("[%s:%d] %s(): " __format, __FILE__, __LINE__, __FUNCTION__ , ##__VA_ARGS__); \
+ } while (0)
+
+#if 0
+#define DBGLOG(__format, ...) \
+ do { \
+ pr_info("[%s:%d] %s(): " __format, __FILE__, __LINE__, __FUNCTION__ , ##__VA_ARGS__); \
+ } while (0)
+#else
+#define DBGLOG(__format, ...) do {} while(0)
+#endif
+
+
+/*
+ HW description: IRQ router
+
+ IMPORTANT NOTE: this hw block is not a "full" interrupt controller
+ - it does not support edge detection
+ - it does not latch the inputs (devices are expected to latch their
+ IRQ output by themselves)
+
+ ---
+
+ CPU block interrupt interface is now 32bits.
+ The 24 first interrupt bits are generated from the system interrupts and the 8 msb interrupts are cpu local interrupts :
+
+ IRQs [23:0] tango system irqs.
+ IRQs [27:24] CPU core cross trigger interface interrupt (1 per core).
+ IRQs [31:28] CPU core PMU (performance unit) interrupt (1 per core).
+
+ The 24 lsb interrupts are generated through a new interrupt map module that maps the tango 128 interrupts to those 24 interrupts.
+ For each of the 128 input system interrupt, one register is dedicated to program the destination interrupt among the 24 available.
+ The mapper is configured as follows, starting at address (0x6f800) :
+
+ offset name description
+ 0x000 irq_in_0_cfg "en"=bit[31]; "inv"=bit[16]; "dest"=bits[4:0]
+ 0x004 irq_in_1_cfg "en"=bit[31]; "inv"=bit[16]; "dest"=bits[4:0]
+ .
+ .
+ .
+ 0x1FC irq_in_127_cfg "en"=bit[31]; "inv"=bit[16]; "dest"=bits[4:0]
+ 0x400 soft_irq_cfg "enable"=bits[15:0]
+ 0x404 soft_irq_map0 "map3"=bits[28:24]; "map2"=bits[20:16]; "map1"=bits[12:8]; "map0"=bits[4:0]
+ 0x408 soft_irq_map1 "map3"=bits[28:24]; "map2"=bits[20:16]; "map1"=bits[12:8]; "map0"=bits[4:0]
+ 0x40C soft_irq_map2 "map3"=bits[28:24]; "map2"=bits[20:16]; "map1"=bits[12:8]; "map0"=bits[4:0]
+ 0x410 soft_irq_map3 "map3"=bits[28:24]; "map2"=bits[20:16]; "map1"=bits[12:8]; "map0"=bits[4:0]
+ 0x414 soft_irq_set "set"=bits[15:0]
+ 0x418 soft_irq_clear "clear"=bits[15:0]
+ 0x41C read_cpu_irq "cpu_block_irq"=bits[23:0]
+ 0x420 read_sys_irq0 "system_irq"=bits[31:0]; (irqs: 0->31)
+ 0x424 read_sys_irq1 "system_irq"=bits[31:0]; (irqs: 32->63)
+ 0x428 read_sys_irq2 "system_irq"=bits[31:0]; (irqs: 64->95)
+ 0x42C read_sys_irq3 "system_irq"=bits[31:0]; (irqs: 96->127)
+
+ irq_in_N_cfg : input N mapping :
+ - dest bits[4:0] => set destination interrupt among the 24 output interrupts. (if multiple inputs are mapped to the same output, result is an OR of the inputs).
+ - inv bit[16] => if set, inverts input interrupt polarity (active at 0).
+ - en bit[31] => enable interrupt. Acts like a mask on the input interrupt.
+ soft_irq : this module supports up to 16 software interrupts.
+ - enable bits[15:0] => enable usage of software IRQs (SIRQ), 1 bit per SIRQ.
+ soft_irq_mapN : For each of the 16 soft IRQ (SIRQ), map them in out IRQ[23:0] vector.
+ - mapN => 5 bits to select where to connect the SIRQ among the 23 bits output IRQ. (if multiple SIRQ are mapped to the same output IRQ, result is an OR of those signals).
+ soft_irq_set : 16bits, write 1 bit at one set the corresponding SIRQ. Read returns the software SIRQ vector value.
+ soft_irq_clear : 16bits, write 1 bit at one clear the corresponding software SIRQ. Read returns the software SIRQ vector value.
+ read_cpu_irq : 24bits, returns output IRQ value (IRQs connected to the ARM cluster).
+ read_sys_irqN : 32bits, returns input system IRQ value before mapping.
+*/
+
+#define ROUTER_INPUTS (128)
+#define ROUTER_OUTPUTS (24)
+
+#define IRQ_ROUTER_ENABLE_MASK (BIT(31))
+#define IRQ_ROUTER_INVERT_MASK (BIT(16))
+
+#define READ_SYS_IRQ_GROUP0 (0x420)
+#define READ_SYS_IRQ_GROUP1 (0x424)
+#define READ_SYS_IRQ_GROUP2 (0x428)
+#define READ_SYS_IRQ_GROUP3 (0x42C)
+
+
+#if 0
+#define SHORT_OR_FULL_NAME full_name
+#else
+#define SHORT_OR_FULL_NAME name
+#endif
+
+#define NODE_NAME(__node__) (__node__ ? __node__->SHORT_OR_FULL_NAME : "<no-node>")
+
+#define BITMASK_VECTOR_SIZE(__count__) (__count__ / 32)
+#define IRQ_TO_OFFSET(__hwirq__) (__hwirq__ * 4)
+
+struct tango_irqrouter;
+
+/*
+ maintains the mapping between a Linux virq and a hwirq
+ on the parent controller.
+ It is used by tango_irqdomain_map() to setup the route
+ between input IRQ and output IRQ
+*/
+struct tango_irqrouter_output {
+ struct tango_irqrouter *context;
+ u32 hwirq;
+ u32 hwirq_level;
+ u32 virq;
+};
+
+/*
+ context for the driver
+*/
+struct tango_irqrouter {
+ raw_spinlock_t lock;
+ struct device_node *node;
+ void __iomem *base;
+ u32 input_count;
+ u32 output_count;
+ u32 irq_mask[BITMASK_VECTOR_SIZE(ROUTER_INPUTS)];
+ u32 irq_invert_mask[BITMASK_VECTOR_SIZE(ROUTER_INPUTS)];
+ struct tango_irqrouter_output output[ROUTER_OUTPUTS];
+};
+
+
+static inline u32 tango_readl(struct tango_irqrouter *irqrouter, int reg)
+{
+ u32 val = readl_relaxed(irqrouter->base + reg);
+ //DBGLOG("r[0x%08x + 0x%08x = 0x%08x] = 0x%08x\n", irqrouter->base, reg, irqrouter->base + reg, val);
+ return val;
+}
+
+static inline void tango_writel(struct tango_irqrouter *irqrouter, int reg, u32 val)
+{
+ //DBGLOG("w[0x%08x + 0x%08x = 0x%08x] = 0x%08x\n", irqrouter->base, reg, irqrouter->base + reg, val);
+ writel_relaxed(val, irqrouter->base + reg);
+}
+
+static inline void tango_setup_irq_route(struct tango_irqrouter *irqrouter, int irq_in, int irq_out)
+{
+ u32 offset = IRQ_TO_OFFSET(irq_in);
+ u32 value = irq_out;
+
+ DBGLOG("route hwirq(in) %d => hwirq(out) %d\n", irq_in, value);
+
+ if (value)
+ value &= ~(IRQ_ROUTER_ENABLE_MASK | IRQ_ROUTER_INVERT_MASK);
+
+ tango_writel(irqrouter, offset, value);
+}
+
+
+static inline void tango_setup_irq_inversion(struct tango_irqrouter *irqrouter, int hwirq, bool invert)
+{
+ u32 offset = IRQ_TO_OFFSET(hwirq);
+ u32 value = tango_readl(irqrouter, offset);
+ u32 hwirq_reg_index = hwirq / 32;
+ u32 hwirq_bit_index = hwirq % 32;
+
+ if (invert) {
+ irqrouter->irq_invert_mask[hwirq_reg_index] |= (1 << hwirq_bit_index);
+ value |= IRQ_ROUTER_INVERT_MASK;
+ }
+ else {
+ irqrouter->irq_invert_mask[hwirq_reg_index] &= ~(1 << hwirq_bit_index);
+ value &= ~(IRQ_ROUTER_INVERT_MASK);
+ }
+
+ DBGLOG("hwirq(in) %d %s inverted\n", hwirq, invert ? "":"not");
+
+ tango_writel(irqrouter, offset, value);
+}
+
+static void tango_irqchip_mask_irq(struct irq_data *data)
+{
+ struct irq_domain *domain = irq_data_get_irq_chip_data(data);
+ struct tango_irqrouter_output *irqrouter_output = domain->host_data;
+ struct tango_irqrouter *irqrouter = irqrouter_output->context;
+ u32 hwirq = data->hwirq;
+ u32 offset = IRQ_TO_OFFSET(hwirq);
+ u32 value = tango_readl(irqrouter, offset);
+ u32 hwirq_reg_index = hwirq / 32;
+ u32 hwirq_bit_index = hwirq % 32;
+
+ DBGLOG("%s: mask hwirq(in) %d : current regvalue 0x%x (routed to hwirq(out) %d)\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ hwirq, value, irqrouter_output->hwirq);
+
+ //mask cache
+ irqrouter->irq_mask[hwirq_reg_index] &= ~(1 << hwirq_bit_index);
+
+ value &= ~(IRQ_ROUTER_ENABLE_MASK);
+ tango_writel(irqrouter, offset, value);
+}
+
+static void tango_irqchip_unmask_irq(struct irq_data *data)
+{
+ struct irq_domain *domain = irq_data_get_irq_chip_data(data);
+ struct tango_irqrouter_output *irqrouter_output = domain->host_data;
+ struct tango_irqrouter *irqrouter = irqrouter_output->context;
+ u32 hwirq = data->hwirq;
+ u32 offset = IRQ_TO_OFFSET(hwirq);
+ u32 value = tango_readl(irqrouter, offset);
+ u32 hwirq_reg_index = hwirq / 32;
+ u32 hwirq_bit_index = hwirq % 32;
+
+ DBGLOG("%s: unmask hwirq(in) %d : current regvalue 0x%x (routed to hwirq(out) %d)\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ hwirq, value, irqrouter_output->hwirq);
+
+ //unmask cache
+ irqrouter->irq_mask[hwirq_reg_index] |= (1 << hwirq_bit_index);
+
+ value |= IRQ_ROUTER_ENABLE_MASK;
+ tango_writel(irqrouter, offset, value);
+}
+
+static int tango_irqchip_set_irq_type(struct irq_data *data, unsigned int type)
+{
+ struct irq_domain *domain = irq_data_get_irq_chip_data(data);
+ struct tango_irqrouter_output *irqrouter_output = domain->host_data;
+ struct tango_irqrouter *irqrouter = irqrouter_output->context;
+ unsigned int hwirq = data->hwirq;
+ unsigned int parent_type = (irqrouter_output->hwirq_level & IRQ_TYPE_SENSE_MASK);
+
+ DBGLOG("%s: type 0x%x for hwirq(in) %d = virq %d (routed to hwirq(out) %d)\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ type, hwirq, data->irq,
+ irqrouter_output->hwirq);
+
+ if (parent_type & (type & IRQ_TYPE_SENSE_MASK))
+ //same polarity
+ tango_setup_irq_inversion(irqrouter, hwirq, 0);
+ else
+ //invert polarity
+ tango_setup_irq_inversion(irqrouter, hwirq, 1);
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ panic("%s: does not support edge triggers\n",
+ NODE_NAME(irq_domain_get_of_node(domain)));
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ break;
+ default:
+ pr_err("%s: invalid trigger mode 0x%x for hwirq %d = virq %d\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ type, hwirq, data->irq);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+#ifdef CONFIG_SMP
+static int tango_irqchip_set_irq_affinity(struct irq_data *data,
+ const struct cpumask *mask_val,
+ bool force)
+{
+ struct irq_domain *domain = irq_data_get_irq_chip_data(data);
+ struct tango_irqrouter_output *irqrouter_output = domain->host_data;
+ struct irq_chip *parent_chip = irq_get_chip(irqrouter_output->virq);
+ struct irq_data *parent_data = irq_get_irq_data(irqrouter_output->virq);
+
+ DBGLOG("%s:\n", NODE_NAME(irq_domain_get_of_node(domain)));
+
+ if (parent_chip && parent_chip->irq_set_affinity)
+ return parent_chip->irq_set_affinity(parent_data, mask_val, force);
+ else
+ return -EINVAL;
+}
+#endif
+
+static struct irq_chip tango_irq_chip_ops = {
+ .name = "ROUTER",
+ .irq_mask = tango_irqchip_mask_irq,
+ .irq_unmask = tango_irqchip_unmask_irq,
+ .irq_set_type = tango_irqchip_set_irq_type,
+#ifdef CONFIG_SMP
+ .irq_set_affinity = tango_irqchip_set_irq_affinity,
+#endif
+};
+
+
+/**
+ * @hwirq: HW IRQ of the device requesting an IRQ
+ * @virq: Linux IRQ (associated to the domain) to be given to the device
+ * @domain: IRQ domain (from the domain, we get the irqrouter_output
+ * in order to know to which output we need to route hwirq to)
+ */
+static int tango_irqdomain_map(struct irq_domain *domain,
+ unsigned int virq,
+ irq_hw_number_t hwirq)
+{
+ struct tango_irqrouter_output *irqrouter_output = domain->host_data;
+ struct tango_irqrouter *irqrouter = irqrouter_output->context;
+
+ DBGLOG("%s: hwirq(in) %d := virq %d, and route hwirq(in) %d => hwirq(out) %d (virq %d)\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ (u32)hwirq, virq,
+ (u32)hwirq, irqrouter_output->hwirq, irqrouter_output->virq);
+
+ if (hwirq >= irqrouter->input_count)
+ panic("%s: invalid hwirq(in) %d >= %d\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ (u32)hwirq,
+ irqrouter->input_count);
+
+ irq_set_chip_and_handler(virq, &tango_irq_chip_ops, handle_level_irq);
+ irq_set_chip_data(virq, domain);
+ irq_set_probe(virq);
+
+ tango_setup_irq_route(irqrouter, hwirq, irqrouter_output->hwirq);
+
+ return 0;
+}
+
+static int tango_irqdomain_xlate(struct irq_domain *domain,
+ struct device_node *controller,
+ const u32 *intspec,
+ unsigned int intsize,
+ unsigned long *out_hwirq,
+ unsigned int *out_type)
+
+{
+ DBGLOG("%s: ctrl 0x%p, intspec 0x%x, intsize %d\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ controller, (u32)intspec, intsize);
+
+ if (irq_domain_get_of_node(domain) != controller)
+ return -EINVAL;
+
+ if (intsize < 2)
+ return -EINVAL;
+
+ DBGLOG("[0] 0x%x [1] 0x%x\n", intspec[0], intspec[1]);
+
+ *out_hwirq = intspec[0];
+ *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;
+
+ DBGLOG("hwirq %d type 0x%x\n", (u32)*out_hwirq, (u32)*out_type);
+
+ return 0;
+}
+
+static u32 tango_dispatch_irqs(struct irq_domain *domain,
+ struct irq_desc *desc,
+ u32 status,
+ int base)
+{
+ u32 hwirq;
+ u32 virq;
+
+ while (status) {
+ hwirq = __ffs(status);
+ virq = irq_find_mapping(domain, base + hwirq);
+ if (unlikely(!virq))
+ handle_bad_irq(desc);
+ else
+ generic_handle_irq(virq);
+
+ status &= ~BIT(hwirq);
+ }
+
+ return status;
+}
+
+
+static void tango_irqdomain_handle_cascade_irq(struct irq_desc *desc)
+{
+ struct irq_domain *domain = irq_desc_get_handler_data(desc);
+ struct tango_irqrouter_output *irqrouter_output = domain->host_data;
+ struct tango_irqrouter *irqrouter = irqrouter_output->context;
+ struct irq_chip *host_chip = irq_desc_get_chip(desc);
+ u32 status, status_0, status_1, status_2, status_3;
+
+ DBGLOG("%s: irqrouter_output 0x%p, hwirq(out) %d\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ irqrouter_output, irqrouter_output->hwirq);
+
+ chained_irq_enter(host_chip, desc);
+
+ raw_spin_lock(&(irqrouter->lock));
+ status_0 = tango_readl(irqrouter, READ_SYS_IRQ_GROUP0); //irqs 0 ->31
+ status_1 = tango_readl(irqrouter, READ_SYS_IRQ_GROUP1); //irqs 32->63
+ status_2 = tango_readl(irqrouter, READ_SYS_IRQ_GROUP2); //irqs 64->95
+ status_3 = tango_readl(irqrouter, READ_SYS_IRQ_GROUP3); //irqs 96->127
+ raw_spin_unlock(&(irqrouter->lock));
+
+ DBGLOG("0: 0x%08x (en 0x%08x, inv 0x%08x), 1: 0x%08x (en 0x%08x, inv 0x%08x), "
+ "2: 0x%08x (en 0x%08x, inv 0x%08x), 3: 0x%08x (en 0x%08x, inv 0x%08x)\n",
+ status_0, irqrouter->irq_mask[0], irqrouter->irq_invert_mask[0],
+ status_1, irqrouter->irq_mask[1], irqrouter->irq_invert_mask[1],
+ status_2, irqrouter->irq_mask[2], irqrouter->irq_invert_mask[2],
+ status_3, irqrouter->irq_mask[3], irqrouter->irq_invert_mask[3]);
+
+#define HANDLE_INVERTED_LINES(__irqstatus__, __x__) ((((~__irqstatus__) & irqrouter->irq_invert_mask[__x__]) & irqrouter->irq_mask[__x__]) | __irqstatus__)
+#define HANDLE_ENABLE_AND_INVERSION_MASKS(__irqstatus__, __y__) (HANDLE_INVERTED_LINES(__irqstatus__, __y__) & irqrouter->irq_mask[__y__])
+
+ //handle inverted irq lines
+ status_0 = HANDLE_ENABLE_AND_INVERSION_MASKS(status_0, 0);
+ status_1 = HANDLE_ENABLE_AND_INVERSION_MASKS(status_1, 1);
+ status_2 = HANDLE_ENABLE_AND_INVERSION_MASKS(status_2, 2);
+ status_3 = HANDLE_ENABLE_AND_INVERSION_MASKS(status_3, 3);
+
+ status = tango_dispatch_irqs(domain, desc, status_0, 0);
+ if (status & status_0)
+ DBGERR("%s: unhandled IRQs (as a mask) 0x%x\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ status & status_0);
+ status = tango_dispatch_irqs(domain, desc, status_1, 32);
+ if (status & status_1)
+ DBGERR("%s: unhandled IRQs (as a mask) 0x%x\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ status & status_1);
+ status = tango_dispatch_irqs(domain, desc, status_2, 64);
+ if (status & status_2)
+ DBGERR("%s: unhandled IRQs (as a mask) 0x%x\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ status & status_2);
+ status = tango_dispatch_irqs(domain, desc, status_3, 96);
+ if (status & status_3)
+ DBGERR("%s: unhandled IRQs (as a mask) 0x%x\n",
+ NODE_NAME(irq_domain_get_of_node(domain)),
+ status & status_3);
+
+ chained_irq_exit(host_chip, desc);
+}
+
+static void of_phandle_args_to_fwspec(struct of_phandle_args *irq_data,
+ struct irq_fwspec *fwspec)
+{
+ int i;
+
+ fwspec->fwnode = irq_data->np ? &irq_data->np->fwnode : NULL;
+ fwspec->param_count = irq_data->args_count;
+
+ for (i = 0; i < irq_data->args_count; i++)
+ fwspec->param[i] = irq_data->args[i];
+}
+
+struct irq_domain_ops tango_irqdomain_ops = {
+ .xlate = tango_irqdomain_xlate,
+ .map = tango_irqdomain_map,
+};
+
+static int __init tango_irq_init_domain(struct tango_irqrouter *irqrouter,
+ u32 index,
+ struct device_node *node)
+{
+ struct irq_domain *domain;
+ struct of_phandle_args of_irq;
+ struct irq_fwspec fwspec_irq;
+ u32 virq, hwirq, hwirq_level, err;
+
+ if (index >= irqrouter->output_count)
+ panic("%s: too many output IRQs\n", node->name);
+
+ //parse a node and associate its hwirq to a virq
+/*
+ unroll irq_of_parse_and_map() begin
+*/
+ err = of_irq_parse_one(node, 0, &of_irq);
+ if (err)
+ panic("%s: failed to get IRQ (%d)", node->name, err);
+
+ of_phandle_args_to_fwspec(&of_irq, &fwspec_irq);
+
+ hwirq = fwspec_irq.param[1];
+ hwirq_level = fwspec_irq.param[2] & IRQ_TYPE_SENSE_MASK;
+
+ if (hwirq >= irqrouter->output_count)
+ panic("%s: invalid hwirq(out) %d >= %d\n", node->name,
+ hwirq,
+ irqrouter->output_count);
+
+ //request a virq for the hwirq
+ virq = irq_create_fwspec_mapping(&fwspec_irq);
+ if (!virq)
+ panic("%s: failed to get virq for hwirq(out) %d", node->name, hwirq);
+/*
+ unroll irq_of_parse_and_map() end
+*/
+
+ irqrouter->output[index].context = irqrouter;
+ irqrouter->output[index].hwirq = hwirq;
+ irqrouter->output[index].hwirq_level = hwirq_level;
+ irqrouter->output[index].virq = virq;
+
+ //create a domain for this virq
+ domain = irq_domain_add_linear(node,
+ irqrouter->input_count,
+ &tango_irqdomain_ops,
+ &(irqrouter->output[index]));
+ if (!domain)
+ panic("%s: failed to create irqdomain", node->name);
+
+ DBGLOG("%s: [%d] domain 0x%p for irqrouter_output 0x%p : "
+ "hwirq(out) %d = virq %d\n",
+ node->full_name, index, domain,
+ &(irqrouter->output[index]),
+ hwirq, virq);
+
+ //associate the domain with the virq
+ irq_set_chained_handler_and_data(virq,
+ tango_irqdomain_handle_cascade_irq,
+ domain);
+
+ return 0;
+}
+
+static int __init tango_of_irq_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct tango_irqrouter *irqrouter;
+ struct device_node *child;
+ void __iomem *base;
+ u32 input_count, output_count, i;
+
+ base = of_iomap(node, 0);
+ if (!base) {
+ DBGERR("%s: failed to map combiner registers\n", node->name);
+ return -ENXIO;
+ }
+
+ of_property_read_u32(node, "inputs", &input_count);
+ if (!input_count) {
+ DBGERR("%s: missing 'inputs' property\n", node->name);
+ return -EINVAL;
+ }
+
+ of_property_read_u32(node, "outputs", &output_count);
+ if (!output_count) {
+ DBGERR("%s: missing 'outputs' property\n", node->name);
+ return -EINVAL;
+ }
+
+ if ((input_count != ROUTER_INPUTS)
+ || (output_count != ROUTER_OUTPUTS)) {
+ DBGERR("%s: input/output count mismatch", node->name);
+ return -EINVAL;
+ }
+
+ irqrouter = kzalloc(sizeof(*irqrouter), GFP_KERNEL);
+ raw_spin_lock_init(&(irqrouter->lock));
+ irqrouter->node = node;
+ irqrouter->base = base;
+ irqrouter->input_count = input_count;
+ irqrouter->output_count = output_count;
+ pr_info("%s: base 0x%p, %d => %d router, parent %s\n",
+ node->full_name, base,
+ input_count, output_count,
+ parent->full_name);
+
+ i = 0;
+ for_each_child_of_node(node, child) {
+ tango_irq_init_domain(irqrouter, i, child);
+ i++;
+ }
+
+ /*
+ clear backward compatible map used by previous generation
+ irq controller ("sigma,smp8642-intc")
+ */
+ tango_setup_irq_route(irqrouter, 125, 0);
+ tango_setup_irq_route(irqrouter, 126, 0);
+ tango_setup_irq_route(irqrouter, 127, 0);
+
+ return 0;
+}
+
+IRQCHIP_DECLARE(tango_irqrouter, "sigma,smp87xx-irqrouter", tango_of_irq_init);
--
1.7.11.2