Skip to content

Instantly share code, notes, and snippets.

@cjdelisle
Last active March 25, 2025 13:45
Show Gist options
  • Save cjdelisle/bb3acab78b5f70dcdfe5dd6338293efe to your computer and use it in GitHub Desktop.
Save cjdelisle/bb3acab78b5f70dcdfe5dd6338293efe to your computer and use it in GitHub Desktop.
Difference between v1 and v2 of patchset "Add EcoNet EN751221 MIPS platform support"
diff --git a/Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml
index 1b0f262c9630..5536319c49c3 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml
@@ -9,7 +9,7 @@ title: EcoNet EN751221 Interrupt Controller
maintainers:
- Caleb James DeLisle <[email protected]>
-description: |
+description:
The EcoNet EN751221 Interrupt Controller is a simple interrupt controller
designed for the MIPS 34Kc MT SMP processor with 2 VPEs. Each interrupt can
be routed to either VPE but not both, so to support per-CPU interrupts, a
@@ -38,31 +38,32 @@ properties:
description: Interrupt line connecting this controller to its parent.
econet,shadow-interrupts:
- $ref: /schemas/types.yaml#/definitions/uint32-array
- description: |
+ $ref: /schemas/types.yaml#/definitions/uint32-matrix
+ description:
An array of interrupt number pairs where each pair represents a shadow
interrupt relationship. The first number in each pair is the primary IRQ,
and the second is its shadow IRQ used for VPE#1 control. For example,
<8 3> means IRQ 8 is shadowed by IRQ 3, so IRQ 3 cannot be mapped, but
- when VPE#1 requests IRQ 8, it will use manipulate the IRQ 3 mask bit.
- maxItems: 40
+ when VPE#1 requests IRQ 8, it will manipulate the IRQ 3 mask bit.
+ minItems: 1
+ maxItems: 20
items:
- minimum: 0
- maximum: 40
+ items:
+ - description: primary per-CPU IRQ
+ - description: shadow IRQ number
required:
- compatible
- reg
- interrupt-controller
- "#interrupt-cells"
- - interrupt-parent
- interrupts
additionalProperties: false
examples:
- |
- intc: interrupt-controller@1fb40000 {
+ interrupt-controller@1fb40000 {
compatible = "econet,en751221-intc";
reg = <0x1fb40000 0x100>;
diff --git a/Documentation/devicetree/bindings/mips/econet.yaml b/Documentation/devicetree/bindings/mips/econet.yaml
index 44da78dc1e29..d8181b58c781 100644
--- a/Documentation/devicetree/bindings/mips/econet.yaml
+++ b/Documentation/devicetree/bindings/mips/econet.yaml
@@ -9,9 +9,6 @@ title: EcoNet MIPS SoCs
maintainers:
- Caleb James DeLisle <[email protected]>
-description:
- Boards with an EcoNet SoC shall have the following properties.
-
properties:
$nodename:
const: '/'
@@ -20,6 +17,8 @@ properties:
oneOf:
- description: Boards with EcoNet EN751221 family SoC
items:
+ - enum:
+ - smartfiber,xp8421-b
- const: econet,en751221
additionalProperties: true
diff --git a/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml b/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
new file mode 100644
index 000000000000..883b904250b2
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
@@ -0,0 +1,78 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/timer/econet,en751221-timer.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: EcoNet EN751221 High Precision Timer (HPT)
+
+maintainers:
+ - Caleb James DeLisle <[email protected]>
+
+description:
+ The EcoNet High Precision Timer (HPT) is a timer peripheral found in various
+ EcoNet SoCs, including the EN751221 and EN751627 families. It provides per-VPE
+ count/compare registers and a per-CPU control register, with a single interrupt
+ line using a percpu-devid interrupt mechanism.
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - const: econet,en751221-timer
+ - items:
+ - const: econet,en751627-timer
+ - const: econet,en751221-timer
+
+ reg: true
+
+ interrupts:
+ maxItems: 1
+ description: A percpu-devid timer interrupt shared across CPUs.
+
+ clocks:
+ maxItems: 1
+
+if:
+ properties:
+ compatible:
+ contains:
+ const: econet,en751627-timer
+then:
+ properties:
+ reg:
+ items:
+ - description: Base address for VPE timers 0 and 1
+ - description: Base address for VPE timers 2 and 3
+else:
+ properties:
+ reg:
+ items:
+ - description: Base address for VPE timers 0 and 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+
+additionalProperties: false
+
+examples:
+ - |
+ timer@1fbf0400 {
+ compatible = "econet,en751627-timer", "econet,en751221-timer";
+ reg = <0x1fbf0400 0x100>, <0x1fbe0000 0x100>;
+ interrupt-parent = <&intc>;
+ interrupts = <30>;
+ clocks = <&hpt_clock>;
+ };
+ - |
+ timer@1fbf0400 {
+ compatible = "econet,en751221-timer";
+ reg = <0x1fbe0400 0x100>;
+ interrupt-parent = <&intc>;
+ interrupts = <30>;
+ clocks = <&hpt_clock>;
+ };
+...
diff --git a/Documentation/devicetree/bindings/timer/econet,timer-hpt.yaml b/Documentation/devicetree/bindings/timer/econet,timer-hpt.yaml
deleted file mode 100644
index 8b7ff9bce947..000000000000
--- a/Documentation/devicetree/bindings/timer/econet,timer-hpt.yaml
+++ /dev/null
@@ -1,58 +0,0 @@
-# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
-%YAML 1.2
----
-$id: http://devicetree.org/schemas/timer/econet,timer-hpt.yaml#
-$schema: http://devicetree.org/meta-schemas/core.yaml#
-
-title: EcoNet High Precision Timer (HPT)
-
-maintainers:
- - Calev James DeLisle <[email protected]>
-
-description: |
- The EcoNet High Precision Timer (HPT) is a timer peripheral found in various
- EcoNet SoCs, including the EN751221 and EN751627 families. It provides per-VPE
- count/compare registers and a per-CPU control register, with a single interrupt
- line using a percpu-devid interrupt mechanism.
-
-properties:
- compatible:
- const: econet,timer-hpt
-
- reg:
- minItems: 1
- maxItems: 2
- description: |
- Physical base address and size of the timer's register space. On 34Kc
- processors, a single region is used. On 1004Kc processors, two regions are
- used, one for each core.
-
- interrupts:
- maxItems: 1
- description: |
- The interrupt number for the timer. This is a percpu-devid interrupt shared
- across CPUs.
-
- clocks:
- maxItems: 1
- description: |
- A clock to get the frequency of the timer.
-
-required:
- - compatible
- - reg
- - interrupts
- - clocks
-
-additionalProperties: false
-
-examples:
- - |
- timer_hpt@1fbf0400 {
- compatible = "econet,timer-hpt";
- reg = <0x1fbf0400 0x100>;
- interrupt-parent = <&intc>;
- interrupts = <30>;
- clocks = <&hpt_clock>;
- };
-...
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 4cd050e50743..d4e69424dcd1 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1388,6 +1388,8 @@ patternProperties:
description: SKOV A/S
"^skyworks,.*":
description: Skyworks Solutions, Inc.
+ "^smartfiber,.*":
+ description: ShenZhen Smartfiber Technology Co, Ltd.
"^smartlabs,.*":
description: SmartLabs LLC
"^smartrg,.*":
diff --git a/MAINTAINERS b/MAINTAINERS
index fcb1c49ee54e..ed5329762584 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8184,14 +8184,14 @@ F: drivers/media/dvb-frontends/ec100*
ECONET MIPS PLATFORM
M: Caleb James DeLisle <[email protected]>
+L: [email protected]
S: Maintained
F: Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml
F: Documentation/devicetree/bindings/mips/econet.yaml
-F: Documentation/devicetree/bindings/timer/econet,timer-hpt.yaml
-F: Documentation/devicetree/bindings/vendor-prefixes.yaml
+F: Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
F: arch/mips/boot/dts/econet/
F: arch/mips/econet/
-F: drivers/clocksource/timer-econet-hpt.c
+F: drivers/clocksource/timer-econet-en751221.c
F: drivers/irqchip/irq-econet-en751221.c
ECRYPT FILE SYSTEM
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index 593ff00855b6..909bf0847af0 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -396,7 +396,7 @@ config ECONET
select CPU_BIG_ENDIAN
select DEBUG_ZBOOT
select EARLY_PRINTK_8250
- select ECONET_TIMER_HPT
+ select ECONET_EN751221_TIMER
select SERIAL_OF_PLATFORM
select SYS_SUPPORTS_BIG_ENDIAN
select SYS_HAS_CPU_MIPS32_R1
diff --git a/arch/mips/boot/dts/econet/Makefile b/arch/mips/boot/dts/econet/Makefile
index 524ba10ce750..b467d5624e39 100644
--- a/arch/mips/boot/dts/econet/Makefile
+++ b/arch/mips/boot/dts/econet/Makefile
@@ -1,2 +1,2 @@
# SPDX-License-Identifier: GPL-2.0
-dtb-$(CONFIG_SOC_ECONET_EN751221_TEST_IMAGE) += en751221_test_image.dtb
+dtb-$(CONFIG_DTB_ECONET_SMARTFIBER_XP8421_B) += en751221_smartfiber_xp8421-b.dtb
diff --git a/arch/mips/boot/dts/econet/en751221.dtsi b/arch/mips/boot/dts/econet/en751221.dtsi
index e4404aed5705..9813b57b54a9 100644
--- a/arch/mips/boot/dts/econet/en751221.dtsi
+++ b/arch/mips/boot/dts/econet/en751221.dtsi
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/dts-v1/;
/ {
@@ -6,7 +6,7 @@ / {
#address-cells = <1>;
#size-cells = <1>;
- hpt_clock: hpt_clock {
+ hpt_clock: clock {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <200000000>; /* 200 MHz */
@@ -51,8 +51,8 @@ uart: serial@1fbf0000 {
clock-frequency = <1843200>;
};
- timer_hpt: timer_hpt@1fbf0400 {
- compatible = "econet,timer-hpt";
+ timer_hpt: timer@1fbf0400 {
+ compatible = "econet,en751221-timer";
reg = <0x1fbf0400 0x100>;
interrupt-parent = <&intc>;
diff --git a/arch/mips/boot/dts/econet/en751221_smartfiber_xp8421-b.dts b/arch/mips/boot/dts/econet/en751221_smartfiber_xp8421-b.dts
new file mode 100644
index 000000000000..8223c5bce67f
--- /dev/null
+++ b/arch/mips/boot/dts/econet/en751221_smartfiber_xp8421-b.dts
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/dts-v1/;
+
+#include "en751221.dtsi"
+
+/ {
+ model = "SmartFiber XP8421-B";
+ compatible = "smartfiber,xp8421-b", "econet,en751221";
+
+ memory@0 {
+ device_type = "memory";
+ reg = <0x00000000 0x1c000000>;
+ };
+
+ chosen {
+ stdout-path = "/serial@1fbf0000:115200";
+ linux,usable-memory-range = <0x00020000 0x1bfe0000>;
+ };
+};
diff --git a/arch/mips/boot/dts/econet/en751221_test_image.dts b/arch/mips/boot/dts/econet/en751221_test_image.dts
deleted file mode 100644
index bc140c4043b2..000000000000
--- a/arch/mips/boot/dts/econet/en751221_test_image.dts
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/dts-v1/;
-
-#include "en751221.dtsi"
-
-/ {
- model = "Generic EN751221";
-
- memory@0 {
- /* We hope at least 64MB will be available wherever we are run */
- device_type = "memory";
- reg = <0x00000000 0x4000000>;
- };
-
- chosen {
- bootargs = "console=ttyS0,115200";
- linux,usable-memory-range = <0x00020000 0x3fe0000>;
- };
-};
diff --git a/arch/mips/econet/Kconfig b/arch/mips/econet/Kconfig
index 12f85d638e47..fd69884cc9a8 100644
--- a/arch/mips/econet/Kconfig
+++ b/arch/mips/econet/Kconfig
@@ -1,42 +1,48 @@
# SPDX-License-Identifier: GPL-2.0
if ECONET
-config SOC_ECONET_EN751221
- bool
- select COMMON_CLK
- select ECONET_EN751221_INTC
- select IRQ_MIPS_CPU
- select SMP
- select SMP_UP
- select SYS_SUPPORTS_SMP
-
choice
prompt "EcoNet SoC selection"
- default SOC_ECONET_EN751221_FAMILY
+ default SOC_ECONET_EN751221
help
Select EcoNet MIPS SoC type. Individual SoCs within a family are
- similar enough that is it enough to select the right family, and
+ very similar, so is it enough to select the right family, and
then customize to the specific SoC using the device tree only.
- config SOC_ECONET_EN751221_FAMILY
+ config SOC_ECONET_EN751221
bool "EN751221 family"
- select SOC_ECONET_EN751221
+ select COMMON_CLK
+ select ECONET_EN751221_INTC
+ select IRQ_MIPS_CPU
+ select SMP
+ select SMP_UP
+ select SYS_SUPPORTS_SMP
help
The EN751221 family includes EN7512, RN7513, EN7521, EN7526.
They are based on single core MIPS 34Kc processors. To boot
this kernel, you will need a device tree such as
MIPS_RAW_APPENDED_DTB=y, and a root filesystem.
+endchoice
+
+choice
+ prompt "Devicetree selection"
+ default DTB_ECONET_NONE
+ help
+ Select the devicetree.
+
+ config DTB_ECONET_NONE
+ bool "None"
- config SOC_ECONET_EN751221_TEST_IMAGE
- bool "EN751221 test image"
- select SOC_ECONET_EN751221
+ config DTB_ECONET_SMARTFIBER_XP8421_B
+ bool "EN751221 SmartFiber XP8421-B"
+ depends on SOC_ECONET_EN751221
select BUILTIN_DTB
help
- Build a minimal kernel that will boot on any EN751221 board
- with at least 64MB of memory. This has a builtin device tree
- so it can boot with nothing more than an appended initramfs.
- This is good for validating that a given SoC is EN751221
- compatible, or for regression testing.
+ The SmartFiber XP8421-B is a device based on the EN751221 SoC.
+ It has 512MB of memory and 256MB of NAND flash. This kernel
+ needs only an appended initramfs to boot. It can be loaded
+ through XMODEM and booted from memory in the bootloader, or
+ it can be packed in tclinux.trx format and written to flash.
endchoice
endif
diff --git a/arch/mips/econet/Platform b/arch/mips/econet/Platform
index bb659876d855..ea5616447bcd 100644
--- a/arch/mips/econet/Platform
+++ b/arch/mips/econet/Platform
@@ -2,4 +2,4 @@
# we put the load address well above where the bootloader loads and then use
# zboot. So please set CONFIG_ZBOOT_LOAD_ADDRESS to the address where your
# bootloader actually places the kernel.
-load-$(CONFIG_ECONET) += 0xffffffff81000000
\ No newline at end of file
+load-$(CONFIG_ECONET) += 0xffffffff81000000
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 1a7a9b4f16f9..976afb0b2312 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -73,8 +73,8 @@ config DW_APB_TIMER_OF
select DW_APB_TIMER
select TIMER_OF
-config ECONET_TIMER_HPT
- bool "EcoNet High Precision Timer" if COMPILE_TEST
+config ECONET_EN751221_TIMER
+ bool "EcoNet EN751221 High Precision Timer" if COMPILE_TEST
depends on HAS_IOMEM
select CLKSRC_MMIO
select TIMER_OF
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index a3dd2a9d2a37..d2998601eda5 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -17,7 +17,7 @@ obj-$(CONFIG_CLKBLD_I8253) += i8253.o
obj-$(CONFIG_CLKSRC_MMIO) += mmio.o
obj-$(CONFIG_DAVINCI_TIMER) += timer-davinci.o
obj-$(CONFIG_DIGICOLOR_TIMER) += timer-digicolor.o
-obj-$(CONFIG_ECONET_TIMER_HPT) += timer-econet-hpt.o
+obj-$(CONFIG_ECONET_EN751221_TIMER) += timer-econet-en751221.o
obj-$(CONFIG_OMAP_DM_TIMER) += timer-ti-dm.o
obj-$(CONFIG_OMAP_DM_SYSTIMER) += timer-ti-dm-systimer.o
obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o
diff --git a/drivers/clocksource/timer-econet-hpt.c b/drivers/clocksource/timer-econet-en751221.c
similarity index 90%
rename from drivers/clocksource/timer-econet-hpt.c
rename to drivers/clocksource/timer-econet-en751221.c
index defd797426c5..9cfeead09377 100644
--- a/drivers/clocksource/timer-econet-hpt.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -23,11 +23,11 @@
#define ECONET_NUM_BLOCKS DIV_ROUND_UP(NR_CPUS, 2)
static struct {
- void __iomem *membase[ECONET_NUM_BLOCKS];
- u32 freq_hz;
+ void __iomem *membase[ECONET_NUM_BLOCKS];
+ u32 freq_hz;
} econet_timer __ro_after_init;
-static DEFINE_PER_CPU(struct clock_event_device, econet_timer_pcpu_m);
+static DEFINE_PER_CPU(struct clock_event_device, econet_timer_pcpu);
/* Each memory block has 2 timers, the order of registers is:
* CTL, CMR0, CNT0, CMR1, CNT1
@@ -64,7 +64,7 @@ static bool cevt_is_pending(int cpu_id)
static irqreturn_t cevt_interrupt(int irq, void *dev_id)
{
- struct clock_event_device *dev = this_cpu_ptr(&econet_timer_pcpu_m);
+ struct clock_event_device *dev = this_cpu_ptr(&econet_timer_pcpu);
int cpu = cpumask_first(dev->cpumask);
if (!cevt_is_pending(cpu)) {
@@ -79,8 +79,8 @@ static irqreturn_t cevt_interrupt(int irq, void *dev_id)
static int cevt_set_next_event(ulong delta, struct clock_event_device *dev)
{
- int cpu;
u32 next;
+ int cpu;
cpu = cpumask_first(dev->cpumask);
next = ioread32(reg_count(cpu)) + delta;
@@ -94,8 +94,8 @@ static int cevt_set_next_event(ulong delta, struct clock_event_device *dev)
static int cevt_init_cpu(uint cpu)
{
+ struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, cpu);
u32 reg;
- struct clock_event_device *cd = &per_cpu(econet_timer_pcpu_m, cpu);
pr_info("%s: Setting up clockevent for CPU %d\n", cd->name, cpu);
@@ -128,9 +128,7 @@ static void __init cevt_dev_init(uint cpu)
static int __init cevt_init(struct device_node *np)
{
- int i;
- int irq;
- int ret;
+ int i, irq, ret;
irq = irq_of_parse_and_map(np, 0);
if (irq <= 0) {
@@ -138,9 +136,7 @@ static int __init cevt_init(struct device_node *np)
return -EINVAL;
}
- ret = request_percpu_irq(
- irq, cevt_interrupt,
- np->name, &econet_timer_pcpu_m);
+ ret = request_percpu_irq(irq, cevt_interrupt, np->name, &econet_timer_pcpu);
if (ret < 0) {
pr_err("%pOFn: IRQ %d setup failed (%d)\n", np, irq, ret);
@@ -148,7 +144,7 @@ static int __init cevt_init(struct device_node *np)
}
for_each_possible_cpu(i) {
- struct clock_event_device *cd = &per_cpu(econet_timer_pcpu_m, i);
+ struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, i);
cd->rating = 310,
cd->features = CLOCK_EVT_FEAT_ONESHOT |
@@ -180,8 +176,7 @@ static int __init timer_init(struct device_node *np)
clk = of_clk_get(np, 0);
if (IS_ERR(clk)) {
- pr_err("%pOFn: Failed to get CPU clock from DT %ld\n", np,
- PTR_ERR(clk));
+ pr_err("%pOFn: Failed to get CPU clock from DT %ld\n", np, PTR_ERR(clk));
return PTR_ERR(clk);
}
@@ -218,4 +213,4 @@ static int __init timer_init(struct device_node *np)
return 0;
}
-TIMER_OF_DECLARE(econet_timer_hpt, "econet,timer-hpt", timer_init);
+TIMER_OF_DECLARE(econet_timer_hpt, "econet,en751221-timer", timer_init);
diff --git a/drivers/irqchip/irq-econet-en751221.c b/drivers/irqchip/irq-econet-en751221.c
index edbb8a3d6d51..886d60c6f8a0 100644
--- a/drivers/irqchip/irq-econet-en751221.c
+++ b/drivers/irqchip/irq-econet-en751221.c
@@ -2,9 +2,26 @@
/*
* EN751221 Interrupt Controller Driver.
*
+ * The EcoNet EN751221 Interrupt Controller is a simple interrupt controller
+ * designed for the MIPS 34Kc MT SMP processor with 2 VPEs. Each interrupt can
+ * be routed to either VPE but not both, so to support per-CPU interrupts, a
+ * secondary IRQ number is allocated to control masking/unmasking on VPE#1. In
+ * this driver, these are called "shadow interrupts". The assignment of shadow
+ * interrupts is defined by the SoC integrator when wiring the interrupt lines,
+ * so they are configurable in the device tree.
+ *
+ * If an interrupt (say 30) needs per-CPU capability, the SoC integrator
+ * allocates another IRQ number (say 29) to be its shadow. The device tree
+ * reflects this by adding the pair <30 29> to the "econet,shadow-interrupts"
+ * property.
+ *
+ * When VPE#1 requests IRQ 30, the driver manipulates the mask bit for IRQ 29,
+ * telling the hardware to mask VPE#1's view of IRQ 30.
+ *
* Copyright (C) 2025 Caleb James DeLisle <[email protected]>
*/
+#include <linux/cleanup.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
@@ -13,55 +30,60 @@
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
-#define INTC_IRQ_COUNT 40
+#define IRQ_COUNT 40
-#define INTC_NO_SHADOW 0xff
-#define INTC_IS_SHADOW 0xfe
+#define NOT_PERCPU 0xff
+#define IS_SHADOW 0xfe
#define REG_MASK0 0x04
#define REG_MASK1 0x50
#define REG_PENDING0 0x08
#define REG_PENDING1 0x54
-static const struct econet_intc {
- const struct irq_chip chip;
-
- const struct irq_domain_ops domain_ops;
-} econet_intc;
-
+/**
+ * @membase: Base address of the interrupt controller registers
+ * @interrupt_shadows: Array of all interrupts, for each value,
+ * - NOT_PERCPU: This interrupt is not per-cpu, so it has no shadow
+ * - IS_SHADOW: This interrupt is a shadow of another per-cpu interrupt
+ * - else: This is a per-cpu interrupt whose shadow is the value
+ */
static struct {
- void __iomem *membase;
- u8 shadow_interrupts[INTC_IRQ_COUNT];
-} econet_intc_rai __ro_after_init;
+ void __iomem *membase;
+ u8 interrupt_shadows[IRQ_COUNT];
+} econet_intc __ro_after_init;
static DEFINE_RAW_SPINLOCK(irq_lock);
+/* IRQs must be disabled */
static void econet_wreg(u32 reg, u32 val, u32 mask)
{
- unsigned long flags;
u32 v;
- raw_spin_lock_irqsave(&irq_lock, flags);
+ guard(raw_spinlock)(&irq_lock);
- v = ioread32(econet_intc_rai.membase + reg);
+ v = ioread32(econet_intc.membase + reg);
v &= ~mask;
v |= val & mask;
- iowrite32(v, econet_intc_rai.membase + reg);
-
- raw_spin_unlock_irqrestore(&irq_lock, flags);
+ iowrite32(v, econet_intc.membase + reg);
}
+/* IRQs must be disabled */
static void econet_chmask(u32 hwirq, bool unmask)
{
- u32 reg;
- u32 mask;
- u32 bit;
+ u32 reg, mask;
u8 shadow;
- shadow = econet_intc_rai.shadow_interrupts[hwirq];
- if (WARN_ON_ONCE(shadow == INTC_IS_SHADOW))
+ /*
+ * If the IRQ is a shadow, it should never be manipulated directly.
+ * It should only be masked/unmasked as a result of the "real" per-cpu
+ * irq being manipulated by a thread running on VPE#1.
+ * If it is per-cpu (has a shadow), and we're on VPE#1, the shadow is what we mask.
+ * This is single processor only, so smp_processor_id() never exceeds 1.
+ */
+ shadow = econet_intc.interrupt_shadows[hwirq];
+ if (WARN_ON_ONCE(shadow == IS_SHADOW))
return;
- else if (shadow < INTC_NO_SHADOW && smp_processor_id() > 0)
+ else if (shadow != NOT_PERCPU && smp_processor_id() == 1)
hwirq = shadow;
if (hwirq >= 32) {
@@ -71,16 +93,17 @@ static void econet_chmask(u32 hwirq, bool unmask)
reg = REG_MASK0;
mask = BIT(hwirq);
}
- bit = (unmask) ? mask : 0;
- econet_wreg(reg, bit, mask);
+ econet_wreg(reg, unmask ? mask : 0, mask);
}
+/* IRQs must be disabled */
static void econet_intc_mask(struct irq_data *d)
{
econet_chmask(d->hwirq, false);
}
+/* IRQs must be disabled */
static void econet_intc_unmask(struct irq_data *d)
{
econet_chmask(d->hwirq, true);
@@ -88,6 +111,8 @@ static void econet_intc_unmask(struct irq_data *d)
static void econet_mask_all(void)
{
+ /* IRQs are generally disabled during init, but guarding here makes it non-obligatory. */
+ guard(irqsave)();
econet_wreg(REG_MASK0, 0, ~0);
econet_wreg(REG_MASK1, 0, ~0);
}
@@ -107,122 +132,128 @@ static void econet_intc_from_parent(struct irq_desc *desc)
{
struct irq_chip *chip = irq_desc_get_chip(desc);
struct irq_domain *domain;
- u32 pending0;
- u32 pending1;
+ u32 pending0, pending1;
chained_irq_enter(chip, desc);
- pending0 = ioread32(econet_intc_rai.membase + REG_PENDING0);
- pending1 = ioread32(econet_intc_rai.membase + REG_PENDING1);
+ pending0 = ioread32(econet_intc.membase + REG_PENDING0);
+ pending1 = ioread32(econet_intc.membase + REG_PENDING1);
if (unlikely(!(pending0 | pending1))) {
spurious_interrupt();
- goto out;
+ } else {
+ domain = irq_desc_get_handler_data(desc);
+ econet_intc_handle_pending(domain, pending0, 0);
+ econet_intc_handle_pending(domain, pending1, 32);
}
- domain = irq_desc_get_handler_data(desc);
-
- econet_intc_handle_pending(domain, pending0, 0);
- econet_intc_handle_pending(domain, pending1, 32);
-
-out:
chained_irq_exit(chip, desc);
}
+static const struct irq_chip econet_irq_chip;
+
static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq)
{
int ret;
- if (hwirq >= INTC_IRQ_COUNT) {
+ if (hwirq >= IRQ_COUNT) {
pr_err("%s: hwirq %lu out of range\n", __func__, hwirq);
return -EINVAL;
- } else if (econet_intc_rai.shadow_interrupts[hwirq] == INTC_IS_SHADOW) {
- pr_err("%s: can't map hwirq %lu, it is a shadow interrupt\n",
- __func__, hwirq);
+ } else if (econet_intc.interrupt_shadows[hwirq] == IS_SHADOW) {
+ pr_err("%s: can't map hwirq %lu, it is a shadow interrupt\n", __func__, hwirq);
return -EINVAL;
}
- if (econet_intc_rai.shadow_interrupts[hwirq] != INTC_NO_SHADOW) {
- irq_set_chip_and_handler(
- irq, &econet_intc.chip, handle_percpu_devid_irq);
- ret = irq_set_percpu_devid(irq);
- if (ret) {
- pr_warn("%s: Failed irq_set_percpu_devid for %u: %d\n",
- d->name, irq, ret);
- }
+
+ if (econet_intc.interrupt_shadows[hwirq] == NOT_PERCPU) {
+ irq_set_chip_and_handler(irq, &econet_irq_chip, handle_level_irq);
} else {
- irq_set_chip_and_handler(
- irq, &econet_intc.chip, handle_level_irq);
+ irq_set_chip_and_handler(irq, &econet_irq_chip, handle_percpu_devid_irq);
+ ret = irq_set_percpu_devid(irq);
+ if (ret)
+ pr_warn("%s: Failed irq_set_percpu_devid for %u: %d\n", d->name, irq, ret);
}
+
irq_set_chip_data(irq, NULL);
return 0;
}
-static const struct econet_intc econet_intc = {
- .chip = {
- .name = "en751221-intc",
- .irq_unmask = econet_intc_unmask,
- .irq_mask = econet_intc_mask,
- .irq_mask_ack = econet_intc_mask,
- },
- .domain_ops = {
- .xlate = irq_domain_xlate_onecell,
- .map = econet_intc_map,
- },
+static const struct irq_chip econet_irq_chip = {
+ .name = "en751221-intc",
+ .irq_unmask = econet_intc_unmask,
+ .irq_mask = econet_intc_mask,
+ .irq_mask_ack = econet_intc_mask,
+};
+
+static const struct irq_domain_ops econet_domain_ops = {
+ .xlate = irq_domain_xlate_onecell,
+ .map = econet_intc_map
};
static int __init get_shadow_interrupts(struct device_node *node)
{
const char *field = "econet,shadow-interrupts";
- int n_shadow_interrupts;
- u32 *shadow_interrupts;
+ int num_shadows;
- n_shadow_interrupts = of_property_count_u32_elems(node, field);
- memset(econet_intc_rai.shadow_interrupts, INTC_NO_SHADOW,
- sizeof(econet_intc_rai.shadow_interrupts));
- if (n_shadow_interrupts <= 0) {
+ num_shadows = of_property_count_u32_elems(node, field);
+
+ memset(econet_intc.interrupt_shadows, NOT_PERCPU,
+ sizeof(econet_intc.interrupt_shadows));
+
+ if (num_shadows <= 0) {
return 0;
- } else if (n_shadow_interrupts % 2) {
+ } else if (num_shadows % 2) {
pr_err("%pOF: %s count is odd, ignoring\n", node, field);
return 0;
}
- shadow_interrupts = kmalloc_array(n_shadow_interrupts, sizeof(u32),
- GFP_KERNEL);
- if (!shadow_interrupts)
+
+ u32 *shadows __free(kfree) = kmalloc_array(num_shadows, sizeof(u32), GFP_KERNEL);
+ if (!shadows)
return -ENOMEM;
- if (of_property_read_u32_array(node, field,
- shadow_interrupts, n_shadow_interrupts)
- ) {
+
+ if (of_property_read_u32_array(node, field, shadows, num_shadows)) {
pr_err("%pOF: Failed to read %s\n", node, field);
- kfree(shadow_interrupts);
return -EINVAL;
}
- for (int i = 0; i < n_shadow_interrupts; i += 2) {
- u32 shadow = shadow_interrupts[i + 1];
- u32 target = shadow_interrupts[i];
- if (shadow > INTC_IRQ_COUNT) {
+ for (int i = 0; i < num_shadows; i += 2) {
+ u32 shadow = shadows[i + 1];
+ u32 target = shadows[i];
+
+ if (shadow > IRQ_COUNT) {
pr_err("%pOF: %s[%d] shadow(%d) out of range\n",
- node, field, i, shadow);
+ node, field, i + 1, shadow);
continue;
}
- if (target >= INTC_IRQ_COUNT) {
- pr_err("%pOF: %s[%d] target(%d) out of range\n",
- node, field, i + 1, target);
+
+ if (target >= IRQ_COUNT) {
+ pr_err("%pOF: %s[%d] target(%d) out of range\n", node, field, i, target);
+ continue;
+ }
+
+ if (econet_intc.interrupt_shadows[target] != NOT_PERCPU) {
+ pr_err("%pOF: %s[%d] target(%d) already has a shadow\n",
+ node, field, i, target);
continue;
}
- econet_intc_rai.shadow_interrupts[target] = shadow;
- econet_intc_rai.shadow_interrupts[shadow] = INTC_IS_SHADOW;
+
+ if (econet_intc.interrupt_shadows[shadow] != NOT_PERCPU) {
+ pr_err("%pOF: %s[%d] shadow(%d) already has a target\n",
+ node, field, i + 1, shadow);
+ continue;
+ }
+
+ econet_intc.interrupt_shadows[target] = shadow;
+ econet_intc.interrupt_shadows[shadow] = IS_SHADOW;
}
- kfree(shadow_interrupts);
+
return 0;
}
static int __init econet_intc_of_init(struct device_node *node, struct device_node *parent)
{
- int ret;
- int irq;
- struct resource res;
struct irq_domain *domain;
+ struct resource res;
+ int ret, irq;
ret = get_shadow_interrupts(node);
if (ret)
@@ -246,8 +277,8 @@ static int __init econet_intc_of_init(struct device_node *node, struct device_no
goto err_dispose_mapping;
}
- econet_intc_rai.membase = ioremap(res.start, resource_size(&res));
- if (!econet_intc_rai.membase) {
+ econet_intc.membase = ioremap(res.start, resource_size(&res));
+ if (!econet_intc.membase) {
pr_err("%pOF: Failed to remap membase\n", node);
ret = -ENOMEM;
goto err_release;
@@ -255,9 +286,7 @@ static int __init econet_intc_of_init(struct device_node *node, struct device_no
econet_mask_all();
- domain = irq_domain_add_linear(
- node, INTC_IRQ_COUNT,
- &econet_intc.domain_ops, NULL);
+ domain = irq_domain_add_linear(node, IRQ_COUNT, &econet_domain_ops, NULL);
if (!domain) {
pr_err("%pOF: Failed to add irqdomain\n", node);
ret = -ENOMEM;
@@ -269,7 +298,7 @@ static int __init econet_intc_of_init(struct device_node *node, struct device_no
return 0;
err_unmap:
- iounmap(econet_intc_rai.membase);
+ iounmap(econet_intc.membase);
err_release:
release_mem_region(res.start, resource_size(&res));
err_dispose_mapping:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment