Last active
March 25, 2025 13:45
-
-
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"
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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