MIPS: OCTEON: Add support for OCTEON III interrupt controller.
authorDavid Daney <david.daney@cavium.com>
Tue, 9 Feb 2016 19:00:11 +0000 (11:00 -0800)
committerRalf Baechle <ralf@linux-mips.org>
Fri, 13 May 2016 12:01:41 +0000 (14:01 +0200)
Add irq_chip support for both IPI and "normal" interrupts of the CIU3
controller.  Document the device tree binding for the CIU3.

Some functions are non-static as they will be used by follow-on
support for MSI-X.

Signed-off-by: David Daney <david.daney@cavium.com>
Acked-by: Rob Herring <robh@kernel.org>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Ian Campbell <ijc+devicetree@hellion.org.uk>
Cc: Kumar Gala <galak@codeaurora.org>
Cc: devicetree@vger.kernel.org
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: linux-mips@linux-mips.org
Cc: linux-kernel@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/12500/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Documentation/devicetree/bindings/mips/cavium/ciu3.txt [new file with mode: 0644]
arch/mips/cavium-octeon/octeon-irq.c
arch/mips/include/asm/octeon/octeon.h

diff --git a/Documentation/devicetree/bindings/mips/cavium/ciu3.txt b/Documentation/devicetree/bindings/mips/cavium/ciu3.txt
new file mode 100644 (file)
index 0000000..616862a
--- /dev/null
@@ -0,0 +1,27 @@
+* Central Interrupt Unit v3
+
+Properties:
+- compatible: "cavium,octeon-7890-ciu3"
+
+  Compatibility with 78XX and 73XX SOCs.
+
+- interrupt-controller:  This is an interrupt controller.
+
+- reg: The base address of the CIU's register bank.
+
+- #interrupt-cells: Must be <2>.  The first cell is source number.
+  The second cell indicates the triggering semantics, and may have a
+  value of either 4 for level semantics, or 1 for edge semantics.
+
+Example:
+       interrupt-controller@1010000000000 {
+               compatible = "cavium,octeon-7890-ciu3";
+               interrupt-controller;
+               /* Interrupts are specified by two parts:
+                * 1) Source number (20 significant bits)
+                * 2) Trigger type: (4 == level, 1 == edge)
+                */
+               #address-cells = <0>;
+               #interrupt-cells = <2>;
+               reg = <0x10100 0x00000000 0x0 0xb0000000>;
+       };
index bc30d3a..9b6a65b 100644 (file)
 
 #include <asm/octeon/octeon.h>
 #include <asm/octeon/cvmx-ciu2-defs.h>
+#include <asm/octeon/cvmx-ciu3-defs.h>
 
 static DEFINE_PER_CPU(unsigned long, octeon_irq_ciu0_en_mirror);
 static DEFINE_PER_CPU(unsigned long, octeon_irq_ciu1_en_mirror);
 static DEFINE_PER_CPU(raw_spinlock_t, octeon_irq_ciu_spinlock);
+static DEFINE_PER_CPU(unsigned int, octeon_irq_ciu3_idt_ip2);
+
+static DEFINE_PER_CPU(unsigned int, octeon_irq_ciu3_idt_ip3);
+static DEFINE_PER_CPU(struct octeon_ciu3_info *, octeon_ciu3_info);
+#define CIU3_MBOX_PER_CORE 10
+
+/*
+ * The 8 most significant bits of the intsn identify the interrupt major block.
+ * Each major block might use its own interrupt domain. Thus 256 domains are
+ * needed.
+ */
+#define MAX_CIU3_DOMAINS               256
+
+typedef irq_hw_number_t (*octeon_ciu3_intsn2hw_t)(struct irq_domain *, unsigned int);
+
+/* Information for each ciu3 in the system */
+struct octeon_ciu3_info {
+       u64                     ciu3_addr;
+       int                     node;
+       struct irq_domain       *domain[MAX_CIU3_DOMAINS];
+       octeon_ciu3_intsn2hw_t  intsn2hw[MAX_CIU3_DOMAINS];
+};
+
+/* Each ciu3 in the system uses its own data (one ciu3 per node) */
+static struct octeon_ciu3_info *octeon_ciu3_info_per_node[4];
 
 struct octeon_irq_ciu_domain_data {
        int num_sum;  /* number of sum registers (2 or 3). */
 };
 
+/* Register offsets from ciu3_addr */
+#define CIU3_CONST             0x220
+#define CIU3_IDT_CTL(_idt)     ((_idt) * 8 + 0x110000)
+#define CIU3_IDT_PP(_idt, _idx)        ((_idt) * 32 + (_idx) * 8 + 0x120000)
+#define CIU3_IDT_IO(_idt)      ((_idt) * 8 + 0x130000)
+#define CIU3_DEST_PP_INT(_pp_ip) ((_pp_ip) * 8 + 0x200000)
+#define CIU3_DEST_IO_INT(_io)  ((_io) * 8 + 0x210000)
+#define CIU3_ISC_CTL(_intsn)   ((_intsn) * 8 + 0x80000000)
+#define CIU3_ISC_W1C(_intsn)   ((_intsn) * 8 + 0x90000000)
+#define CIU3_ISC_W1S(_intsn)   ((_intsn) * 8 + 0xa0000000)
+
 static __read_mostly int octeon_irq_ciu_to_irq[8][64];
 
 struct octeon_ciu_chip_data {
@@ -39,10 +76,11 @@ struct octeon_ciu_chip_data {
                struct {                /* only used for ciu/ciu2 */
                        u8 line;
                        u8 bit;
-                       u8 gpio_line;
                };
        };
+       int gpio_line;
        int current_cpu;        /* Next CPU expected to take this irq */
+       int ciu_node; /* NUMA node number of the CIU */
 };
 
 struct octeon_core_chip_data {
@@ -626,6 +664,18 @@ static void octeon_irq_ciu_enable_all_v2(struct irq_data *data)
        }
 }
 
