include $(TOPDIR)/rules.mk
-ARCH:=mips
BOARD:=econet
BOARDNAME:=EcoNet EN75xx MIPS
FEATURES:=dt source-only squashfs nand usb
-SUBTARGETS:=en751221
+SUBTARGETS:=en751221 en7528
KERNEL_PATCHVER:=6.12
--- /dev/null
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/dts-v1/;
+
+#include <dt-bindings/interrupt-controller/mips-gic.h>
+
+/ {
+ compatible = "econet,en7528";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ hpt_clock: clock {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <200000000>; /* 200 MHz */
+ };
+
+ spi_clock: spi-clock {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <40000000>; /* 40 MHz */
+ };
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cpu@0 {
+ device_type = "cpu";
+ compatible = "mips,mips1004Kc";
+ reg = <0>;
+ };
+
+ cpu@1 {
+ device_type = "cpu";
+ compatible = "mips,mips1004Kc";
+ reg = <1>;
+ };
+ };
+
+ cpuintc: interrupt-controller {
+ compatible = "mti,cpu-interrupt-controller";
+ interrupt-controller;
+ #address-cells = <0>;
+ #interrupt-cells = <1>;
+ };
+
+ gic: interrupt-controller@1f8c0000 {
+ compatible = "mti,gic";
+ reg = <0x1f8c0000 0x20000>;
+
+ interrupt-controller;
+ #interrupt-cells = <3>;
+
+ interrupt-parent = <&cpuintc>;
+ interrupts = <2>;
+ };
+
+ timer_hpt: timer@1fbf0400 {
+ compatible = "econet,en7528-timer";
+ reg = <0x1fbf0400 0x14>,
+ <0x1fbe0000 0x14>;
+
+ interrupt-parent = <&gic>;
+ interrupts = <GIC_SHARED 30 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SHARED 29 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SHARED 37 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SHARED 36 IRQ_TYPE_LEVEL_HIGH>;
+
+ clocks = <&hpt_clock>;
+ };
+
+ spi_ctrl: spi@1fa10000 {
+ compatible = "airoha,en7581-snand";
+ reg = <0x1fa10000 0x140>,
+ <0x1fa11000 0x160>;
+
+ clocks = <&spi_clock>;
+ clock-names = "spi";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ nand: nand@0 {
+ compatible = "spi-nand";
+ reg = <0>;
+ spi-max-frequency = <40000000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <2>;
+ };
+ };
+
+ uart: serial@1fbf0000 {
+ compatible = "airoha,en7523-uart";
+ reg = <0x1fbf0000 0x30>;
+ reg-io-width = <4>;
+ reg-shift = <2>;
+
+ interrupt-parent = <&gic>;
+ interrupts = <GIC_SHARED 2 IRQ_TYPE_LEVEL_HIGH>;
+
+ clock-frequency = <7372800>;
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+
+/dts-v1/;
+
+#include "en7528.dtsi"
+
+/ {
+ model = "Generic EN7528";
+ compatible = "econet,en7528-generic", "econet,en7528";
+
+ memory@0 {
+ // We hope at least 64MB will be available on every device
+ device_type = "memory";
+ reg = <0x00000000 0x4000000>;
+ };
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ linux,usable-memory-range = <0x00020000 0x3fe0000>;
+ };
+
+ aliases {
+ serial0 = &uart;
+ };
+};
+
+&nand {
+ status = "okay";
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@1 {
+ // We don't know how big the flash is
+ // Put 1GB and let it truncate
+ label = "all_flash";
+ reg = <0x0 0x40000000>;
+ read-only;
+ };
+
+ partition@2 {
+ // We don't know how big the bootloader
+ // is, but when we're doing testing, lets
+ // make sure nobody touches anything below 4MB
+ label = "bootloader";
+ reg = <0x0 0x00400000>;
+ read-only;
+ };
+
+ partition@3 {
+ label = "rest_of_flash";
+ reg = <0x00400000 0x40000000>;
+ };
+ };
+};
CONFIG_RFS_ACCEL=y
CONFIG_RPS=y
CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES=y
+# CONFIG_SERIAL_8250_AIROHA is not set
CONFIG_SERIAL_MCTRL_GPIO=y
CONFIG_SERIAL_OF_PLATFORM=y
CONFIG_SGL_ALLOC=y
CONFIG_SMP_UP=y
CONFIG_SOCK_RX_QUEUE_MAPPING=y
CONFIG_SOC_ECONET_EN751221=y
+# CONFIG_SOC_ECONET_EN7528 is not set
CONFIG_SPI=y
CONFIG_SPI_AIROHA_EN7523=y
+# CONFIG_SPI_AIROHA_SNFI is not set
CONFIG_SPI_MASTER=y
CONFIG_SPI_MEM=y
CONFIG_SYSCTL_EXCEPTION_TRACE=y
+ARCH:=mips
BOARDNAME:=en751221
CPU_TYPE:=24kc
KERNELNAME:=vmlinuz.bin
--- /dev/null
+CONFIG_ARCH_32BIT_OFF_T=y
+CONFIG_ARCH_HIBERNATION_POSSIBLE=y
+CONFIG_ARCH_KEEP_MEMBLOCK=y
+CONFIG_ARCH_MMAP_RND_BITS_MAX=15
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=15
+CONFIG_ARCH_SUSPEND_POSSIBLE=y
+CONFIG_BOARD_SCACHE=y
+CONFIG_CLKSRC_MMIO=y
+CONFIG_CLONE_BACKWARDS=y
+CONFIG_COMMON_CLK=y
+# CONFIG_COMMON_CLK_EN7523 is not set
+CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1
+CONFIG_COMPAT_32BIT_TIME=y
+CONFIG_CONTEXT_TRACKING=y
+CONFIG_CONTEXT_TRACKING_IDLE=y
+CONFIG_CPU_GENERIC_DUMP_TLB=y
+CONFIG_CPU_HAS_DIEI=y
+CONFIG_CPU_HAS_PREFETCH=y
+CONFIG_CPU_HAS_RIXI=y
+CONFIG_CPU_HAS_SYNC=y
+CONFIG_CPU_LITTLE_ENDIAN=y
+CONFIG_CPU_MIPS32=y
+# CONFIG_CPU_MIPS32_R1 is not set
+CONFIG_CPU_MIPS32_R2=y
+CONFIG_CPU_MIPSR2=y
+CONFIG_CPU_MIPSR2_IRQ_EI=y
+CONFIG_CPU_MIPSR2_IRQ_VI=y
+CONFIG_CPU_MITIGATIONS=y
+CONFIG_CPU_NEEDS_NO_SMARTMIPS_OR_MICROMIPS=y
+CONFIG_CPU_R4K_CACHE_TLB=y
+CONFIG_CPU_RMAP=y
+CONFIG_CPU_SUPPORTS_32BIT_KERNEL=y
+CONFIG_CPU_SUPPORTS_HIGHMEM=y
+CONFIG_CPU_SUPPORTS_MSA=y
+CONFIG_CRC16=y
+CONFIG_CRYPTO_DEFLATE=y
+CONFIG_CRYPTO_ECB=y
+CONFIG_CRYPTO_HASH_INFO=y
+CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y
+CONFIG_CRYPTO_LIB_GF128MUL=y
+CONFIG_CRYPTO_LIB_POLY1305_RSIZE=2
+CONFIG_CRYPTO_LIB_SHA1=y
+CONFIG_CRYPTO_LIB_UTILS=y
+CONFIG_CRYPTO_LZO=y
+CONFIG_CRYPTO_ZSTD=y
+CONFIG_DEBUG_INFO=y
+CONFIG_DEBUG_ZBOOT=y
+CONFIG_DMA_NEED_SYNC=y
+CONFIG_DMA_NONCOHERENT=y
+CONFIG_DTB_ECONET_NONE=y
+CONFIG_DTC=y
+CONFIG_EARLY_PRINTK=y
+CONFIG_EARLY_PRINTK_8250=y
+CONFIG_ECONET=y
+CONFIG_ECONET_EN751221_TIMER=y
+CONFIG_EXCLUSIVE_SYSTEM_RAM=y
+CONFIG_FS_IOMAP=y
+CONFIG_FUNCTION_ALIGNMENT=0
+CONFIG_FW_LOADER_PAGED_BUF=y
+CONFIG_FW_LOADER_SYSFS=y
+CONFIG_GENERIC_ATOMIC64=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_CMOS_UPDATE=y
+CONFIG_GENERIC_CPU_AUTOPROBE=y
+CONFIG_GENERIC_GETTIMEOFDAY=y
+CONFIG_GENERIC_IDLE_POLL_SETUP=y
+CONFIG_GENERIC_IOMAP=y
+CONFIG_GENERIC_IRQ_CHIP=y
+CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y
+CONFIG_GENERIC_IRQ_SHOW=y
+CONFIG_GENERIC_LIB_ASHLDI3=y
+CONFIG_GENERIC_LIB_ASHRDI3=y
+CONFIG_GENERIC_LIB_CMPDI2=y
+CONFIG_GENERIC_LIB_LSHRDI3=y
+CONFIG_GENERIC_LIB_UCMPDI2=y
+CONFIG_GENERIC_PCI_IOMAP=y
+CONFIG_GENERIC_SCHED_CLOCK=y
+CONFIG_GENERIC_SMP_IDLE_THREAD=y
+CONFIG_GENERIC_TIME_VSYSCALL=y
+CONFIG_GPIO_CDEV=y
+CONFIG_HARDWARE_WATCHPOINTS=y
+CONFIG_HAS_DMA=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT=y
+CONFIG_HAS_IOPORT_MAP=y
+CONFIG_HZ_PERIODIC=y
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_IRQCHIP=y
+CONFIG_IRQ_DOMAIN=y
+CONFIG_IRQ_DOMAIN_HIERARCHY=y
+CONFIG_IRQ_FORCED_THREADING=y
+CONFIG_IRQ_MIPS_CPU=y
+CONFIG_IRQ_WORK=y
+# CONFIG_JFFS2_FS is not set
+CONFIG_LIBFDT=y
+CONFIG_LOCK_DEBUGGING_SUPPORT=y
+CONFIG_LZO_COMPRESS=y
+CONFIG_LZO_DECOMPRESS=y
+CONFIG_MIGRATION=y
+CONFIG_MIPS=y
+CONFIG_MIPS_ASID_BITS=8
+CONFIG_MIPS_ASID_SHIFT=0
+CONFIG_MIPS_CM=y
+# CONFIG_MIPS_CMDLINE_FROM_BOOTLOADER is not set
+CONFIG_MIPS_CMDLINE_FROM_DTB=y
+CONFIG_MIPS_CPC=y
+CONFIG_MIPS_CPS=y
+# CONFIG_MIPS_CPS_NS16550_BOOL is not set
+CONFIG_MIPS_CPU_SCACHE=y
+CONFIG_MIPS_GIC=y
+CONFIG_MIPS_L1_CACHE_SHIFT=5
+CONFIG_MIPS_MT=y
+CONFIG_MIPS_MT_FPAFF=y
+CONFIG_MIPS_MT_SMP=y
+# CONFIG_MIPS_NO_APPENDED_DTB is not set
+CONFIG_MIPS_NR_CPU_NR_MAP=4
+CONFIG_MIPS_PERF_SHARED_TC_COUNTERS=y
+CONFIG_MIPS_RAW_APPENDED_DTB=y
+CONFIG_MIPS_SPRAM=y
+CONFIG_MMU_LAZY_TLB_REFCOUNT=y
+CONFIG_MODULES_USE_ELF_REL=y
+CONFIG_MTD_NAND_CORE=y
+CONFIG_MTD_NAND_ECC=y
+CONFIG_MTD_NAND_MTK_BMT=y
+CONFIG_MTD_SPI_NAND=y
+CONFIG_MTD_UBI=y
+CONFIG_MTD_UBI_BEB_LIMIT=13
+CONFIG_MTD_UBI_BLOCK=y
+CONFIG_MTD_UBI_WL_THRESHOLD=4096
+CONFIG_NEED_DMA_MAP_STATE=y
+CONFIG_NEED_SRCU_NMI_SAFE=y
+CONFIG_NET_EGRESS=y
+CONFIG_NET_FLOW_LIMIT=y
+CONFIG_NET_INGRESS=y
+CONFIG_NET_XGRESS=y
+CONFIG_NO_GENERIC_PCI_IOPORT_MAP=y
+CONFIG_NR_CPUS=4
+CONFIG_NVMEM=y
+CONFIG_NVMEM_LAYOUTS=y
+CONFIG_NVMEM_SYSFS=y
+CONFIG_OF=y
+CONFIG_OF_ADDRESS=y
+CONFIG_OF_EARLY_FLATTREE=y
+CONFIG_OF_FLATTREE=y
+CONFIG_OF_GPIO=y
+CONFIG_OF_IRQ=y
+CONFIG_OF_KOBJ=y
+CONFIG_PADATA=y
+CONFIG_PAGE_POOL=y
+CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
+CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
+CONFIG_PCI_DRIVERS_LEGACY=y
+CONFIG_PERF_USE_VMALLOC=y
+CONFIG_PGTABLE_LEVELS=2
+CONFIG_PTP_1588_CLOCK_OPTIONAL=y
+CONFIG_QUEUED_RWLOCKS=y
+CONFIG_QUEUED_SPINLOCKS=y
+CONFIG_RANDSTRUCT_NONE=y
+CONFIG_RATIONAL=y
+CONFIG_REGMAP=y
+CONFIG_REGMAP_MMIO=y
+CONFIG_RFS_ACCEL=y
+CONFIG_RPS=y
+# CONFIG_SCHED_CORE is not set
+CONFIG_SCHED_SMT=y
+CONFIG_SERIAL_8250_AIROHA=y
+CONFIG_SERIAL_MCTRL_GPIO=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SGL_ALLOC=y
+CONFIG_SMP=y
+CONFIG_SMP_UP=y
+CONFIG_SOCK_RX_QUEUE_MAPPING=y
+# CONFIG_SOC_ECONET_EN751221 is not set
+CONFIG_SOC_ECONET_EN7528=y
+CONFIG_SPI=y
+# CONFIG_SPI_AIROHA_EN7523 is not set
+CONFIG_SPI_AIROHA_SNFI=y
+CONFIG_SPI_MASTER=y
+CONFIG_SPI_MEM=y
+CONFIG_SPLIT_PTE_PTLOCKS=y
+CONFIG_SYNC_R4K=y
+CONFIG_SYSCTL_EXCEPTION_TRACE=y
+CONFIG_SYS_HAS_CPU_MIPS32_R1=y
+CONFIG_SYS_HAS_CPU_MIPS32_R2=y
+CONFIG_SYS_HAS_EARLY_PRINTK=y
+CONFIG_SYS_SUPPORTS_32BIT_KERNEL=y
+CONFIG_SYS_SUPPORTS_ARBIT_HZ=y
+CONFIG_SYS_SUPPORTS_BIG_ENDIAN=y
+CONFIG_SYS_SUPPORTS_HIGHMEM=y
+CONFIG_SYS_SUPPORTS_HOTPLUG_CPU=y
+CONFIG_SYS_SUPPORTS_LITTLE_ENDIAN=y
+CONFIG_SYS_SUPPORTS_MIPS16=y
+CONFIG_SYS_SUPPORTS_MIPS_CPS=y
+CONFIG_SYS_SUPPORTS_MULTITHREADING=y
+CONFIG_SYS_SUPPORTS_SCHED_SMT=y
+CONFIG_SYS_SUPPORTS_SMP=y
+CONFIG_SYS_SUPPORTS_ZBOOT=y
+CONFIG_SYS_SUPPORTS_ZBOOT_UART16550=y
+CONFIG_TARGET_ISA_REV=2
+CONFIG_TICK_CPU_ACCOUNTING=y
+CONFIG_TIMER_OF=y
+CONFIG_TIMER_PROBE=y
+CONFIG_TREE_RCU=y
+CONFIG_TREE_SRCU=y
+CONFIG_UBIFS_FS=y
+CONFIG_USE_GENERIC_EARLY_PRINTK_8250=y
+CONFIG_USE_OF=y
+CONFIG_WEAK_ORDERING=y
+CONFIG_XPS=y
+CONFIG_XXHASH=y
+CONFIG_ZBOOT_LOAD_ADDRESS=0x80020000
+CONFIG_ZLIB_DEFLATE=y
+CONFIG_ZLIB_INFLATE=y
+CONFIG_ZSTD_COMMON=y
+CONFIG_ZSTD_COMPRESS=y
+CONFIG_ZSTD_DECOMPRESS=y
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2025 OpenWrt.org
+
+define Profile/Default
+ NAME:=Default Profile
+endef
+
+define Profile/Default/Description
+ Default package set compatible with most EN7528 boards.
+endef
+$(eval $(call Profile,Default))
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only
+
+ARCH:=mipsel
+SUBTARGET:=en7528
+BOARDNAME:=EN7528 based boards
+CPU_TYPE:=24kc
+KERNELNAME:=vmlinuz.bin
+
+define Target/Description
+ Build firmware images for EcoNet EN7528 based boards.
+endef
--- /dev/null
+define Device/en7528_generic
+ DEVICE_VENDOR := EN7528
+ DEVICE_MODEL := Generic
+ DEVICE_DTS := en7528_generic
+endef
+TARGET_DEVICES += en7528_generic
--- /dev/null
+From: Ahmed Naseef <naseefkm@gmail.com>
+Subject: mips: econet: add EN7528 SoC support
+
+The EN7528 is a little endian dual-core MIPS 1004Kc SoC used in xPON
+devices. Unlike the big endian EN751221, EN7528 uses the MIPS GIC
+interrupt controller for SMP.
+
+This adds boot support for the EN7528 SoC family:
+- New SOC_ECONET_EN7528 Kconfig option
+- Little endian support for ECONET platform
+- UART base address adjustment for endianness
+- CPS SMP ops registration for multi-core support
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/arch/mips/Kconfig
++++ b/arch/mips/Kconfig
+@@ -391,13 +391,13 @@ config MACH_DECSTATION
+ config ECONET
+ bool "EcoNet MIPS family"
+ select BOOT_RAW
+- select CPU_BIG_ENDIAN
+ select DEBUG_ZBOOT if DEBUG_KERNEL
+ select EARLY_PRINTK_8250
+ select ECONET_EN751221_TIMER
+ select SERIAL_8250
+ select SERIAL_OF_PLATFORM
+ select SYS_SUPPORTS_BIG_ENDIAN
++ select SYS_SUPPORTS_LITTLE_ENDIAN
+ select SYS_HAS_CPU_MIPS32_R1
+ select SYS_HAS_CPU_MIPS32_R2
+ select SYS_HAS_EARLY_PRINTK
+--- a/arch/mips/econet/Kconfig
++++ b/arch/mips/econet/Kconfig
+@@ -12,6 +12,7 @@ choice
+ config SOC_ECONET_EN751221
+ bool "EN751221 family"
+ select COMMON_CLK
++ select CPU_BIG_ENDIAN
+ select ECONET_EN751221_INTC
+ select IRQ_MIPS_CPU
+ select SMP
+@@ -22,6 +23,23 @@ choice
+ 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.
++
++ config SOC_ECONET_EN7528
++ bool "EN7528 family"
++ select COMMON_CLK
++ select CPU_LITTLE_ENDIAN
++ select IRQ_MIPS_CPU
++ select MIPS_CPU_SCACHE
++ select MIPS_GIC
++ select SMP
++ select SMP_UP
++ select SYS_SUPPORTS_HIGHMEM
++ select SYS_SUPPORTS_MIPS_CPS
++ select SYS_SUPPORTS_MULTITHREADING
++ select SYS_SUPPORTS_SMP
++ help
++ The EN7528 family with dual-core MIPS 1004Kc.
++ Requires MIPS_RAW_APPENDED_DTB=y for boot.
+ endchoice
+
+ choice
+--- a/arch/mips/econet/init.c
++++ b/arch/mips/econet/init.c
+@@ -16,11 +16,16 @@
+ #include <asm/prom.h>
+ #include <asm/smp-ops.h>
+ #include <asm/reboot.h>
++#include <asm/mips-cps.h>
+
+ #define CR_AHB_RSTCR ((void __iomem *)CKSEG1ADDR(0x1fb00040))
+ #define RESET BIT(31)
+
+-#define UART_BASE CKSEG1ADDR(0x1fbf0003)
++#ifdef CONFIG_CPU_LITTLE_ENDIAN
++#define UART_BASE CKSEG1ADDR(0x1fbf0000) /* LE: byte at offset 0 */
++#else
++#define UART_BASE CKSEG1ADDR(0x1fbf0003) /* BE: byte at offset 3 */
++#endif
+ #define UART_REG_SHIFT 2
+
+ static void hw_reset(char *command)
+@@ -51,11 +56,18 @@ void __init plat_mem_setup(void)
+ early_init_dt_scan_memory();
+ }
+
+-/* 3. Overload __weak device_tree_init(), add SMP_UP ops */
++/* 3. Overload __weak device_tree_init(), register SMP ops */
+ void __init device_tree_init(void)
+ {
+ unflatten_and_copy_device_tree();
+
++ /* EN7528 dual-core: probe CM/CPC and register CPS SMP ops */
++ mips_cm_probe();
++ mips_cpc_probe();
++
++ if (!register_cps_smp_ops())
++ return;
++
+ register_up_smp_ops();
+ }
+
+--- a/arch/mips/boot/compressed/uart-16550.c
++++ b/arch/mips/boot/compressed/uart-16550.c
+@@ -21,7 +21,11 @@
+ #endif
+
+ #ifdef CONFIG_ECONET
++#ifdef CONFIG_CPU_LITTLE_ENDIAN
++#define EN75_UART_BASE 0x1fbf0000
++#else
+ #define EN75_UART_BASE 0x1fbf0003
++#endif
+ #define PORT(offset) (CKSEG1ADDR(EN75_UART_BASE) + (4 * (offset)))
+ #endif
+
--- /dev/null
+From: Ahmed Naseef <naseefkm@gmail.com>
+Subject: mips: econet: timer: add EN7528 support to EN751221 timer driver
+
+Extend the existing EN751221 timer driver to support EN7528/EN751627 SoCs.
+The driver now auto-detects the IRQ mode based on device tree:
+- EN751221: Single percpu IRQ (legacy interrupt controller)
+- EN7528/EN751627: Separate IRQ per CPU (GIC shared interrupts)
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/drivers/clocksource/Kconfig
++++ b/drivers/clocksource/Kconfig
+@@ -79,7 +79,10 @@ config ECONET_EN751221_TIMER
+ select CLKSRC_MMIO
+ select TIMER_OF
+ help
+- Support for CPU timer found on EcoNet MIPS based SoCs.
++ Support for CPU timer found on EcoNet EN75xx MIPS based SoCs
++ (EN751221, EN751627, EN7528). The driver supports both GIC-based
++ (separate IRQ per CPU) and legacy interrupt controller (percpu IRQ)
++ modes.
+
+ config FTTMR010_TIMER
+ bool "Faraday Technology timer driver" if COMPILE_TEST
+--- a/drivers/clocksource/timer-econet-en751221.c
++++ b/drivers/clocksource/timer-econet-en751221.c
+@@ -2,12 +2,20 @@
+ /*
+ * Timer present on EcoNet EN75xx MIPS based SoCs.
+ *
++ * This driver supports both:
++ * - EN751221: Single percpu IRQ mode (legacy interrupt controller)
++ * - EN7528/EN751627: Separate IRQ per CPU mode (GIC shared interrupts)
++ *
++ * The mode is auto-detected based on IRQ count in device tree.
++ *
+ * Copyright (C) 2025 by Caleb James DeLisle <cjd@cjdns.fr>
++ * Copyright (C) 2025 by Ahmed Naseef <naseefkm@gmail.com>
+ */
+
+ #include <linux/io.h>
+ #include <linux/cpumask.h>
+ #include <linux/interrupt.h>
++#include <linux/irq.h>
+ #include <linux/clockchips.h>
+ #include <linux/sched_clock.h>
+ #include <linux/of.h>
+@@ -21,10 +29,14 @@
+ #define ECONET_MAX_DELTA GENMASK(ECONET_BITS - 2, 0)
+ /* 34Kc hardware has 1 block and 1004Kc has 2. */
+ #define ECONET_NUM_BLOCKS DIV_ROUND_UP(NR_CPUS, 2)
++#define ECONET_MAX_IRQS 4
+
+ static struct {
+ void __iomem *membase[ECONET_NUM_BLOCKS];
+ u32 freq_hz;
++ int irqs[ECONET_MAX_IRQS];
++ int num_irqs;
++ bool use_percpu_irq;
+ } econet_timer __ro_after_init;
+
+ static DEFINE_PER_CPU(struct clock_event_device, econet_timer_pcpu);
+@@ -98,12 +110,21 @@ static int cevt_init_cpu(uint cpu)
+ struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, cpu);
+ u32 reg;
+
++ if (!econet_timer.use_percpu_irq && cpu >= econet_timer.num_irqs)
++ return -EINVAL;
++
+ pr_debug("%s: Setting up clockevent for CPU %d\n", cd->name, cpu);
+
+ reg = ioread32(reg_ctl(cpu)) | ctl_bit_enabled(cpu);
+ iowrite32(reg, reg_ctl(cpu));
+
+- enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
++ if (econet_timer.use_percpu_irq) {
++ enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
++ } else {
++ if (irq_force_affinity(econet_timer.irqs[cpu], cpumask_of(cpu)))
++ pr_warn("%s: failed to set IRQ %d affinity to CPU %d\n",
++ cd->name, econet_timer.irqs[cpu], cpu);
++ }
+
+ /* Do this last because it synchronously configures the timer */
+ clockevents_config_and_register(cd, econet_timer.freq_hz,
+@@ -126,7 +147,21 @@ static void __init cevt_dev_init(uint cp
+ iowrite32(U32_MAX, reg_compare(cpu));
+ }
+
+-static int __init cevt_init(struct device_node *np)
++static void __init cevt_setup_clockevent(struct clock_event_device *cd,
++ struct device_node *np,
++ int irq, int cpu)
++{
++ cd->rating = 310;
++ cd->features = CLOCK_EVT_FEAT_ONESHOT |
++ CLOCK_EVT_FEAT_C3STOP |
++ CLOCK_EVT_FEAT_PERCPU;
++ cd->set_next_event = cevt_set_next_event;
++ cd->irq = irq;
++ cd->cpumask = cpumask_of(cpu);
++ cd->name = np->name;
++}
++
++static int __init cevt_init_percpu(struct device_node *np)
+ {
+ int i, irq, ret;
+
+@@ -137,42 +172,85 @@ static int __init cevt_init(struct devic
+ }
+
+ 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);
+- goto err_unmap_irq;
++ irq_dispose_mapping(irq);
++ return ret;
+ }
+
+ for_each_possible_cpu(i) {
+ struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, i);
+
+- cd->rating = 310,
+- cd->features = CLOCK_EVT_FEAT_ONESHOT |
+- CLOCK_EVT_FEAT_C3STOP |
+- CLOCK_EVT_FEAT_PERCPU;
+- cd->set_next_event = cevt_set_next_event;
+- cd->irq = irq;
+- cd->cpumask = cpumask_of(i);
+- cd->name = np->name;
++ cevt_setup_clockevent(cd, np, irq, i);
++ cevt_dev_init(i);
++ }
++
++ return 0;
++}
++
++static int __init cevt_init_separate(struct device_node *np)
++{
++ int i, ret;
++
++ for (i = 0; i < econet_timer.num_irqs; i++) {
++ struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, i);
++
++ econet_timer.irqs[i] = irq_of_parse_and_map(np, i);
++ if (econet_timer.irqs[i] <= 0) {
++ pr_err("%pOFn: irq_of_parse_and_map failed", np);
++ ret = -EINVAL;
++ goto err_free_irqs;
++ }
++
++ ret = request_irq(econet_timer.irqs[i], cevt_interrupt,
++ IRQF_TIMER | IRQF_NOBALANCING,
++ np->name, NULL);
++ if (ret < 0) {
++ pr_err("%pOFn: IRQ %d setup failed (%d)\n", np,
++ econet_timer.irqs[i], ret);
++ irq_dispose_mapping(econet_timer.irqs[i]);
++ goto err_free_irqs;
++ }
+
++ cevt_setup_clockevent(cd, np, econet_timer.irqs[i], i);
+ cevt_dev_init(i);
+ }
+
+- cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+- "clockevents/econet/timer:starting",
+- cevt_init_cpu, NULL);
+ return 0;
+
+-err_unmap_irq:
+- irq_dispose_mapping(irq);
++err_free_irqs:
++ while (--i >= 0) {
++ free_irq(econet_timer.irqs[i], NULL);
++ irq_dispose_mapping(econet_timer.irqs[i]);
++ }
+ return ret;
+ }
+
++static int __init cevt_init(struct device_node *np)
++{
++ econet_timer.num_irqs = of_irq_count(np);
++ if (econet_timer.num_irqs <= 0 || econet_timer.num_irqs > ECONET_MAX_IRQS) {
++ pr_err("%pOFn: invalid IRQ count %d\n", np, econet_timer.num_irqs);
++ return -EINVAL;
++ }
++
++ /* Auto-detect mode based on IRQ count:
++ * 1 IRQ = percpu mode (EN751221)
++ * N IRQs = separate IRQ per CPU (EN7528/EN751627)
++ */
++ econet_timer.use_percpu_irq = (econet_timer.num_irqs == 1);
++
++ if (econet_timer.use_percpu_irq)
++ return cevt_init_percpu(np);
++ else
++ return cevt_init_separate(np);
++}
++
+ static int __init timer_init(struct device_node *np)
+ {
+ int num_blocks = DIV_ROUND_UP(num_possible_cpus(), 2);
+ struct clk *clk;
+- int ret;
++ int ret, i;
+
+ clk = of_clk_get(np, 0);
+ if (IS_ERR(clk)) {
+@@ -182,11 +260,12 @@ static int __init timer_init(struct devi
+
+ econet_timer.freq_hz = clk_get_rate(clk);
+
+- for (int i = 0; i < num_blocks; i++) {
++ for (i = 0; i < num_blocks; i++) {
+ econet_timer.membase[i] = of_iomap(np, i);
+ if (!econet_timer.membase[i]) {
+ pr_err("%pOFn: failed to map register [%d]\n", np, i);
+- return -ENXIO;
++ ret = -ENXIO;
++ goto err_unmap;
+ }
+ }
+
+@@ -196,21 +275,34 @@ static int __init timer_init(struct devi
+ clocksource_mmio_readl_up);
+ if (ret) {
+ pr_err("%pOFn: clocksource_mmio_init failed: %d", np, ret);
+- return ret;
++ goto err_unmap;
+ }
+
+ ret = cevt_init(np);
+ if (ret < 0)
+- return ret;
++ goto err_unmap;
++
++ cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
++ "clockevents/econet/timer:starting",
++ cevt_init_cpu, NULL);
+
+ sched_clock_register(sched_clock_read, ECONET_BITS,
+ econet_timer.freq_hz);
+
+- pr_info("%pOFn: using %u.%03u MHz high precision timer\n", np,
++ pr_info("%pOFn: using %u.%03u MHz high precision timer (%s mode)\n", np,
+ econet_timer.freq_hz / 1000000,
+- (econet_timer.freq_hz / 1000) % 1000);
++ (econet_timer.freq_hz / 1000) % 1000,
++ econet_timer.use_percpu_irq ? "percpu" : "separate IRQ");
+
+ return 0;
++
++err_unmap:
++ for (i = 0; i < num_blocks; i++) {
++ if (econet_timer.membase[i])
++ iounmap(econet_timer.membase[i]);
++ }
++ return ret;
+ }
+
+-TIMER_OF_DECLARE(econet_timer_hpt, "econet,en751221-timer", timer_init);
++TIMER_OF_DECLARE(econet_en751221_timer, "econet,en751221-timer", timer_init);
++TIMER_OF_DECLARE(econet_en7528_timer, "econet,en7528-timer", timer_init);
--- /dev/null
+spi: airoha-snfi: enable for EcoNet EN7528
+
+Enable the Airoha SNFI (SPI NAND Flash Interface) driver for EcoNet
+EN7528 SoC. The EN7528 shares the same SPI controller and NFI DMA
+engine as the Airoha EN7523/EN7581, with identical register layouts.
+
+Using the DMA-capable SNFI driver provides significantly better
+performance compared to the manual mode spi-en7523 driver.
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/drivers/spi/Kconfig
++++ b/drivers/spi/Kconfig
+@@ -59,7 +59,7 @@ comment "SPI Master Controller Drivers"
+
+ config SPI_AIROHA_SNFI
+ tristate "Airoha SPI NAND Flash Interface"
+- depends on ARCH_AIROHA || COMPILE_TEST
++ depends on ARCH_AIROHA || ECONET || COMPILE_TEST
+ depends on SPI_MASTER
+ select REGMAP_MMIO
+ help
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
-@@ -392,6 +392,7 @@ config ECONET
+@@ -391,6 +391,7 @@ config MACH_DECSTATION
+ config ECONET
bool "EcoNet MIPS family"
select BOOT_RAW
- select CPU_BIG_ENDIAN
+ select DMA_NONCOHERENT
select DEBUG_ZBOOT if DEBUG_KERNEL
select EARLY_PRINTK_8250
- select ECONET_EN751221_TIMER
+ select SERIAL_8250
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -71,7 +71,7 @@ config USB_XHCI_HISTB
--- /dev/null
+--- /dev/null
++++ b/drivers/tty/serial/8250/8250_en7523.c
+@@ -0,0 +1,94 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Airoha EN7523 driver.
++ *
++ * Copyright (c) 2022 Genexis Sweden AB
++ * Author: Benjamin Larsson <benjamin.larsson@genexis.eu>
++ */
++#include <linux/clk.h>
++#include <linux/io.h>
++#include <linux/module.h>
++#include <linux/of_irq.h>
++#include <linux/of_platform.h>
++#include <linux/pinctrl/consumer.h>
++#include <linux/platform_device.h>
++#include <linux/pm_runtime.h>
++#include <linux/serial_8250.h>
++#include <linux/serial_reg.h>
++#include <linux/console.h>
++#include <linux/dma-mapping.h>
++#include <linux/tty.h>
++#include <linux/tty_flip.h>
++
++#include "8250.h"
++
++
++/* The Airoha UART is 16550-compatible except for the baud rate calculation.
++ *
++ * crystal_clock = 20 MHz
++ * xindiv_clock = crystal_clock / clock_div
++ * (x/y) = XYD, 32 bit register with 16 bits of x and and then 16 bits of y
++ * clock_div = XINCLK_DIVCNT (default set to 10 (0x4)),
++ * - 3 bit register [ 1, 2, 4, 8, 10, 12, 16, 20 ]
++ *
++ * baud_rate = ((xindiv_clock) * (x/y)) / ([BRDH,BRDL] * 16)
++ *
++ * XYD_y seems to need to be larger then XYD_x for things to work.
++ * Setting [BRDH,BRDL] to [0,1] and XYD_y to 65000 give even values
++ * for usual baud rates.
++ *
++ * Selecting divider needs to fulfill
++ * 1.8432 MHz <= xindiv_clk <= APB clock / 2
++ * The clocks are unknown but a divider of value 1 did not work.
++ *
++ * Optimally the XYD, BRD and XINCLK_DIVCNT registers could be searched to
++ * find values that gives the least error for every baud rate. But searching
++ * the space takes time and in practise only a few rates are of interest.
++ * With some value combinations not working a tested subset is used giving
++ * a usable range from 110 to 460800 baud.
++ */
++
++#define CLOCK_DIV_TAB_ELEMS 3
++#define XYD_Y 65000
++#define XINDIV_CLOCK 20000000
++#define UART_BRDL_20M 0x01
++#define UART_BRDH_20M 0x00
++
++static int clock_div_tab[] = { 10, 4, 2};
++static int clock_div_reg[] = { 4, 2, 1};
++
++
++int en7523_set_uart_baud_rate (struct uart_port *port, unsigned int baud)
++{
++ struct uart_8250_port *up = up_to_u8250p(port);
++ unsigned int xyd_x, nom, denom;
++ int i;
++
++ /* set DLAB to access the baud rate divider registers (BRDH, BRDL) */
++ serial_port_out(port, UART_LCR, up->lcr | UART_LCR_DLAB);
++
++ /* set baud rate calculation defaults */
++
++ /* set BRDIV ([BRDH,BRDL]) to 1 */
++ serial_port_out(port, UART_BRDL, UART_BRDL_20M);
++ serial_port_out(port, UART_BRDH, UART_BRDH_20M);
++
++ /* calculate XYD_x and XINCLKDR register */
++
++ for (i = 0 ; i < CLOCK_DIV_TAB_ELEMS ; i++) {
++ denom = (XINDIV_CLOCK/40) / clock_div_tab[i];
++ nom = (baud * (XYD_Y/40));
++ xyd_x = ((nom/denom) << 4);
++ if (xyd_x < XYD_Y) break;
++ }
++
++ serial_port_out(port, UART_XINCLKDR, clock_div_reg[i]);
++ serial_port_out(port, UART_XYD, (xyd_x<<16) | XYD_Y);
++
++ /* unset DLAB */
++ serial_port_out(port, UART_LCR, up->lcr);
++
++ return 0;
++}
++
++EXPORT_SYMBOL_GPL(en7523_set_uart_baud_rate);
+--- a/drivers/tty/serial/8250/8250_of.c
++++ b/drivers/tty/serial/8250/8250_of.c
+@@ -341,6 +341,7 @@ static const struct of_device_id of_plat
+ { .compatible = "ti,da830-uart", .data = (void *)PORT_DA830, },
+ { .compatible = "nuvoton,wpcm450-uart", .data = (void *)PORT_NPCM, },
+ { .compatible = "nuvoton,npcm750-uart", .data = (void *)PORT_NPCM, },
++ { .compatible = "airoha,en7523-uart", .data = (void *)PORT_AIROHA, },
+ { /* end of list */ },
+ };
+ MODULE_DEVICE_TABLE(of, of_platform_serial_table);
+--- a/drivers/tty/serial/8250/8250_port.c
++++ b/drivers/tty/serial/8250/8250_port.c
+@@ -319,6 +319,14 @@ static const struct serial8250_config ua
+ .rxtrig_bytes = {1, 8, 16, 30},
+ .flags = UART_CAP_FIFO | UART_CAP_AFE,
+ },
++ [PORT_AIROHA] = {
++ .name = "Airoha 16550",
++ .fifo_size = 8,
++ .tx_loadsz = 1,
++ .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01,
++ .rxtrig_bytes = {1, 4},
++ .flags = UART_CAP_FIFO,
++ },
+ };
+
+ /* Uart divisor latch read */
+@@ -2835,6 +2843,12 @@ serial8250_do_set_termios(struct uart_po
+
+ serial8250_set_divisor(port, baud, quot, frac);
+
++#ifdef CONFIG_SERIAL_8250_AIROHA
++ /* Airoha SoCs have custom registers for baud rate settings */
++ if (port->type == PORT_AIROHA)
++ en7523_set_uart_baud_rate(port, baud);
++#endif
++
+ /*
+ * LCR DLAB must be set to enable 64-byte FIFO mode. If the FCR
+ * is written without DLAB set, this mode will be disabled.
+--- a/drivers/tty/serial/8250/Kconfig
++++ b/drivers/tty/serial/8250/Kconfig
+@@ -355,6 +355,16 @@ config SERIAL_8250_ACORN
+ system, say Y to this option. The driver can handle 1, 2, or 3 port
+ cards. If unsure, say N.
+
++config SERIAL_8250_AIROHA
++ tristate "Airoha UART support"
++ depends on (ARCH_AIROHA || COMPILE_TEST) && OF && SERIAL_8250
++ help
++ Selecting this option enables an Airoha SoC specific baud rate
++ calculation routine on an otherwise 16550 compatible UART hardware.
++
++ If you have an Airoha based board and want to use the serial port,
++ say Y to this option. If unsure, say N.
++
+ config SERIAL_8250_BCM2835AUX
+ tristate "BCM2835 auxiliar mini UART support"
+ depends on ARCH_BCM2835 || COMPILE_TEST
+--- a/drivers/tty/serial/8250/Makefile
++++ b/drivers/tty/serial/8250/Makefile
+@@ -20,6 +20,7 @@ obj-$(CONFIG_SERIAL_8250_CONSOLE) += 825
+
+ obj-$(CONFIG_SERIAL_8250_ACCENT) += 8250_accent.o
+ obj-$(CONFIG_SERIAL_8250_ACORN) += 8250_acorn.o
++obj-$(CONFIG_SERIAL_8250_AIROHA) += 8250_en7523.o
+ obj-$(CONFIG_SERIAL_8250_ASPEED_VUART) += 8250_aspeed_vuart.o
+ obj-$(CONFIG_SERIAL_8250_BCM2835AUX) += 8250_bcm2835aux.o
+ obj-$(CONFIG_SERIAL_8250_BCM7271) += 8250_bcm7271.o
+--- a/include/uapi/linux/serial_reg.h
++++ b/include/uapi/linux/serial_reg.h
+@@ -383,5 +383,17 @@
+ #define UART_ALTR_EN_TXFIFO_LW 0x01 /* Enable the TX FIFO Low Watermark */
+ #define UART_ALTR_TX_LOW 0x41 /* Tx FIFO Low Watermark */
+
++/*
++ * These are definitions for the Airoha EN75XX uart registers
++ * Normalized because of 32 bits registers.
++ */
++#define UART_BRDL 0
++#define UART_BRDH 1
++#define UART_XINCLKDR 10
++#define UART_XYD 11
++#define UART_TXLVLCNT 12
++#define UART_RXLVLCNT 13
++#define UART_FINTLVL 14
++
+ #endif /* _LINUX_SERIAL_REG_H */
+
+--- a/include/uapi/linux/serial_core.h
++++ b/include/uapi/linux/serial_core.h
+@@ -31,6 +31,7 @@
+ #define PORT_ALTR_16550_F128 28 /* Altera 16550 UART with 128 FIFOs */
+ #define PORT_RT2880 29 /* Ralink RT2880 internal UART */
+ #define PORT_16550A_FSL64 30 /* Freescale 16550 UART with 64 FIFOs */
++#define PORT_AIROHA 31 /* Airoha 16550 UART */
+
+ /*
+ * ARM specific type numbers. These are not currently guaranteed
+--- a/include/linux/serial_8250.h
++++ b/include/linux/serial_8250.h
+@@ -195,6 +195,7 @@ void serial8250_do_set_mctrl(struct uart
+ void serial8250_do_set_divisor(struct uart_port *port, unsigned int baud,
+ unsigned int quot);
+ int fsl8250_handle_irq(struct uart_port *port);
++int en7523_set_uart_baud_rate(struct uart_port *port, unsigned int baud);
+ int serial8250_handle_irq(struct uart_port *port, unsigned int iir);
+ u16 serial8250_rx_chars(struct uart_8250_port *up, u16 lsr);
+ void serial8250_read_char(struct uart_8250_port *up, u16 lsr);
--- /dev/null
+serial: 8250: airoha: add EcoNet platform support
+
+The EcoNet EN75xx SoCs use the same UART IP core as the Airoha
+AN7523 SoCs. Add ECONET to the Kconfig dependency to enable
+the Airoha UART driver for EcoNet platforms.
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/drivers/tty/serial/8250/Kconfig
++++ b/drivers/tty/serial/8250/Kconfig
+@@ -357,7 +357,7 @@ config SERIAL_8250_ACORN
+
+ config SERIAL_8250_AIROHA
+ tristate "Airoha UART support"
+- depends on (ARCH_AIROHA || COMPILE_TEST) && OF && SERIAL_8250
++ depends on (ARCH_AIROHA || ECONET || COMPILE_TEST) && OF && SERIAL_8250
+ help
+ Selecting this option enables an Airoha SoC specific baud rate
+ calculation routine on an otherwise 16550 compatible UART hardware.