Add device tree binding for the BCM63168 interrupt controller.
This controller is similar to the SMP-capable BCM7038 and
the BCM3380 but with packed interrupt registers.
Signed-off-by: Simon Arlott <[email protected]>
---
.../interrupt-controller/brcm,bcm63168-l1-intc.txt | 57 ++++++++++++++++++++++
1 file changed, 57 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt
diff --git a/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt
new file mode 100644
index 0000000..636a6db
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt
@@ -0,0 +1,57 @@
+Broadcom BCM63168-style Level 1 interrupt controller
+
+This block is a first level interrupt controller that is typically connected
+directly to one of the HW INT lines on each CPU.
+
+Key elements of the hardware design include:
+
+- 64 or 128 incoming level IRQ lines
+
+- Most onchip peripherals are wired directly to an L1 input
+
+- A separate instance of the register set for each CPU, allowing individual
+ peripheral IRQs to be routed to any CPU
+
+- Contains one or more enable/status word pairs per CPU
+
+- No atomic set/clear operations
+
+- No polarity/level/edge settings
+
+- No FIFO or priority encoder logic; software is expected to read all
+ 2-4 status words to determine which IRQs are pending
+
+Required properties:
+
+- compatible: should be "brcm,bcm63168-l1-intc"
+- reg: specifies the base physical address and size of the registers;
+ the number of supported IRQs is inferred from the size argument
+- interrupt-controller: identifies the node as an interrupt controller
+- #interrupt-cells: specifies the number of cells needed to encode an interrupt
+ source, should be 1.
+- interrupt-parent: specifies the phandle to the parent interrupt controller(s)
+ this one is cascaded from
+- interrupts: specifies the interrupt line(s) in the interrupt-parent controller
+ node; valid values depend on the type of parent interrupt controller
+
+If multiple reg ranges and interrupt-parent entries are present on an SMP
+system, the driver will allow IRQ SMP affinity to be set up through the
+/proc/irq/ interface. In the simplest possible configuration, only one
+reg range and one interrupt-parent is needed.
+
+The driver operates in native CPU endian by default, there is no support for
+specifying an alternative endianness.
+
+Example:
+
+periph_intc: periph_intc@10000000 {
+ compatible = "brcm,bcm63168-l1-intc";
+ reg = <0x10000020 0x20>,
+ <0x10000040 0x20>;
+
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ interrupt-parent = <&cpu_intc>;
+ interrupts = <2>, <3>;
+};
--
2.1.4
--
Simon Arlott
Add the BCM63168 interrupt controller based on the SMP-capable BCM7038
and the BCM3380 but with packed interrupt registers.
Add the BCM63168 interrupt controller to a list with the existing BCM7038
so that interrupts on CPU1 are not ignored.
Update the maintainers file list for BMIPS to include this driver.
Signed-off-by: Simon Arlott <[email protected]>
---
MAINTAINERS | 1 +
arch/mips/Kconfig | 1 +
arch/mips/bmips/irq.c | 10 +-
drivers/irqchip/Kconfig | 5 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-bcm63168-l1.c | 372 ++++++++++++++++++++++++++++++++++++++
6 files changed, 388 insertions(+), 2 deletions(-)
create mode 100644 drivers/irqchip/irq-bcm63168-l1.c
diff --git a/MAINTAINERS b/MAINTAINERS
index e9caa4b..63bf54a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2373,6 +2373,7 @@ F: arch/mips/bmips/*
F: arch/mips/include/asm/mach-bmips/*
F: arch/mips/kernel/*bmips*
F: arch/mips/boot/dts/brcm/bcm*.dts*
+F: drivers/irqchip/irq-bcm63*
F: drivers/irqchip/irq-bcm7*
F: drivers/irqchip/irq-brcmstb*
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index e3aa5b0..1a8decd 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -149,6 +149,7 @@ config BMIPS_GENERIC
select CSRC_R4K
select SYNC_R4K
select COMMON_CLK
+ select BCM63168_L1_IRQ
select BCM7038_L1_IRQ
select BCM7120_L2_IRQ
select BRCMSTB_L2_IRQ
diff --git a/arch/mips/bmips/irq.c b/arch/mips/bmips/irq.c
index e7fc6f934..7983cef 100644
--- a/arch/mips/bmips/irq.c
+++ b/arch/mips/bmips/irq.c
@@ -15,6 +15,12 @@
#include <asm/irq_cpu.h>
#include <asm/time.h>
+static const struct of_device_id smp_intc_dt_match[] = {
+ { .compatible = "brcm,bcm7038-l1-intc" },
+ { .compatible = "brcm,bcm63168-l1-intc" },
+ {}
+};
+
unsigned int get_c0_compare_int(void)
{
return CP0_LEGACY_COMPARE_IRQ;
@@ -24,8 +30,8 @@ void __init arch_init_irq(void)
{
struct device_node *dn;
- /* Only the STB (bcm7038) controller supports SMP IRQ affinity */
- dn = of_find_compatible_node(NULL, NULL, "brcm,bcm7038-l1-intc");
+ /* Only these controllers support SMP IRQ affinity */
+ dn = of_find_matching_node(NULL, smp_intc_dt_match);
if (dn)
of_node_put(dn);
else
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 4d7294e..82ce318 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -65,6 +65,11 @@ config I8259
bool
select IRQ_DOMAIN
+config BCM63168_L1_IRQ
+ bool
+ select GENERIC_IRQ_CHIP
+ select IRQ_DOMAIN
+
config BCM7038_L1_IRQ
bool
select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 177f78f..a3a21e2 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o
obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o
obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o
+obj-$(CONFIG_BCM63168_L1_IRQ) += irq-bcm63168-l1.o
obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o
obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o
obj-$(CONFIG_BRCMSTB_L2_IRQ) += irq-brcmstb-l2.o
diff --git a/drivers/irqchip/irq-bcm63168-l1.c b/drivers/irqchip/irq-bcm63168-l1.c
new file mode 100644
index 0000000..5a144af
--- /dev/null
+++ b/drivers/irqchip/irq-bcm63168-l1.c
@@ -0,0 +1,372 @@
+/*
+ * Broadcom BCM63168 style Level 1 interrupt controller driver
+ *
+ * Copyright (C) 2014 Broadcom Corporation
+ * Copyright 2015 Simon Arlott
+ *
+ * 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.
+ *
+ * This is based on the BCM7038 (which supports SMP) but with a single
+ * enable register instead of separate mask/set/clear registers.
+ *
+ * The BCM3380 has a similar mask/status register layout, but each pair
+ * of words is at separate locations (and SMP is not supported).
+ *
+ * ENABLE/STATUS words are packed next to each other for each CPU:
+ *
+ * 6368:
+ * 0x1000_0020: CPU0_W0_ENABLE
+ * 0x1000_0024: CPU0_W1_ENABLE
+ * 0x1000_0028: CPU0_W0_STATUS IRQs 31-63
+ * 0x1000_002c: CPU0_W1_STATUS IRQs 0-31
+ * 0x1000_0030: CPU1_W0_ENABLE
+ * 0x1000_0034: CPU1_W1_ENABLE
+ * 0x1000_0038: CPU1_W0_STATUS IRQs 31-63
+ * 0x1000_003c: CPU1_W1_STATUS IRQs 0-31
+ *
+ * 63168:
+ * 0x1000_0020: CPU0_W0_ENABLE
+ * 0x1000_0024: CPU0_W1_ENABLE
+ * 0x1000_0028: CPU0_W2_ENABLE
+ * 0x1000_002c: CPU0_W3_ENABLE
+ * 0x1000_0030: CPU0_W0_STATUS IRQs 96-127
+ * 0x1000_0034: CPU0_W1_STATUS IRQs 64-95
+ * 0x1000_0038: CPU0_W2_STATUS IRQs 32-63
+ * 0x1000_003c: CPU0_W3_STATUS IRQs 0-31
+ * 0x1000_0040: CPU1_W0_ENABLE
+ * 0x1000_0044: CPU1_W1_ENABLE
+ * 0x1000_0048: CPU1_W2_ENABLE
+ * 0x1000_004c: CPU1_W3_ENABLE
+ * 0x1000_0050: CPU1_W0_STATUS IRQs 96-127
+ * 0x1000_0054: CPU1_W1_STATUS IRQs 64-95
+ * 0x1000_0058: CPU1_W2_STATUS IRQs 32-63
+ * 0x1000_005c: CPU1_W3_STATUS IRQs 0-31
+ *
+ * IRQs are numbered in CPU native endian order
+ * (which is big-endian in these examples)
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/cpumask.h>
+#include <linux/kconfig.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+#include <linux/types.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+
+#define IRQS_PER_WORD 32
+#define REG_BYTES_PER_IRQ_WORD (sizeof(u32) * 2)
+#define MAX_WORDS 4
+
+struct bcm63168_l1_cpu;
+
+struct bcm63168_l1_chip {
+ raw_spinlock_t lock[MAX_WORDS];
+ unsigned int n_words;
+ struct irq_domain *domain;
+ struct cpumask cpumask;
+ struct bcm63168_l1_cpu *cpus[NR_CPUS];
+};
+
+struct bcm63168_l1_cpu {
+ void __iomem *map_base;
+ unsigned int parent_irq;
+ u32 enable_cache[];
+};
+
+static inline unsigned int reg_enable(struct bcm63168_l1_chip *intc,
+ unsigned int word)
+{
+#ifdef __BIG_ENDIAN
+ return (1 * intc->n_words - word - 1) * sizeof(u32);
+#else
+ return (0 * intc->n_words + word) * sizeof(u32);
+#endif
+}
+
+static inline unsigned int reg_status(struct bcm63168_l1_chip *intc,
+ unsigned int word)
+{
+#ifdef __BIG_ENDIAN
+ return (2 * intc->n_words - word - 1) * sizeof(u32);
+#else
+ return (1 * intc->n_words + word) * sizeof(u32);
+#endif
+}
+
+static inline unsigned int cpu_for_irq(struct bcm63168_l1_chip *intc,
+ struct irq_data *d)
+{
+ return cpumask_first_and(&intc->cpumask, irq_data_get_affinity_mask(d));
+}
+
+static void bcm63168_l1_irq_handle(struct irq_desc *desc)
+{
+ struct bcm63168_l1_chip *intc = irq_desc_get_handler_data(desc);
+ struct bcm63168_l1_cpu *cpu;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int idx;
+
+#ifdef CONFIG_SMP
+ cpu = intc->cpus[cpu_logical_map(smp_processor_id())];
+#else
+ cpu = intc->cpus[0];
+#endif
+
+ chained_irq_enter(chip, desc);
+
+ for (idx = 0; idx < intc->n_words; idx++) {
+ int base = idx * IRQS_PER_WORD;
+ unsigned long pending;
+ irq_hw_number_t hwirq;
+ unsigned int irq;
+
+ pending = __raw_readl(cpu->map_base + reg_status(intc, idx));
+ pending &= __raw_readl(cpu->map_base + reg_enable(intc, idx));
+
+ for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) {
+ irq = irq_linear_revmap(intc->domain, base + hwirq);
+ if (irq)
+ do_IRQ(irq);
+ else
+ spurious_interrupt();
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static inline void __bcm63168_l1_unmask(struct irq_data *d)
+{
+ struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+ unsigned int cpu_idx = cpu_for_irq(intc, d);
+
+ intc->cpus[cpu_idx]->enable_cache[word] |= mask;
+ __raw_writel(intc->cpus[cpu_idx]->enable_cache[word],
+ intc->cpus[cpu_idx]->map_base + reg_enable(intc, word));
+}
+
+static inline void __bcm63168_l1_mask(struct irq_data *d)
+{
+ struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+ unsigned int cpu_idx = cpu_for_irq(intc, d);
+
+ intc->cpus[cpu_idx]->enable_cache[word] &= ~mask;
+ __raw_writel(intc->cpus[cpu_idx]->enable_cache[word],
+ intc->cpus[cpu_idx]->map_base + reg_enable(intc, word));
+}
+
+static void bcm63168_l1_unmask(struct irq_data *d)
+{
+ struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&intc->lock[word], flags);
+ __bcm63168_l1_unmask(d);
+ raw_spin_unlock_irqrestore(&intc->lock[word], flags);
+}
+
+static void bcm63168_l1_mask(struct irq_data *d)
+{
+ struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&intc->lock[word], flags);
+ __bcm63168_l1_mask(d);
+ raw_spin_unlock_irqrestore(&intc->lock[word], flags);
+}
+
+static int bcm63168_l1_set_affinity(struct irq_data *d,
+ const struct cpumask *dest,
+ bool force)
+{
+ struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+ unsigned int old_cpu = cpu_for_irq(intc, d);
+ unsigned int new_cpu;
+ struct cpumask valid;
+ unsigned long flags;
+ bool enabled;
+
+ if (!cpumask_and(&valid, &intc->cpumask, dest))
+ return -EINVAL;
+
+ new_cpu = cpumask_any_and(&valid, cpu_online_mask);
+ if (new_cpu >= nr_cpu_ids)
+ return -EINVAL;
+
+ dest = cpumask_of(new_cpu);
+
+ raw_spin_lock_irqsave(&intc->lock[word], flags);
+ if (old_cpu != new_cpu) {
+ enabled = intc->cpus[old_cpu]->enable_cache[word] & mask;
+ if (enabled)
+ __bcm63168_l1_mask(d);
+ cpumask_copy(irq_data_get_affinity_mask(d), dest);
+ if (enabled)
+ __bcm63168_l1_unmask(d);
+ } else {
+ cpumask_copy(irq_data_get_affinity_mask(d), dest);
+ }
+ raw_spin_unlock_irqrestore(&intc->lock[word], flags);
+
+ return IRQ_SET_MASK_OK_NOCOPY;
+}
+
+static int __init bcm63168_l1_init_one(struct device_node *dn,
+ unsigned int idx,
+ struct bcm63168_l1_chip *intc)
+{
+ struct resource res;
+ resource_size_t sz;
+ struct bcm63168_l1_cpu *cpu;
+ unsigned int i, n_words;
+
+ if (of_address_to_resource(dn, idx, &res))
+ return -EINVAL;
+ sz = resource_size(&res);
+ n_words = sz / REG_BYTES_PER_IRQ_WORD;
+
+ if (n_words > MAX_WORDS)
+ return -EINVAL;
+ else if (!intc->n_words)
+ intc->n_words = n_words;
+ else if (intc->n_words != n_words)
+ return -EINVAL;
+
+ cpu = intc->cpus[idx] = kzalloc(sizeof(*cpu) + n_words * sizeof(u32),
+ GFP_KERNEL);
+ if (!cpu)
+ return -ENOMEM;
+
+ cpu->map_base = ioremap(res.start, sz);
+ if (!cpu->map_base)
+ return -ENOMEM;
+
+ for (i = 0; i < n_words; i++) {
+ cpu->enable_cache[i] = 0;
+ __raw_writel(0, cpu->map_base + reg_enable(intc, i));
+ }
+
+ cpu->parent_irq = irq_of_parse_and_map(dn, idx);
+ if (!cpu->parent_irq) {
+ pr_err("failed to map parent interrupt %d\n", cpu->parent_irq);
+ return -EINVAL;
+ }
+ irq_set_chained_handler_and_data(cpu->parent_irq,
+ bcm63168_l1_irq_handle, intc);
+
+ return 0;
+}
+
+static struct irq_chip bcm63168_l1_irq_chip = {
+ .name = "bcm63168-l1",
+ .irq_mask = bcm63168_l1_mask,
+ .irq_unmask = bcm63168_l1_unmask,
+ .irq_set_affinity = bcm63168_l1_set_affinity,
+};
+
+static int bcm63168_l1_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hw_irq)
+{
+ irq_set_chip_and_handler(virq,
+ &bcm63168_l1_irq_chip, handle_percpu_irq);
+ irq_set_chip_data(virq, d->host_data);
+ return 0;
+}
+
+static const struct irq_domain_ops bcm63168_l1_domain_ops = {
+ .xlate = irq_domain_xlate_onecell,
+ .map = bcm63168_l1_map,
+};
+
+static int __init bcm63168_l1_of_init(struct device_node *dn,
+ struct device_node *parent)
+{
+ struct bcm63168_l1_chip *intc;
+ unsigned int idx;
+ int ret;
+
+ intc = kzalloc(sizeof(*intc), GFP_KERNEL);
+ if (!intc)
+ return -ENOMEM;
+
+ cpumask_clear(&intc->cpumask);
+
+ for_each_possible_cpu(idx) {
+ ret = bcm63168_l1_init_one(dn, idx, intc);
+ if (ret)
+ pr_err("failed to init intc L1 for cpu %d: %d\n",
+ idx, ret);
+ else
+ cpumask_set_cpu(idx, &intc->cpumask);
+ }
+
+ if (!cpumask_weight(&intc->cpumask)) {
+ ret = -ENODEV;
+ goto out_free;
+ }
+
+ for (idx = 0; idx < intc->n_words; idx++)
+ raw_spin_lock_init(&intc->lock[idx]);
+
+ intc->domain = irq_domain_add_linear(dn, IRQS_PER_WORD * intc->n_words,
+ &bcm63168_l1_domain_ops,
+ intc);
+ if (!intc->domain) {
+ ret = -ENOMEM;
+ goto out_unmap;
+ }
+
+ pr_info("registered BCM63168 L1 intc (IRQs: %d)\n",
+ IRQS_PER_WORD * intc->n_words);
+ for_each_cpu(idx, &intc->cpumask) {
+ struct bcm63168_l1_cpu *cpu = intc->cpus[idx];
+
+ pr_info(" CPU%u at MMIO 0x%p (irq = %d)\n", idx,
+ cpu->map_base, cpu->parent_irq);
+ }
+
+ return 0;
+
+out_unmap:
+ for_each_possible_cpu(idx) {
+ struct bcm63168_l1_cpu *cpu = intc->cpus[idx];
+
+ if (cpu) {
+ if (cpu->map_base)
+ iounmap(cpu->map_base);
+ kfree(cpu);
+ }
+ }
+out_free:
+ kfree(intc);
+ return ret;
+}
+
+IRQCHIP_DECLARE(bcm63168_l1, "brcm,bcm63168-l1-intc", bcm63168_l1_of_init);
--
2.1.4
--
Simon Arlott
Hi,
On Sun, Nov 15, 2015 at 5:51 PM, Simon Arlott <[email protected]> wrote:
> Add device tree binding for the BCM63168 interrupt controller.
Well, that's what I get for waiting for -rc1, I also planned to submit
irqchip drivers[1] for bcm63xx/bmips for the next window ;p.
>
> This controller is similar to the SMP-capable BCM7038 and
> the BCM3380 but with packed interrupt registers.
>
> Signed-off-by: Simon Arlott <[email protected]>
> ---
> .../interrupt-controller/brcm,bcm63168-l1-intc.txt | 57 ++++++++++++++++++++++
> 1 file changed, 57 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt
> new file mode 100644
> index 0000000..636a6db
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt
> @@ -0,0 +1,57 @@
> +Broadcom BCM63168-style Level 1 interrupt controller
Usually I would go by which SoC introduced it, and AFAICT tell BCM6358
was the first with per-CPU irq blocks, but since I don't see anything
preventing it to be used with the older, single thread SoCs, the
driver/binding would even support BCM6345's one.
> +
> +This block is a first level interrupt controller that is typically connected
> +directly to one of the HW INT lines on each CPU.
> +
> +Key elements of the hardware design include:
> +
> +- 64 or 128 incoming level IRQ lines
Which, in this case, has 32 incoming IRQ lines.
> +
> +- Most onchip peripherals are wired directly to an L1 input
> +
> +- A separate instance of the register set for each CPU, allowing individual
> + peripheral IRQs to be routed to any CPU
> +
> +- Contains one or more enable/status word pairs per CPU
> +
> +- No atomic set/clear operations
> +
> +- No polarity/level/edge settings
> +
> +- No FIFO or priority encoder logic; software is expected to read all
> + 2-4 status words to determine which IRQs are pending
> +
> +Required properties:
> +
> +- compatible: should be "brcm,bcm63168-l1-intc"
It's a bit strange using the newer chip name for older chips, so I
suggest using brcm,bcm6345-l1-intc or brcm,bcm6358-l1-intc (I went
with bcm6345-periph-intc, to avoid the l1/l2 discussion).
Also I'm not sure whether BCM63168 or BCM63268 is the right name for
the family, as Broadcom seems to use the latter in their LDKs.
Furthermore, it's always good to specify that it should contain a
brcm,bcm<soc>-l1-intc, in case one ever needs to tell them apart.
> +- reg: specifies the base physical address and size of the registers;
> + the number of supported IRQs is inferred from the size argument
> +- interrupt-controller: identifies the node as an interrupt controller
> +- #interrupt-cells: specifies the number of cells needed to encode an interrupt
> + source, should be 1.
> +- interrupt-parent: specifies the phandle to the parent interrupt controller(s)
> + this one is cascaded from
> +- interrupts: specifies the interrupt line(s) in the interrupt-parent controller
> + node; valid values depend on the type of parent interrupt controller
> +
> +If multiple reg ranges and interrupt-parent entries are present on an SMP
> +system, the driver will allow IRQ SMP affinity to be set up through the
> +/proc/irq/ interface. In the simplest possible configuration, only one
> +reg range and one interrupt-parent is needed.
> +
> +The driver operates in native CPU endian by default, there is no support for
> +specifying an alternative endianness.
> +
> +Example:
> +
> +periph_intc: periph_intc@10000000 {
interrupt-controller@10000020
> + compatible = "brcm,bcm63168-l1-intc";
> + reg = <0x10000020 0x20>,
> + <0x10000040 0x20>;
> +
> + interrupt-controller;
> + #interrupt-cells = <1>;
> +
> + interrupt-parent = <&cpu_intc>;
> + interrupts = <2>, <3>;
> +};
Regards
Jonas
[1] https://github.com/openwrt/openwrt/blob/master/target/linux/brcm63xx/patches-4.1/320-irqchip-add-support-for-bcm6345-style-periphery-irq-.patch
On Sun, Nov 15, 2015 at 5:53 PM, Simon Arlott <[email protected]> wrote:
> Add the BCM63168 interrupt controller based on the SMP-capable BCM7038
> and the BCM3380 but with packed interrupt registers.
>
> Add the BCM63168 interrupt controller to a list with the existing BCM7038
> so that interrupts on CPU1 are not ignored.
>
> Update the maintainers file list for BMIPS to include this driver.
>
> Signed-off-by: Simon Arlott <[email protected]>
> ---
> MAINTAINERS | 1 +
> arch/mips/Kconfig | 1 +
> arch/mips/bmips/irq.c | 10 +-
> drivers/irqchip/Kconfig | 5 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-bcm63168-l1.c | 372 ++++++++++++++++++++++++++++++++++++++
> 6 files changed, 388 insertions(+), 2 deletions(-)
> create mode 100644 drivers/irqchip/irq-bcm63168-l1.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e9caa4b..63bf54a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2373,6 +2373,7 @@ F: arch/mips/bmips/*
> F: arch/mips/include/asm/mach-bmips/*
> F: arch/mips/kernel/*bmips*
> F: arch/mips/boot/dts/brcm/bcm*.dts*
> +F: drivers/irqchip/irq-bcm63*
> F: drivers/irqchip/irq-bcm7*
> F: drivers/irqchip/irq-brcmstb*
>
> diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
> index e3aa5b0..1a8decd 100644
> --- a/arch/mips/Kconfig
> +++ b/arch/mips/Kconfig
> @@ -149,6 +149,7 @@ config BMIPS_GENERIC
> select CSRC_R4K
> select SYNC_R4K
> select COMMON_CLK
> + select BCM63168_L1_IRQ
> select BCM7038_L1_IRQ
> select BCM7120_L2_IRQ
> select BRCMSTB_L2_IRQ
> diff --git a/arch/mips/bmips/irq.c b/arch/mips/bmips/irq.c
> index e7fc6f934..7983cef 100644
> --- a/arch/mips/bmips/irq.c
> +++ b/arch/mips/bmips/irq.c
> @@ -15,6 +15,12 @@
> #include <asm/irq_cpu.h>
> #include <asm/time.h>
>
> +static const struct of_device_id smp_intc_dt_match[] = {
> + { .compatible = "brcm,bcm7038-l1-intc" },
> + { .compatible = "brcm,bcm63168-l1-intc" },
> + {}
> +};
> +
> unsigned int get_c0_compare_int(void)
> {
> return CP0_LEGACY_COMPARE_IRQ;
> @@ -24,8 +30,8 @@ void __init arch_init_irq(void)
> {
> struct device_node *dn;
>
> - /* Only the STB (bcm7038) controller supports SMP IRQ affinity */
> - dn = of_find_compatible_node(NULL, NULL, "brcm,bcm7038-l1-intc");
> + /* Only these controllers support SMP IRQ affinity */
> + dn = of_find_matching_node(NULL, smp_intc_dt_match);
> if (dn)
> of_node_put(dn);
> else
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 4d7294e..82ce318 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -65,6 +65,11 @@ config I8259
> bool
> select IRQ_DOMAIN
>
> +config BCM63168_L1_IRQ
> + bool
> + select GENERIC_IRQ_CHIP
> + select IRQ_DOMAIN
> +
> config BCM7038_L1_IRQ
> bool
> select GENERIC_IRQ_CHIP
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 177f78f..a3a21e2 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -43,6 +43,7 @@ obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o
> obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
> obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o
> obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o
> +obj-$(CONFIG_BCM63168_L1_IRQ) += irq-bcm63168-l1.o
> obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o
> obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o
> obj-$(CONFIG_BRCMSTB_L2_IRQ) += irq-brcmstb-l2.o
> diff --git a/drivers/irqchip/irq-bcm63168-l1.c b/drivers/irqchip/irq-bcm63168-l1.c
> new file mode 100644
> index 0000000..5a144af
> --- /dev/null
> +++ b/drivers/irqchip/irq-bcm63168-l1.c
> @@ -0,0 +1,372 @@
> +/*
> + * Broadcom BCM63168 style Level 1 interrupt controller driver
> + *
> + * Copyright (C) 2014 Broadcom Corporation
> + * Copyright 2015 Simon Arlott
> + *
> + * 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.
> + *
> + * This is based on the BCM7038 (which supports SMP) but with a single
> + * enable register instead of separate mask/set/clear registers.
> + *
> + * The BCM3380 has a similar mask/status register layout, but each pair
> + * of words is at separate locations (and SMP is not supported).
> + *
> + * ENABLE/STATUS words are packed next to each other for each CPU:
> + *
> + * 6368:
> + * 0x1000_0020: CPU0_W0_ENABLE
> + * 0x1000_0024: CPU0_W1_ENABLE
> + * 0x1000_0028: CPU0_W0_STATUS IRQs 31-63
> + * 0x1000_002c: CPU0_W1_STATUS IRQs 0-31
> + * 0x1000_0030: CPU1_W0_ENABLE
> + * 0x1000_0034: CPU1_W1_ENABLE
> + * 0x1000_0038: CPU1_W0_STATUS IRQs 31-63
> + * 0x1000_003c: CPU1_W1_STATUS IRQs 0-31
> + *
> + * 63168:
> + * 0x1000_0020: CPU0_W0_ENABLE
> + * 0x1000_0024: CPU0_W1_ENABLE
> + * 0x1000_0028: CPU0_W2_ENABLE
> + * 0x1000_002c: CPU0_W3_ENABLE
> + * 0x1000_0030: CPU0_W0_STATUS IRQs 96-127
> + * 0x1000_0034: CPU0_W1_STATUS IRQs 64-95
> + * 0x1000_0038: CPU0_W2_STATUS IRQs 32-63
> + * 0x1000_003c: CPU0_W3_STATUS IRQs 0-31
> + * 0x1000_0040: CPU1_W0_ENABLE
> + * 0x1000_0044: CPU1_W1_ENABLE
> + * 0x1000_0048: CPU1_W2_ENABLE
> + * 0x1000_004c: CPU1_W3_ENABLE
> + * 0x1000_0050: CPU1_W0_STATUS IRQs 96-127
> + * 0x1000_0054: CPU1_W1_STATUS IRQs 64-95
> + * 0x1000_0058: CPU1_W2_STATUS IRQs 32-63
> + * 0x1000_005c: CPU1_W3_STATUS IRQs 0-31
> + *
> + * IRQs are numbered in CPU native endian order
> + * (which is big-endian in these examples)
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/bitops.h>
> +#include <linux/cpumask.h>
> +#include <linux/kconfig.h>
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/ioport.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/smp.h>
> +#include <linux/types.h>
> +#include <linux/irqchip.h>
> +#include <linux/irqchip/chained_irq.h>
> +
> +#define IRQS_PER_WORD 32
> +#define REG_BYTES_PER_IRQ_WORD (sizeof(u32) * 2)
> +#define MAX_WORDS 4
> +
> +struct bcm63168_l1_cpu;
> +
> +struct bcm63168_l1_chip {
> + raw_spinlock_t lock[MAX_WORDS];
> + unsigned int n_words;
> + struct irq_domain *domain;
> + struct cpumask cpumask;
> + struct bcm63168_l1_cpu *cpus[NR_CPUS];
> +};
> +
> +struct bcm63168_l1_cpu {
> + void __iomem *map_base;
> + unsigned int parent_irq;
> + u32 enable_cache[];
> +};
> +
> +static inline unsigned int reg_enable(struct bcm63168_l1_chip *intc,
> + unsigned int word)
> +{
> +#ifdef __BIG_ENDIAN
> + return (1 * intc->n_words - word - 1) * sizeof(u32);
> +#else
> + return (0 * intc->n_words + word) * sizeof(u32);
Huh, do the words really change the order when running in LE? I would
have expected
to each 32-bit word to contain the same interrupts, just bit-order reversed.
> +#endif
> +}
> +
> +static inline unsigned int reg_status(struct bcm63168_l1_chip *intc,
> + unsigned int word)
> +{
> +#ifdef __BIG_ENDIAN
> + return (2 * intc->n_words - word - 1) * sizeof(u32);
> +#else
> + return (1 * intc->n_words + word) * sizeof(u32);
> +#endif
> +}
> +
> +static inline unsigned int cpu_for_irq(struct bcm63168_l1_chip *intc,
> + struct irq_data *d)
> +{
> + return cpumask_first_and(&intc->cpumask, irq_data_get_affinity_mask(d));
> +}
> +
> +static void bcm63168_l1_irq_handle(struct irq_desc *desc)
> +{
> + struct bcm63168_l1_chip *intc = irq_desc_get_handler_data(desc);
> + struct bcm63168_l1_cpu *cpu;
> + struct irq_chip *chip = irq_desc_get_chip(desc);
> + unsigned int idx;
> +
> +#ifdef CONFIG_SMP
> + cpu = intc->cpus[cpu_logical_map(smp_processor_id())];
> +#else
> + cpu = intc->cpus[0];
> +#endif
This looks expensive, can they change during runtime? If not, maybe
just assign intc->cpus[] accordingly at probe time, so you can just do
intc->cpus[smp_processor_id()] without any #ifdefs.
> +
> + chained_irq_enter(chip, desc);
> +
> + for (idx = 0; idx < intc->n_words; idx++) {
> + int base = idx * IRQS_PER_WORD;
> + unsigned long pending;
> + irq_hw_number_t hwirq;
> + unsigned int irq;
> +
> + pending = __raw_readl(cpu->map_base + reg_status(intc, idx));
> + pending &= __raw_readl(cpu->map_base + reg_enable(intc, idx));
Is it save to access the registers without any locking? 7038-l1
doesn't think so.
> +
> + for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) {
> + irq = irq_linear_revmap(intc->domain, base + hwirq);
> + if (irq)
> + do_IRQ(irq);
> + else
> + spurious_interrupt();
> + }
> + }
> +
> + chained_irq_exit(chip, desc);
> +}
> +
> +static inline void __bcm63168_l1_unmask(struct irq_data *d)
> +{
> + struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
> + u32 word = d->hwirq / IRQS_PER_WORD;
> + u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
> + unsigned int cpu_idx = cpu_for_irq(intc, d);
> +
> + intc->cpus[cpu_idx]->enable_cache[word] |= mask;
> + __raw_writel(intc->cpus[cpu_idx]->enable_cache[word],
> + intc->cpus[cpu_idx]->map_base + reg_enable(intc, word));
> +}
> +
> +static inline void __bcm63168_l1_mask(struct irq_data *d)
> +{
> + struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
> + u32 word = d->hwirq / IRQS_PER_WORD;
> + u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
> + unsigned int cpu_idx = cpu_for_irq(intc, d);
> +
> + intc->cpus[cpu_idx]->enable_cache[word] &= ~mask;
> + __raw_writel(intc->cpus[cpu_idx]->enable_cache[word],
> + intc->cpus[cpu_idx]->map_base + reg_enable(intc, word));
> +}
> +
> +static void bcm63168_l1_unmask(struct irq_data *d)
> +{
> + struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
> + u32 word = d->hwirq / IRQS_PER_WORD;
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&intc->lock[word], flags);
> + __bcm63168_l1_unmask(d);
> + raw_spin_unlock_irqrestore(&intc->lock[word], flags);
> +}
> +
> +static void bcm63168_l1_mask(struct irq_data *d)
> +{
> + struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
> + u32 word = d->hwirq / IRQS_PER_WORD;
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&intc->lock[word], flags);
> + __bcm63168_l1_mask(d);
> + raw_spin_unlock_irqrestore(&intc->lock[word], flags);
> +}
> +
> +static int bcm63168_l1_set_affinity(struct irq_data *d,
> + const struct cpumask *dest,
> + bool force)
> +{
> + struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
> + u32 word = d->hwirq / IRQS_PER_WORD;
> + u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
> + unsigned int old_cpu = cpu_for_irq(intc, d);
> + unsigned int new_cpu;
> + struct cpumask valid;
> + unsigned long flags;
> + bool enabled;
> +
> + if (!cpumask_and(&valid, &intc->cpumask, dest))
> + return -EINVAL;
> +
> + new_cpu = cpumask_any_and(&valid, cpu_online_mask);
> + if (new_cpu >= nr_cpu_ids)
> + return -EINVAL;
> +
> + dest = cpumask_of(new_cpu);
> +
> + raw_spin_lock_irqsave(&intc->lock[word], flags);
> + if (old_cpu != new_cpu) {
> + enabled = intc->cpus[old_cpu]->enable_cache[word] & mask;
> + if (enabled)
> + __bcm63168_l1_mask(d);
> + cpumask_copy(irq_data_get_affinity_mask(d), dest);
> + if (enabled)
> + __bcm63168_l1_unmask(d);
> + } else {
> + cpumask_copy(irq_data_get_affinity_mask(d), dest);
> + }
> + raw_spin_unlock_irqrestore(&intc->lock[word], flags);
> +
> + return IRQ_SET_MASK_OK_NOCOPY;
> +}
> +
> +static int __init bcm63168_l1_init_one(struct device_node *dn,
> + unsigned int idx,
> + struct bcm63168_l1_chip *intc)
> +{
> + struct resource res;
> + resource_size_t sz;
> + struct bcm63168_l1_cpu *cpu;
> + unsigned int i, n_words;
> +
> + if (of_address_to_resource(dn, idx, &res))
> + return -EINVAL;
> + sz = resource_size(&res);
> + n_words = sz / REG_BYTES_PER_IRQ_WORD;
> +
> + if (n_words > MAX_WORDS)
> + return -EINVAL;
> + else if (!intc->n_words)
> + intc->n_words = n_words;
> + else if (intc->n_words != n_words)
> + return -EINVAL;
> +
> + cpu = intc->cpus[idx] = kzalloc(sizeof(*cpu) + n_words * sizeof(u32),
> + GFP_KERNEL);
> + if (!cpu)
> + return -ENOMEM;
> +
> + cpu->map_base = ioremap(res.start, sz);
> + if (!cpu->map_base)
> + return -ENOMEM;
> +
> + for (i = 0; i < n_words; i++) {
> + cpu->enable_cache[i] = 0;
> + __raw_writel(0, cpu->map_base + reg_enable(intc, i));
> + }
> +
> + cpu->parent_irq = irq_of_parse_and_map(dn, idx);
> + if (!cpu->parent_irq) {
> + pr_err("failed to map parent interrupt %d\n", cpu->parent_irq);
> + return -EINVAL;
> + }
> + irq_set_chained_handler_and_data(cpu->parent_irq,
> + bcm63168_l1_irq_handle, intc);
> +
> + return 0;
> +}
> +
> +static struct irq_chip bcm63168_l1_irq_chip = {
> + .name = "bcm63168-l1",
> + .irq_mask = bcm63168_l1_mask,
> + .irq_unmask = bcm63168_l1_unmask,
> + .irq_set_affinity = bcm63168_l1_set_affinity,
> +};
You are already allocing memory, why not alloc this one as well?
> +
> +static int bcm63168_l1_map(struct irq_domain *d, unsigned int virq,
> + irq_hw_number_t hw_irq)
> +{
> + irq_set_chip_and_handler(virq,
> + &bcm63168_l1_irq_chip, handle_percpu_irq);
> + irq_set_chip_data(virq, d->host_data);
> + return 0;
> +}
> +
> +static const struct irq_domain_ops bcm63168_l1_domain_ops = {
> + .xlate = irq_domain_xlate_onecell,
> + .map = bcm63168_l1_map,
> +};
> +
> +static int __init bcm63168_l1_of_init(struct device_node *dn,
> + struct device_node *parent)
> +{
> + struct bcm63168_l1_chip *intc;
> + unsigned int idx;
> + int ret;
> +
> + intc = kzalloc(sizeof(*intc), GFP_KERNEL);
> + if (!intc)
> + return -ENOMEM;
> +
> + cpumask_clear(&intc->cpumask);
intc->cpumask is already cleared since kzalloc'd.
> +
> + for_each_possible_cpu(idx) {
> + ret = bcm63168_l1_init_one(dn, idx, intc);
> + if (ret)
> + pr_err("failed to init intc L1 for cpu %d: %d\n",
> + idx, ret);
> + else
> + cpumask_set_cpu(idx, &intc->cpumask);
> + }
> +
> + if (!cpumask_weight(&intc->cpumask)) {
> + ret = -ENODEV;
> + goto out_free;
> + }
> +
> + for (idx = 0; idx < intc->n_words; idx++)
> + raw_spin_lock_init(&intc->lock[idx]);
Do you really need a spinlock for each word?
> +
> + intc->domain = irq_domain_add_linear(dn, IRQS_PER_WORD * intc->n_words,
> + &bcm63168_l1_domain_ops,
> + intc);
> + if (!intc->domain) {
> + ret = -ENOMEM;
> + goto out_unmap;
> + }
> +
> + pr_info("registered BCM63168 L1 intc (IRQs: %d)\n",
> + IRQS_PER_WORD * intc->n_words);
> + for_each_cpu(idx, &intc->cpumask) {
> + struct bcm63168_l1_cpu *cpu = intc->cpus[idx];
> +
> + pr_info(" CPU%u at MMIO 0x%p (irq = %d)\n", idx,
> + cpu->map_base, cpu->parent_irq);
> + }
> +
> + return 0;
> +
> +out_unmap:
> + for_each_possible_cpu(idx) {
> + struct bcm63168_l1_cpu *cpu = intc->cpus[idx];
> +
> + if (cpu) {
> + if (cpu->map_base)
> + iounmap(cpu->map_base);
> + kfree(cpu);
> + }
> + }
> +out_free:
> + kfree(intc);
> + return ret;
> +}
> +
> +IRQCHIP_DECLARE(bcm63168_l1, "brcm,bcm63168-l1-intc", bcm63168_l1_of_init);
> --
> 2.1.4
Jonas
On Sun, Nov 15, 2015 at 04:51:16PM +0000, Simon Arlott wrote:
> Add device tree binding for the BCM63168 interrupt controller.
>
> This controller is similar to the SMP-capable BCM7038 and
> the BCM3380 but with packed interrupt registers.
>
> Signed-off-by: Simon Arlott <[email protected]>
Acked-by: Rob Herring <[email protected]>
> ---
> .../interrupt-controller/brcm,bcm63168-l1-intc.txt | 57 ++++++++++++++++++++++
> 1 file changed, 57 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt
> new file mode 100644
> index 0000000..636a6db
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm63168-l1-intc.txt
> @@ -0,0 +1,57 @@
> +Broadcom BCM63168-style Level 1 interrupt controller
> +
> +This block is a first level interrupt controller that is typically connected
> +directly to one of the HW INT lines on each CPU.
> +
> +Key elements of the hardware design include:
> +
> +- 64 or 128 incoming level IRQ lines
> +
> +- Most onchip peripherals are wired directly to an L1 input
> +
> +- A separate instance of the register set for each CPU, allowing individual
> + peripheral IRQs to be routed to any CPU
> +
> +- Contains one or more enable/status word pairs per CPU
> +
> +- No atomic set/clear operations
> +
> +- No polarity/level/edge settings
> +
> +- No FIFO or priority encoder logic; software is expected to read all
> + 2-4 status words to determine which IRQs are pending
> +
> +Required properties:
> +
> +- compatible: should be "brcm,bcm63168-l1-intc"
> +- reg: specifies the base physical address and size of the registers;
> + the number of supported IRQs is inferred from the size argument
> +- interrupt-controller: identifies the node as an interrupt controller
> +- #interrupt-cells: specifies the number of cells needed to encode an interrupt
> + source, should be 1.
> +- interrupt-parent: specifies the phandle to the parent interrupt controller(s)
> + this one is cascaded from
> +- interrupts: specifies the interrupt line(s) in the interrupt-parent controller
> + node; valid values depend on the type of parent interrupt controller
> +
> +If multiple reg ranges and interrupt-parent entries are present on an SMP
> +system, the driver will allow IRQ SMP affinity to be set up through the
> +/proc/irq/ interface. In the simplest possible configuration, only one
> +reg range and one interrupt-parent is needed.
> +
> +The driver operates in native CPU endian by default, there is no support for
> +specifying an alternative endianness.
> +
> +Example:
> +
> +periph_intc: periph_intc@10000000 {
> + compatible = "brcm,bcm63168-l1-intc";
> + reg = <0x10000020 0x20>,
> + <0x10000040 0x20>;
> +
> + interrupt-controller;
> + #interrupt-cells = <1>;
> +
> + interrupt-parent = <&cpu_intc>;
> + interrupts = <2>, <3>;
> +};
> --
> 2.1.4
>
> --
> Simon Arlott
Add device tree binding for the BCM6345 interrupt controller.
This controller is similar to the SMP-capable BCM7038 and
the BCM3380 but with packed interrupt registers.
Signed-off-by: Simon Arlott <[email protected]>
---
On 16/11/15 15:34, Rob Herring wrote:
> On Sun, Nov 15, 2015 at 04:51:16PM +0000, Simon Arlott wrote:
>> Add device tree binding for the BCM63168 interrupt controller.
>>
>> This controller is similar to the SMP-capable BCM7038 and
>> the BCM3380 but with packed interrupt registers.
>>
>> Signed-off-by: Simon Arlott <[email protected]>
>
> Acked-by: Rob Herring <[email protected]>
I'm going to rename this to bcm6345-l1 as suggested by Jonas Gorski, and
the binding now specifies a <soc> version of the compatible name to be
included.
.../interrupt-controller/brcm,bcm6345-l1-intc.txt | 57 ++++++++++++++++++++++
1 file changed, 57 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-l1-intc.txt
diff --git a/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-l1-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-l1-intc.txt
new file mode 100644
index 0000000..c5bdcf1
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-l1-intc.txt
@@ -0,0 +1,57 @@
+Broadcom BCM6345-style Level 1 interrupt controller
+
+This block is a first level interrupt controller that is typically connected
+directly to one of the HW INT lines on each CPU.
+
+Key elements of the hardware design include:
+
+- 32, 64 or 128 incoming level IRQ lines
+
+- Most onchip peripherals are wired directly to an L1 input
+
+- A separate instance of the register set for each CPU, allowing individual
+ peripheral IRQs to be routed to any CPU
+
+- Contains one or more enable/status word pairs per CPU
+
+- No atomic set/clear operations
+
+- No polarity/level/edge settings
+
+- No FIFO or priority encoder logic; software is expected to read all
+ 2-4 status words to determine which IRQs are pending
+
+Required properties:
+
+- compatible: should be "brcm,bcm<soc>-l1-intc", "brcm,bcm6345-l1-intc"
+- reg: specifies the base physical address and size of the registers;
+ the number of supported IRQs is inferred from the size argument
+- interrupt-controller: identifies the node as an interrupt controller
+- #interrupt-cells: specifies the number of cells needed to encode an interrupt
+ source, should be 1.
+- interrupt-parent: specifies the phandle to the parent interrupt controller(s)
+ this one is cascaded from
+- interrupts: specifies the interrupt line(s) in the interrupt-parent controller
+ node; valid values depend on the type of parent interrupt controller
+
+If multiple reg ranges and interrupt-parent entries are present on an SMP
+system, the driver will allow IRQ SMP affinity to be set up through the
+/proc/irq/ interface. In the simplest possible configuration, only one
+reg range and one interrupt-parent is needed.
+
+The driver operates in native CPU endian by default, there is no support for
+specifying an alternative endianness.
+
+Example:
+
+periph_intc: periph_intc@10000000 {
+ compatible = "brcm,bcm63168-l1-intc", "brcm,bcm6345-l1-intc";
+ reg = <0x10000020 0x20>,
+ <0x10000040 0x20>;
+
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ interrupt-parent = <&cpu_intc>;
+ interrupts = <2>, <3>;
+};
--
2.1.4
--
Simon Arlott
On 16/11/15 12:56, Jonas Gorski wrote:
> On Sun, Nov 15, 2015 at 5:53 PM, Simon Arlott <[email protected]> wrote:
>> Add the BCM63168 interrupt controller based on the SMP-capable BCM7038
>> and the BCM3380 but with packed interrupt registers.
>>
>> Add the BCM63168 interrupt controller to a list with the existing BCM7038
>> so that interrupts on CPU1 are not ignored.
This will be renamed to bcm6345-l1.
...
>> diff --git a/drivers/irqchip/irq-bcm63168-l1.c b/drivers/irqchip/irq-bcm63168-l1.c
>> new file mode 100644
>> index 0000000..5a144af
>> --- /dev/null
>> +++ b/drivers/irqchip/irq-bcm63168-l1.c
>> @@ -0,0 +1,372 @@
>> +/*
>> + * Broadcom BCM63168 style Level 1 interrupt controller driver
>> + *
>> + * Copyright (C) 2014 Broadcom Corporation
>> + * Copyright 2015 Simon Arlott
>> + *
>> + * 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.
>> + *
>> + * This is based on the BCM7038 (which supports SMP) but with a single
>> + * enable register instead of separate mask/set/clear registers.
>> + *
>> + * The BCM3380 has a similar mask/status register layout, but each pair
>> + * of words is at separate locations (and SMP is not supported).
>> + *
>> + * ENABLE/STATUS words are packed next to each other for each CPU:
>> + *
>> + * 6368:
>> + * 0x1000_0020: CPU0_W0_ENABLE
>> + * 0x1000_0024: CPU0_W1_ENABLE
>> + * 0x1000_0028: CPU0_W0_STATUS IRQs 31-63
>> + * 0x1000_002c: CPU0_W1_STATUS IRQs 0-31
>> + * 0x1000_0030: CPU1_W0_ENABLE
>> + * 0x1000_0034: CPU1_W1_ENABLE
>> + * 0x1000_0038: CPU1_W0_STATUS IRQs 31-63
>> + * 0x1000_003c: CPU1_W1_STATUS IRQs 0-31
>> + *
>> + * 63168:
>> + * 0x1000_0020: CPU0_W0_ENABLE
>> + * 0x1000_0024: CPU0_W1_ENABLE
>> + * 0x1000_0028: CPU0_W2_ENABLE
>> + * 0x1000_002c: CPU0_W3_ENABLE
>> + * 0x1000_0030: CPU0_W0_STATUS IRQs 96-127
>> + * 0x1000_0034: CPU0_W1_STATUS IRQs 64-95
>> + * 0x1000_0038: CPU0_W2_STATUS IRQs 32-63
>> + * 0x1000_003c: CPU0_W3_STATUS IRQs 0-31
>> + * 0x1000_0040: CPU1_W0_ENABLE
>> + * 0x1000_0044: CPU1_W1_ENABLE
>> + * 0x1000_0048: CPU1_W2_ENABLE
>> + * 0x1000_004c: CPU1_W3_ENABLE
>> + * 0x1000_0050: CPU1_W0_STATUS IRQs 96-127
>> + * 0x1000_0054: CPU1_W1_STATUS IRQs 64-95
>> + * 0x1000_0058: CPU1_W2_STATUS IRQs 32-63
>> + * 0x1000_005c: CPU1_W3_STATUS IRQs 0-31
>> + *
>> + * IRQs are numbered in CPU native endian order
>> + * (which is big-endian in these examples)
>> + */
>> +
...
>> +static inline unsigned int reg_enable(struct bcm63168_l1_chip *intc,
>> + unsigned int word)
>> +{
>> +#ifdef __BIG_ENDIAN
>> + return (1 * intc->n_words - word - 1) * sizeof(u32);
>> +#else
>> + return (0 * intc->n_words + word) * sizeof(u32);
>
> Huh, do the words really change the order when running in LE? I would
> have expected
> to each 32-bit word to contain the same interrupts, just bit-order reversed.
Without having a LE version of this SoC to check it on, I don't know...
but this device binding is specified as native endian and the current
ordering is definitely BE as interrupt "0" is in the last register.
>> +#endif
>> +}
>> +
>> +static inline unsigned int reg_status(struct bcm63168_l1_chip *intc,
>> + unsigned int word)
>> +{
>> +#ifdef __BIG_ENDIAN
>> + return (2 * intc->n_words - word - 1) * sizeof(u32);
>> +#else
>> + return (1 * intc->n_words + word) * sizeof(u32);
>> +#endif
>> +}
>> +
>> +static inline unsigned int cpu_for_irq(struct bcm63168_l1_chip *intc,
>> + struct irq_data *d)
>> +{
>> + return cpumask_first_and(&intc->cpumask, irq_data_get_affinity_mask(d));
>> +}
>> +
>> +static void bcm63168_l1_irq_handle(struct irq_desc *desc)
>> +{
>> + struct bcm63168_l1_chip *intc = irq_desc_get_handler_data(desc);
>> + struct bcm63168_l1_cpu *cpu;
>> + struct irq_chip *chip = irq_desc_get_chip(desc);
>> + unsigned int idx;
>> +
>> +#ifdef CONFIG_SMP
>> + cpu = intc->cpus[cpu_logical_map(smp_processor_id())];
>> +#else
>> + cpu = intc->cpus[0];
>> +#endif
>
> This looks expensive, can they change during runtime? If not, maybe
> just assign intc->cpus[] accordingly at probe time, so you can just do
> intc->cpus[smp_processor_id()] without any #ifdefs.
It's an array lookup in mips, so it becomes:
cpu = intc->cpus[__cpu_logical_map[smp_processor_id()]];
I could just remove the ifdef because smp_processor_id() becomes 0. It
looks like it still does the array lookup.
The bcm7038-l1 driver had this ifdef.
>> +
>> + chained_irq_enter(chip, desc);
>> +
>> + for (idx = 0; idx < intc->n_words; idx++) {
>> + int base = idx * IRQS_PER_WORD;
>> + unsigned long pending;
>> + irq_hw_number_t hwirq;
>> + unsigned int irq;
>> +
>> + pending = __raw_readl(cpu->map_base + reg_status(intc, idx));
>> + pending &= __raw_readl(cpu->map_base + reg_enable(intc, idx));
>
> Is it save to access the registers without any locking? 7038-l1
> doesn't think so.
7038-l1 is using a lock because it's accessing its own copy of the
enable mask. I read the controller's enable register to determine what
is currently enabled.
When the enable mask is modified it uses intc->cpus[n]->enable_cache
(with a lock held) and then writes it to the controller.
This way I don't need to have both CPUs compete with each other for the
same lock within the interrupt handler itself.
>> +
>> + for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) {
>> + irq = irq_linear_revmap(intc->domain, base + hwirq);
>> + if (irq)
>> + do_IRQ(irq);
>> + else
>> + spurious_interrupt();
>> + }
>> + }
>> +
>> + chained_irq_exit(chip, desc);
>> +}
>> +
>> +static inline void __bcm63168_l1_unmask(struct irq_data *d)
>> +{
>> + struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
>> + u32 word = d->hwirq / IRQS_PER_WORD;
>> + u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
>> + unsigned int cpu_idx = cpu_for_irq(intc, d);
>> +
>> + intc->cpus[cpu_idx]->enable_cache[word] |= mask;
>> + __raw_writel(intc->cpus[cpu_idx]->enable_cache[word],
>> + intc->cpus[cpu_idx]->map_base + reg_enable(intc, word));
>> +}
>> +
>> +static inline void __bcm63168_l1_mask(struct irq_data *d)
>> +{
>> + struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
>> + u32 word = d->hwirq / IRQS_PER_WORD;
>> + u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
>> + unsigned int cpu_idx = cpu_for_irq(intc, d);
>> +
>> + intc->cpus[cpu_idx]->enable_cache[word] &= ~mask;
>> + __raw_writel(intc->cpus[cpu_idx]->enable_cache[word],
>> + intc->cpus[cpu_idx]->map_base + reg_enable(intc, word));
>> +}
>> +
>> +static void bcm63168_l1_unmask(struct irq_data *d)
>> +{
>> + struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
>> + u32 word = d->hwirq / IRQS_PER_WORD;
>> + unsigned long flags;
>> +
>> + raw_spin_lock_irqsave(&intc->lock[word], flags);
>> + __bcm63168_l1_unmask(d);
>> + raw_spin_unlock_irqrestore(&intc->lock[word], flags);
>> +}
>> +
>> +static void bcm63168_l1_mask(struct irq_data *d)
>> +{
>> + struct bcm63168_l1_chip *intc = irq_data_get_irq_chip_data(d);
>> + u32 word = d->hwirq / IRQS_PER_WORD;
>> + unsigned long flags;
>> +
>> + raw_spin_lock_irqsave(&intc->lock[word], flags);
>> + __bcm63168_l1_mask(d);
>> + raw_spin_unlock_irqrestore(&intc->lock[word], flags);
>> +}
>> +
...
>> +
>> +static struct irq_chip bcm63168_l1_irq_chip = {
>> + .name = "bcm63168-l1",
>> + .irq_mask = bcm63168_l1_mask,
>> + .irq_unmask = bcm63168_l1_unmask,
>> + .irq_set_affinity = bcm63168_l1_set_affinity,
>> +};
>
> You are already allocing memory, why not alloc this one as well?
The data is a const name and set of function pointers that is never
modified.
>> +
>> +static int bcm63168_l1_map(struct irq_domain *d, unsigned int virq,
>> + irq_hw_number_t hw_irq)
>> +{
>> + irq_set_chip_and_handler(virq,
>> + &bcm63168_l1_irq_chip, handle_percpu_irq);
>> + irq_set_chip_data(virq, d->host_data);
>> + return 0;
>> +}
>> +
>> +static const struct irq_domain_ops bcm63168_l1_domain_ops = {
>> + .xlate = irq_domain_xlate_onecell,
>> + .map = bcm63168_l1_map,
>> +};
>> +
>> +static int __init bcm63168_l1_of_init(struct device_node *dn,
>> + struct device_node *parent)
>> +{
>> + struct bcm63168_l1_chip *intc;
>> + unsigned int idx;
>> + int ret;
>> +
>> + intc = kzalloc(sizeof(*intc), GFP_KERNEL);
>> + if (!intc)
>> + return -ENOMEM;
>> +
>> + cpumask_clear(&intc->cpumask);
>
> intc->cpumask is already cleared since kzalloc'd.
Ok.
>> +
>> + for_each_possible_cpu(idx) {
>> + ret = bcm63168_l1_init_one(dn, idx, intc);
>> + if (ret)
>> + pr_err("failed to init intc L1 for cpu %d: %d\n",
>> + idx, ret);
>> + else
>> + cpumask_set_cpu(idx, &intc->cpumask);
>> + }
>> +
>> + if (!cpumask_weight(&intc->cpumask)) {
>> + ret = -ENODEV;
>> + goto out_free;
>> + }
>> +
>> + for (idx = 0; idx < intc->n_words; idx++)
>> + raw_spin_lock_init(&intc->lock[idx]);
>
> Do you really need a spinlock for each word?
No, this made more sense when I was using handle_level_irq and had
interrupts going to both CPUs, but the controller is too eager to
dispatch the same interrupt to both CPUs almost-simultaneously.
I'll change it to a single spinlock.
>> +
>> + intc->domain = irq_domain_add_linear(dn, IRQS_PER_WORD * intc->n_words,
>> + &bcm63168_l1_domain_ops,
>> + intc);
>> + if (!intc->domain) {
>> + ret = -ENOMEM;
>> + goto out_unmap;
>> + }
>> +
>> + pr_info("registered BCM63168 L1 intc (IRQs: %d)\n",
>> + IRQS_PER_WORD * intc->n_words);
>> + for_each_cpu(idx, &intc->cpumask) {
>> + struct bcm63168_l1_cpu *cpu = intc->cpus[idx];
>> +
>> + pr_info(" CPU%u at MMIO 0x%p (irq = %d)\n", idx,
>> + cpu->map_base, cpu->parent_irq);
>> + }
>> +
>> + return 0;
>> +
>> +out_unmap:
>> + for_each_possible_cpu(idx) {
>> + struct bcm63168_l1_cpu *cpu = intc->cpus[idx];
>> +
>> + if (cpu) {
>> + if (cpu->map_base)
>> + iounmap(cpu->map_base);
>> + kfree(cpu);
>> + }
>> + }
>> +out_free:
>> + kfree(intc);
>> + return ret;
>> +}
>> +
>> +IRQCHIP_DECLARE(bcm63168_l1, "brcm,bcm63168-l1-intc", bcm63168_l1_of_init);
>> --
>> 2.1.4
>
>
> Jonas
>
--
Simon Arlott
On Mon, Nov 16, 2015 at 07:12:36PM +0000, Simon Arlott wrote:
> Add device tree binding for the BCM6345 interrupt controller.
>
> This controller is similar to the SMP-capable BCM7038 and
> the BCM3380 but with packed interrupt registers.
>
> Signed-off-by: Simon Arlott <[email protected]>
> ---
> On 16/11/15 15:34, Rob Herring wrote:
> > On Sun, Nov 15, 2015 at 04:51:16PM +0000, Simon Arlott wrote:
> >> Add device tree binding for the BCM63168 interrupt controller.
> >>
> >> This controller is similar to the SMP-capable BCM7038 and
> >> the BCM3380 but with packed interrupt registers.
> >>
> >> Signed-off-by: Simon Arlott <[email protected]>
> >
> > Acked-by: Rob Herring <[email protected]>
>
> I'm going to rename this to bcm6345-l1 as suggested by Jonas Gorski, and
> the binding now specifies a <soc> version of the compatible name to be
> included.
Acked-by: Rob Herring <[email protected]>
> .../interrupt-controller/brcm,bcm6345-l1-intc.txt | 57 ++++++++++++++++++++++
> 1 file changed, 57 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-l1-intc.txt
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-l1-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-l1-intc.txt
> new file mode 100644
> index 0000000..c5bdcf1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-l1-intc.txt
> @@ -0,0 +1,57 @@
> +Broadcom BCM6345-style Level 1 interrupt controller
> +
> +This block is a first level interrupt controller that is typically connected
> +directly to one of the HW INT lines on each CPU.
> +
> +Key elements of the hardware design include:
> +
> +- 32, 64 or 128 incoming level IRQ lines
> +
> +- Most onchip peripherals are wired directly to an L1 input
> +
> +- A separate instance of the register set for each CPU, allowing individual
> + peripheral IRQs to be routed to any CPU
> +
> +- Contains one or more enable/status word pairs per CPU
> +
> +- No atomic set/clear operations
> +
> +- No polarity/level/edge settings
> +
> +- No FIFO or priority encoder logic; software is expected to read all
> + 2-4 status words to determine which IRQs are pending
> +
> +Required properties:
> +
> +- compatible: should be "brcm,bcm<soc>-l1-intc", "brcm,bcm6345-l1-intc"
> +- reg: specifies the base physical address and size of the registers;
> + the number of supported IRQs is inferred from the size argument
> +- interrupt-controller: identifies the node as an interrupt controller
> +- #interrupt-cells: specifies the number of cells needed to encode an interrupt
> + source, should be 1.
> +- interrupt-parent: specifies the phandle to the parent interrupt controller(s)
> + this one is cascaded from
> +- interrupts: specifies the interrupt line(s) in the interrupt-parent controller
> + node; valid values depend on the type of parent interrupt controller
> +
> +If multiple reg ranges and interrupt-parent entries are present on an SMP
> +system, the driver will allow IRQ SMP affinity to be set up through the
> +/proc/irq/ interface. In the simplest possible configuration, only one
> +reg range and one interrupt-parent is needed.
> +
> +The driver operates in native CPU endian by default, there is no support for
> +specifying an alternative endianness.
> +
> +Example:
> +
> +periph_intc: periph_intc@10000000 {
> + compatible = "brcm,bcm63168-l1-intc", "brcm,bcm6345-l1-intc";
> + reg = <0x10000020 0x20>,
> + <0x10000040 0x20>;
> +
> + interrupt-controller;
> + #interrupt-cells = <1>;
> +
> + interrupt-parent = <&cpu_intc>;
> + interrupts = <2>, <3>;
> +};
> --
> 2.1.4
>
> --
> Simon Arlott
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
Add the BCM6345 interrupt controller based on the SMP-capable BCM7038
and the BCM3380 but with packed interrupt registers.
Add the BCM6345 interrupt controller to a list with the existing BCM7038
so that interrupts on CPU1 are not ignored.
Update the maintainers file list for BMIPS to include this driver.
Signed-off-by: Simon Arlott <[email protected]>
---
Renamed from bcm63168 to bcm6345.
Compared interrupt register word ordering with the bcm7038 driver
which is LE and has the words in LE order, so using ordering that
matches the CPU is appropriate.
Removed unnecessary cpumask memset.
Changed to use only one spinlock.
MAINTAINERS | 1 +
arch/mips/Kconfig | 1 +
arch/mips/bmips/irq.c | 10 +-
drivers/irqchip/Kconfig | 5 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-bcm6345-l1.c | 366 +++++++++++++++++++++++++++++++++++++++
6 files changed, 382 insertions(+), 2 deletions(-)
create mode 100644 drivers/irqchip/irq-bcm6345-l1.c
diff --git a/MAINTAINERS b/MAINTAINERS
index e9caa4b..63bf54a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2373,6 +2373,7 @@ F: arch/mips/bmips/*
F: arch/mips/include/asm/mach-bmips/*
F: arch/mips/kernel/*bmips*
F: arch/mips/boot/dts/brcm/bcm*.dts*
+F: drivers/irqchip/irq-bcm63*
F: drivers/irqchip/irq-bcm7*
F: drivers/irqchip/irq-brcmstb*
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index e3aa5b0..e536f3f 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -149,6 +149,7 @@ config BMIPS_GENERIC
select CSRC_R4K
select SYNC_R4K
select COMMON_CLK
+ select BCM6345_L1_IRQ
select BCM7038_L1_IRQ
select BCM7120_L2_IRQ
select BRCMSTB_L2_IRQ
diff --git a/arch/mips/bmips/irq.c b/arch/mips/bmips/irq.c
index e7fc6f934..7efefcf 100644
--- a/arch/mips/bmips/irq.c
+++ b/arch/mips/bmips/irq.c
@@ -15,6 +15,12 @@
#include <asm/irq_cpu.h>
#include <asm/time.h>
+static const struct of_device_id smp_intc_dt_match[] = {
+ { .compatible = "brcm,bcm7038-l1-intc" },
+ { .compatible = "brcm,bcm6345-l1-intc" },
+ {}
+};
+
unsigned int get_c0_compare_int(void)
{
return CP0_LEGACY_COMPARE_IRQ;
@@ -24,8 +30,8 @@ void __init arch_init_irq(void)
{
struct device_node *dn;
- /* Only the STB (bcm7038) controller supports SMP IRQ affinity */
- dn = of_find_compatible_node(NULL, NULL, "brcm,bcm7038-l1-intc");
+ /* Only these controllers support SMP IRQ affinity */
+ dn = of_find_matching_node(NULL, smp_intc_dt_match);
if (dn)
of_node_put(dn);
else
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 4d7294e..d307bb3 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -65,6 +65,11 @@ config I8259
bool
select IRQ_DOMAIN
+config BCM6345_L1_IRQ
+ bool
+ select GENERIC_IRQ_CHIP
+ select IRQ_DOMAIN
+
config BCM7038_L1_IRQ
bool
select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 177f78f..ded59cf 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o
obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o
obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o
+obj-$(CONFIG_BCM6345_L1_IRQ) += irq-bcm6345-l1.o
obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o
obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o
obj-$(CONFIG_BRCMSTB_L2_IRQ) += irq-brcmstb-l2.o
diff --git a/drivers/irqchip/irq-bcm6345-l1.c b/drivers/irqchip/irq-bcm6345-l1.c
new file mode 100644
index 0000000..31a69f4
--- /dev/null
+++ b/drivers/irqchip/irq-bcm6345-l1.c
@@ -0,0 +1,366 @@
+/*
+ * Broadcom BCM6345 style Level 1 interrupt controller driver
+ *
+ * Copyright (C) 2014 Broadcom Corporation
+ * Copyright 2015 Simon Arlott
+ *
+ * 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.
+ *
+ * This is based on the BCM7038 (which supports SMP) but with a single
+ * enable register instead of separate mask/set/clear registers.
+ *
+ * The BCM3380 has a similar mask/status register layout, but each pair
+ * of words is at separate locations (and SMP is not supported).
+ *
+ * ENABLE/STATUS words are packed next to each other for each CPU:
+ *
+ * BCM6368:
+ * 0x1000_0020: CPU0_W0_ENABLE
+ * 0x1000_0024: CPU0_W1_ENABLE
+ * 0x1000_0028: CPU0_W0_STATUS IRQs 31-63
+ * 0x1000_002c: CPU0_W1_STATUS IRQs 0-31
+ * 0x1000_0030: CPU1_W0_ENABLE
+ * 0x1000_0034: CPU1_W1_ENABLE
+ * 0x1000_0038: CPU1_W0_STATUS IRQs 31-63
+ * 0x1000_003c: CPU1_W1_STATUS IRQs 0-31
+ *
+ * BCM63168:
+ * 0x1000_0020: CPU0_W0_ENABLE
+ * 0x1000_0024: CPU0_W1_ENABLE
+ * 0x1000_0028: CPU0_W2_ENABLE
+ * 0x1000_002c: CPU0_W3_ENABLE
+ * 0x1000_0030: CPU0_W0_STATUS IRQs 96-127
+ * 0x1000_0034: CPU0_W1_STATUS IRQs 64-95
+ * 0x1000_0038: CPU0_W2_STATUS IRQs 32-63
+ * 0x1000_003c: CPU0_W3_STATUS IRQs 0-31
+ * 0x1000_0040: CPU1_W0_ENABLE
+ * 0x1000_0044: CPU1_W1_ENABLE
+ * 0x1000_0048: CPU1_W2_ENABLE
+ * 0x1000_004c: CPU1_W3_ENABLE
+ * 0x1000_0050: CPU1_W0_STATUS IRQs 96-127
+ * 0x1000_0054: CPU1_W1_STATUS IRQs 64-95
+ * 0x1000_0058: CPU1_W2_STATUS IRQs 32-63
+ * 0x1000_005c: CPU1_W3_STATUS IRQs 0-31
+ *
+ * IRQs are numbered in CPU native endian order
+ * (which is big-endian in these examples)
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/cpumask.h>
+#include <linux/kconfig.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+#include <linux/types.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+
+#define IRQS_PER_WORD 32
+#define REG_BYTES_PER_IRQ_WORD (sizeof(u32) * 2)
+
+struct bcm6345_l1_cpu;
+
+struct bcm6345_l1_chip {
+ raw_spinlock_t lock;
+ unsigned int n_words;
+ struct irq_domain *domain;
+ struct cpumask cpumask;
+ struct bcm6345_l1_cpu *cpus[NR_CPUS];
+};
+
+struct bcm6345_l1_cpu {
+ void __iomem *map_base;
+ unsigned int parent_irq;
+ u32 enable_cache[];
+};
+
+static inline unsigned int reg_enable(struct bcm6345_l1_chip *intc,
+ unsigned int word)
+{
+#ifdef __BIG_ENDIAN
+ return (1 * intc->n_words - word - 1) * sizeof(u32);
+#else
+ return (0 * intc->n_words + word) * sizeof(u32);
+#endif
+}
+
+static inline unsigned int reg_status(struct bcm6345_l1_chip *intc,
+ unsigned int word)
+{
+#ifdef __BIG_ENDIAN
+ return (2 * intc->n_words - word - 1) * sizeof(u32);
+#else
+ return (1 * intc->n_words + word) * sizeof(u32);
+#endif
+}
+
+static inline unsigned int cpu_for_irq(struct bcm6345_l1_chip *intc,
+ struct irq_data *d)
+{
+ return cpumask_first_and(&intc->cpumask, irq_data_get_affinity_mask(d));
+}
+
+static void bcm6345_l1_irq_handle(struct irq_desc *desc)
+{
+ struct bcm6345_l1_chip *intc = irq_desc_get_handler_data(desc);
+ struct bcm6345_l1_cpu *cpu;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int idx;
+
+#ifdef CONFIG_SMP
+ cpu = intc->cpus[cpu_logical_map(smp_processor_id())];
+#else
+ cpu = intc->cpus[0];
+#endif
+
+ chained_irq_enter(chip, desc);
+
+ for (idx = 0; idx < intc->n_words; idx++) {
+ int base = idx * IRQS_PER_WORD;
+ unsigned long pending;
+ irq_hw_number_t hwirq;
+ unsigned int irq;
+
+ pending = __raw_readl(cpu->map_base + reg_status(intc, idx));
+ pending &= __raw_readl(cpu->map_base + reg_enable(intc, idx));
+
+ for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) {
+ irq = irq_linear_revmap(intc->domain, base + hwirq);
+ if (irq)
+ do_IRQ(irq);
+ else
+ spurious_interrupt();
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static inline void __bcm6345_l1_unmask(struct irq_data *d)
+{
+ struct bcm6345_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+ unsigned int cpu_idx = cpu_for_irq(intc, d);
+
+ intc->cpus[cpu_idx]->enable_cache[word] |= mask;
+ __raw_writel(intc->cpus[cpu_idx]->enable_cache[word],
+ intc->cpus[cpu_idx]->map_base + reg_enable(intc, word));
+}
+
+static inline void __bcm6345_l1_mask(struct irq_data *d)
+{
+ struct bcm6345_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+ unsigned int cpu_idx = cpu_for_irq(intc, d);
+
+ intc->cpus[cpu_idx]->enable_cache[word] &= ~mask;
+ __raw_writel(intc->cpus[cpu_idx]->enable_cache[word],
+ intc->cpus[cpu_idx]->map_base + reg_enable(intc, word));
+}
+
+static void bcm6345_l1_unmask(struct irq_data *d)
+{
+ struct bcm6345_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&intc->lock, flags);
+ __bcm6345_l1_unmask(d);
+ raw_spin_unlock_irqrestore(&intc->lock, flags);
+}
+
+static void bcm6345_l1_mask(struct irq_data *d)
+{
+ struct bcm6345_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&intc->lock, flags);
+ __bcm6345_l1_mask(d);
+ raw_spin_unlock_irqrestore(&intc->lock, flags);
+}
+
+static int bcm6345_l1_set_affinity(struct irq_data *d,
+ const struct cpumask *dest,
+ bool force)
+{
+ struct bcm6345_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+ unsigned int old_cpu = cpu_for_irq(intc, d);
+ unsigned int new_cpu;
+ struct cpumask valid;
+ unsigned long flags;
+ bool enabled;
+
+ if (!cpumask_and(&valid, &intc->cpumask, dest))
+ return -EINVAL;
+
+ new_cpu = cpumask_any_and(&valid, cpu_online_mask);
+ if (new_cpu >= nr_cpu_ids)
+ return -EINVAL;
+
+ dest = cpumask_of(new_cpu);
+
+ raw_spin_lock_irqsave(&intc->lock, flags);
+ if (old_cpu != new_cpu) {
+ enabled = intc->cpus[old_cpu]->enable_cache[word] & mask;
+ if (enabled)
+ __bcm6345_l1_mask(d);
+ cpumask_copy(irq_data_get_affinity_mask(d), dest);
+ if (enabled)
+ __bcm6345_l1_unmask(d);
+ } else {
+ cpumask_copy(irq_data_get_affinity_mask(d), dest);
+ }
+ raw_spin_unlock_irqrestore(&intc->lock, flags);
+
+ return IRQ_SET_MASK_OK_NOCOPY;
+}
+
+static int __init bcm6345_l1_init_one(struct device_node *dn,
+ unsigned int idx,
+ struct bcm6345_l1_chip *intc)
+{
+ struct resource res;
+ resource_size_t sz;
+ struct bcm6345_l1_cpu *cpu;
+ unsigned int i, n_words;
+
+ if (of_address_to_resource(dn, idx, &res))
+ return -EINVAL;
+ sz = resource_size(&res);
+ n_words = sz / REG_BYTES_PER_IRQ_WORD;
+
+ if (!intc->n_words)
+ intc->n_words = n_words;
+ else if (intc->n_words != n_words)
+ return -EINVAL;
+
+ cpu = intc->cpus[idx] = kzalloc(sizeof(*cpu) + n_words * sizeof(u32),
+ GFP_KERNEL);
+ if (!cpu)
+ return -ENOMEM;
+
+ cpu->map_base = ioremap(res.start, sz);
+ if (!cpu->map_base)
+ return -ENOMEM;
+
+ for (i = 0; i < n_words; i++) {
+ cpu->enable_cache[i] = 0;
+ __raw_writel(0, cpu->map_base + reg_enable(intc, i));
+ }
+
+ cpu->parent_irq = irq_of_parse_and_map(dn, idx);
+ if (!cpu->parent_irq) {
+ pr_err("failed to map parent interrupt %d\n", cpu->parent_irq);
+ return -EINVAL;
+ }
+ irq_set_chained_handler_and_data(cpu->parent_irq,
+ bcm6345_l1_irq_handle, intc);
+
+ return 0;
+}
+
+static struct irq_chip bcm6345_l1_irq_chip = {
+ .name = "bcm6345-l1",
+ .irq_mask = bcm6345_l1_mask,
+ .irq_unmask = bcm6345_l1_unmask,
+ .irq_set_affinity = bcm6345_l1_set_affinity,
+};
+
+static int bcm6345_l1_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hw_irq)
+{
+ irq_set_chip_and_handler(virq,
+ &bcm6345_l1_irq_chip, handle_percpu_irq);
+ irq_set_chip_data(virq, d->host_data);
+ return 0;
+}
+
+static const struct irq_domain_ops bcm6345_l1_domain_ops = {
+ .xlate = irq_domain_xlate_onecell,
+ .map = bcm6345_l1_map,
+};
+
+static int __init bcm6345_l1_of_init(struct device_node *dn,
+ struct device_node *parent)
+{
+ struct bcm6345_l1_chip *intc;
+ unsigned int idx;
+ int ret;
+
+ intc = kzalloc(sizeof(*intc), GFP_KERNEL);
+ if (!intc)
+ return -ENOMEM;
+
+ for_each_possible_cpu(idx) {
+ ret = bcm6345_l1_init_one(dn, idx, intc);
+ if (ret)
+ pr_err("failed to init intc L1 for cpu %d: %d\n",
+ idx, ret);
+ else
+ cpumask_set_cpu(idx, &intc->cpumask);
+ }
+
+ if (!cpumask_weight(&intc->cpumask)) {
+ ret = -ENODEV;
+ goto out_free;
+ }
+
+ raw_spin_lock_init(&intc->lock);
+
+ intc->domain = irq_domain_add_linear(dn, IRQS_PER_WORD * intc->n_words,
+ &bcm6345_l1_domain_ops,
+ intc);
+ if (!intc->domain) {
+ ret = -ENOMEM;
+ goto out_unmap;
+ }
+
+ pr_info("registered BCM6345 L1 intc (IRQs: %d)\n",
+ IRQS_PER_WORD * intc->n_words);
+ for_each_cpu(idx, &intc->cpumask) {
+ struct bcm6345_l1_cpu *cpu = intc->cpus[idx];
+
+ pr_info(" CPU%u at MMIO 0x%p (irq = %d)\n", idx,
+ cpu->map_base, cpu->parent_irq);
+ }
+
+ return 0;
+
+out_unmap:
+ for_each_possible_cpu(idx) {
+ struct bcm6345_l1_cpu *cpu = intc->cpus[idx];
+
+ if (cpu) {
+ if (cpu->map_base)
+ iounmap(cpu->map_base);
+ kfree(cpu);
+ }
+ }
+out_free:
+ kfree(intc);
+ return ret;
+}
+
+IRQCHIP_DECLARE(bcm6345_l1, "brcm,bcm6345-l1-intc", bcm6345_l1_of_init);
--
2.1.4
--
Simon Arlott
Add the BCM6345 interrupt controller based on the SMP-capable BCM7038
and the BCM3380 but with packed interrupt registers.
Add the BCM6345 interrupt controller to a list with the existing BCM7038
so that interrupts on CPU1 are not ignored.
Update the maintainers file list for BMIPS to include this driver.
Signed-off-by: Simon Arlott <[email protected]>
---
On 21/11/15 12:38, Simon Arlott wrote:
> Add the BCM6345 interrupt controller based on the SMP-capable BCM7038
> drivers/irqchip/irq-bcm6345-l1.c | 366 +++++++++++++++++++++++++++++++++++++++
Removed unused variable "word" from bcm6345_l1_unmask/bcm6345_l1_mask.
MAINTAINERS | 1 +
arch/mips/Kconfig | 1 +
arch/mips/bmips/irq.c | 10 +-
drivers/irqchip/Kconfig | 5 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-bcm6345-l1.c | 364 +++++++++++++++++++++++++++++++++++++++
6 files changed, 380 insertions(+), 2 deletions(-)
create mode 100644 drivers/irqchip/irq-bcm6345-l1.c
diff --git a/MAINTAINERS b/MAINTAINERS
index e9caa4b..63bf54a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2373,6 +2373,7 @@ F: arch/mips/bmips/*
F: arch/mips/include/asm/mach-bmips/*
F: arch/mips/kernel/*bmips*
F: arch/mips/boot/dts/brcm/bcm*.dts*
+F: drivers/irqchip/irq-bcm63*
F: drivers/irqchip/irq-bcm7*
F: drivers/irqchip/irq-brcmstb*
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index e3aa5b0..e536f3f 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -149,6 +149,7 @@ config BMIPS_GENERIC
select CSRC_R4K
select SYNC_R4K
select COMMON_CLK
+ select BCM6345_L1_IRQ
select BCM7038_L1_IRQ
select BCM7120_L2_IRQ
select BRCMSTB_L2_IRQ
diff --git a/arch/mips/bmips/irq.c b/arch/mips/bmips/irq.c
index e7fc6f934..7efefcf 100644
--- a/arch/mips/bmips/irq.c
+++ b/arch/mips/bmips/irq.c
@@ -15,6 +15,12 @@
#include <asm/irq_cpu.h>
#include <asm/time.h>
+static const struct of_device_id smp_intc_dt_match[] = {
+ { .compatible = "brcm,bcm7038-l1-intc" },
+ { .compatible = "brcm,bcm6345-l1-intc" },
+ {}
+};
+
unsigned int get_c0_compare_int(void)
{
return CP0_LEGACY_COMPARE_IRQ;
@@ -24,8 +30,8 @@ void __init arch_init_irq(void)
{
struct device_node *dn;
- /* Only the STB (bcm7038) controller supports SMP IRQ affinity */
- dn = of_find_compatible_node(NULL, NULL, "brcm,bcm7038-l1-intc");
+ /* Only these controllers support SMP IRQ affinity */
+ dn = of_find_matching_node(NULL, smp_intc_dt_match);
if (dn)
of_node_put(dn);
else
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 4d7294e..d307bb3 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -65,6 +65,11 @@ config I8259
bool
select IRQ_DOMAIN
+config BCM6345_L1_IRQ
+ bool
+ select GENERIC_IRQ_CHIP
+ select IRQ_DOMAIN
+
config BCM7038_L1_IRQ
bool
select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 177f78f..ded59cf 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o
obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o
obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o
+obj-$(CONFIG_BCM6345_L1_IRQ) += irq-bcm6345-l1.o
obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o
obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o
obj-$(CONFIG_BRCMSTB_L2_IRQ) += irq-brcmstb-l2.o
diff --git a/drivers/irqchip/irq-bcm6345-l1.c b/drivers/irqchip/irq-bcm6345-l1.c
new file mode 100644
index 0000000..b844c89
--- /dev/null
+++ b/drivers/irqchip/irq-bcm6345-l1.c
@@ -0,0 +1,364 @@
+/*
+ * Broadcom BCM6345 style Level 1 interrupt controller driver
+ *
+ * Copyright (C) 2014 Broadcom Corporation
+ * Copyright 2015 Simon Arlott
+ *
+ * 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.
+ *
+ * This is based on the BCM7038 (which supports SMP) but with a single
+ * enable register instead of separate mask/set/clear registers.
+ *
+ * The BCM3380 has a similar mask/status register layout, but each pair
+ * of words is at separate locations (and SMP is not supported).
+ *
+ * ENABLE/STATUS words are packed next to each other for each CPU:
+ *
+ * BCM6368:
+ * 0x1000_0020: CPU0_W0_ENABLE
+ * 0x1000_0024: CPU0_W1_ENABLE
+ * 0x1000_0028: CPU0_W0_STATUS IRQs 31-63
+ * 0x1000_002c: CPU0_W1_STATUS IRQs 0-31
+ * 0x1000_0030: CPU1_W0_ENABLE
+ * 0x1000_0034: CPU1_W1_ENABLE
+ * 0x1000_0038: CPU1_W0_STATUS IRQs 31-63
+ * 0x1000_003c: CPU1_W1_STATUS IRQs 0-31
+ *
+ * BCM63168:
+ * 0x1000_0020: CPU0_W0_ENABLE
+ * 0x1000_0024: CPU0_W1_ENABLE
+ * 0x1000_0028: CPU0_W2_ENABLE
+ * 0x1000_002c: CPU0_W3_ENABLE
+ * 0x1000_0030: CPU0_W0_STATUS IRQs 96-127
+ * 0x1000_0034: CPU0_W1_STATUS IRQs 64-95
+ * 0x1000_0038: CPU0_W2_STATUS IRQs 32-63
+ * 0x1000_003c: CPU0_W3_STATUS IRQs 0-31
+ * 0x1000_0040: CPU1_W0_ENABLE
+ * 0x1000_0044: CPU1_W1_ENABLE
+ * 0x1000_0048: CPU1_W2_ENABLE
+ * 0x1000_004c: CPU1_W3_ENABLE
+ * 0x1000_0050: CPU1_W0_STATUS IRQs 96-127
+ * 0x1000_0054: CPU1_W1_STATUS IRQs 64-95
+ * 0x1000_0058: CPU1_W2_STATUS IRQs 32-63
+ * 0x1000_005c: CPU1_W3_STATUS IRQs 0-31
+ *
+ * IRQs are numbered in CPU native endian order
+ * (which is big-endian in these examples)
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/cpumask.h>
+#include <linux/kconfig.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+#include <linux/types.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+
+#define IRQS_PER_WORD 32
+#define REG_BYTES_PER_IRQ_WORD (sizeof(u32) * 2)
+
+struct bcm6345_l1_cpu;
+
+struct bcm6345_l1_chip {
+ raw_spinlock_t lock;
+ unsigned int n_words;
+ struct irq_domain *domain;
+ struct cpumask cpumask;
+ struct bcm6345_l1_cpu *cpus[NR_CPUS];
+};
+
+struct bcm6345_l1_cpu {
+ void __iomem *map_base;
+ unsigned int parent_irq;
+ u32 enable_cache[];
+};
+
+static inline unsigned int reg_enable(struct bcm6345_l1_chip *intc,
+ unsigned int word)
+{
+#ifdef __BIG_ENDIAN
+ return (1 * intc->n_words - word - 1) * sizeof(u32);
+#else
+ return (0 * intc->n_words + word) * sizeof(u32);
+#endif
+}
+
+static inline unsigned int reg_status(struct bcm6345_l1_chip *intc,
+ unsigned int word)
+{
+#ifdef __BIG_ENDIAN
+ return (2 * intc->n_words - word - 1) * sizeof(u32);
+#else
+ return (1 * intc->n_words + word) * sizeof(u32);
+#endif
+}
+
+static inline unsigned int cpu_for_irq(struct bcm6345_l1_chip *intc,
+ struct irq_data *d)
+{
+ return cpumask_first_and(&intc->cpumask, irq_data_get_affinity_mask(d));
+}
+
+static void bcm6345_l1_irq_handle(struct irq_desc *desc)
+{
+ struct bcm6345_l1_chip *intc = irq_desc_get_handler_data(desc);
+ struct bcm6345_l1_cpu *cpu;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int idx;
+
+#ifdef CONFIG_SMP
+ cpu = intc->cpus[cpu_logical_map(smp_processor_id())];
+#else
+ cpu = intc->cpus[0];
+#endif
+
+ chained_irq_enter(chip, desc);
+
+ for (idx = 0; idx < intc->n_words; idx++) {
+ int base = idx * IRQS_PER_WORD;
+ unsigned long pending;
+ irq_hw_number_t hwirq;
+ unsigned int irq;
+
+ pending = __raw_readl(cpu->map_base + reg_status(intc, idx));
+ pending &= __raw_readl(cpu->map_base + reg_enable(intc, idx));
+
+ for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) {
+ irq = irq_linear_revmap(intc->domain, base + hwirq);
+ if (irq)
+ do_IRQ(irq);
+ else
+ spurious_interrupt();
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static inline void __bcm6345_l1_unmask(struct irq_data *d)
+{
+ struct bcm6345_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+ unsigned int cpu_idx = cpu_for_irq(intc, d);
+
+ intc->cpus[cpu_idx]->enable_cache[word] |= mask;
+ __raw_writel(intc->cpus[cpu_idx]->enable_cache[word],
+ intc->cpus[cpu_idx]->map_base + reg_enable(intc, word));
+}
+
+static inline void __bcm6345_l1_mask(struct irq_data *d)
+{
+ struct bcm6345_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+ unsigned int cpu_idx = cpu_for_irq(intc, d);
+
+ intc->cpus[cpu_idx]->enable_cache[word] &= ~mask;
+ __raw_writel(intc->cpus[cpu_idx]->enable_cache[word],
+ intc->cpus[cpu_idx]->map_base + reg_enable(intc, word));
+}
+
+static void bcm6345_l1_unmask(struct irq_data *d)
+{
+ struct bcm6345_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&intc->lock, flags);
+ __bcm6345_l1_unmask(d);
+ raw_spin_unlock_irqrestore(&intc->lock, flags);
+}
+
+static void bcm6345_l1_mask(struct irq_data *d)
+{
+ struct bcm6345_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&intc->lock, flags);
+ __bcm6345_l1_mask(d);
+ raw_spin_unlock_irqrestore(&intc->lock, flags);
+}
+
+static int bcm6345_l1_set_affinity(struct irq_data *d,
+ const struct cpumask *dest,
+ bool force)
+{
+ struct bcm6345_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+ unsigned int old_cpu = cpu_for_irq(intc, d);
+ unsigned int new_cpu;
+ struct cpumask valid;
+ unsigned long flags;
+ bool enabled;
+
+ if (!cpumask_and(&valid, &intc->cpumask, dest))
+ return -EINVAL;
+
+ new_cpu = cpumask_any_and(&valid, cpu_online_mask);
+ if (new_cpu >= nr_cpu_ids)
+ return -EINVAL;
+
+ dest = cpumask_of(new_cpu);
+
+ raw_spin_lock_irqsave(&intc->lock, flags);
+ if (old_cpu != new_cpu) {
+ enabled = intc->cpus[old_cpu]->enable_cache[word] & mask;
+ if (enabled)
+ __bcm6345_l1_mask(d);
+ cpumask_copy(irq_data_get_affinity_mask(d), dest);
+ if (enabled)
+ __bcm6345_l1_unmask(d);
+ } else {
+ cpumask_copy(irq_data_get_affinity_mask(d), dest);
+ }
+ raw_spin_unlock_irqrestore(&intc->lock, flags);
+
+ return IRQ_SET_MASK_OK_NOCOPY;
+}
+
+static int __init bcm6345_l1_init_one(struct device_node *dn,
+ unsigned int idx,
+ struct bcm6345_l1_chip *intc)
+{
+ struct resource res;
+ resource_size_t sz;
+ struct bcm6345_l1_cpu *cpu;
+ unsigned int i, n_words;
+
+ if (of_address_to_resource(dn, idx, &res))
+ return -EINVAL;
+ sz = resource_size(&res);
+ n_words = sz / REG_BYTES_PER_IRQ_WORD;
+
+ if (!intc->n_words)
+ intc->n_words = n_words;
+ else if (intc->n_words != n_words)
+ return -EINVAL;
+
+ cpu = intc->cpus[idx] = kzalloc(sizeof(*cpu) + n_words * sizeof(u32),
+ GFP_KERNEL);
+ if (!cpu)
+ return -ENOMEM;
+
+ cpu->map_base = ioremap(res.start, sz);
+ if (!cpu->map_base)
+ return -ENOMEM;
+
+ for (i = 0; i < n_words; i++) {
+ cpu->enable_cache[i] = 0;
+ __raw_writel(0, cpu->map_base + reg_enable(intc, i));
+ }
+
+ cpu->parent_irq = irq_of_parse_and_map(dn, idx);
+ if (!cpu->parent_irq) {
+ pr_err("failed to map parent interrupt %d\n", cpu->parent_irq);
+ return -EINVAL;
+ }
+ irq_set_chained_handler_and_data(cpu->parent_irq,
+ bcm6345_l1_irq_handle, intc);
+
+ return 0;
+}
+
+static struct irq_chip bcm6345_l1_irq_chip = {
+ .name = "bcm6345-l1",
+ .irq_mask = bcm6345_l1_mask,
+ .irq_unmask = bcm6345_l1_unmask,
+ .irq_set_affinity = bcm6345_l1_set_affinity,
+};
+
+static int bcm6345_l1_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hw_irq)
+{
+ irq_set_chip_and_handler(virq,
+ &bcm6345_l1_irq_chip, handle_percpu_irq);
+ irq_set_chip_data(virq, d->host_data);
+ return 0;
+}
+
+static const struct irq_domain_ops bcm6345_l1_domain_ops = {
+ .xlate = irq_domain_xlate_onecell,
+ .map = bcm6345_l1_map,
+};
+
+static int __init bcm6345_l1_of_init(struct device_node *dn,
+ struct device_node *parent)
+{
+ struct bcm6345_l1_chip *intc;
+ unsigned int idx;
+ int ret;
+
+ intc = kzalloc(sizeof(*intc), GFP_KERNEL);
+ if (!intc)
+ return -ENOMEM;
+
+ for_each_possible_cpu(idx) {
+ ret = bcm6345_l1_init_one(dn, idx, intc);
+ if (ret)
+ pr_err("failed to init intc L1 for cpu %d: %d\n",
+ idx, ret);
+ else
+ cpumask_set_cpu(idx, &intc->cpumask);
+ }
+
+ if (!cpumask_weight(&intc->cpumask)) {
+ ret = -ENODEV;
+ goto out_free;
+ }
+
+ raw_spin_lock_init(&intc->lock);
+
+ intc->domain = irq_domain_add_linear(dn, IRQS_PER_WORD * intc->n_words,
+ &bcm6345_l1_domain_ops,
+ intc);
+ if (!intc->domain) {
+ ret = -ENOMEM;
+ goto out_unmap;
+ }
+
+ pr_info("registered BCM6345 L1 intc (IRQs: %d)\n",
+ IRQS_PER_WORD * intc->n_words);
+ for_each_cpu(idx, &intc->cpumask) {
+ struct bcm6345_l1_cpu *cpu = intc->cpus[idx];
+
+ pr_info(" CPU%u at MMIO 0x%p (irq = %d)\n", idx,
+ cpu->map_base, cpu->parent_irq);
+ }
+
+ return 0;
+
+out_unmap:
+ for_each_possible_cpu(idx) {
+ struct bcm6345_l1_cpu *cpu = intc->cpus[idx];
+
+ if (cpu) {
+ if (cpu->map_base)
+ iounmap(cpu->map_base);
+ kfree(cpu);
+ }
+ }
+out_free:
+ kfree(intc);
+ return ret;
+}
+
+IRQCHIP_DECLARE(bcm6345_l1, "brcm,bcm6345-l1-intc", bcm6345_l1_of_init);
--
2.1.4
--
Simon Arlott