+static int octeon_irq_ciu_set_type(struct irq_data *data, unsigned int t)
+{
+       irqd_set_trigger_type(data, t);
+
+       if (t & IRQ_TYPE_EDGE_BOTH)
+               irq_set_handler_locked(data, handle_edge_irq);
+       else
+               irq_set_handler_locked(data, handle_level_irq);
+
+       return IRQ_SET_MASK_OK;
+}
+
 static void octeon_irq_gpio_setup(struct irq_data *data)
 {
        union cvmx_gpio_bit_cfgx cfg;
@@ -863,6 +913,16 @@ static int octeon_irq_ciu_set_affinity_sum2(struct irq_data *data,
 }
 #endif
 
+static unsigned int edge_startup(struct irq_data *data)
+{
+       /* ack any pending edge-irq at startup, so there is
+        * an _edge_ to fire on when the event reappears.
+        */
+       data->chip->irq_ack(data);
+       data->chip->irq_enable(data);
+       return 0;
+}
+
 /*
  * Newer octeon chips have support for lockless CIU operation.
  */
@@ -2271,10 +2331,598 @@ static int __init octeon_irq_init_cib(struct device_node *ciu_node,
        return 0;
 }
 
+int octeon_irq_ciu3_xlat(struct irq_domain *d,
+                        struct device_node *node,
+                        const u32 *intspec,
+                        unsigned int intsize,
+                        unsigned long *out_hwirq,
+                        unsigned int *out_type)
+{
+       struct octeon_ciu3_info *ciu3_info = d->host_data;
+       unsigned int hwirq, type, intsn_major;
+       union cvmx_ciu3_iscx_ctl isc;
+
+       if (intsize < 2)
+               return -EINVAL;
+       hwirq = intspec[0];
+       type = intspec[1];
+
+       if (hwirq >= (1 << 20))
+               return -EINVAL;
+
+       intsn_major = hwirq >> 12;
+       switch (intsn_major) {
+       case 0x04: /* Software handled separately. */
+               return -EINVAL;
+       default:
+               break;
+       }
+
+       isc.u64 =  cvmx_read_csr(ciu3_info->ciu3_addr + CIU3_ISC_CTL(hwirq));
+       if (!isc.s.imp)
+               return -EINVAL;
+
+       switch (type) {
+       case 4: /* official value for level triggering. */
+               *out_type = IRQ_TYPE_LEVEL_HIGH;
+               break;
+       case 0: /* unofficial value, but we might as well let it work. */
+       case 1: /* official value for edge triggering. */
+               *out_type = IRQ_TYPE_EDGE_RISING;
+               break;
+       default: /* Nothing else is acceptable. */
+               return -EINVAL;
+       }
+
+       *out_hwirq = hwirq;
+
+       return 0;
+}
+
+void octeon_irq_ciu3_enable(struct irq_data *data)
+{
+       int cpu;
+       union cvmx_ciu3_iscx_ctl isc_ctl;
+       union cvmx_ciu3_iscx_w1c isc_w1c;
+       u64 isc_ctl_addr;
+
+       struct octeon_ciu_chip_data *cd;
+
+       cpu = next_cpu_for_irq(data);
+
+       cd = irq_data_get_irq_chip_data(data);
+
+       isc_w1c.u64 = 0;
+       isc_w1c.s.en = 1;
+       cvmx_write_csr(cd->ciu3_addr + CIU3_ISC_W1C(cd->intsn), isc_w1c.u64);
+
+       isc_ctl_addr = cd->ciu3_addr + CIU3_ISC_CTL(cd->intsn);
+       isc_ctl.u64 = 0;
+       isc_ctl.s.en = 1;
+       isc_ctl.s.idt = per_cpu(octeon_irq_ciu3_idt_ip2, cpu);
+       cvmx_write_csr(isc_ctl_addr, isc_ctl.u64);
+       cvmx_read_csr(isc_ctl_addr);
+}
+
+void octeon_irq_ciu3_disable(struct irq_data *data)
+{
+       u64 isc_ctl_addr;
+       union cvmx_ciu3_iscx_w1c isc_w1c;
+
+       struct octeon_ciu_chip_data *cd;
+
+       cd = irq_data_get_irq_chip_data(data);
+
+       isc_w1c.u64 = 0;
+       isc_w1c.s.en = 1;
+
+       isc_ctl_addr = cd->ciu3_addr + CIU3_ISC_CTL(cd->intsn);
+       cvmx_write_csr(cd->ciu3_addr + CIU3_ISC_W1C(cd->intsn), isc_w1c.u64);
+       cvmx_write_csr(isc_ctl_addr, 0);
+       cvmx_read_csr(isc_ctl_addr);
+}
+
+void octeon_irq_ciu3_ack(struct irq_data *data)
+{
+       u64 isc_w1c_addr;
+       union cvmx_ciu3_iscx_w1c isc_w1c;
+       struct octeon_ciu_chip_data *cd;
+       u32 trigger_type = irqd_get_trigger_type(data);
+
+       /*
+        * We use a single irq_chip, so we have to do nothing to ack a
+        * level interrupt.
+        */
+       if (!(trigger_type & IRQ_TYPE_EDGE_BOTH))
+               return;
+
+       cd = irq_data_get_irq_chip_data(data);
+
+       isc_w1c.u64 = 0;
+       isc_w1c.s.raw = 1;
+
+       isc_w1c_addr = cd->ciu3_addr + CIU3_ISC_W1C(cd->intsn);
+       cvmx_write_csr(isc_w1c_addr, isc_w1c.u64);
+       cvmx_read_csr(isc_w1c_addr);
+}
+
+void octeon_irq_ciu3_mask(struct irq_data *data)
+{
+       union cvmx_ciu3_iscx_w1c isc_w1c;
+       u64 isc_w1c_addr;
+       struct octeon_ciu_chip_data *cd;
+
+       cd = irq_data_get_irq_chip_data(data);
+
+       isc_w1c.u64 = 0;
+       isc_w1c.s.en = 1;
+
+       isc_w1c_addr = cd->ciu3_addr + CIU3_ISC_W1C(cd->intsn);
+       cvmx_write_csr(isc_w1c_addr, isc_w1c.u64);
+       cvmx_read_csr(isc_w1c_addr);
+}
+
+void octeon_irq_ciu3_mask_ack(struct irq_data *data)
+{
+       union cvmx_ciu3_iscx_w1c isc_w1c;
+       u64 isc_w1c_addr;
+       struct octeon_ciu_chip_data *cd;
+       u32 trigger_type = irqd_get_trigger_type(data);
+
+       cd = irq_data_get_irq_chip_data(data);
+
+       isc_w1c.u64 = 0;
+       isc_w1c.s.en = 1;
+
+       /*
+        * We use a single irq_chip, so only ack an edge (!level)
+        * interrupt.
+        */
+       if (trigger_type & IRQ_TYPE_EDGE_BOTH)
+               isc_w1c.s.raw = 1;
+
+       isc_w1c_addr = cd->ciu3_addr + CIU3_ISC_W1C(cd->intsn);
+       cvmx_write_csr(isc_w1c_addr, isc_w1c.u64);
+       cvmx_read_csr(isc_w1c_addr);
+}
+
+#ifdef CONFIG_SMP
+int octeon_irq_ciu3_set_affinity(struct irq_data *data,
+                                const struct cpumask *dest, bool force)
+{
+       union cvmx_ciu3_iscx_ctl isc_ctl;
+       union cvmx_ciu3_iscx_w1c isc_w1c;
+       u64 isc_ctl_addr;
+       int cpu;
+       bool enable_one = !irqd_irq_disabled(data) && !irqd_irq_masked(data);
+       struct octeon_ciu_chip_data *cd = irq_data_get_irq_chip_data(data);
+
+       if (!cpumask_subset(dest, cpumask_of_node(cd->ciu_node)))
+               return -EINVAL;
+
+       if (!enable_one)
+               return IRQ_SET_MASK_OK;
+
+       cd = irq_data_get_irq_chip_data(data);
+       cpu = cpumask_first(dest);
+       if (cpu >= nr_cpu_ids)
+               cpu = smp_processor_id();
+       cd->current_cpu = cpu;
+
+       isc_w1c.u64 = 0;
+       isc_w1c.s.en = 1;
+       cvmx_write_csr(cd->ciu3_addr + CIU3_ISC_W1C(cd->intsn), isc_w1c.u64);
+
+       isc_ctl_addr = cd->ciu3_addr + CIU3_ISC_CTL(cd->intsn);
+       isc_ctl.u64 = 0;
+       isc_ctl.s.en = 1;
+       isc_ctl.s.idt = per_cpu(octeon_irq_ciu3_idt_ip2, cpu);
+       cvmx_write_csr(isc_ctl_addr, isc_ctl.u64);
+       cvmx_read_csr(isc_ctl_addr);
+
+       return IRQ_SET_MASK_OK;
+}
+#endif
+
+static struct irq_chip octeon_irq_chip_ciu3 = {
+       .name = "CIU3",
+       .irq_startup = edge_startup,
+       .irq_enable = octeon_irq_ciu3_enable,
+       .irq_disable = octeon_irq_ciu3_disable,
+       .irq_ack = octeon_irq_ciu3_ack,
+       .irq_mask = octeon_irq_ciu3_mask,
+       .irq_mask_ack = octeon_irq_ciu3_mask_ack,
+       .irq_unmask = octeon_irq_ciu3_enable,
+       .irq_set_type = octeon_irq_ciu_set_type,
+#ifdef CONFIG_SMP
+       .irq_set_affinity = octeon_irq_ciu3_set_affinity,
+       .irq_cpu_offline = octeon_irq_cpu_offline_ciu,
+#endif
+};
+
+int octeon_irq_ciu3_mapx(struct irq_domain *d, unsigned int virq,
+                        irq_hw_number_t hw, struct irq_chip *chip)
+{
+       struct octeon_ciu3_info *ciu3_info = d->host_data;
+       struct octeon_ciu_chip_data *cd = kzalloc_node(sizeof(*cd), GFP_KERNEL,
+                                                      ciu3_info->node);
+       if (!cd)
+               return -ENOMEM;
+       cd->intsn = hw;
+       cd->current_cpu = -1;
+       cd->ciu3_addr = ciu3_info->ciu3_addr;
+       cd->ciu_node = ciu3_info->node;
+       irq_set_chip_and_handler(virq, chip, handle_edge_irq);
+       irq_set_chip_data(virq, cd);
+
+       return 0;
+}
+
+static int octeon_irq_ciu3_map(struct irq_domain *d,
+                              unsigned int virq, irq_hw_number_t hw)
+{
+       return octeon_irq_ciu3_mapx(d, virq, hw, &octeon_irq_chip_ciu3);
+}
+
+static struct irq_domain_ops octeon_dflt_domain_ciu3_ops = {
+       .map = octeon_irq_ciu3_map,
+       .unmap = octeon_irq_free_cd,
+       .xlate = octeon_irq_ciu3_xlat,
+};
+
+static void octeon_irq_ciu3_ip2(void)
+{
+       union cvmx_ciu3_destx_pp_int dest_pp_int;
+       struct octeon_ciu3_info *ciu3_info;
+       u64 ciu3_addr;
+
+       ciu3_info = __this_cpu_read(octeon_ciu3_info);
+       ciu3_addr = ciu3_info->ciu3_addr;
+
+       dest_pp_int.u64 = cvmx_read_csr(ciu3_addr + CIU3_DEST_PP_INT(3 * cvmx_get_local_core_num()));
+
+       if (likely(dest_pp_int.s.intr)) {
+               irq_hw_number_t intsn = dest_pp_int.s.intsn;
+               irq_hw_number_t hw;
+               struct irq_domain *domain;
+               /* Get the domain to use from the major block */
+               int block = intsn >> 12;
+               int ret;
+
+               domain = ciu3_info->domain[block];
+               if (ciu3_info->intsn2hw[block])
+                       hw = ciu3_info->intsn2hw[block](domain, intsn);
+               else
+                       hw = intsn;
+
+               ret = handle_domain_irq(domain, hw, NULL);
+               if (ret < 0) {
+                       union cvmx_ciu3_iscx_w1c isc_w1c;
+                       u64 isc_w1c_addr = ciu3_addr + CIU3_ISC_W1C(intsn);
+
+                       isc_w1c.u64 = 0;
+                       isc_w1c.s.en = 1;
+                       cvmx_write_csr(isc_w1c_addr, isc_w1c.u64);
+                       cvmx_read_csr(isc_w1c_addr);
+                       spurious_interrupt();
+               }
+       } else {
+               spurious_interrupt();
+       }
+}
+
+/*
+ * 10 mbox per core starting from zero.
+ * Base mbox is core * 10
+ */
+static unsigned int octeon_irq_ciu3_base_mbox_intsn(int core)
+{
+       /* SW (mbox) are 0x04 in bits 12..19 */
+       return 0x04000 + CIU3_MBOX_PER_CORE * core;
+}
+
+static unsigned int octeon_irq_ciu3_mbox_intsn_for_core(int core, unsigned int mbox)
+{
+       return octeon_irq_ciu3_base_mbox_intsn(core) + mbox;
+}
+
+static unsigned int octeon_irq_ciu3_mbox_intsn_for_cpu(int cpu, unsigned int mbox)
+{
+       int local_core = octeon_coreid_for_cpu(cpu) & 0x3f;
+
+       return octeon_irq_ciu3_mbox_intsn_for_core(local_core, mbox);
+}
+
+static void octeon_irq_ciu3_mbox(void)
+{
+       union cvmx_ciu3_destx_pp_int dest_pp_int;
+       struct octeon_ciu3_info *ciu3_info;
+       u64 ciu3_addr;
+       int core = cvmx_get_local_core_num();
+
+       ciu3_info = __this_cpu_read(octeon_ciu3_info);
+       ciu3_addr = ciu3_info->ciu3_addr;
+
+       dest_pp_int.u64 = cvmx_read_csr(ciu3_addr + CIU3_DEST_PP_INT(1 + 3 * core));
+
+       if (likely(dest_pp_int.s.intr)) {
+               irq_hw_number_t intsn = dest_pp_int.s.intsn;
+               int mbox = intsn - octeon_irq_ciu3_base_mbox_intsn(core);
+
+               if (likely(mbox >= 0 && mbox < CIU3_MBOX_PER_CORE)) {
+                       do_IRQ(mbox + OCTEON_IRQ_MBOX0);
+               } else {
+                       union cvmx_ciu3_iscx_w1c isc_w1c;
+                       u64 isc_w1c_addr = ciu3_addr + CIU3_ISC_W1C(intsn);
+
+                       isc_w1c.u64 = 0;
+                       isc_w1c.s.en = 1;
+                       cvmx_write_csr(isc_w1c_addr, isc_w1c.u64);
+                       cvmx_read_csr(isc_w1c_addr);
+                       spurious_interrupt();
+               }
+       } else {
+               spurious_interrupt();
+       }
+}
+
+void octeon_ciu3_mbox_send(int cpu, unsigned int mbox)
+{
+       struct octeon_ciu3_info *ciu3_info;
+       unsigned int intsn;
+       union cvmx_ciu3_iscx_w1s isc_w1s;
+       u64 isc_w1s_addr;
+
+       if (WARN_ON_ONCE(mbox >= CIU3_MBOX_PER_CORE))
+               return;
+
+       intsn = octeon_irq_ciu3_mbox_intsn_for_cpu(cpu, mbox);
+       ciu3_info = per_cpu(octeon_ciu3_info, cpu);
+       isc_w1s_addr = ciu3_info->ciu3_addr + CIU3_ISC_W1S(intsn);
+
+       isc_w1s.u64 = 0;
+       isc_w1s.s.raw = 1;
+
+       cvmx_write_csr(isc_w1s_addr, isc_w1s.u64);
+       cvmx_read_csr(isc_w1s_addr);
+}
+
+static void octeon_irq_ciu3_mbox_set_enable(struct irq_data *data, int cpu, bool en)
+{
+       struct octeon_ciu3_info *ciu3_info;
+       unsigned int intsn;
+       u64 isc_ctl_addr, isc_w1c_addr;
+       union cvmx_ciu3_iscx_ctl isc_ctl;
+       unsigned int mbox = data->irq - OCTEON_IRQ_MBOX0;
+
+       intsn = octeon_irq_ciu3_mbox_intsn_for_cpu(cpu, mbox);
+       ciu3_info = per_cpu(octeon_ciu3_info, cpu);
+       isc_w1c_addr = ciu3_info->ciu3_addr + CIU3_ISC_W1C(intsn);
+       isc_ctl_addr = ciu3_info->ciu3_addr + CIU3_ISC_CTL(intsn);
+
+       isc_ctl.u64 = 0;
+       isc_ctl.s.en = 1;
+
+       cvmx_write_csr(isc_w1c_addr, isc_ctl.u64);
+       cvmx_write_csr(isc_ctl_addr, 0);
+       if (en) {
+               unsigned int idt = per_cpu(octeon_irq_ciu3_idt_ip3, cpu);
+
+               isc_ctl.u64 = 0;
+               isc_ctl.s.en = 1;
+               isc_ctl.s.idt = idt;
+               cvmx_write_csr(isc_ctl_addr, isc_ctl.u64);
+       }
+       cvmx_read_csr(isc_ctl_addr);
+}
+
+static void octeon_irq_ciu3_mbox_enable(struct irq_data *data)
+{
+       int cpu;
+       unsigned int mbox = data->irq - OCTEON_IRQ_MBOX0;
+
+       WARN_ON(mbox >= CIU3_MBOX_PER_CORE);
+
+       for_each_online_cpu(cpu)
+               octeon_irq_ciu3_mbox_set_enable(data, cpu, true);
+}
+
+static void octeon_irq_ciu3_mbox_disable(struct irq_data *data)
+{
+       int cpu;
+       unsigned int mbox = data->irq - OCTEON_IRQ_MBOX0;
+
+       WARN_ON(mbox >= CIU3_MBOX_PER_CORE);
+
+       for_each_online_cpu(cpu)
+               octeon_irq_ciu3_mbox_set_enable(data, cpu, false);
+}
+
+static void octeon_irq_ciu3_mbox_ack(struct irq_data *data)
+{
+       struct octeon_ciu3_info *ciu3_info;
+       unsigned int intsn;
+       u64 isc_w1c_addr;
+       union cvmx_ciu3_iscx_w1c isc_w1c;
+       unsigned int mbox = data->irq - OCTEON_IRQ_MBOX0;
+
+       intsn = octeon_irq_ciu3_mbox_intsn_for_core(cvmx_get_local_core_num(), mbox);
+
+       isc_w1c.u64 = 0;
+       isc_w1c.s.raw = 1;
+
+       ciu3_info = __this_cpu_read(octeon_ciu3_info);
+       isc_w1c_addr = ciu3_info->ciu3_addr + CIU3_ISC_W1C(intsn);
+       cvmx_write_csr(isc_w1c_addr, isc_w1c.u64);
+       cvmx_read_csr(isc_w1c_addr);
+}
+
+static void octeon_irq_ciu3_mbox_cpu_online(struct irq_data *data)
+{
+       octeon_irq_ciu3_mbox_set_enable(data, smp_processor_id(), true);
+}
+
+static void octeon_irq_ciu3_mbox_cpu_offline(struct irq_data *data)
+{
+       octeon_irq_ciu3_mbox_set_enable(data, smp_processor_id(), false);
+}
+
+static int octeon_irq_ciu3_alloc_resources(struct octeon_ciu3_info *ciu3_info)
+{
+       u64 b = ciu3_info->ciu3_addr;
+       int idt_ip2, idt_ip3, idt_ip4;
+       int unused_idt2;
+       int core = cvmx_get_local_core_num();
+       int i;
+
+       __this_cpu_write(octeon_ciu3_info, ciu3_info);
+
+       /*
+        * 4 idt per core starting from 1 because zero is reserved.
+        * Base idt per core is 4 * core + 1
+        */
+       idt_ip2 = core * 4 + 1;
+       idt_ip3 = core * 4 + 2;
+       idt_ip4 = core * 4 + 3;
+       unused_idt2 = core * 4 + 4;
+       __this_cpu_write(octeon_irq_ciu3_idt_ip2, idt_ip2);
+       __this_cpu_write(octeon_irq_ciu3_idt_ip3, idt_ip3);
+
+       /* ip2 interrupts for this CPU */
+       cvmx_write_csr(b + CIU3_IDT_CTL(idt_ip2), 0);
+       cvmx_write_csr(b + CIU3_IDT_PP(idt_ip2, 0), 1ull << core);
+       cvmx_write_csr(b + CIU3_IDT_IO(idt_ip2), 0);
+
+       /* ip3 interrupts for this CPU */
+       cvmx_write_csr(b + CIU3_IDT_CTL(idt_ip3), 1);
+       cvmx_write_csr(b + CIU3_IDT_PP(idt_ip3, 0), 1ull << core);
+       cvmx_write_csr(b + CIU3_IDT_IO(idt_ip3), 0);
+
+       /* ip4 interrupts for this CPU */
+       cvmx_write_csr(b + CIU3_IDT_CTL(idt_ip4), 2);
+       cvmx_write_csr(b + CIU3_IDT_PP(idt_ip4, 0), 0);
+       cvmx_write_csr(b + CIU3_IDT_IO(idt_ip4), 0);
+
+       cvmx_write_csr(b + CIU3_IDT_CTL(unused_idt2), 0);
+       cvmx_write_csr(b + CIU3_IDT_PP(unused_idt2, 0), 0);
+       cvmx_write_csr(b + CIU3_IDT_IO(unused_idt2), 0);
+
+       for (i = 0; i < CIU3_MBOX_PER_CORE; i++) {
+               unsigned int intsn = octeon_irq_ciu3_mbox_intsn_for_core(core, i);
+
+               cvmx_write_csr(b + CIU3_ISC_W1C(intsn), 2);
+               cvmx_write_csr(b + CIU3_ISC_CTL(intsn), 0);
+       }
+
+       return 0;
+}
+
+static void octeon_irq_setup_secondary_ciu3(void)
+{
+       struct octeon_ciu3_info *ciu3_info;
+
+       ciu3_info = octeon_ciu3_info_per_node[cvmx_get_node_num()];
+       octeon_irq_ciu3_alloc_resources(ciu3_info);
+       irq_cpu_online();
+
+       /* Enable the CIU lines */
+       set_c0_status(STATUSF_IP3 | STATUSF_IP2);
+       if (octeon_irq_use_ip4)
+               set_c0_status(STATUSF_IP4);
+       else
+               clear_c0_status(STATUSF_IP4);
+}
+
+static struct irq_chip octeon_irq_chip_ciu3_mbox = {
+       .name = "CIU3-M",
+       .irq_enable = octeon_irq_ciu3_mbox_enable,
+       .irq_disable = octeon_irq_ciu3_mbox_disable,
+       .irq_ack = octeon_irq_ciu3_mbox_ack,
+
+       .irq_cpu_online = octeon_irq_ciu3_mbox_cpu_online,
+       .irq_cpu_offline = octeon_irq_ciu3_mbox_cpu_offline,
+       .flags = IRQCHIP_ONOFFLINE_ENABLED,
+};
+
+static int __init octeon_irq_init_ciu3(struct device_node *ciu_node,
+                                      struct device_node *parent)
+{
+       int i;
+       int node;
+       struct irq_domain *domain;
+       struct octeon_ciu3_info *ciu3_info;
+       const __be32 *zero_addr;
+       u64 base_addr;
+       union cvmx_ciu3_const consts;
+
+       node = 0; /* of_node_to_nid(ciu_node); */
+       ciu3_info = kzalloc_node(sizeof(*ciu3_info), GFP_KERNEL, node);
+
+       if (!ciu3_info)
+               return -ENOMEM;
+
+       zero_addr = of_get_address(ciu_node, 0, NULL, NULL);
+       if (WARN_ON(!zero_addr))
+               return -EINVAL;
+
+       base_addr = of_translate_address(ciu_node, zero_addr);
+       base_addr = (u64)phys_to_virt(base_addr);
+
+       ciu3_info->ciu3_addr = base_addr;
+       ciu3_info->node = node;
+
+       consts.u64 = cvmx_read_csr(base_addr + CIU3_CONST);
+
+       octeon_irq_setup_secondary = octeon_irq_setup_secondary_ciu3;
+
+       octeon_irq_ip2 = octeon_irq_ciu3_ip2;
+       octeon_irq_ip3 = octeon_irq_ciu3_mbox;
+       octeon_irq_ip4 = octeon_irq_ip4_mask;
+
+       if (node == cvmx_get_node_num()) {
+               /* Mips internal */
+               octeon_irq_init_core();
+
+               /* Only do per CPU things if it is the CIU of the boot node. */
+               i = irq_alloc_descs_from(OCTEON_IRQ_MBOX0, 8, node);
+               WARN_ON(i < 0);
+
+               for (i = 0; i < 8; i++)
+                       irq_set_chip_and_handler(i + OCTEON_IRQ_MBOX0,
+                                                &octeon_irq_chip_ciu3_mbox, handle_percpu_irq);
+       }
+
+       /*
+        * Initialize all domains to use the default domain. Specific major
+        * blocks will overwrite the default domain as needed.
+        */
+       domain = irq_domain_add_tree(ciu_node, &octeon_dflt_domain_ciu3_ops,
+                                    ciu3_info);
+       for (i = 0; i < MAX_CIU3_DOMAINS; i++)
+               ciu3_info->domain[i] = domain;
+
+       octeon_ciu3_info_per_node[node] = ciu3_info;
+
+       if (node == cvmx_get_node_num()) {
+               /* Only do per CPU things if it is the CIU of the boot node. */
+               octeon_irq_ciu3_alloc_resources(ciu3_info);
+               if (node == 0)
+                       irq_set_default_host(domain);
+
+               octeon_irq_use_ip4 = false;
+               /* Enable the CIU lines */
+               set_c0_status(STATUSF_IP2 | STATUSF_IP3);
+               clear_c0_status(STATUSF_IP4);
+       }
+
+       return 0;
+}
+
 static struct of_device_id ciu_types[] __initdata = {
        {.compatible = "cavium,octeon-3860-ciu", .data = octeon_irq_init_ciu},
        {.compatible = "cavium,octeon-3860-gpio", .data = octeon_irq_init_gpio},
        {.compatible = "cavium,octeon-6880-ciu2", .data = octeon_irq_init_ciu2},
+       {.compatible = "cavium,octeon-7890-ciu3", .data = octeon_irq_init_ciu3},
        {.compatible = "cavium,octeon-7130-cib", .data = octeon_irq_init_cib},
        {}
 };
index de9f74e..53d51ae 100644 (file)
@@ -299,6 +299,25 @@ static inline void octeon_npi_write32(uint64_t address, uint32_t val)
        cvmx_read64_uint32(address ^ 4);
 }
 
+struct irq_domain;
+struct device_node;
+struct irq_data;
+struct irq_chip;
+void octeon_ciu3_mbox_send(int cpu, unsigned int mbox);
+int octeon_irq_ciu3_xlat(struct irq_domain *d,
+                        struct device_node *node,
+                        const u32 *intspec,
+                        unsigned int intsize,
+                        unsigned long *out_hwirq,
+                        unsigned int *out_type);
+void octeon_irq_ciu3_enable(struct irq_data *data);
+void octeon_irq_ciu3_disable(struct irq_data *data);
+void octeon_irq_ciu3_ack(struct irq_data *data);
+void octeon_irq_ciu3_mask(struct irq_data *data);
+void octeon_irq_ciu3_mask_ack(struct irq_data *data);
+int octeon_irq_ciu3_mapx(struct irq_domain *d, unsigned int virq,
+                        irq_hw_number_t hw, struct irq_chip *chip);
+
 /* Octeon multiplier save/restore routines from octeon_switch.S */
 void octeon_mult_save(void);
 void octeon_mult_restore(void);