All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support
       [not found] <20260825224330.713151-1-maskachoska.ref@yahoo.com>
@ 2026-08-25 22:41 ` AK Sharma
  2026-08-25 22:41   ` [PATCH v1 01/12] mips: en75xx: add EcoNet/Airoha EN75xx " AK Sharma
                     ` (13 more replies)
  0 siblings, 14 replies; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:41 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

This series adds U-Boot support for the EcoNet/Airoha EN75xx MIPS SoC
family: the big-endian MIPS34Kc EN7512/EN7521 and the little-endian
MIPS1004Kc EN7528, used in many GPON/XPON ONT home gateways. It extends
the existing Airoha SNFI SPI-NAND and GDM Ethernet drivers.

It brings up the SoC (TPL/SPL, cache/CM init), and adds the UART, the
EN75 BMT SPI-NAND bad-block mapping, weak bootm decompression hooks for
the coherent CM, and Ethernet. Reference boards for the EN7512 and EN7528
are included, plus the EN7528 R3-hook boot variant.

The DDR calibration stage is a vendor artifact and is not shipped
in-tree. The reference board packages it as a binman "blob-ext"; build
those configs with BINMAN_ALLOW_MISSING=1 and supply the blob to run
(see arch/mips/mach-en75xx/README.ddr-blob). A native DDR-init
implementation is a known TODO.

Tested on a Nokia G-2425G-A (EN7528, MT29F2G01 SPI-NAND): boots to the
prompt with correct 256 MiB DRAM, working Ethernet/TFTP and SPI-NAND.

AK Sharma (12):
  mips: en75xx: add EcoNet/Airoha EN75xx SoC support
  mips: en75xx: add TPL and SPL early boot stages
  serial: en75xx: add EcoNet/Airoha EN75xx UART driver
  spi: airoha-snfi: add EN7528 SPI-NAND controller support
  mtd: spi-nand: add EN75 BMT bad-block management support
  net: airoha-eth: add EN7528 Ethernet support
  mtd: env, cmd: add EN75 NAND remap handling
  boot: image: add weak hooks for the decompression buffer and flush
  mips: dts: add EcoNet EN75xx device trees
  board: econet: add EN7512 reference board
  board: econet: add EN7528 board support
  doc: board: econet: add EN75xx documentation

 arch/mips/Kconfig                             |  21 +
 arch/mips/Makefile                            |   2 +
 arch/mips/cpu/start.S                         |   2 +
 arch/mips/dts/Makefile                        |   2 +
 .../dts/econet,en7512-reference-u-boot.dtsi   |  58 ++
 arch/mips/dts/econet,en7512-reference.dts     |  27 +
 arch/mips/dts/econet,en7512.dtsi              |  88 ++
 arch/mips/dts/econet,en7528-ram-u-boot.dtsi   |   6 +
 arch/mips/dts/econet,en7528-ram.dts           |  72 ++
 .../dts/econet,en7528-reference-u-boot.dtsi   |  62 ++
 arch/mips/dts/econet,en7528-reference.dts     |  25 +
 arch/mips/dts/econet,en7528.dtsi              | 164 ++++
 arch/mips/mach-en75xx/Kconfig                 | 118 +++
 arch/mips/mach-en75xx/Makefile                |  17 +
 arch/mips/mach-en75xx/README.ddr-blob         |  21 +
 arch/mips/mach-en75xx/cache.c                 | 121 +++
 arch/mips/mach-en75xx/cpu.c                   |  95 ++
 arch/mips/mach-en75xx/early_sfc.c             | 352 ++++++++
 arch/mips/mach-en75xx/include/mach/en75xx.h   | 104 +++
 arch/mips/mach-en75xx/spl/Makefile            |   2 +
 arch/mips/mach-en75xx/spl/spl.c               |  47 +
 arch/mips/mach-en75xx/tpl/Makefile            |   3 +
 arch/mips/mach-en75xx/tpl/start.S             | 101 +++
 arch/mips/mach-en75xx/tpl/tpl.c               | 158 ++++
 board/econet/en7512/MAINTAINERS               |   8 +
 board/econet/en7512/Makefile                  |   2 +
 board/econet/en7512/board.c                   |  34 +
 board/econet/en7528/MAINTAINERS               |  13 +
 board/econet/en7528/Makefile                  |   2 +
 board/econet/en7528/board.c                   | 110 +++
 board/econet/en7528/mk_tclinux_uboot.py       | 112 +++
 board/econet/en7528/tclinux_stub.S            |  65 ++
 boot/image.c                                  |  34 +-
 cmd/mtd.c                                     |  30 +-
 configs/en7512_reference_defconfig            |  93 ++
 configs/en7528_jcow414_r3hook_defconfig       |  76 ++
 configs/en7528_ram_defconfig                  |  62 ++
 configs/en7528_reference_defconfig            |  40 +
 defenvs/en7528_jcow414_r3hook_env             |   5 +
 doc/board/econet/en7512.rst                   |  70 ++
 doc/board/econet/en7528.rst                   |  39 +
 doc/board/econet/index.rst                    |  10 +
 doc/board/index.rst                           |   1 +
 drivers/mtd/nand/spi/Kconfig                  |   8 +
 drivers/mtd/nand/spi/Makefile                 |   1 +
 drivers/mtd/nand/spi/core.c                   |   9 +-
 drivers/mtd/nand/spi/en75_bmt.c               | 665 ++++++++++++++
 drivers/mtd/nand/spi/en75_bmt.h               |  24 +
 drivers/net/Kconfig                           |   8 +-
 drivers/net/airoha_eth.c                      | 850 +++++++++++++++++-
 drivers/phy/Kconfig                           |   2 +-
 drivers/serial/Kconfig                        |   7 +
 drivers/serial/Makefile                       |   1 +
 drivers/serial/serial_en75xx.c                | 170 ++++
 drivers/spi/Kconfig                           |   3 +-
 drivers/spi/airoha_snfi_spi.c                 | 409 +++++++--
 drivers/usb/host/Kconfig                      |   2 +-
 env/Kconfig                                   |   2 +-
 env/mtd.c                                     |  98 +-
 include/configs/en7512.h                      |   8 +
 include/configs/en7528.h                      |   9 +
 include/image.h                               |   5 +
 62 files changed, 4578 insertions(+), 177 deletions(-)
 create mode 100644 arch/mips/dts/econet,en7512-reference-u-boot.dtsi
 create mode 100644 arch/mips/dts/econet,en7512-reference.dts
 create mode 100644 arch/mips/dts/econet,en7512.dtsi
 create mode 100644 arch/mips/dts/econet,en7528-ram-u-boot.dtsi
 create mode 100644 arch/mips/dts/econet,en7528-ram.dts
 create mode 100644 arch/mips/dts/econet,en7528-reference-u-boot.dtsi
 create mode 100644 arch/mips/dts/econet,en7528-reference.dts
 create mode 100644 arch/mips/dts/econet,en7528.dtsi
 create mode 100644 arch/mips/mach-en75xx/Kconfig
 create mode 100644 arch/mips/mach-en75xx/Makefile
 create mode 100644 arch/mips/mach-en75xx/README.ddr-blob
 create mode 100644 arch/mips/mach-en75xx/cache.c
 create mode 100644 arch/mips/mach-en75xx/cpu.c
 create mode 100644 arch/mips/mach-en75xx/early_sfc.c
 create mode 100644 arch/mips/mach-en75xx/include/mach/en75xx.h
 create mode 100644 arch/mips/mach-en75xx/spl/Makefile
 create mode 100644 arch/mips/mach-en75xx/spl/spl.c
 create mode 100644 arch/mips/mach-en75xx/tpl/Makefile
 create mode 100644 arch/mips/mach-en75xx/tpl/start.S
 create mode 100644 arch/mips/mach-en75xx/tpl/tpl.c
 create mode 100644 board/econet/en7512/MAINTAINERS
 create mode 100644 board/econet/en7512/Makefile
 create mode 100644 board/econet/en7512/board.c
 create mode 100644 board/econet/en7528/MAINTAINERS
 create mode 100644 board/econet/en7528/Makefile
 create mode 100644 board/econet/en7528/board.c
 create mode 100644 board/econet/en7528/mk_tclinux_uboot.py
 create mode 100644 board/econet/en7528/tclinux_stub.S
 create mode 100644 configs/en7512_reference_defconfig
 create mode 100644 configs/en7528_jcow414_r3hook_defconfig
 create mode 100644 configs/en7528_ram_defconfig
 create mode 100644 configs/en7528_reference_defconfig
 create mode 100644 defenvs/en7528_jcow414_r3hook_env
 create mode 100644 doc/board/econet/en7512.rst
 create mode 100644 doc/board/econet/en7528.rst
 create mode 100644 doc/board/econet/index.rst
 create mode 100644 drivers/mtd/nand/spi/en75_bmt.c
 create mode 100644 drivers/mtd/nand/spi/en75_bmt.h
 create mode 100644 drivers/serial/serial_en75xx.c
 create mode 100644 include/configs/en7512.h
 create mode 100644 include/configs/en7528.h

-- 
2.53.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH v1 01/12] mips: en75xx: add EcoNet/Airoha EN75xx SoC support
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
@ 2026-08-25 22:41   ` AK Sharma
  2026-08-25 22:41   ` [PATCH v1 02/12] mips: en75xx: add TPL and SPL early boot stages AK Sharma
                     ` (12 subsequent siblings)
  13 siblings, 0 replies; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:41 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Add the core support for the EcoNet/Airoha EN75xx MIPS SoC family: the
big-endian MIPS34Kc EN7512/EN7521 and the little-endian MIPS1004Kc
EN7528. This adds the ARCH_EN75XX Kconfig entry, the mach-en75xx CPU and
cache init, and the SoC register definitions.

dram_init() takes the DRAM size published by the DDR stage in
REG_SAVE_INFO and falls back to probing with get_ram_size() when that
field reads back an implausibly small value (some bootbases leave it
unset), sizing through the uncached KSEG1 alias and capping the scan at
256 MiB to stay clear of the SoC register island.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 arch/mips/Kconfig                           |  21 ++++
 arch/mips/Makefile                          |   2 +
 arch/mips/cpu/start.S                       |   2 +
 arch/mips/mach-en75xx/Kconfig               | 118 +++++++++++++++++++
 arch/mips/mach-en75xx/Makefile              |  17 +++
 arch/mips/mach-en75xx/cache.c               | 121 ++++++++++++++++++++
 arch/mips/mach-en75xx/cpu.c                 |  95 +++++++++++++++
 arch/mips/mach-en75xx/include/mach/en75xx.h | 104 +++++++++++++++++
 8 files changed, 480 insertions(+)
 create mode 100644 arch/mips/mach-en75xx/Kconfig
 create mode 100644 arch/mips/mach-en75xx/Makefile
 create mode 100644 arch/mips/mach-en75xx/cache.c
 create mode 100644 arch/mips/mach-en75xx/cpu.c
 create mode 100644 arch/mips/mach-en75xx/include/mach/en75xx.h

diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index 36612756..c0c98f37 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -88,6 +88,26 @@ config ARCH_MTMIPS
 	select SUPPORT_LITTLE_ENDIAN
 	select SUPPORT_SPL
 
+config ARCH_EN75XX
+	bool "Support EcoNet/Airoha EN75xx MIPS platforms"
+	select HAS_FIXED_TIMER_FREQUENCY
+	select DM
+	select DM_SERIAL
+	select EN75XX_SERIAL
+	select OF_CONTROL
+	select ROM_EXCEPTION_VECTORS
+	select SUPPORT_BIG_ENDIAN
+	select SUPPORT_LITTLE_ENDIAN
+	select SUPPORTS_CPU_MIPS32_R2
+	select SUPPORT_SPL
+	select SPL_SKIP_LOWLEVEL_INIT if SPL
+	select SUPPORT_TPL
+	select SYS_CACHE_SHIFT_5
+	select MIPS_TUNE_34KC
+	help
+	  Support the EcoNet/Airoha EN75xx MIPS SoCs: the big-endian MIPS34Kc
+	  EN7512/EN7521 and the little-endian MIPS1004Kc EN7528.
+
 config ARCH_JZ47XX
 	bool "Support Ingenic JZ47xx"
 	select SUPPORT_SPL
@@ -201,6 +221,7 @@ source "arch/mips/mach-mscc/Kconfig"
 source "arch/mips/mach-bmips/Kconfig"
 source "arch/mips/mach-jz47xx/Kconfig"
 source "arch/mips/mach-pic32/Kconfig"
+source "arch/mips/mach-en75xx/Kconfig"
 source "arch/mips/mach-mtmips/Kconfig"
 source "arch/mips/mach-octeon/Kconfig"
 
diff --git a/arch/mips/Makefile b/arch/mips/Makefile
index 453c7885..7c3976aa 100644
--- a/arch/mips/Makefile
+++ b/arch/mips/Makefile
@@ -9,6 +9,7 @@ endif
 
 ifeq ($(CONFIG_TPL_BUILD),y)
 head-$(CONFIG_SOC_MT7621) := arch/mips/mach-mtmips/mt7621/tpl/start.o
+head-$(CONFIG_ARCH_EN75XX) := arch/mips/mach-en75xx/tpl/start.o
 endif
 
 libs-y += arch/mips/cpu/
@@ -16,6 +17,7 @@ libs-y += arch/mips/lib/
 
 machine-$(CONFIG_ARCH_ATH79) += ath79
 machine-$(CONFIG_ARCH_BMIPS) += bmips
+machine-$(CONFIG_ARCH_EN75XX) += en75xx
 machine-$(CONFIG_ARCH_JZ47XX) += jz47xx
 machine-$(CONFIG_MACH_PIC32) += pic32
 machine-$(CONFIG_ARCH_MTMIPS) += mtmips
diff --git a/arch/mips/cpu/start.S b/arch/mips/cpu/start.S
index a95c95bc..48eb69b1 100644
--- a/arch/mips/cpu/start.S
+++ b/arch/mips/cpu/start.S
@@ -124,7 +124,9 @@ ENTRY(_start)
 #endif
 
 reset:
+#ifndef CONFIG_MIPS_SKIP_CP0_COUNT_CLEAR
 	mtc0	zero, CP0_COUNT	# clear cp0 count for most accurate boot timing
+#endif
 #if __mips_isa_rev >= 6
 	mfc0	t0, CP0_CONFIG, 5
 	and	t0, t0, MIPS_CONF5_VP
diff --git a/arch/mips/mach-en75xx/Kconfig b/arch/mips/mach-en75xx/Kconfig
new file mode 100644
index 00000000..5c512559
--- /dev/null
+++ b/arch/mips/mach-en75xx/Kconfig
@@ -0,0 +1,118 @@
+menu "EcoNet/Airoha EN75xx platforms"
+	depends on ARCH_EN75XX
+
+config SYS_VENDOR
+	default "econet"
+
+# Common SoC-identity flag selected by every EN7528 board (reference/RAM/...).
+config SOC_EN7528
+	bool
+	select MIPS_SKIP_CP0_COUNT_CLEAR
+
+config MIPS_SKIP_CP0_COUNT_CLEAR
+	bool
+	help
+	  Do not mtc0 zero,CP0_COUNT at reset. On EN7528 Count is GIC/CM
+	  backed and that write stalls the CPU. Other MIPS targets keep
+	  the generic clear in arch/mips/cpu/start.S.
+
+config SYS_SOC
+	default "en7528" if SOC_EN7528
+	default "en7512"
+
+config SYS_BOARD
+	default "en7528" if SOC_EN7528
+	default "en7512"
+
+config SYS_CONFIG_NAME
+	default "en7528" if SOC_EN7528
+	default "en7512"
+
+config SYS_MALLOC_F_LEN
+	default 0x2000
+
+config SYS_DCACHE_SIZE
+	default 32768
+
+config SYS_DCACHE_LINE_SIZE
+	default 32
+
+config SYS_ICACHE_SIZE
+	default 32768
+
+config SYS_ICACHE_LINE_SIZE
+	default 32
+
+config TEXT_BASE
+	default 0x81E00000 if BOARD_EN7528_R3HOOK
+	default 0x80020000 if BOARD_EN7528_RAM
+	default 0x81000000
+
+config SPL_TEXT_BASE
+	default 0x80010000
+
+config SPL_SIZE_LIMIT
+	default 0x30000
+
+config TPL_TEXT_BASE
+	default 0xbfc00000
+
+config TPL_MAX_SIZE
+	default 0x8000
+
+
+config BUILD_TARGET
+	default "u-boot.img" if BOARD_EN7512_REFERENCE
+	default "u-boot.bin" if BOARD_EN7528_REFERENCE
+	default "u-boot.bin" if BOARD_EN7528_RAM
+	default "u-boot.bin" if BOARD_EN7528_R3HOOK
+
+choice
+	prompt "EN75xx reference board"
+	default BOARD_EN7512_REFERENCE
+
+config BOARD_EN7512_REFERENCE
+	bool "EN7512/EN7521 reference board"
+	select BINMAN
+	select SPL_LOADER_SUPPORT if SPL
+	help
+	  Build a boot image compatible with the EN7512/EN7521 TCBoot flow.
+	  TPL initializes the serial flash controller, executes the legacy DDR
+	  calibration stage from FE SRAM and loads SPL into DDR.
+
+config BOARD_EN7528_REFERENCE
+	bool "EN7528 reference board (DASAN H660GM-A)"
+	select SOC_EN7528
+	select BINMAN
+	select SPL_LOADER_SUPPORT if SPL
+	help
+	  Build a boot image for the little-endian MIPS1004Kc EN7528. TPL brings
+	  up the SoC and runs the lifted FE-SRAM DDR3 stage (en7528_ddr.bin,
+	  carved from the stock bootbase) before loading SPL into DDR. The exact
+	  DDR-stage entry/handoff is validated over UART during bring-up; see
+	  doc/board/econet/en7528-porting.md.
+
+config BOARD_EN7528_RAM
+	bool "EN7528 RAM-boot (loaded by the stock bootbase at 0x80020000)"
+	select SOC_EN7528
+	help
+	  Build a standalone U-Boot proper (no TPL/SPL, no DDR blob) linked at
+	  0x80020000 to be RAM-loaded by the stock bootbase, which has already
+	  initialized DRAM. XMODEM it in from the bldr> prompt ("xmdm 80020000
+	  <len>") and run it with "jump 80020000". This is the non-destructive
+	  bring-up path that exercises the whole upper stack (console, DTS, DM,
+	  U-Boot proper) on real EN7528 silicon without needing the DDR stage.
+
+config BOARD_EN7528_R3HOOK
+	bool "EN7528 JCOW414 R3-hook (FIT + bootm, U-Boot at flash 0x40000)"
+	select SOC_EN7528
+	help
+	  NAND-resident U-Boot for the JCOW414 R3-hook: vendor bootbase + our
+	  R3 (flash 0x20000) SFC-reads this image from flash 0x40000 into
+	  DRAM 0x81E00000. Linked above the OpenWrt FIT kernel load address
+	  (0x80020000) so bootm decompress cannot overwrite U-Boot. Env lives
+	  in MTD at 0xC0000/0xE0000; bootcmd loads the FIT at 0x100000.
+
+endchoice
+
+endmenu
diff --git a/arch/mips/mach-en75xx/Makefile b/arch/mips/mach-en75xx/Makefile
new file mode 100644
index 00000000..f40e49ec
--- /dev/null
+++ b/arch/mips/mach-en75xx/Makefile
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+ifeq ($(CONFIG_TPL_BUILD),y)
+obj-y += early_sfc.o
+obj-y += tpl/
+else
+obj-y += cpu.o
+ifeq ($(CONFIG_SPL_BUILD),y)
+obj-y += early_sfc.o
+obj-y += spl/
+else
+# U-Boot proper
+obj-y += cache.o
+# PIO SFC reader for `en75 sfctest` vs the DM SPI-NAND path.
+obj-$(CONFIG_CMD_EN75BOOT) += early_sfc.o
+endif
+endif
diff --git a/arch/mips/mach-en75xx/cache.c b/arch/mips/mach-en75xx/cache.c
new file mode 100644
index 00000000..3eea715e
--- /dev/null
+++ b/arch/mips/mach-en75xx/cache.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * EN75xx (EN7528) cache and Linux-handoff helpers.
+ *
+ * The CM L2 on this part hangs on Hit_Writeback_Inv_SD (cache op 0x17).
+ * Override the generic MIPS flush routines so they never issue that op,
+ * decompress kernels through KSEG1, and join the CM coherent domain
+ * before jumping to Linux.
+ */
+
+#include <cpu_func.h>
+#include <image.h>
+#include <stdio.h>
+#include <asm/addrspace.h>
+#include <asm/cache.h>
+#include <asm/cacheops.h>
+#include <asm/io.h>
+#include <asm/mipsregs.h>
+#include <asm/system.h>
+#include <mach/en75xx.h>
+
+#define EN75XX_L1_LINE	32
+#define EN75XX_L2_LINE	32
+#define EN75XX_GCR_CL_COHERENCE	0x2008
+
+#define cache_loop(start, end, lsize, op) do {				\
+	const void *_addr = (const void *)((start) & ~((lsize) - 1));	\
+	const void *_aend = (const void *)(((end) - 1) & ~((lsize) - 1)); \
+	if (!(lsize) || (start) == (end))				\
+		break;							\
+	for (; _addr <= _aend; _addr += (lsize))			\
+		mips_cache((op), _addr);				\
+} while (0)
+
+void flush_cache(ulong start_addr, ulong size)
+{
+	if (!size)
+		return;
+
+	/*
+	 * Write back L1 D so U-Boot's own cached stores reach DRAM.
+	 * Do not Hit_Writeback_Inv_SD (hangs). Do not invent an L2 line
+	 * size here: Hit_Invalidate_SD on U-Boot's working set during
+	 * DM init fails. Kernel images use en75xx_inval_kernel() after
+	 * a KSEG1 decompress.
+	 */
+	cache_loop(start_addr, start_addr + size, EN75XX_L1_LINE,
+		   HIT_WRITEBACK_INV_D);
+	cache_loop(start_addr, start_addr + size, EN75XX_L1_LINE,
+		   HIT_INVALIDATE_I);
+	sync();
+	instruction_hazard_barrier();
+}
+
+void flush_dcache_range(ulong start_addr, ulong stop)
+{
+	if (start_addr == stop)
+		return;
+
+	cache_loop(start_addr, stop, EN75XX_L1_LINE, HIT_WRITEBACK_INV_D);
+	sync();
+}
+
+void en75xx_inval_kernel(ulong start_addr, ulong size)
+{
+	if (!size)
+		return;
+
+	cache_loop(start_addr, start_addr + size, EN75XX_L1_LINE,
+		   HIT_INVALIDATE_D);
+	cache_loop(start_addr, start_addr + size, EN75XX_L2_LINE,
+		   HIT_INVALIDATE_SD);
+	cache_loop(start_addr, start_addr + size, EN75XX_L1_LINE,
+		   HIT_INVALIDATE_I);
+	sync();
+	instruction_hazard_barrier();
+}
+
+void *image_get_decomp_buf(void *load_buf)
+{
+	void *dst = load_buf;
+
+	if (((uintptr_t)dst & 0xe0000000UL) == CKSEG0) {
+		dst = (void *)CKSEG1ADDR((unsigned long)dst);
+		printf("   Uncached decompress via %p\n", dst);
+	}
+	return dst;
+}
+
+void image_decomp_flush(ulong load, ulong image_len)
+{
+	if (!image_len)
+		return;
+
+	printf("   EN75xx L2/I invalidate %lu bytes\n", image_len);
+	en75xx_inval_kernel(load, image_len);
+}
+
+void en75xx_prepare_linux(void)
+{
+	unsigned long cmgcr, cm;
+
+	/* Airoha WDT — same block as EN75XX_TIMER_BASE. */
+	writel(0, (void __iomem *)EN75XX_WDT_CTRL);
+
+	/*
+	 * mips_cm_map() never writes GCR_CL_COHERENCE, so the boot core is
+	 * out of the coherent domain and a CM/CPS kernel's vector fetches
+	 * go stale. Same join as arch/mips/mach-mtmips/mt7621.
+	 */
+	cmgcr = read_c0_cmgcrbase();
+	cm = ((unsigned long)cmgcr << 4) + CKSEG1;
+	writel(0xff, (void __iomem *)(cm + EN75XX_GCR_CL_COHERENCE));
+	instruction_hazard_barrier();
+	printf("## EN75xx: WDT off, CM COHERENCE=0xff GCR@%08lx\n", cm);
+}
+
+void arch_preboot_os(void)
+{
+	en75xx_prepare_linux();
+}
diff --git a/arch/mips/mach-en75xx/cpu.c b/arch/mips/mach-en75xx/cpu.c
new file mode 100644
index 00000000..a5732345
--- /dev/null
+++ b/arch/mips/mach-en75xx/cpu.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <init.h>
+#include <asm/global_data.h>
+#include <asm/io.h>
+#include <linux/sizes.h>
+#include <mach/en75xx.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+int dram_init(void)
+{
+	/*
+	 * The DDR stage (blob, or the stock bootbase on the chainload path)
+	 * publishes the DRAM size in REG_SAVE_INFO. EN7528: MB in bits [31:20]
+	 * (live-verified 0x20038400 => 512 MiB on the JCOW407).
+	 */
+	u32 val = __raw_readl((void __iomem *)EN75XX_REG_SAVE_INFO);
+	u32 size_mb = (val >> EN75XX_SAVE_DRAM_SHIFT) & EN75XX_SAVE_DRAM_MASK;
+
+	/*
+	 * Some bootbases do not publish a valid DRAM size in REG_SAVE_INFO
+	 * (the field reads back implausibly small). Fall back to sizing the
+	 * DRAM at runtime in that case, probing through the uncached KSEG1
+	 * alias to avoid cache-aliasing false positives and capping the scan
+	 * at 256 MiB to stay clear of the SoC register island at 0x1fa00000.
+	 */
+	if (size_mb < 64)
+		gd->ram_size = get_ram_size((long *)0xa0000000, SZ_256M);
+	else
+		gd->ram_size = (phys_size_t)size_mb << 20;
+
+	return 0;
+}
+
+int print_cpuinfo(void)
+{
+	u32 val = __raw_readl((void __iomem *)EN75XX_REG_SAVE_INFO);
+	u32 clk = (val >> EN75XX_SAVE_CLK_SHIFT) & EN75XX_SAVE_CLK_MASK;
+
+#if defined(CONFIG_SOC_EN7528)
+	printf("CPU:   EcoNet/Airoha EN7528 MIPS1004Kc\n");
+#else
+	printf("CPU:   EcoNet/Airoha EN7512/EN7521 MIPS34K\n");
+#endif
+	if (clk)
+		printf("Clock: %u MHz\n", clk);
+	else
+		printf("Clock: unknown\n");
+
+	return 0;
+}
+
+ulong notrace get_tbclk(void)
+{
+	u32 val = __raw_readl((void __iomem *)EN75XX_REG_SAVE_INFO);
+	u32 clk = (val >> EN75XX_SAVE_CLK_SHIFT) & EN75XX_SAVE_CLK_MASK;
+
+	/*
+	 * CP0 Count rate per SoC: EN7528 = clk_field * 2 MHz (hardware-measured
+	 * via sleep timing), EN7512 = clk_field / 2. See EN75XX_COUNT_HZ_PER_CLK.
+	 */
+	if (clk)
+		return (ulong)clk * EN75XX_COUNT_HZ_PER_CLK;
+
+	return CONFIG_SYS_MIPS_TIMER_FREQ;
+}
+
+phys_addr_t board_get_usable_ram_top(phys_size_t total_size)
+{
+	/*
+	 * DRAM is aliased into KSEG0 at 0x80000000, but KSEG0 ends at
+	 * 0x9fffffff and the SoC MMIO / FE-SRAM island sits at phys 0x1fa00000
+	 * (= KSEG0 0x9fa00000). On a 512 MiB part, ram_base + size = 0xa0000000
+	 * would relocate U-Boot past KSEG0 / into the island and hang. Relocating
+	 * into high DRAM (0x9exxxxxx) also hangs on this SoC (cache-flush over the
+	 * large range / unstable high DRAM). U-Boot gains nothing from a high top,
+	 * so clamp to the proven-good 0x90000000 (256 MiB); Linux still gets the
+	 * full DRAM via the /memory DT node.
+	 */
+	phys_addr_t top = 0x80000000UL + gd->ram_size;
+
+	if (top > 0x90000000UL)
+		top = 0x90000000UL;
+
+	return top;
+}
+
+void _machine_restart(void)
+{
+	__raw_writel(0x80000000, (void __iomem *)EN75XX_RESET_CONTROL);
+
+	for (;;)
+		;
+}
diff --git a/arch/mips/mach-en75xx/include/mach/en75xx.h b/arch/mips/mach-en75xx/include/mach/en75xx.h
new file mode 100644
index 00000000..ba114116
--- /dev/null
+++ b/arch/mips/mach-en75xx/include/mach/en75xx.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Generic register map for the EcoNet/Airoha EN75xx MIPS SoCs.
+ *
+ * The EN7512/EN7521 (MIPS 34Kc, big-endian) and the EN7528 (MIPS 1004Kc,
+ * little-endian) share the same TCBoot-derived boot model and almost the
+ * same peripheral register map. Values that differ between the two are
+ * selected here on CONFIG_SOC_EN7528.
+ */
+#ifndef __MACH_EN75XX_H__
+#define __MACH_EN75XX_H__
+
+/* --- Peripheral bases (identical across the family) --- */
+#define EN75XX_FLASH_BASE		0xbfc00000
+#define EN75XX_SFC_BASE			0xbfa10000
+#define EN75XX_CHIP_SCU_BASE		0xbfa20000
+#define EN75XX_FE_SRAM_BASE		0xbfa30000
+#define EN75XX_FE_SRAM_CACHED		0x9fa30000
+#define EN75XX_SYSCTL_BASE		0xbfb00000
+#define EN75XX_SMC_BASE			0xbfb10000
+#define EN75XX_DRAMC_BASE		0xbfb20000
+#define EN75XX_INTC_BASE		0xbfb40000
+#define EN75XX_UART0_BASE		0xbfbf0000
+#define EN75XX_TIMER_BASE		0xbfbf0100
+#define EN75XX_WDT_CTRL			0xbfbf0100	/* airoha WDT; writel(0) stops it */
+#define EN75XX_GPIO_BASE		0xbfbf0200
+
+/* --- SYSCTL scratch/handoff registers (identical) --- */
+#define EN75XX_RESET_CONTROL		(EN75XX_SYSCTL_BASE + 0x040)
+#define EN75XX_FE_SRAM_SEL		(EN75XX_SYSCTL_BASE + 0x958)
+#define EN75XX_SCREG_WR0		(EN75XX_SYSCTL_BASE + 0x280)
+#define EN75XX_REG_SAVE_INFO		(EN75XX_SYSCTL_BASE + 0x284)
+
+/* --- Flash image layout: DDR blob offset is common; SPL/U-Boot offsets
+ *     differ because the EN7528 DDR blob is much larger (see below). --- */
+#define EN75XX_DDR_BLOB_OFFSET		0x00008000
+
+/* --- DRAM-size / clock info in REG_SAVE_INFO (0xbfb00284).
+ *     Field positions differ per SoC - see the EN7528/EN7512 blocks below.
+ *     Read as: (val >> _SHIFT) & _MASK. --- */
+
+#if defined(CONFIG_SOC_EN7528)
+/*
+ * EN7528 (MIPS 1004Kc, little-endian).
+ *
+ * The lifted DDR stage (en7528_ddr.bin, carved from the JCOW407 stock
+ * bootbase) is the full FE-SRAM first stage: it self-inits SoC registers,
+ * calibrates DDR3 and hands control back via *(EN75XX_SCREG_WR0). It is
+ * loaded at the FE-SRAM base 0x9fa30000 (NOT +0x2800 like EN7512) and is
+ * 0x5a70 bytes (JCOW407 V1.6 carve).
+ *
+ * NOTE (hardware bring-up): the blob's own entry word is `j 0xbfc00060`,
+ * i.e. it is a plain subroutine: entry 0x9fa30000 = NOP pad -> stub @+0x290
+ * -> main(), and one jalr *(SCREG_WR0) hands control back to us.
+ * The exact entry/handoff must be validated over UART — see
+ * doc/board/econet/en7528-porting.md. The constants below build a coherent
+ * image and are the starting point for that bring-up.
+ */
+#define EN75XX_DDR_BLOB_ADDR		(EN75XX_FE_SRAM_CACHED + 0x0000)
+#define EN75XX_DDR_BLOB_SIZE		0x00005a70
+/* The 0xf0a0-byte blob at flash 0x8000 ends at 0x170a0, so SPL starts at
+ * 0x20000 (EN7512 could use 0x10000 with its 0x4f70 blob). */
+#define EN75XX_SPL_IMAGE_OFFSET		0x00020000
+#define EN75XX_UBOOT_IMAGE_OFFSET	0x00050000
+/* Keep TPL scratch clear of the (large) EN7528 blob image. */
+#define EN75XX_SPL_HEADER_ADDR		(EN75XX_FE_SRAM_BASE + 0x0f000)
+#define EN75XX_TPL_STACK_ADDR		(EN75XX_FE_SRAM_BASE + 0x0fff0)
+#define EN75XX_TPL_SAVED_RA		(EN75XX_FE_SRAM_BASE + 0x0ff00)
+#define EN75XX_SAVE_DRAM_SHIFT		20
+#define EN75XX_SAVE_DRAM_MASK		0xfff		/* MB, bits [31:20] */
+#define EN75XX_SAVE_CLK_SHIFT		10
+#define EN75XX_SAVE_CLK_MASK		0x3ff		/* MHz, bits [19:10] */
+#define EN75XX_COUNT_HZ_PER_CLK	2000000	/* CP0 Count = clk_field * 2 MHz (measured) */
+#else
+/*
+ * EN7512/EN7521 (MIPS 34Kc, big-endian). The vendor spram DDR-only stage
+ * is linked for and entered at 0x9fa32800 and is 0x4f70 bytes.
+ */
+#define EN75XX_DDR_BLOB_ADDR		(EN75XX_FE_SRAM_CACHED + 0x2800)
+#define EN75XX_DDR_BLOB_SIZE		0x00004f70
+#define EN75XX_SPL_IMAGE_OFFSET		0x00010000
+#define EN75XX_UBOOT_IMAGE_OFFSET	0x00050000
+#define EN75XX_SPL_HEADER_ADDR		(EN75XX_FE_SRAM_BASE + 0x40)
+#define EN75XX_TPL_STACK_ADDR		(EN75XX_FE_SRAM_BASE + 0xbff0)
+#define EN75XX_TPL_SAVED_RA		(EN75XX_FE_SRAM_BASE + 0x00)
+#define EN75XX_SAVE_DRAM_SHIFT		0
+#define EN75XX_SAVE_DRAM_MASK		0xfff		/* MB, bits [11:0] */
+#define EN75XX_SAVE_CLK_SHIFT		12
+#define EN75XX_SAVE_CLK_MASK		0xfff		/* MHz, bits [23:12] */
+#define EN75XX_COUNT_HZ_PER_CLK	500000
+#endif
+
+#ifndef __ASSEMBLY__
+#include <stddef.h>
+#include <linux/types.h>
+
+int en75xx_sfc_read(u32 offset, void *dst, size_t len);
+int en75xx_sfc_init(void);
+void en75xx_run_ddr_blob(void);
+void en75xx_inval_kernel(unsigned long addr, unsigned long size);
+void en75xx_prepare_linux(void);
+#endif
+
+#endif /* __MACH_EN75XX_H__ */
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 02/12] mips: en75xx: add TPL and SPL early boot stages
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
  2026-08-25 22:41   ` [PATCH v1 01/12] mips: en75xx: add EcoNet/Airoha EN75xx " AK Sharma
@ 2026-08-25 22:41   ` AK Sharma
  2026-08-25 22:53     ` Tom Rini
  2026-08-25 22:41   ` [PATCH v1 03/12] serial: en75xx: add EcoNet/Airoha EN75xx UART driver AK Sharma
                     ` (11 subsequent siblings)
  13 siblings, 1 reply; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:41 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Add the TPL and SPL stages for the EN75xx. The TPL brings up the cache
and SoC straps, runs the vendor DDR calibration stage from flash via the
early SFC (serial-flash controller) reader, then the SPL loads U-Boot
proper.

The DDR calibration stage is a vendor artifact read from flash at boot
and is not shipped in-tree; see arch/mips/mach-en75xx/README.ddr-blob for
how to obtain it and its checksums.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 arch/mips/mach-en75xx/README.ddr-blob |  21 ++
 arch/mips/mach-en75xx/early_sfc.c     | 352 ++++++++++++++++++++++++++
 arch/mips/mach-en75xx/spl/Makefile    |   2 +
 arch/mips/mach-en75xx/spl/spl.c       |  47 ++++
 arch/mips/mach-en75xx/tpl/Makefile    |   3 +
 arch/mips/mach-en75xx/tpl/start.S     | 101 ++++++++
 arch/mips/mach-en75xx/tpl/tpl.c       | 158 ++++++++++++
 7 files changed, 684 insertions(+)
 create mode 100644 arch/mips/mach-en75xx/README.ddr-blob
 create mode 100644 arch/mips/mach-en75xx/early_sfc.c
 create mode 100644 arch/mips/mach-en75xx/spl/Makefile
 create mode 100644 arch/mips/mach-en75xx/spl/spl.c
 create mode 100644 arch/mips/mach-en75xx/tpl/Makefile
 create mode 100644 arch/mips/mach-en75xx/tpl/start.S
 create mode 100644 arch/mips/mach-en75xx/tpl/tpl.c

diff --git a/arch/mips/mach-en75xx/README.ddr-blob b/arch/mips/mach-en75xx/README.ddr-blob
new file mode 100644
index 00000000..b0074941
--- /dev/null
+++ b/arch/mips/mach-en75xx/README.ddr-blob
@@ -0,0 +1,21 @@
+EN75xx DDR calibration blob
+===========================
+
+The EN75xx TPL runs a small vendor DDR calibration stage that is NOT part
+of the U-Boot source tree. The TPL reads it from SPI-NAND at
+EN75XX_DDR_BLOB_OFFSET (0x8000) with the early SFC reader (see tpl/tpl.c)
+and executes it from FE-SRAM before DRAM is available.
+
+Obtain the blob by extracting the FE-SRAM DDR calibration stage
+("spram.bin") from the device's vendor bootloader and flashing it at
+EN75XX_DDR_BLOB_OFFSET.
+
+Expected images:
+
+  EN7512/EN7521  20336 bytes, linked/entered at 0x9fa32800
+                 sha256 792068a4573b20cb8c39cb27df06c45683c11034e6fd07da24ddf7084828faa5
+  EN7528         23152 bytes
+                 sha256 c2ff94b25100d46c0f2d8b42ebeb887bd6bfd0d49cd1ba5df61eea84ac227540
+
+Providing a source implementation of the DDR2/DDR3 calibration to remove
+this runtime dependency is a known TODO.
diff --git a/arch/mips/mach-en75xx/early_sfc.c b/arch/mips/mach-en75xx/early_sfc.c
new file mode 100644
index 00000000..39fc070d
--- /dev/null
+++ b/arch/mips/mach-en75xx/early_sfc.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Minimal EN7512/EN7521 serial-flash reader used before driver model.
+ * Reconstructed from the vendor TCBoot move_data stage.
+ */
+
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <asm/io.h>
+#include <mach/en75xx.h>
+
+#define SF_READ_IDLE_EN		0x004
+#define SF_MTX_MODE_TOG		0x014
+#define SF_RDCTL_FSM			0x018
+#define SF_MACMUX_SEL			0x01c
+#define SF_MANUAL_EN			0x020
+#define SF_MANUAL_OPFIFO_EMPTY		0x024
+#define SF_MANUAL_OPFIFO_WDATA		0x028
+#define SF_MANUAL_OPFIFO_FULL		0x02c
+#define SF_MANUAL_OPFIFO_WR		0x030
+#define SF_MANUAL_DFIFO_FULL		0x034
+#define SF_MANUAL_DFIFO_WDATA		0x038
+#define SF_MANUAL_DFIFO_EMPTY		0x03c
+#define SF_MANUAL_DFIFO_RD		0x040
+#define SF_MANUAL_DFIFO_RDATA		0x044
+#define SF_SI_CK_SEL			0x09c
+#define SF_STRAP			0x114
+
+#define SF_STRAP_ADDR_4B		BIT(0)
+#define SF_STRAP_SPI_NAND		BIT(1)
+#define SF_STRAP_DUMMY_APPEND		BIT(2)
+
+#define OP_CSH				0x00
+#define OP_CSL				0x01
+#define OP_CK				0x02
+#define OP_OUTS				0x08
+#define OP_INS				0x0c
+
+#define OP_SHIFT			9
+#define OP_CMD_MASK			0x1f
+#define OP_LEN_MASK			0x1ff
+
+#define SPIN_LIMIT			1000000
+#define NAND_PAGE_SIZE			2048
+#define NOR_READ_CHUNK			1024
+
+static inline void __iomem *sf_reg(u32 reg)
+{
+	return (void __iomem *)(EN75XX_SFC_BASE + reg);
+}
+
+static int sf_wait_eq(u32 reg, u32 expected)
+{
+	unsigned int timeout = SPIN_LIMIT;
+
+	while (timeout--) {
+		if (__raw_readl(sf_reg(reg)) == expected)
+			return 0;
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int sf_op(u32 op, u32 len)
+{
+	u32 val = ((op & OP_CMD_MASK) << OP_SHIFT) | (len & OP_LEN_MASK);
+	int ret;
+
+	ret = sf_wait_eq(SF_MANUAL_OPFIFO_FULL, 0);
+	if (ret)
+		return ret;
+
+	__raw_writel(val, sf_reg(SF_MANUAL_OPFIFO_WDATA));
+	__raw_writel(1, sf_reg(SF_MANUAL_OPFIFO_WR));
+
+	return sf_wait_eq(SF_MANUAL_OPFIFO_EMPTY, 1);
+}
+
+static int sf_put_byte(u8 val)
+{
+	int ret = sf_wait_eq(SF_MANUAL_DFIFO_FULL, 0);
+
+	if (ret)
+		return ret;
+
+	__raw_writel(val, sf_reg(SF_MANUAL_DFIFO_WDATA));
+	return 0;
+}
+
+static int sf_put_bytes(const u8 *buf, size_t len)
+{
+	size_t i;
+	int ret;
+
+	for (i = 0; i < len; i++) {
+		ret = sf_put_byte(buf[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int sf_get_byte(u8 *val)
+{
+	int ret = sf_wait_eq(SF_MANUAL_DFIFO_EMPTY, 0);
+
+	if (ret)
+		return ret;
+
+	*val = __raw_readl(sf_reg(SF_MANUAL_DFIFO_RDATA)) & 0xff;
+	__raw_writel(1, sf_reg(SF_MANUAL_DFIFO_RD));
+	return 0;
+}
+
+static int sf_finish(void)
+{
+	int ret;
+
+	ret = sf_op(OP_CSH, 1);
+	if (ret)
+		return ret;
+
+	return sf_op(OP_CK, 5);
+}
+
+int en75xx_sfc_init(void)
+{
+	int ret;
+
+	__raw_writel(0x9, sf_reg(SF_SI_CK_SEL));
+	__raw_writel(0, sf_reg(SF_READ_IDLE_EN));
+
+	ret = sf_wait_eq(SF_RDCTL_FSM, 0);
+	if (ret)
+		return ret;
+
+	__raw_writel(0x9, sf_reg(SF_MTX_MODE_TOG));
+	__raw_writel(1, sf_reg(SF_MACMUX_SEL));
+	__raw_writel(1, sf_reg(SF_MANUAL_EN));
+
+	return 0;
+}
+
+static int sf_nand_load_page(u32 page)
+{
+	u8 page_addr[] = { page >> 16, page >> 8, page };
+	u8 status;
+	unsigned int timeout = SPIN_LIMIT;
+	int ret;
+
+	ret = sf_op(OP_CSL, 1);
+	if (ret)
+		return ret;
+	ret = sf_op(OP_OUTS, 1);
+	if (ret)
+		return ret;
+	ret = sf_put_byte(0x13);
+	if (ret)
+		return ret;
+	ret = sf_op(OP_OUTS, ARRAY_SIZE(page_addr));
+	if (ret)
+		return ret;
+	ret = sf_put_bytes(page_addr, ARRAY_SIZE(page_addr));
+	if (ret)
+		return ret;
+	ret = sf_finish();
+	if (ret)
+		return ret;
+
+	while (timeout--) {
+		ret = sf_op(OP_CSL, 1);
+		if (ret)
+			return ret;
+		ret = sf_op(OP_OUTS, 1);
+		if (ret)
+			return ret;
+		ret = sf_put_byte(0x0f);
+		if (ret)
+			return ret;
+		ret = sf_op(OP_OUTS, 1);
+		if (ret)
+			return ret;
+		ret = sf_put_byte(0xc0);
+		if (ret)
+			return ret;
+		ret = sf_op(OP_INS, 1);
+		if (ret)
+			return ret;
+		ret = sf_get_byte(&status);
+		if (ret)
+			return ret;
+		ret = sf_finish();
+		if (ret)
+			return ret;
+		if (!(status & BIT(0)))
+			return 0;
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int sf_nand_read_cache(u32 column, u8 *dst, size_t len,
+			      bool dummy_append)
+{
+	u8 address[3];
+	size_t i;
+	int ret;
+
+	if (dummy_append) {
+		address[0] = column >> 8;
+		address[1] = column;
+		address[2] = 0;
+	} else {
+		address[0] = 0;
+		address[1] = column >> 8;
+		address[2] = column;
+	}
+
+	ret = sf_op(OP_CSL, 1);
+	if (ret)
+		return ret;
+	ret = sf_op(OP_OUTS, 1);
+	if (ret)
+		return ret;
+	ret = sf_put_byte(0x03);
+	if (ret)
+		return ret;
+	ret = sf_op(OP_OUTS, ARRAY_SIZE(address));
+	if (ret)
+		return ret;
+	ret = sf_put_bytes(address, ARRAY_SIZE(address));
+	if (ret)
+		return ret;
+
+	for (i = 0; i < len; i++) {
+		ret = sf_op(OP_INS, 1);
+		if (ret)
+			return ret;
+		ret = sf_get_byte(&dst[i]);
+		if (ret)
+			return ret;
+	}
+
+	return sf_finish();
+}
+
+static int sf_nor_read_once(u32 offset, u8 *dst, size_t len, bool addr4b,
+			    bool dummy_append)
+{
+	u8 address[4];
+	size_t addr_len = 0;
+	size_t i;
+	int ret;
+
+	if (addr4b)
+		address[addr_len++] = offset >> 24;
+	if (dummy_append) {
+		address[addr_len++] = offset >> 8;
+		address[addr_len++] = offset;
+		address[addr_len++] = 0;
+	} else {
+		address[addr_len++] = offset >> 16;
+		address[addr_len++] = offset >> 8;
+		address[addr_len++] = offset;
+	}
+
+	ret = sf_op(OP_CSL, 1);
+	if (ret)
+		return ret;
+	ret = sf_op(OP_OUTS, 1);
+	if (ret)
+		return ret;
+	ret = sf_put_byte(0x03);
+	if (ret)
+		return ret;
+
+	ret = sf_op(OP_OUTS, addr_len);
+	if (ret)
+		return ret;
+	ret = sf_put_bytes(address, addr_len);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < len; i++) {
+		ret = sf_op(OP_INS, 1);
+		if (ret)
+			return ret;
+		ret = sf_get_byte(&dst[i]);
+		if (ret)
+			return ret;
+	}
+
+	return sf_finish();
+}
+
+static int sf_nor_read(u32 offset, u8 *dst, size_t len, bool addr4b,
+		       bool dummy_append)
+{
+	while (len) {
+		size_t chunk = NOR_READ_CHUNK - (offset % NOR_READ_CHUNK);
+		int ret;
+
+		if (chunk > len)
+			chunk = len;
+		ret = sf_nor_read_once(offset, dst, chunk, addr4b,
+				       dummy_append);
+		if (ret)
+			return ret;
+		offset += chunk;
+		dst += chunk;
+		len -= chunk;
+	}
+
+	return 0;
+}
+
+int en75xx_sfc_read(u32 offset, void *dst, size_t len)
+{
+	u32 strap = __raw_readl(sf_reg(SF_STRAP));
+	u8 *buf = dst;
+	int ret;
+
+	if (!(strap & SF_STRAP_SPI_NAND))
+		return sf_nor_read(offset, buf, len, strap & SF_STRAP_ADDR_4B,
+				   strap & SF_STRAP_DUMMY_APPEND);
+
+	while (len) {
+		u32 page = offset / NAND_PAGE_SIZE;
+		u32 column = offset % NAND_PAGE_SIZE;
+		size_t chunk = NAND_PAGE_SIZE - column;
+
+		if (chunk > len)
+			chunk = len;
+
+		ret = sf_nand_load_page(page);
+		if (ret)
+			return ret;
+		ret = sf_nand_read_cache(column, buf, chunk,
+					 strap & SF_STRAP_DUMMY_APPEND);
+		if (ret)
+			return ret;
+
+		offset += chunk;
+		buf += chunk;
+		len -= chunk;
+	}
+
+	return 0;
+}
diff --git a/arch/mips/mach-en75xx/spl/Makefile b/arch/mips/mach-en75xx/spl/Makefile
new file mode 100644
index 00000000..d19fc6e6
--- /dev/null
+++ b/arch/mips/mach-en75xx/spl/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0+
+obj-y += spl.o
diff --git a/arch/mips/mach-en75xx/spl/spl.c b/arch/mips/mach-en75xx/spl/spl.c
new file mode 100644
index 00000000..a42b2837
--- /dev/null
+++ b/arch/mips/mach-en75xx/spl/spl.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <init.h>
+#include <spl.h>
+#include <spl_load.h>
+#include <mach/en75xx.h>
+
+void __noreturn board_init_f(ulong dummy)
+{
+	spl_init();
+
+	if (IS_ENABLED(CONFIG_SPL_SERIAL))
+		preloader_console_init();
+
+	board_init_r(NULL, 0);
+}
+
+static ulong en75xx_spl_read(struct spl_load_info *load, ulong offset,
+			     ulong count, void *buf)
+{
+	if (en75xx_sfc_read(offset, buf, count))
+		return 0;
+
+	return count;
+}
+
+static int en75xx_spl_load_image(struct spl_image_info *spl_image,
+				 struct spl_boot_device *bootdev)
+{
+	struct spl_load_info load;
+
+	if (en75xx_sfc_init())
+		return -EIO;
+
+	spl_load_init(&load, en75xx_spl_read, NULL, 1);
+	return spl_load(spl_image, bootdev, &load, 0,
+			EN75XX_UBOOT_IMAGE_OFFSET);
+}
+
+SPL_LOAD_IMAGE_METHOD("EN75XX SFC", 0, BOOT_DEVICE_BOARD,
+		      en75xx_spl_load_image);
+
+u32 spl_boot_device(void)
+{
+	return BOOT_DEVICE_BOARD;
+}
diff --git a/arch/mips/mach-en75xx/tpl/Makefile b/arch/mips/mach-en75xx/tpl/Makefile
new file mode 100644
index 00000000..da834c75
--- /dev/null
+++ b/arch/mips/mach-en75xx/tpl/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0+
+extra-y += start.o
+obj-y += tpl.o
diff --git a/arch/mips/mach-en75xx/tpl/start.S b/arch/mips/mach-en75xx/tpl/start.S
new file mode 100644
index 00000000..1c192e2e
--- /dev/null
+++ b/arch/mips/mach-en75xx/tpl/start.S
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#include <asm/asm.h>
+#include <asm/regdef.h>
+#include <asm/mipsregs.h>
+#include <mach/en75xx.h>
+
+	.set noreorder
+
+ENTRY(_start)
+	b	reset
+	 mtc0	zero, CP0_COUNT
+
+	/*
+	 * TCBoot image descriptor. The magic is byte-reversed in the vendor
+	 * format, while section offsets are stored as big-endian words.
+	 */
+	.org	0x0c
+	.byte	0x36, 0x35, 0x37, 0x38
+	.word	EN75XX_DDR_BLOB_OFFSET
+	.word	EN75XX_DDR_BLOB_OFFSET + EN75XX_DDR_BLOB_SIZE
+	.word	EN75XX_SPL_IMAGE_OFFSET
+	.word	EN75XX_UBOOT_IMAGE_OFFSET
+
+	/* The vendor reset code begins at offset 0x48. */
+	.org	0x48
+reset:
+	/* Disable interrupts and force KSEG0 uncached before DDR exists. */
+	mfc0	t0, CP0_STATUS
+	li	t1, ~(ST0_IE | ST0_KSU)
+	and	t0, t0, t1
+	li	t1, ST0_BEV | ST0_ERL
+	or	t0, t0, t1
+	mtc0	t0, CP0_STATUS
+	mtc0	zero, CP0_CAUSE
+	mtc0	zero, CP0_COMPARE
+
+	mfc0	t0, CP0_CONFIG
+	li	t1, ~7
+	and	t0, t0, t1
+	ori	t0, t0, 2
+	mtc0	t0, CP0_CONFIG
+	ehb
+
+	/* Arbiter and static-memory mappings copied from TCBoot. */
+	li	t0, EN75XX_SYSCTL_BASE
+	li	t1, 0x80071f1e
+	sw	t1, 0x20(t0)
+	li	t1, 0x00071f1f
+	sw	t1, 0x24(t0)
+	li	t1, 0x80050000
+	sw	t1, 0x34(t0)
+
+	li	t0, EN75XX_SMC_BASE
+	li	t1, 0x102d1040
+	sw	t1, 0x00(t0)
+	li	t1, 0x200028d0
+	sw	t1, 0x14(t0)
+
+	/* Disable the interrupt controller. */
+	li	t0, EN75XX_INTC_BASE
+	sw	zero, 0x00(t0)
+	sw	zero, 0x04(t0)
+
+	/* Enable PBUS access to the 48 KiB FE SRAM. */
+	li	t0, EN75XX_FE_SRAM_SEL
+	lw	t1, 0(t0)
+	ori	t1, t1, 1
+	sw	t1, 0(t0)
+	sync
+
+	li	sp, EN75XX_TPL_STACK_ADDR
+	li	fp, EN75XX_TPL_STACK_ADDR
+
+	la	t9, tpl_main
+	jalr	t9
+	 nop
+
+1:	b	1b
+	 nop
+END(_start)
+
+LEAF(en75xx_run_ddr_blob)
+	li	t0, EN75XX_TPL_SAVED_RA
+	sw	ra, 0(t0)
+
+	la	t1, 1f
+	li	t0, EN75XX_SCREG_WR0
+	sw	t1, 0(t0)
+	sync
+
+	li	t9, EN75XX_DDR_BLOB_ADDR
+	jr	t9
+	 nop
+1:
+	li	t0, EN75XX_SCREG_WR0
+	sw	zero, 0(t0)
+	li	t0, EN75XX_TPL_SAVED_RA
+	lw	ra, 0(t0)
+	jr	ra
+	 nop
+END(en75xx_run_ddr_blob)
diff --git a/arch/mips/mach-en75xx/tpl/tpl.c b/arch/mips/mach-en75xx/tpl/tpl.c
new file mode 100644
index 00000000..ce4ae6b6
--- /dev/null
+++ b/arch/mips/mach-en75xx/tpl/tpl.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <compiler.h>
+#include <linux/byteorder/generic.h>
+#include <linux/types.h>
+#include <asm/io.h>
+#include <mach/en75xx.h>
+
+#define IH_MAGIC	0x27051956
+#define IH_HDR_SIZE	64
+
+struct en75xx_legacy_header {
+	u32 magic;
+	u32 hcrc;
+	u32 time;
+	u32 size;
+	u32 load;
+	u32 ep;
+	u32 dcrc;
+	u8 os;
+	u8 arch;
+	u8 type;
+	u8 comp;
+	u8 name[32];
+};
+
+static u32 get_be32(u32 val)
+{
+	return be32_to_cpu(val);
+}
+
+/*
+ * 16550 register access differs by SoC endianness:
+ *   EN7512 (big-endian): 8-bit registers live in the top byte of each 32-bit
+ *   word, i.e. at byte offset (reg << 2) + 3.
+ *   EN7528 (little-endian, reg-io-width=4, reg-shift=2): 32-bit word access at
+ *   (reg << 2); the 8-bit value sits in the low byte.
+ */
+#if defined(CONFIG_SOC_EN7528)
+static inline void tpl_uart_wr(u32 reg, u32 val)
+{
+	__raw_writel(val, (void __iomem *)(EN75XX_UART0_BASE + (reg << 2)));
+}
+
+static inline u32 tpl_uart_rd(u32 reg)
+{
+	return __raw_readl((void __iomem *)(EN75XX_UART0_BASE + (reg << 2)));
+}
+#else
+static inline void tpl_uart_wr(u32 reg, u32 val)
+{
+	__raw_writeb(val, (void __iomem *)(EN75XX_UART0_BASE + (reg << 2) + 3));
+}
+
+static inline u32 tpl_uart_rd(u32 reg)
+{
+	return __raw_readb((void __iomem *)(EN75XX_UART0_BASE + (reg << 2) + 3));
+}
+#endif
+
+#define UART_RBR_THR	0	/* +0x00: receive/transmit, DLL when DLAB=1 */
+#define UART_IER_DLM	1	/* +0x04: interrupt enable, DLM when DLAB=1 */
+#define UART_IIR_FCR	2	/* +0x08: FIFO control */
+#define UART_LCR	3	/* +0x0c: line control (DLAB) */
+#define UART_MCR	4	/* +0x10: modem control */
+#define UART_LSR	5	/* +0x14: line status (bit5 = THR empty) */
+
+static void tpl_putc(u8 ch)
+{
+	while (!(tpl_uart_rd(UART_LSR) & 0x20))
+		;
+	tpl_uart_wr(UART_RBR_THR, ch);
+}
+
+static void tpl_hang(u8 code)
+{
+	tpl_putc(code);
+	tpl_putc('\r');
+	tpl_putc('\n');
+
+	for (;;)
+		;
+}
+
+static void tpl_uart_init(void)
+{
+#if defined(CONFIG_SOC_EN7528)
+	/*
+	 * EN7528 UART fractional divider (RE'd from the stock bootbase, live-
+	 * verified): 115200 8N1 = XYD 0xEA00FDE8 (X=round(baud*13/25)=0xEA00,
+	 * Y=65000), DLL=1, DLM=0. XYD is reg 11 @ +0x2c.
+	 */
+	tpl_uart_wr(UART_LCR, 0x80);		/* DLAB = 1 */
+	tpl_uart_wr(11, 0xea00fde8);		/* XYD fractional divider */
+	tpl_uart_wr(UART_RBR_THR, 0x01);	/* DLL = 1 */
+	tpl_uart_wr(UART_IER_DLM, 0x00);	/* DLM = 0 */
+	tpl_uart_wr(UART_LCR, 0x03);		/* 8N1, DLAB = 0 */
+	tpl_uart_wr(UART_IIR_FCR, 0x07);	/* enable + clear FIFOs */
+	tpl_uart_wr(UART_MCR, 0x00);
+	tpl_uart_wr(UART_IER_DLM, 0x00);
+#else
+	void __iomem *base = (void __iomem *)EN75XX_UART0_BASE;
+
+	__raw_writeb(0x80, base + 0x0f);
+	__raw_writel(0xea00fde8, base + 0x2c);
+	__raw_writeb(0x01, base + 0x03);
+	__raw_writeb(0x00, base + 0x07);
+	__raw_writeb(0x03, base + 0x0f);
+	__raw_writeb(0x0f, base + 0x0b);
+	__raw_writeb(0x00, base + 0x13);
+	__raw_writeb(0x00, base + 0x27);
+	__raw_writeb(0x00, base + 0x07);
+#endif
+}
+
+void __noreturn tpl_main(void)
+{
+	struct en75xx_legacy_header *hdr =
+		(struct en75xx_legacy_header *)EN75XX_SPL_HEADER_ADDR;
+	void (*entry)(void);
+	u32 load, size, ep;
+	int ret;
+
+	tpl_uart_init();
+	if (en75xx_sfc_init())
+		tpl_hang('I');
+
+	ret = en75xx_sfc_read(EN75XX_DDR_BLOB_OFFSET,
+			      (void *)EN75XX_DDR_BLOB_ADDR,
+			      EN75XX_DDR_BLOB_SIZE);
+	if (ret)
+		tpl_hang('F');
+
+	en75xx_run_ddr_blob();
+
+	ret = en75xx_sfc_read(EN75XX_SPL_IMAGE_OFFSET, hdr, IH_HDR_SIZE);
+	if (ret || get_be32(hdr->magic) != IH_MAGIC)
+		tpl_hang('H');
+
+	size = get_be32(hdr->size);
+	load = get_be32(hdr->load);
+	ep = get_be32(hdr->ep);
+
+	if (!size || (load & 0xe0000000) != 0x80000000 ||
+	    (ep & 0xe0000000) != 0x80000000 || load + size < load)
+		tpl_hang('L');
+
+	/* Write through KSEG1 so no dirty cache lines hide the SPL image. */
+	ret = en75xx_sfc_read(EN75XX_SPL_IMAGE_OFFSET + IH_HDR_SIZE,
+			      (void *)(load | 0x20000000), size);
+	if (ret)
+		tpl_hang('S');
+
+	__asm__ volatile("sync" : : : "memory");
+	entry = (void (*)(void))ep;
+	entry();
+	__builtin_unreachable();
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 03/12] serial: en75xx: add EcoNet/Airoha EN75xx UART driver
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
  2026-08-25 22:41   ` [PATCH v1 01/12] mips: en75xx: add EcoNet/Airoha EN75xx " AK Sharma
  2026-08-25 22:41   ` [PATCH v1 02/12] mips: en75xx: add TPL and SPL early boot stages AK Sharma
@ 2026-08-25 22:41   ` AK Sharma
  2026-08-25 22:41   ` [PATCH v1 04/12] spi: airoha-snfi: add EN7528 SPI-NAND controller support AK Sharma
                     ` (10 subsequent siblings)
  13 siblings, 0 replies; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:41 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Add a DM serial driver for the UART on the EcoNet/Airoha EN75xx SoCs.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 drivers/serial/Kconfig         |   7 ++
 drivers/serial/Makefile        |   1 +
 drivers/serial/serial_en75xx.c | 170 +++++++++++++++++++++++++++++++++
 3 files changed, 178 insertions(+)
 create mode 100644 drivers/serial/serial_en75xx.c

diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 5f8b98f0..2a8fee98 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -1098,6 +1098,13 @@ config ZYNQ_SERIAL
 	  This driver supports the Cadence UART. It is found e.g. in Xilinx
 	  Zynq/ZynqMP.
 
+config EN75XX_SERIAL
+	bool "EcoNet/Airoha EN75xx serial port support"
+	depends on ARCH_EN75XX
+	help
+	  Support the byte-lane 16550-like UART used by EN7512/EN7521/EN7528.
+	  The controller also uses a vendor-specific fractional divider.
+
 config MTK_SERIAL
 	bool "MediaTek High-speed UART support"
 	depends on DM_SERIAL
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 66088b44..75ec49b7 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_OWL_SERIAL) += serial_owl.o
 obj-$(CONFIG_OMAP_SERIAL) += serial_omap.o
 obj-$(CONFIG_MTK_SERIAL) += serial_mtk.o
 obj-$(CONFIG_MT7620_SERIAL) += serial_mt7620.o
+obj-$(CONFIG_EN75XX_SERIAL) += serial_en75xx.o
 obj-$(CONFIG_HTIF_CONSOLE) += serial_htif.o
 obj-$(CONFIG_SIFIVE_SERIAL) += serial_sifive.o
 obj-$(CONFIG_XEN_SERIAL) += serial_xen.o
diff --git a/drivers/serial/serial_en75xx.c b/drivers/serial/serial_en75xx.c
new file mode 100644
index 00000000..8753ce55
--- /dev/null
+++ b/drivers/serial/serial_en75xx.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* EcoNet/Airoha EN75xx UART */
+
+#include <dm.h>
+#include <errno.h>
+#include <serial.h>
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+/* 16550 register indices (the byte/word offset is derived per SoC below) */
+#define R_RBR		0	/* THR / DLL when DLAB=1 */
+#define R_IER		1	/* DLM when DLAB=1 */
+#define R_FCR		2
+#define R_LCR		3
+#define R_MCR		4
+#define R_LSR		5
+#define R_MISCC		9
+#define R_XYD		11	/* EN7512 fractional divider (32-bit) */
+
+#define UART_LCR_DLAB	0x80
+#define UART_LCR_8N1	0x03
+#define UART_LSR_DR	0x01
+#define UART_LSR_THRE	0x20
+
+struct en75xx_serial_plat {
+	void __iomem *base;
+	bool le;		/* EN7528: little-endian, 32-bit word access */
+	u32 uartclk;		/* EN7528 UART source clock (0 => EN7512) */
+};
+
+/*
+ * EN7512 (big-endian): the 8-bit registers live in the top byte of each
+ * 32-bit word, i.e. byte offset (reg << 2) + 3, byte access.
+ * EN7528 (little-endian, reg-io-width=4, reg-shift=2): 32-bit word access at
+ * (reg << 2), value in the low byte.
+ */
+static u8 uart_rd(struct en75xx_serial_plat *plat, u32 reg)
+{
+	if (plat->le)
+		return readl(plat->base + (reg << 2)) & 0xff;
+
+	return readb(plat->base + (reg << 2) + 3);
+}
+
+static void uart_wr(struct en75xx_serial_plat *plat, u32 reg, u32 val)
+{
+	if (plat->le)
+		writel(val, plat->base + (reg << 2));
+	else
+		writeb(val, plat->base + (reg << 2) + 3);
+}
+
+static void en75xx_serial_hw_init(struct en75xx_serial_plat *plat,
+				  int baudrate)
+{
+	if (baudrate <= 0)
+		baudrate = CONFIG_BAUDRATE;
+
+	/*
+	 * EN7512 (BE) and EN7528 (LE) share the divider: Y=65000,
+	 * X=round(baud*13/25), packed into the 32-bit XYD reg (11 @ +0x2c)
+	 * as {X[31:16],Y[15:0]}. 16-bit regs go through uart_wr()
+	 * (endian-correct); the XYD store is a native 32-bit write, valid
+	 * on both. Live-verified on JCOW407: XYD == 0xEA00FDE8 at 115200.
+	 */
+	u32 div_x = DIV_ROUND_CLOSEST((u32)baudrate * 13, 25);
+
+	if (div_x > 0xffff)
+		div_x = 0xffff;
+
+	uart_wr(plat, R_LCR, UART_LCR_DLAB);
+	__raw_writel((div_x << 16) | 65000, plat->base + (R_XYD << 2));
+	uart_wr(plat, R_RBR, 1);
+	uart_wr(plat, R_IER, 0);
+	uart_wr(plat, R_LCR, UART_LCR_8N1);
+	uart_wr(plat, R_FCR, 0x0f);
+	uart_wr(plat, R_MCR, 0);
+	uart_wr(plat, R_MISCC, 0);
+	uart_wr(plat, R_IER, 0);
+}
+
+static int en75xx_serial_putc(struct udevice *dev, const char ch)
+{
+	struct en75xx_serial_plat *plat = dev_get_plat(dev);
+
+	if (!(uart_rd(plat, R_LSR) & UART_LSR_THRE))
+		return -EAGAIN;
+
+	uart_wr(plat, R_RBR, ch);
+	return 0;
+}
+
+static int en75xx_serial_getc(struct udevice *dev)
+{
+	struct en75xx_serial_plat *plat = dev_get_plat(dev);
+
+	if (!(uart_rd(plat, R_LSR) & UART_LSR_DR))
+		return -EAGAIN;
+
+	return uart_rd(plat, R_RBR);
+}
+
+static int en75xx_serial_pending(struct udevice *dev, bool input)
+{
+	struct en75xx_serial_plat *plat = dev_get_plat(dev);
+	u8 lsr = uart_rd(plat, R_LSR);
+
+	if (input)
+		return !!(lsr & UART_LSR_DR);
+
+	return !(lsr & UART_LSR_THRE);
+}
+
+static int en75xx_serial_setbrg(struct udevice *dev, int baudrate)
+{
+	struct en75xx_serial_plat *plat = dev_get_plat(dev);
+
+	en75xx_serial_hw_init(plat, baudrate);
+	return 0;
+}
+
+static int en75xx_serial_of_to_plat(struct udevice *dev)
+{
+	struct en75xx_serial_plat *plat = dev_get_plat(dev);
+
+	plat->base = dev_remap_addr(dev);
+	if (!plat->base)
+		return -EINVAL;
+
+	plat->le = (bool)dev_get_driver_data(dev);
+	if (plat->le)
+		plat->uartclk = dev_read_u32_default(dev, "clock-frequency",
+						     2000000);
+
+	return 0;
+}
+
+static int en75xx_serial_probe(struct udevice *dev)
+{
+	struct en75xx_serial_plat *plat = dev_get_plat(dev);
+
+	en75xx_serial_hw_init(plat, CONFIG_BAUDRATE);
+	return 0;
+}
+
+static const struct dm_serial_ops en75xx_serial_ops = {
+	.putc = en75xx_serial_putc,
+	.pending = en75xx_serial_pending,
+	.getc = en75xx_serial_getc,
+	.setbrg = en75xx_serial_setbrg,
+};
+
+static const struct udevice_id en75xx_serial_ids[] = {
+	{ .compatible = "econet,en7512-uart", .data = 0 },
+	{ .compatible = "econet,en7528-uart", .data = 1 },
+	{ .compatible = "airoha,en7523-uart", .data = 1 },
+	{ }
+};
+
+U_BOOT_DRIVER(serial_en75xx) = {
+	.name = "serial_en75xx",
+	.id = UCLASS_SERIAL,
+	.of_match = en75xx_serial_ids,
+	.of_to_plat = en75xx_serial_of_to_plat,
+	.plat_auto = sizeof(struct en75xx_serial_plat),
+	.probe = en75xx_serial_probe,
+	.ops = &en75xx_serial_ops,
+	.flags = DM_FLAG_PRE_RELOC,
+};
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 04/12] spi: airoha-snfi: add EN7528 SPI-NAND controller support
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (2 preceding siblings ...)
  2026-08-25 22:41   ` [PATCH v1 03/12] serial: en75xx: add EcoNet/Airoha EN75xx UART driver AK Sharma
@ 2026-08-25 22:41   ` AK Sharma
  2026-08-25 22:42   ` [PATCH v1 05/12] mtd: spi-nand: add EN75 BMT bad-block management support AK Sharma
                     ` (9 subsequent siblings)
  13 siblings, 0 replies; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:41 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Extend the Airoha SNFI driver for the serial-NAND controller on the
EN7528, including the two-plane column handling required by the
MT29F2G01 SPI-NAND.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 drivers/spi/Kconfig           |   3 +-
 drivers/spi/airoha_snfi_spi.c | 409 ++++++++++++++++++++++++++++------
 2 files changed, 337 insertions(+), 75 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index cfbedd64..83a79bee 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -62,7 +62,8 @@ config ADI_SPI3
 
 config AIROHA_SNFI_SPI
 	bool "Airoha SPI memory controller driver"
-	depends on ARCH_AIROHA
+	depends on ARCH_AIROHA || ARCH_EN75XX
+	select REGMAP
 	select SPI_MEM
 	help
 	  Enable the Airoha SPI memory controller driver. This driver is
diff --git a/drivers/spi/airoha_snfi_spi.c b/drivers/spi/airoha_snfi_spi.c
index 769ec956..738a805d 100644
--- a/drivers/spi/airoha_snfi_spi.c
+++ b/drivers/spi/airoha_snfi_spi.c
@@ -9,6 +9,9 @@
  */
 
 #include <asm/unaligned.h>
+#if IS_ENABLED(CONFIG_ARCH_AIROHA)
+#include <asm/arch/scu-regmap.h>
+#endif
 #include <clk.h>
 #include <dm.h>
 #include <dm/device_compat.h>
@@ -16,11 +19,34 @@
 #include <linux/bitfield.h>
 #include <linux/dma-mapping.h>
 #include <linux/mtd/spinand.h>
+#include <linux/sizes.h>
 #include <linux/time.h>
 #include <regmap.h>
 #include <spi.h>
 #include <spi-mem.h>
 
+#if IS_ENABLED(CONFIG_ARCH_EN75XX)
+#include <asm/addrspace.h>
+/*
+ * EN7528 (MIPS 1004Kc) DMA coherency.
+ *
+ * This SoC inherits its cache setup from the vendor bootbase and U-Boot runs
+ * with CONFIG_MIPS_CACHE_SETUP disabled, where the dcache flush/invalidate
+ * primitives are not reliable.  dma_map_single() therefore cannot make the
+ * shared buffer coherent: a multi-page read returns the same stale cache line
+ * for every page.  Use the uncached KSEG1 alias for all CPU accesses to the
+ * DMA buffer and give the controller the physical address instead.
+ * (Same remedy as the airoha_eth ring/frame buffers on this SoC.)
+ */
+#define snfi_uncached(p)	((void *)CKSEG1ADDR((unsigned long)(p)))
+#define snfi_dma_addr(p, l, d)	((dma_addr_t)CPHYSADDR((unsigned long)(p)))
+#define snfi_dma_unmap(a, l, d)	do { } while (0)
+#else
+#define snfi_uncached(p)	(p)
+#define snfi_dma_addr(p, l, d)	dma_map_single((p), (l), (d))
+#define snfi_dma_unmap(a, l, d)	dma_unmap_single((a), (l), (d))
+#endif
+
 /* SPI */
 #define REG_SPI_CTRL_READ_MODE			0x0000
 #define REG_SPI_CTRL_READ_IDLE_EN		0x0004
@@ -80,12 +106,19 @@
 
 #define REG_SPI_CTRL_NFI2SPI_EN			0x0130
 #define SPI_CTRL_NFI2SPI_EN			BIT(0)
+#define REG_SCUCLK_BOOT_TRP			0x00b8
+#define SCUCLK_BOOT_TRP_BOOT_FROM_EMMC		BIT(6)
+#define SPI_NFI_SNF_NFI_CNFG_SPI_MODE			BIT(2)
+#define SPI_CTRL_SFC_STRAP_BOOT_FROM_SPI_NAND	BIT(1)
 
 /* NFI2SPI */
 #define REG_SPI_NFI_CNFG			0x0000
 #define SPI_NFI_DMA_MODE			BIT(0)
 #define SPI_NFI_READ_MODE			BIT(1)
 #define SPI_NFI_DMA_BURST_EN			BIT(2)
+#define SPI_NFI_DMA_WR_BYTE_SWAP_EN		BIT(3)
+#define SPI_NFI_DMA_RD_BYTE_SWAP_EN		BIT(4)
+#define SPI_NFI_ECC_DATA_SOURCE_INV_EN		BIT(5)
 #define SPI_NFI_HW_ECC_EN			BIT(8)
 #define SPI_NFI_AUTO_FDM_EN			BIT(9)
 #define SPI_NFI_OPMODE				GENMASK(14, 12)
@@ -93,6 +126,8 @@
 #define REG_SPI_NFI_PAGEFMT			0x0004
 #define SPI_NFI_PAGE_SIZE			GENMASK(1, 0)
 #define SPI_NFI_SPARE_SIZE			GENMASK(5, 4)
+#define SPI_NFI_FDM_NUM				GENMASK(11, 8)
+#define SPI_NFI_FDM_ECC_NUM			GENMASK(15, 12)
 
 #define REG_SPI_NFI_CON				0x0008
 #define SPI_NFI_FIFO_FLUSH			BIT(0)
@@ -213,15 +248,89 @@ enum airoha_snand_cs {
 	SPI_CHIP_SEL_LOW,
 };
 
+enum airoha_snand_bootstrap {
+	AIROHA_SNAND_BOOTSTRAP_UNKNOWN,
+	AIROHA_SNAND_BOOTSTRAP_NAND,
+	AIROHA_SNAND_BOOTSTRAP_NOR,
+	AIROHA_SNAND_BOOTSTRAP_EMMC,
+};
+
 struct airoha_snand_priv {
+	struct udevice *dev;
 	struct regmap *regmap_ctrl;
 	struct regmap *regmap_nfi;
 	struct clk *spi_clk;
 
 	u8 *txrx_buf;
+	bool en751221;
 	int dma;
 };
 
+static const char *airoha_snand_bootstrap_name(enum airoha_snand_bootstrap type)
+{
+	switch (type) {
+	case AIROHA_SNAND_BOOTSTRAP_NAND:
+		return "NAND";
+	case AIROHA_SNAND_BOOTSTRAP_NOR:
+		return "NOR";
+	case AIROHA_SNAND_BOOTSTRAP_EMMC:
+		return "eMMC";
+	case AIROHA_SNAND_BOOTSTRAP_UNKNOWN:
+	default:
+		return "unknown";
+	}
+}
+
+static enum airoha_snand_bootstrap
+airoha_snand_get_bootstrap(struct airoha_snand_priv *priv, u32 *boot_trp,
+				   u32 *snf_nfi_cnfg, u32 *sfc_strap)
+{
+	int err;
+
+	*boot_trp = 0;
+	*snf_nfi_cnfg = 0;
+	*sfc_strap = 0;
+
+	if (!priv->en751221) {
+#if IS_ENABLED(CONFIG_ARCH_AIROHA)
+		struct regmap *regmap_scu = airoha_get_scu_regmap();
+
+		if (!regmap_scu)
+			return AIROHA_SNAND_BOOTSTRAP_UNKNOWN;
+
+		err = regmap_read(regmap_scu, REG_SCUCLK_BOOT_TRP, boot_trp);
+		if (err)
+			return AIROHA_SNAND_BOOTSTRAP_UNKNOWN;
+
+		if (*boot_trp & SCUCLK_BOOT_TRP_BOOT_FROM_EMMC)
+			return AIROHA_SNAND_BOOTSTRAP_EMMC;
+#else
+		return AIROHA_SNAND_BOOTSTRAP_UNKNOWN;
+#endif
+	}
+
+	if (priv->regmap_nfi) {
+		err = regmap_read(priv->regmap_nfi,
+				  REG_SPI_NFI_SNF_NFI_CNFG,
+				  snf_nfi_cnfg);
+		if (err)
+			return AIROHA_SNAND_BOOTSTRAP_UNKNOWN;
+
+		/* SNF_NFI_CNFG bit 2 selects SPI-NFI. If it is clear, the
+		 * boot source is still NAND through the parallel NAND path.
+		 */
+		if (!(*snf_nfi_cnfg & SPI_NFI_SNF_NFI_CNFG_SPI_MODE))
+			return AIROHA_SNAND_BOOTSTRAP_NAND;
+	}
+
+	err = regmap_read(priv->regmap_ctrl, REG_SPI_CTRL_SFC_STRAP, sfc_strap);
+	if (err)
+		return AIROHA_SNAND_BOOTSTRAP_UNKNOWN;
+
+	return (*sfc_strap & SPI_CTRL_SFC_STRAP_BOOT_FROM_SPI_NAND) ?
+		AIROHA_SNAND_BOOTSTRAP_NAND : AIROHA_SNAND_BOOTSTRAP_NOR;
+}
+
 static int airoha_snand_set_fifo_op(struct airoha_snand_priv *priv,
 				    u8 op_cmd, int op_len)
 {
@@ -254,7 +363,19 @@ static int airoha_snand_set_fifo_op(struct airoha_snand_priv *priv,
 
 static int airoha_snand_set_cs(struct airoha_snand_priv *priv, u8 cs)
 {
-	return airoha_snand_set_fifo_op(priv, cs, sizeof(cs));
+	int count = priv->en751221 ? 2 : 1;
+	int err;
+
+	/* EN751221 sporadically drops writes unless the CS operation is sent
+	 * twice. This mirrors the quirk used by the Linux EcoNet port.
+	 */
+	while (count--) {
+		err = airoha_snand_set_fifo_op(priv, cs, sizeof(cs));
+		if (err)
+			return err;
+	}
+
+	return 0;
 }
 
 static int airoha_snand_write_data_to_fifo(struct airoha_snand_priv *priv,
@@ -367,7 +488,7 @@ static int airoha_snand_set_mode(struct airoha_snand_priv *priv,
 	case SPI_MODE_DMA:
 		err = regmap_write(priv->regmap_ctrl,
 				   REG_SPI_CTRL_NFI2SPI_EN,
-				   SPI_CTRL_MANUAL_EN);
+				   SPI_CTRL_NFI2SPI_EN);
 		if (err < 0)
 			return err;
 
@@ -465,17 +586,81 @@ static int airoha_snand_read_data(struct airoha_snand_priv *priv,
 	return 0;
 }
 
+static int airoha_snand_nfi_reset(struct airoha_snand_priv *priv)
+{
+	/* Same reset value used by the vendor driver: reset the NFI state
+	 * machine and flush its FIFO before a DMA transaction.
+	 */
+	return regmap_write(priv->regmap_nfi, REG_SPI_NFI_CON,
+			    SPI_NFI_FIFO_FLUSH | SPI_NFI_RST);
+}
+
+static int airoha_snand_nfi_clear_intr(struct airoha_snand_priv *priv)
+{
+	/* NFI interrupt status bits are write-one-to-clear on this block.
+	 * Clear stale completion state before arming a new DMA transaction so
+	 * polling cannot succeed on an old AHB_DONE value.
+	 */
+	return regmap_write(priv->regmap_nfi, REG_SPI_NFI_INTR,
+			    SPI_NFI_ALL_IRQ_EN);
+}
+
+static int airoha_snand_nfi_clear_done(struct airoha_snand_priv *priv)
+{
+	/* READ_FROM_CACHE_DONE and LOAD_TO_CACHE_DONE are also sticky W1C bits.
+	 * Clear them before DMA so polling observes the current transaction only.
+	 */
+	return regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
+			    SPI_NFI_READ_FROM_CACHE_DONE |
+			    SPI_NFI_LOAD_TO_CACHE_DONE);
+}
+
 static int airoha_snand_nfi_init(struct airoha_snand_priv *priv)
 {
 	int err;
 
-	/* switch to SNFI mode */
+	/* Switch to SNFI mode. */
 	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_NFI_CNFG,
 			   SPI_NFI_SPI_MODE);
 	if (err)
 		return err;
 
-	/* Enable DMA */
+	err = airoha_snand_nfi_reset(priv);
+	if (err)
+		return err;
+
+	err = airoha_snand_nfi_clear_intr(priv);
+	if (err)
+		return err;
+
+	err = airoha_snand_nfi_clear_done(priv);
+	if (err)
+		return err;
+
+	/* Mirror the safe vendor configuration used when the SPI NAND uses its
+	 * internal ECC: disable controller ECC/FDM and byte-swap helpers.  The
+	 * SPI NAND core still controls on-die ECC, QE, die-select and status
+	 * handling through normal SPI-MEM commands.
+	 */
+	err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_CNFG,
+				 SPI_NFI_DMA_WR_BYTE_SWAP_EN |
+				 SPI_NFI_DMA_RD_BYTE_SWAP_EN |
+				 SPI_NFI_ECC_DATA_SOURCE_INV_EN |
+				 SPI_NFI_HW_ECC_EN |
+				 SPI_NFI_AUTO_FDM_EN,
+				 0);
+	if (err)
+		return err;
+
+	/* FDM is disabled above, but clear its page-format fields as well so a
+	 * previous boot stage cannot leave stale vendor ECC layout behind.
+	 */
+	err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_PAGEFMT,
+				 SPI_NFI_FDM_NUM | SPI_NFI_FDM_ECC_NUM, 0);
+	if (err)
+		return err;
+
+	/* Enable only the DMA completion interrupt source used by polling. */
 	return regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_INTR_EN,
 				  SPI_NFI_ALL_IRQ_EN, SPI_NFI_AHB_DONE_EN);
 }
@@ -551,6 +736,54 @@ static int airoha_snand_dirmap_create(struct spi_mem_dirmap_desc *desc)
 	return 0;
 }
 
+
+static ssize_t airoha_snand_no_dirmap_write(struct spi_mem_dirmap_desc *desc,
+					    u64 offs, size_t len, const void *buf)
+{
+	struct spi_mem_op op = desc->info.op_tmpl;
+	int err;
+
+	op.addr.val = desc->info.offset + offs;
+	op.data.buf.out = buf;
+	op.data.nbytes = len;
+
+	err = spi_mem_exec_op(desc->slave, &op);
+	if (err)
+		return err;
+
+	return op.data.nbytes;
+}
+
+static bool airoha_snand_dma_write_ok(struct spi_mem_dirmap_desc *desc,
+					      u64 offs, size_t len)
+{
+	/*
+	 * The NFI DMA path programs a contiguous cache window.  Do not emulate
+	 * unaligned/short writes by padding the beginning or the end with 0xff:
+	 * PROGRAM LOAD/RANDOM LOAD operates on the SPI NAND cache, so padding can
+	 * poison bytes that the caller did not ask us to touch.  This is especially
+	 * dangerous for boot blocks updated in small chunks.
+	 *
+	 * Keep DMA only for exact transfers that the controller can issue without
+	 * synthetic padding.  Everything else falls back to the manual SPI-MEM path,
+	 * which sends the exact column address and exact byte count requested by the
+	 * SPI NAND core.
+	 */
+	if (!len)
+		return false;
+
+	if (!IS_ALIGNED(desc->info.offset + offs, 16))
+		return false;
+
+	if (!IS_ALIGNED(len, 64))
+		return false;
+
+	if (len > SPI_NAND_CACHE_SIZE)
+		return false;
+
+	return true;
+}
+
 static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 					u64 offs, size_t len, void *buf)
 {
@@ -609,9 +842,15 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 	if (err < 0)
 		return err;
 
-	/* NFI reset */
-	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_CON,
-			   SPI_NFI_FIFO_FLUSH | SPI_NFI_RST);
+	err = airoha_snand_nfi_reset(priv);
+	if (err)
+		goto error_dma_mode_off;
+
+	err = airoha_snand_nfi_clear_intr(priv);
+	if (err)
+		goto error_dma_mode_off;
+
+	err = airoha_snand_nfi_clear_done(priv);
 	if (err)
 		goto error_dma_mode_off;
 
@@ -653,7 +892,7 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 	if (err)
 		goto error_dma_mode_off;
 
-	dma_addr = dma_map_single(txrx_buf, SPI_NAND_CACHE_SIZE,
+	dma_addr = snfi_dma_addr(txrx_buf, SPI_NAND_CACHE_SIZE,
 				  DMA_FROM_DEVICE);
 
 	/* set dma addr */
@@ -721,9 +960,8 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 	 * SPI_NFI_READ_FROM_CACHE_DONE bit must be written at the end
 	 * of dirmap_read operation even if it is already set.
 	 */
-	err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
-				 SPI_NFI_READ_FROM_CACHE_DONE,
-				 SPI_NFI_READ_FROM_CACHE_DONE);
+	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
+			   SPI_NFI_READ_FROM_CACHE_DONE);
 	if (err)
 		goto error_dma_unmap;
 
@@ -736,7 +974,7 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 	/* DMA read need delay for data ready from controller to DRAM */
 	udelay(1);
 
-	dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_FROM_DEVICE);
+	snfi_dma_unmap(dma_addr, SPI_NAND_CACHE_SIZE, DMA_FROM_DEVICE);
 
 	err = airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
 	if (err < 0)
@@ -747,7 +985,7 @@ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
 	return len;
 
 error_dma_unmap:
-	dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_FROM_DEVICE);
+	snfi_dma_unmap(dma_addr, SPI_NAND_CACHE_SIZE, DMA_FROM_DEVICE);
 error_dma_mode_off:
 	airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
 	return err;
@@ -765,22 +1003,10 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	size_t bytes;
 	int err;
 
-	if (!priv->dma) {
-		/* simplified version of spi_mem_no_dirmap_write() */
-		struct spi_mem_op op = desc->info.op_tmpl;
-
-		op.addr.val = desc->info.offset + offs;
-		op.data.buf.out = buf;
-		op.data.nbytes = len;
-		err = spi_mem_exec_op(desc->slave, &op);
-		if (err)
-			return err;
+	if (!priv->dma || !airoha_snand_dma_write_ok(desc, offs, len))
+		return airoha_snand_no_dirmap_write(desc, offs, len, buf);
 
-		return op.data.nbytes;
-	}
-
-	/* minimum oob size is 64 */
-	bytes = round_up(offs + len, 64);
+	bytes = len;
 
 	opcode = desc->info.op_tmpl.cmd.opcode;
 	switch (opcode) {
@@ -797,19 +1023,21 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 		return -EOPNOTSUPP;
 	}
 
-	if (offs > 0)
-		memset(txrx_buf, 0xff, offs);
-	memcpy(txrx_buf + offs, buf, len);
-	if (bytes > offs + len)
-		memset(txrx_buf + offs + len, 0xff, bytes - offs - len);
+	memcpy(txrx_buf, buf, len);
 
 	err = airoha_snand_set_mode(priv, SPI_MODE_DMA);
 	if (err < 0)
 		return err;
 
-	/* NFI reset */
-	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_CON,
-			   SPI_NFI_FIFO_FLUSH | SPI_NFI_RST);
+	err = airoha_snand_nfi_reset(priv);
+	if (err)
+		goto error_dma_mode_off;
+
+	err = airoha_snand_nfi_clear_intr(priv);
+	if (err)
+		goto error_dma_mode_off;
+
+	err = airoha_snand_nfi_clear_done(priv);
 	if (err)
 		goto error_dma_mode_off;
 
@@ -851,7 +1079,7 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	if (err)
 		goto error_dma_mode_off;
 
-	dma_addr = dma_map_single(txrx_buf, SPI_NAND_CACHE_SIZE,
+	dma_addr = snfi_dma_addr(txrx_buf, SPI_NAND_CACHE_SIZE,
 				  DMA_TO_DEVICE);
 
 	/* set dma addr */
@@ -887,9 +1115,9 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	if (err)
 		goto error_dma_unmap;
 
-	/* set write addr: zero page offset + descriptor write offset */
+	/* set write addr: descriptor base column + requested dirmap offset */
 	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_PG_CTL2,
-			   desc->info.offset);
+			   desc->info.offset + offs);
 	if (err)
 		goto error_dma_unmap;
 
@@ -908,6 +1136,9 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	if (err)
 		goto error_dma_unmap;
 
+	/* Vendor code gives the DMA engine a short settle time after WR_TRIG. */
+	udelay(1);
+
 	err = regmap_read_poll_timeout(priv->regmap_nfi, REG_SPI_NFI_INTR,
 				       val, (val & SPI_NFI_AHB_DONE), 0,
 				       1 * MSEC_PER_SEC);
@@ -925,13 +1156,12 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	 * SPI_NFI_LOAD_TO_CACHE_DONE bit must be written at the end
 	 * of dirmap_write operation even if it is already set.
 	 */
-	err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
-				 SPI_NFI_LOAD_TO_CACHE_DONE,
-				 SPI_NFI_LOAD_TO_CACHE_DONE);
+	err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
+			   SPI_NFI_LOAD_TO_CACHE_DONE);
 	if (err)
 		goto error_dma_unmap;
 
-	dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE);
+	snfi_dma_unmap(dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE);
 
 	err = airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
 	if (err < 0)
@@ -940,7 +1170,7 @@ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
 	return len;
 
 error_dma_unmap:
-	dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE);
+	snfi_dma_unmap(dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE);
 error_dma_mode_off:
 	airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
 	return err;
@@ -986,7 +1216,7 @@ static int airoha_snand_exec_op(struct spi_slave *slave,
 	err = airoha_snand_write_data(priv, data, op_len,
 				      op->cmd.buswidth);
 	if (err)
-		return err;
+		goto out_cs_high;
 
 	/* addr part */
 	data += op_len;
@@ -994,7 +1224,7 @@ static int airoha_snand_exec_op(struct spi_slave *slave,
 		err = airoha_snand_write_data(priv, data, addr_len,
 					      op->addr.buswidth);
 		if (err)
-			return err;
+			goto out_cs_high;
 	}
 
 	/* dummy */
@@ -1003,7 +1233,7 @@ static int airoha_snand_exec_op(struct spi_slave *slave,
 		err = airoha_snand_write_data(priv, data, dummy_len,
 					      op->dummy.buswidth);
 		if (err)
-			return err;
+			goto out_cs_high;
 	}
 
 	/* data */
@@ -1017,23 +1247,36 @@ static int airoha_snand_exec_op(struct spi_slave *slave,
 						      op->data.nbytes,
 						      op->data.buswidth);
 		if (err)
-			return err;
+			goto out_cs_high;
 	}
 
-	return airoha_snand_set_cs(priv, SPI_CHIP_SEL_HIGH);
+out_cs_high:
+	/* Always release CS.  Leaving CS asserted after a FIFO timeout can lock
+	 * the shared SPI bus, which is visible on boards using NOR + NAND.
+	 */
+	if (airoha_snand_set_cs(priv, SPI_CHIP_SEL_HIGH) && !err)
+		err = -EIO;
+
+	return err;
 }
 
 static int airoha_snand_probe(struct udevice *dev)
 {
 	struct airoha_snand_priv *priv = dev_get_priv(dev);
+	enum airoha_snand_bootstrap type;
+	u32 boot_trp, snf_nfi_cnfg, sfc_strap;
 	int ret;
-	u32 sfc_strap;
 
-	priv->txrx_buf = memalign(ARCH_DMA_MINALIGN, SPI_NAND_CACHE_SIZE);
+	priv->txrx_buf = snfi_uncached(memalign(ARCH_DMA_MINALIGN,
+						  SPI_NAND_CACHE_SIZE));
 	if (!priv->txrx_buf) {
-		dev_err(dev, "failed to alloacate memory for dirmap\n");
+		dev_err(dev, "failed to allocate memory for dirmap\n");
 		return -ENOMEM;
 	}
+	priv->dev = dev;
+	priv->en751221 = of_machine_is_compatible("econet,en751221") ||
+			   of_machine_is_compatible("econet,en7512") ||
+			   of_machine_is_compatible("econet,en7521");
 
 	ret = regmap_init_mem_index(dev_ofnode(dev), &priv->regmap_ctrl, 0);
 	if (ret) {
@@ -1043,36 +1286,48 @@ static int airoha_snand_probe(struct udevice *dev)
 
 	ret = regmap_init_mem_index(dev_ofnode(dev), &priv->regmap_nfi, 1);
 	if (ret) {
-		dev_err(dev, "failed to init spi nfi regmap\n");
-		return ret;
+		if (!priv->en751221) {
+			dev_err(dev, "failed to init spi nfi regmap\n");
+			return ret;
+		}
+
+		/* EN751221 has no separate NFI/SNFI DMA register bank. */
+		priv->regmap_nfi = NULL;
 	}
 
-	priv->spi_clk = devm_clk_get(dev, "spi");
+	priv->spi_clk = devm_clk_get_optional(dev, "spi");
 	if (IS_ERR(priv->spi_clk)) {
-		dev_err(dev, "unable to get spi clk\n");
-		return PTR_ERR(priv->regmap_ctrl);
-	}
-	clk_enable(priv->spi_clk);
+		ret = PTR_ERR(priv->spi_clk);
+		if (ret != -ENOSYS) {
+			dev_err(dev, "unable to get spi clk\n");
+			return ret;
+		}
 
-	priv->dma = 1;
-	if (device_is_compatible(dev, "airoha,en7523-snand")){
-		ret = regmap_read(priv->regmap_ctrl, REG_SPI_CTRL_SFC_STRAP, &sfc_strap);
+		/* EN751221 does not require the U-Boot clock framework. */
+		priv->spi_clk = NULL;
+	}
+	if (priv->spi_clk) {
+		ret = clk_enable(priv->spi_clk);
 		if (ret)
 			return ret;
-
-		if (!(sfc_strap & 0x04)) {
-			priv->dma = 0;
-			printf("\n"
-				"=== WARNING ======================================================\n"
-				"Detected booting in RESERVED mode (UART_TXD was short to GND).\n"
-				"This mode is known for incorrect DMA reading of some flashes.\n"
-				"Usage of DMA for flash operations will be disabled to prevent data\n"
-				"damage. Unplug your serial console and power cycle the board\n"
-				"to boot with full performance.\n"
-				"==================================================================\n\n");
-		}
 	}
 
+	type = airoha_snand_get_bootstrap(priv, &boot_trp, &snf_nfi_cnfg,
+					   &sfc_strap);
+
+	dev_dbg(priv->dev,
+		 "bootstrap: %s (boot_trp=0x%08x, snf_nfi_cnfg=0x%08x, sfc_strap=0x%08x)\n",
+		 airoha_snand_bootstrap_name(type), boot_trp, snf_nfi_cnfg,
+		 sfc_strap);
+
+	/* EN751221 uses only the manual FIFO path. EN7523/AN7581 can use
+	 * the second register bank for SNFI DMA operations.
+	 */
+	priv->dma = !!priv->regmap_nfi;
+
+	if (!priv->regmap_nfi)
+		return 0;
+
 	return airoha_snand_nfi_init(priv);
 }
 
@@ -1081,6 +1336,10 @@ static int airoha_snand_nfi_set_speed(struct udevice *bus, uint speed)
 	struct airoha_snand_priv *priv = dev_get_priv(bus);
 	int ret;
 
+	/* EN751221 leaves the SFC clock configured by the previous stage. */
+	if (!priv->spi_clk)
+		return 0;
+
 	ret = clk_set_rate(priv->spi_clk, speed);
 	if (ret < 0)
 		return ret;
@@ -1108,6 +1367,7 @@ static const struct dm_spi_ops airoha_snfi_spi_ops = {
 };
 
 static const struct udevice_id airoha_snand_ids[] = {
+	{ .compatible = "airoha,en7523-spi" },
 	{ .compatible = "airoha,en7581-snand" },
 	{ }
 };
@@ -1120,3 +1380,4 @@ U_BOOT_DRIVER(airoha_snfi_spi) = {
 	.priv_auto = sizeof(struct airoha_snand_priv),
 	.probe = airoha_snand_probe,
 };
+
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 05/12] mtd: spi-nand: add EN75 BMT bad-block management support
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (3 preceding siblings ...)
  2026-08-25 22:41   ` [PATCH v1 04/12] spi: airoha-snfi: add EN7528 SPI-NAND controller support AK Sharma
@ 2026-08-25 22:42   ` AK Sharma
  2026-08-25 22:59     ` Tom Rini
  2026-08-25 22:42   ` [PATCH v1 06/12] net: airoha-eth: add EN7528 Ethernet support AK Sharma
                     ` (8 subsequent siblings)
  13 siblings, 1 reply; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:42 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Add read support for the EcoNet/Airoha BMT/BBT (bad-block management
table) layout used by the EN75xx vendor bootloaders, so U-Boot maps the
SPI-NAND logical blocks the same way as the vendor firmware.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 drivers/mtd/nand/spi/Kconfig    |   8 +
 drivers/mtd/nand/spi/Makefile   |   1 +
 drivers/mtd/nand/spi/core.c     |   9 +-
 drivers/mtd/nand/spi/en75_bmt.c | 665 ++++++++++++++++++++++++++++++++
 drivers/mtd/nand/spi/en75_bmt.h |  24 ++
 5 files changed, 706 insertions(+), 1 deletion(-)
 create mode 100644 drivers/mtd/nand/spi/en75_bmt.c
 create mode 100644 drivers/mtd/nand/spi/en75_bmt.h

diff --git a/drivers/mtd/nand/spi/Kconfig b/drivers/mtd/nand/spi/Kconfig
index 1124dada..09531914 100644
--- a/drivers/mtd/nand/spi/Kconfig
+++ b/drivers/mtd/nand/spi/Kconfig
@@ -6,3 +6,11 @@ menuconfig MTD_SPI_NAND
 	select SPI_MEM
 	help
 	  This is the framework for the SPI NAND device drivers.
+
+config MTD_EN75_BMT
+	bool "EcoNet EN75xx vendor BMT/BBT translation (read-only)"
+	depends on MTD_SPI_NAND
+	help
+	  Honour the EcoNet/Airoha factory BBT and BMT so logical NAND
+	  addresses match the bootbase and Linux. Read-only: never rewrite
+	  the tables.
diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
index a7a0b2cb..20aa718d 100644
--- a/drivers/mtd/nand/spi/Makefile
+++ b/drivers/mtd/nand/spi/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 spinand-objs := core.o otp.o
+spinand-objs += $(if $(CONFIG_MTD_EN75_BMT),en75_bmt.o)
 spinand-objs += alliancememory.o ato.o esmt.o fmsh.o foresee.o gigadevice.o macronix.o
 spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
 obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 14af4264..29fdefe2 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -33,6 +33,7 @@
 #include <linux/mtd/spinand.h>
 #include <linux/printk.h>
 #include <linux/delay.h>
+#include "en75_bmt.h"
 #endif
 
 struct spinand_plat {
@@ -1703,15 +1704,21 @@ static int spinand_probe(struct udevice *dev)
 #ifndef __UBOOT__
 	ret = mtd_device_register(mtd, NULL, 0);
 #else
+	ret = en75_bmt_attach(mtd);
+	if (ret)
+		goto err_spinand_cleanup;
+
 	ret = add_mtd_device(mtd);
 #endif
 	if (ret)
-		goto err_spinand_cleanup;
+		goto err_bmt_detach;
 
 	plat->mtd = mtd;
 
 	return 0;
 
+err_bmt_detach:
+	en75_bmt_detach(mtd);
 err_spinand_cleanup:
 	spinand_cleanup(spinand);
 
diff --git a/drivers/mtd/nand/spi/en75_bmt.c b/drivers/mtd/nand/spi/en75_bmt.c
new file mode 100644
index 00000000..1cb3ca01
--- /dev/null
+++ b/drivers/mtd/nand/spi/en75_bmt.c
@@ -0,0 +1,665 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * EcoNet/Airoha EN75xx vendor BMT/BBT translation layer (READ-ONLY).
+ *
+ * The vendor bootbase and the Linux en75_bmt driver do not "skip" bad NAND
+ * blocks the way stock MTD does - they *remap* them into a reserve pool kept at
+ * the top of the chip, so that a logical block keeps a stable address after a
+ * block goes bad. A bootloader that ignores this reads the wrong data on any
+ * device that has ever remapped a block.
+ *
+ * This is a deliberately read-only port of that scheme:
+ *
+ *   - the factory Bad Block Table ("RAWB") and the Block Mapping Table ("BMT")
+ *     are located, checksum-verified and parsed,
+ *   - every MTD access is translated logical -> physical exactly as the vendor
+ *     does (BBT shift-up, then BMT lookup),
+ *   - mtd->size is reduced to the user area so partitions land where Linux and
+ *     the bootbase expect them,
+ *   - *nothing is ever written to the tables*. If a block turns bad we report
+ *     the error upward instead of allocating a replacement; leave repair to
+ *     Linux, which implements the full scheme.
+ *
+ * Deliberately NOT ported: BBT reconstruction / factory-BBT writing. Rewriting
+ * the factory BBT shifts the physical address of every block above the first
+ * bad one, which in a bootloader means bricking the board. Omission is the
+ * safest form of "disabled".
+ *
+ * Derived from the Linux drivers by Caleb James DeLisle:
+ *   target/linux/econet/files/drivers/mtd/nand/en75_bmt.c   (back end)
+ *   target/linux/generic/files/drivers/mtd/nand/mtk_bmt.c   (MTD glue)
+ */
+
+#include <malloc.h>
+#include <asm/cache.h>
+#include <dm/ofnode.h>
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/mtd/mtd.h>
+#include <linux/printk.h>
+#include <linux/sizes.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include "en75_bmt.h"
+
+#define LOG_PFX				"en75_bmt"
+
+/* Vendor firmware calls this POOL_GOOD_BLOCK_PERCENT. Must agree with the
+ * bootbase, which prints the resulting figure as "bmt pool size: N".
+ */
+#define REQUIRED_GOOD_BLOCKS(total)	((total) * 8 / 100)
+
+#define MAX_BMT_SIZE			256
+#define MAX_BBT_SIZE			1000
+#define DEFAULT_BBT_TABLE_SIZE		1000
+#define INITIAL_READ_TRIES		3
+
+/* Marked bad by the Linux driver (the bootbase uses 0x00 for everything). */
+#define BLOCK_WORN_MARK			0x55
+
+/*
+ * On-disk structures. All multi-byte fields are host-order little-endian.
+ */
+
+struct bmt_table_header {
+	char signature[3];	/* "BMT" */
+	u8 version;		/* 1 */
+	u8 bad_count;		/* unused */
+	u8 size;		/* number of mappings */
+	u8 checksum;		/* bmt_checksum() */
+	u8 reserved[13];
+};
+
+static_assert(sizeof(struct bmt_table_header) == 20);
+
+struct bmt_entry {
+	u16 from;		/* physical (post-BBT-shift) block being replaced */
+	u16 to;			/* reserve block standing in for it */
+};
+
+struct bmt_table {
+	struct bmt_table_header header;
+	struct bmt_entry table[MAX_BMT_SIZE];
+};
+
+struct bbt_table_header {
+	char signature[4];	/* "RAWB" */
+	u32 checksum;		/* bbt_checksum(), only 16 bits used */
+	u8 version;		/* 1 */
+	u8 size;		/* number of bad blocks */
+	u8 reserved[2];		/* 0xffff */
+};
+
+static_assert(sizeof(struct bbt_table_header) == 12);
+
+struct bbt_table {
+	struct bbt_table_header header;
+	u16 table[MAX_BBT_SIZE];	/* ascending order required */
+};
+
+static_assert(sizeof(struct bbt_table) == 2012);
+
+/*
+ * Runtime state. Single instance: there is one NAND chip on these boards, and
+ * the Linux driver makes the same assumption.
+ */
+struct en75_bmt {
+	struct mtd_info *mtd;
+
+	/* saved raw ops - internal I/O must bypass our own translation */
+	int (*_read_oob)(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops);
+	int (*_write_oob)(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops);
+	int (*_erase)(struct mtd_info *mtd, struct erase_info *instr);
+	int (*_block_isbad)(struct mtd_info *mtd, loff_t ofs);
+	int (*_block_markbad)(struct mtd_info *mtd, loff_t ofs);
+
+	struct bbt_table bbt;
+	struct bmt_table bmt;
+
+	u16 bbt_table_size;
+	u16 total_blks;
+	u16 reserve_area_begin;
+	u32 blk_size;
+	u32 pg_size;
+	u16 blk_shift;
+	u16 pg_shift;
+
+	u8 *data_buf;
+	bool attached;
+};
+
+static struct en75_bmt bmtd;
+
+static inline u32 blk_pg(u16 block)
+{
+	return (u32)(block << (bmtd.blk_shift - bmtd.pg_shift));
+}
+
+/* Read via the *saved* op: reserve-area offsets sit above the shrunken
+ * mtd->size and would be rejected by the bounds-checked wrappers, and going
+ * through our own translation here would recurse.
+ */
+static int bbt_nand_read(u32 page, u8 *dat, int dat_len, u8 *fdm, int fdm_len)
+{
+	struct mtd_oob_ops ops = {
+		.mode = MTD_OPS_PLACE_OOB,
+		.ooboffs = 0,
+		.oobbuf = fdm,
+		.ooblen = fdm_len,
+		.datbuf = dat,
+		.len = dat_len,
+	};
+	int ret;
+
+	ret = bmtd._read_oob(bmtd.mtd, (loff_t)page << bmtd.pg_shift, &ops);
+	if (ret < 0)
+		return ret;
+	return 0;
+}
+
+/*
+ * In-memory helpers (no I/O)
+ */
+
+static u16 bbt_checksum(const struct bbt_table *bbt, u16 table_size)
+{
+	const u8 *data = (const u8 *)bbt->table;
+	u16 checksum = bbt->header.version + bbt->header.size;
+	int i;
+
+	for (i = 0; i < table_size * (int)sizeof(bbt->table[0]); i++)
+		checksum += data[i];
+
+	return checksum;
+}
+
+static u8 bmt_checksum(const struct bmt_table *bmt, int check_entries)
+{
+	const u8 *data = (const u8 *)&bmt->table;
+	u8 checksum = bmt->header.version + bmt->header.size;
+	int length, i;
+
+	if (check_entries > MAX_BMT_SIZE)
+		check_entries = MAX_BMT_SIZE;
+	length = check_entries * (int)sizeof(bmt->table[0]);
+	for (i = 0; i < length; i++)
+		checksum += data[i];
+
+	return checksum;
+}
+
+static void sort_bbt(struct bbt_table *bbt)
+{
+	int i, j;
+
+	/* Insertion sort: at most a few hundred u16s, and it avoids pulling in
+	 * qsort() for something this small. Ascending order is required both by
+	 * the shift algorithm below and by the vendor firmware.
+	 */
+	for (i = 1; i < bbt->header.size; i++) {
+		u16 key = bbt->table[i];
+
+		for (j = i - 1; j >= 0 && bbt->table[j] > key; j--)
+			bbt->table[j + 1] = bbt->table[j];
+		bbt->table[j + 1] = key;
+	}
+}
+
+/*
+ * Stage 1: each factory-bad block shifts everything above it up by one. This
+ * is a shift, not a skip-list, which is why the BBT must be sorted and why
+ * changing it after the fact moves every block above the change.
+ */
+static int get_mapping_block_bbt(int block)
+{
+	int size = bmtd.bbt.header.size;
+	int i;
+
+	for (i = 0; i < size; i++)
+		if (bmtd.bbt.table[i] <= block)
+			block++;
+
+	if (block >= bmtd.reserve_area_begin || block < 0)
+		return -EINVAL;
+
+	return block;
+}
+
+/* Stage 2: BMT lookup, scanned backwards so a later duplicate 'from' wins. */
+static int get_mapping_block(int block)
+{
+	int i;
+
+	block = get_mapping_block_bbt(block);
+	if (block < 0)
+		return block;
+
+	for (i = bmtd.bmt.header.size - 1; i >= 0; i--)
+		if (bmtd.bmt.table[i].from == block)
+			return bmtd.bmt.table[i].to;
+
+	return block;
+}
+
+enum block_is_bad {
+	BB_GOOD,
+	BB_FACTORY_BAD,
+	BB_WORN,
+	BB_UNKNOWN_BAD,
+};
+
+static enum block_is_bad fdm_is_bad(const u8 fdm[4])
+{
+	if (fdm[0] == 0xff && fdm[1] == 0xff)
+		return BB_GOOD;
+	if (fdm[0] == BLOCK_WORN_MARK)
+		return BB_WORN;
+	if (fdm[0] == 0x00 || fdm[1] == 0x00)
+		return BB_FACTORY_BAD;
+	return BB_UNKNOWN_BAD;
+}
+
+static int try_parse_bbt(struct bbt_table *out, const u8 *buf, int len, u16 table_size)
+{
+	static struct bbt_table workspace;
+	size_t bbt_size = sizeof(workspace.header) +
+			  table_size * sizeof(workspace.table[0]);
+
+	if (len < (int)bbt_size)
+		return -EINVAL;
+
+	memcpy(&workspace, buf, bbt_size);
+
+	if (strncmp(workspace.header.signature, "RAWB", 4))
+		return -EINVAL;
+
+	if ((u16)workspace.header.checksum != bbt_checksum(&workspace, table_size))
+		return -EINVAL;
+
+	sort_bbt(&workspace);
+	memcpy(out, &workspace, bbt_size);
+
+	return 0;
+}
+
+static int try_parse_bmt(struct bmt_table *out, const u8 *buf, int len)
+{
+	static struct bmt_table workspace;
+
+	if (len < (int)sizeof(*out))
+		return -EINVAL;
+
+	memcpy(&workspace, buf, sizeof(workspace));
+
+	if (strncmp(workspace.header.signature, "BMT", 3))
+		return -EINVAL;
+
+	if (workspace.header.checksum !=
+	    bmt_checksum(&workspace, workspace.header.size))
+		return -EINVAL;
+
+	memcpy(out, &workspace, sizeof(workspace));
+
+	return 0;
+}
+
+/*
+ * Walk down from the last block until REQUIRED_GOOD_BLOCKS good ones have been
+ * seen; that boundary is where the reserve pool starts. The BBT and BMT are
+ * found by signature+checksum along the way, not at fixed addresses.
+ */
+static int scan_reserve(void)
+{
+	u16 total_blks = bmtd.total_blks;
+	int required = REQUIRED_GOOD_BLOCKS(total_blks);
+	int good_blocks = 0;
+	bool found_bbt = false, found_bmt = false;
+	int cursor;
+
+	for (cursor = total_blks - 1; cursor > 0; cursor--) {
+		u8 fdm[4] = { 0xff, 0xff, 0xff, 0xff };
+		int ret = -EIO;
+		int i;
+
+		for (i = 0; i < INITIAL_READ_TRIES; i++) {
+			ret = bbt_nand_read(blk_pg(cursor), bmtd.data_buf,
+					    bmtd.pg_size, fdm, sizeof(fdm));
+			if (!ret)
+				break;
+		}
+
+		if (ret || fdm_is_bad(fdm)) {
+			pr_info("%s: skipping bad block %d in reserve area\n",
+				LOG_PFX, cursor);
+		} else {
+			good_blocks++;
+			if (!found_bbt &&
+			    !try_parse_bbt(&bmtd.bbt, bmtd.data_buf,
+					   bmtd.pg_size, bmtd.bbt_table_size)) {
+				printf("%s: found BBT in block %d\n", LOG_PFX, cursor);
+				found_bbt = true;
+			} else if (!found_bmt &&
+				   !try_parse_bmt(&bmtd.bmt, bmtd.data_buf,
+						  bmtd.pg_size)) {
+				printf("%s: found BMT in block %d\n", LOG_PFX, cursor);
+				found_bmt = true;
+			}
+		}
+
+		if (good_blocks >= required)
+			break;
+	}
+
+	if (!cursor) {
+		pr_err("%s: not enough valid blocks found, need %d got %d\n",
+		       LOG_PFX, required, good_blocks);
+		return -ENOSPC;
+	}
+
+	bmtd.reserve_area_begin = cursor;
+
+	/*
+	 * Refuse to run without a valid BBT. The alternative - reconstructing
+	 * it - is exactly the operation that can shift every block on the chip,
+	 * so a bootloader must not attempt it.
+	 */
+	if (!found_bbt) {
+		pr_err("%s: no valid BBT found, refusing to translate\n", LOG_PFX);
+		return -ENOENT;
+	}
+	if (!found_bmt)
+		pr_info("%s: no BMT found, assuming no remapped blocks\n", LOG_PFX);
+
+	return 0;
+}
+
+/*
+ * MTD operation wrappers. Each translates then defers to the saved raw op.
+ * Unlike the Linux driver these never allocate a replacement block: a failure
+ * is reported to the caller instead.
+ */
+
+static int en75_bmt_read(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
+{
+	struct mtd_oob_ops cur_ops = *ops;
+	int max_bitflips = 0;
+	int ret = 0;
+
+	ops->retlen = 0;
+	ops->oobretlen = 0;
+
+	while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) {
+		u32 offset = from & (bmtd.blk_size - 1);
+		u32 block = from >> bmtd.blk_shift;
+		int cur_block, cur_ret;
+		loff_t cur_from;
+
+		cur_block = get_mapping_block(block);
+		if (cur_block < 0)
+			return -EIO;
+
+		cur_from = ((loff_t)cur_block << bmtd.blk_shift) + offset;
+
+		cur_ops.oobretlen = 0;
+		cur_ops.retlen = 0;
+		cur_ops.len = min_t(u32, mtd->erasesize - offset,
+				    ops->len - ops->retlen);
+
+		cur_ret = bmtd._read_oob(mtd, cur_from, &cur_ops);
+		if (cur_ret < 0) {
+			ret = cur_ret;
+			if (!mtd_is_bitflip(cur_ret))
+				goto out;
+		} else {
+			max_bitflips = max_t(int, max_bitflips, cur_ret);
+		}
+
+		ops->retlen += cur_ops.retlen;
+		ops->oobretlen += cur_ops.oobretlen;
+
+		cur_ops.ooboffs = 0;
+		cur_ops.datbuf += cur_ops.retlen;
+		cur_ops.oobbuf += cur_ops.oobretlen;
+		cur_ops.ooblen -= cur_ops.oobretlen;
+
+		if (!cur_ops.len)
+			cur_ops.len = mtd->erasesize - offset;
+
+		from += cur_ops.len;
+	}
+
+out:
+	if (ret < 0 && !mtd_is_bitflip(ret))
+		return ret;
+
+	return max_bitflips;
+}
+
+static int en75_bmt_write(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops)
+{
+	struct mtd_oob_ops cur_ops = *ops;
+	int ret;
+
+	ops->retlen = 0;
+	ops->oobretlen = 0;
+
+	while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) {
+		u32 offset = to & (bmtd.blk_size - 1);
+		u32 block = to >> bmtd.blk_shift;
+		int cur_block;
+		loff_t cur_to;
+
+		cur_block = get_mapping_block(block);
+		if (cur_block < 0)
+			return -EIO;
+
+		cur_to = ((loff_t)cur_block << bmtd.blk_shift) + offset;
+
+		cur_ops.oobretlen = 0;
+		cur_ops.retlen = 0;
+		cur_ops.len = min_t(u32, bmtd.blk_size - offset,
+				    ops->len - ops->retlen);
+
+		ret = bmtd._write_oob(mtd, cur_to, &cur_ops);
+		if (ret < 0) {
+			/* No remap-on-failure here: the block stays bad and the
+			 * caller finds out. Repairing it is Linux's job.
+			 */
+			pr_err("%s: write failed at block %u (physical %d); "
+			       "not remapping (read-only BMT)\n",
+			       LOG_PFX, block, cur_block);
+			return ret;
+		}
+
+		ops->retlen += cur_ops.retlen;
+		ops->oobretlen += cur_ops.oobretlen;
+
+		cur_ops.ooboffs = 0;
+		cur_ops.datbuf += cur_ops.retlen;
+		cur_ops.oobbuf += cur_ops.oobretlen;
+		cur_ops.ooblen -= cur_ops.oobretlen;
+
+		if (!cur_ops.len)
+			cur_ops.len = mtd->erasesize - offset;
+
+		to += cur_ops.len;
+	}
+
+	return 0;
+}
+
+static int en75_bmt_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+	struct erase_info mapped_instr = {
+		.len = bmtd.blk_size,
+	};
+	u64 start_addr, end_addr;
+	int ret = 0;
+
+	start_addr = instr->addr & (~mtd->erasesize_mask);
+	end_addr = instr->addr + instr->len;
+
+	while (start_addr < end_addr) {
+		u16 orig_block = start_addr >> bmtd.blk_shift;
+		int block = get_mapping_block(orig_block);
+
+		if (block < 0)
+			return -EIO;
+
+		mapped_instr.addr = (loff_t)block << bmtd.blk_shift;
+		ret = bmtd._erase(mtd, &mapped_instr);
+		if (ret) {
+			pr_err("%s: erase failed at block %u (physical %d); "
+			       "not remapping (read-only BMT)\n",
+			       LOG_PFX, orig_block, block);
+			instr->fail_addr = start_addr;
+			break;
+		}
+		start_addr += mtd->erasesize;
+	}
+
+	return ret;
+}
+
+static int en75_bmt_block_isbad(struct mtd_info *mtd, loff_t ofs)
+{
+	u16 orig_block = ofs >> bmtd.blk_shift;
+	int block = get_mapping_block(orig_block);
+
+	if (block < 0)
+		return -EIO;
+
+	return bmtd._block_isbad(mtd, (loff_t)block << bmtd.blk_shift);
+}
+
+static int en75_bmt_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+	u16 orig_block = ofs >> bmtd.blk_shift;
+	int block = get_mapping_block(orig_block);
+
+	if (block < 0)
+		return -EIO;
+
+	return bmtd._block_markbad(mtd, (loff_t)block << bmtd.blk_shift);
+}
+
+static void en75_bmt_replace_ops(struct mtd_info *mtd)
+{
+	bmtd._read_oob		= mtd->_read_oob;
+	bmtd._write_oob		= mtd->_write_oob;
+	bmtd._erase		= mtd->_erase;
+	bmtd._block_isbad	= mtd->_block_isbad;
+	bmtd._block_markbad	= mtd->_block_markbad;
+
+	mtd->_read_oob		= en75_bmt_read;
+	mtd->_write_oob		= en75_bmt_write;
+	mtd->_erase		= en75_bmt_erase;
+	mtd->_block_isbad	= en75_bmt_block_isbad;
+	mtd->_block_markbad	= en75_bmt_block_markbad;
+}
+
+static void en75_bmt_restore_ops(struct mtd_info *mtd)
+{
+	mtd->_read_oob		= bmtd._read_oob;
+	mtd->_write_oob		= bmtd._write_oob;
+	mtd->_erase		= bmtd._erase;
+	mtd->_block_isbad	= bmtd._block_isbad;
+	mtd->_block_markbad	= bmtd._block_markbad;
+}
+
+int en75_bmt_attach(struct mtd_info *mtd)
+{
+	ofnode node = mtd_get_ofnode(mtd);
+	u32 assert_reserve = 0;
+	u32 val;
+	int ret;
+
+	if (!ofnode_valid(node) || !ofnode_read_bool(node, "econet,bmt"))
+		return 0;
+
+	if (bmtd.attached) {
+		pr_err("%s: already attached to an MTD device\n", LOG_PFX);
+		return -EBUSY;
+	}
+
+	memset(&bmtd, 0, sizeof(bmtd));
+	bmtd.mtd = mtd;
+	bmtd.blk_size = mtd->erasesize;
+	bmtd.pg_size = mtd->writesize;
+	bmtd.blk_shift = ffs(mtd->erasesize) - 1;
+	bmtd.pg_shift = ffs(mtd->writesize) - 1;
+	/* Must be taken before the size is shrunk below. */
+	bmtd.total_blks = mtd->size >> bmtd.blk_shift;
+
+	bmtd.bbt_table_size = DEFAULT_BBT_TABLE_SIZE;
+	if (!ofnode_read_u32(node, "econet,bbt-table-size", &val)) {
+		if (val == 0 || val > MAX_BBT_SIZE) {
+			pr_err("%s: econet,bbt-table-size %u out of range\n",
+			       LOG_PFX, val);
+			return -EINVAL;
+		}
+		bmtd.bbt_table_size = val;
+	}
+	ofnode_read_u32(node, "econet,assert-reserve-size", &assert_reserve);
+
+	bmtd.data_buf = memalign(ARCH_DMA_MINALIGN, bmtd.pg_size);
+	if (!bmtd.data_buf)
+		return -ENOMEM;
+
+	en75_bmt_replace_ops(mtd);
+
+	ret = scan_reserve();
+	if (ret)
+		goto err_restore;
+
+	if (assert_reserve &&
+	    assert_reserve != bmtd.total_blks - bmtd.reserve_area_begin) {
+		/* Mismatch means our idea of the pool boundary differs from the
+		 * bootbase's, so every translation would be wrong.
+		 */
+		pr_err("%s: reserve size %d != asserted %u, refusing\n", LOG_PFX,
+		       bmtd.total_blks - bmtd.reserve_area_begin, assert_reserve);
+		ret = -EINVAL;
+		goto err_restore;
+	}
+
+	/* Hide the reserve area and the factory-bad shift from everything
+	 * above, so DT partitions are validated against the same size Linux
+	 * sees. Must happen before partitions are parsed.
+	 */
+	mtd->size = (loff_t)(bmtd.reserve_area_begin - bmtd.bbt.header.size)
+		    << bmtd.blk_shift;
+
+	bmtd.attached = true;
+
+	printf("%s: blocks: total: %d, user: %d, factory_bad: %d, worn: %d reserve: %d\n",
+	       LOG_PFX, bmtd.total_blks, bmtd.reserve_area_begin,
+	       bmtd.bbt.header.size, bmtd.bmt.header.size,
+	       bmtd.total_blks - bmtd.reserve_area_begin);
+	printf("%s: %llu MiB usable space (read-only BMT: no remapping)\n",
+	       LOG_PFX, (u64)mtd->size >> 20);
+
+	return 0;
+
+err_restore:
+	en75_bmt_restore_ops(mtd);
+	free(bmtd.data_buf);
+	bmtd.data_buf = NULL;
+	bmtd.mtd = NULL;
+	return ret;
+}
+
+void en75_bmt_detach(struct mtd_info *mtd)
+{
+	if (!bmtd.attached || bmtd.mtd != mtd)
+		return;
+
+	en75_bmt_restore_ops(mtd);
+	free(bmtd.data_buf);
+	bmtd.data_buf = NULL;
+	bmtd.mtd = NULL;
+	bmtd.attached = false;
+}
diff --git a/drivers/mtd/nand/spi/en75_bmt.h b/drivers/mtd/nand/spi/en75_bmt.h
new file mode 100644
index 00000000..e9d50b8d
--- /dev/null
+++ b/drivers/mtd/nand/spi/en75_bmt.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * EcoNet/Airoha EN75xx vendor BMT/BBT translation layer (read-only).
+ */
+#ifndef __EN75_BMT_H
+#define __EN75_BMT_H
+
+struct mtd_info;
+
+#if CONFIG_IS_ENABLED(MTD_EN75_BMT)
+/*
+ * Install the translation layer on a NAND whose DT node carries "econet,bmt".
+ * Must be called before add_mtd_device(), because it shrinks mtd->size to the
+ * user area and partitions are validated against that.
+ * Returns 0 when attached *or* when the node opts out; negative on error.
+ */
+int en75_bmt_attach(struct mtd_info *mtd);
+void en75_bmt_detach(struct mtd_info *mtd);
+#else
+static inline int en75_bmt_attach(struct mtd_info *mtd) { return 0; }
+static inline void en75_bmt_detach(struct mtd_info *mtd) { }
+#endif
+
+#endif /* __EN75_BMT_H */
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 06/12] net: airoha-eth: add EN7528 Ethernet support
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (4 preceding siblings ...)
  2026-08-25 22:42   ` [PATCH v1 05/12] mtd: spi-nand: add EN75 BMT bad-block management support AK Sharma
@ 2026-08-25 22:42   ` AK Sharma
  2026-08-25 22:42   ` [PATCH v1 07/12] mtd: env, cmd: add EN75 NAND remap handling AK Sharma
                     ` (7 subsequent siblings)
  13 siblings, 0 replies; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:42 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Extend the Airoha GDM Ethernet driver to support the EN7528: the
gigabit switch (mt7530) ports, the GDMA/QDMA rings and the MAC init.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 drivers/net/Kconfig      |   8 +-
 drivers/net/airoha_eth.c | 850 ++++++++++++++++++++++++++++++++++++---
 drivers/phy/Kconfig      |   2 +-
 3 files changed, 808 insertions(+), 52 deletions(-)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 66661868..7bcc58ad 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -122,13 +122,13 @@ source "drivers/net/airoha/Kconfig"
 
 config AIROHA_ETH
 	bool "Airoha Ethernet QDMA Driver"
-	depends on ARCH_AIROHA
+	depends on ARCH_AIROHA || ARCH_EN75XX
 	select MISC
 	select PHYLIB
 	select DEVRES
-	select DM_ETH_PHY
-	select DM_RESET
-	select MDIO_MT7531_MMIO
+	select DM_ETH_PHY if ARCH_AIROHA
+	select DM_RESET if ARCH_AIROHA
+	select MDIO_MT7531_MMIO if ARCH_AIROHA
 	help
 	  This Driver support Airoha Ethernet QDMA Driver
 	  Say Y to enable support for the Airoha Ethernet QDMA.
diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c
index e5d39b95..640e8bb2 100644
--- a/drivers/net/airoha_eth.c
+++ b/drivers/net/airoha_eth.c
@@ -4,6 +4,7 @@
  * and simplified for U-Boot usage for single TX/RX ring.
  *
  * Copyright (c) 2024 AIROHA Inc
+ * EN751221 support based on econet_eth by Caleb James DeLisle.
  * Author: Lorenzo Bianconi <lorenzo@kernel.org>
  *         Christian Marangi <ansuelsmth@gmail.org>
  */
@@ -13,6 +14,7 @@
 #include <dm/devres.h>
 #include <dm/lists.h>
 #include <mapmem.h>
+#include <malloc.h>
 #include <miiphy.h>
 #include <net.h>
 #include <regmap.h>
@@ -24,16 +26,27 @@
 #include <linux/ethtool.h>
 #include <linux/io.h>
 #include <linux/iopoll.h>
+#include <linux/mii.h>
 #include <linux/time.h>
+#if IS_ENABLED(CONFIG_ARCH_EN75XX)
+#include <asm/addrspace.h>	/* CKSEG1ADDR: uncached DMA-ring alias */
+#endif
+#if IS_ENABLED(CONFIG_ARCH_AIROHA)
 #include <asm/arch/scu-regmap.h>
+#endif
 
 #include "airoha/pcs-airoha.h"
 
 #define AIROHA_MAX_NUM_GDM_PORTS	4
 #define AIROHA_MAX_NUM_QDMA		1
-#define AIROHA_MAX_NUM_RSTS		3
+#define AIROHA_MAX_NUM_RSTS		4
 #define AIROHA_MAX_NUM_XSI_RSTS		4
 
+#define AIROHA_MAX_NUM_SWITCH_PORT	4
+#define AIROHA_MAX_PBUS_TRY		10
+#define AIROHA_PBUS_SLEEP		100
+#define AIROHA_PBUS_C22_MASK		0x800000
+
 #define AIROHA_MAX_PACKET_SIZE		2048
 #define AIROHA_NUM_TX_RING		1
 #define AIROHA_NUM_RX_RING		1
@@ -53,6 +66,8 @@
 #define   SWITCH_BC_FFP			GENMASK(31, 24)
 #define   SWITCH_UNM_FFP		GENMASK(23, 16)
 #define   SWITCH_UNU_FFP		GENMASK(15, 8)
+#define   SWITCH_CPU_EN			BIT(7)
+#define   SWITCH_CPU_PORT		GENMASK(6, 4)
 #define SWITCH_PMCR(_n)			0x3000 + ((_n) * 0x100)
 #define   SWITCH_IPG_CFG		GENMASK(19, 18)
 #define     SWITCH_IPG_CFG_NORMAL	FIELD_PREP(SWITCH_IPG_CFG, 0x0)
@@ -86,6 +101,19 @@
 #define   SWITCH_PHY_PRE_EN		BIT(15)
 #define   SWITCH_PHY_END_ADDR		GENMASK(12, 8)
 #define   SWITCH_PHY_ST_ADDR		GENMASK(4, 0)
+#define SWITCH_GEPHY_CONN_CFG		0x7c14
+#define   SWITCH_DPHY_CKIN_SEL		BIT(31)
+#define   SWITCH_PHY_CORE_REG_CLK_SEL	BIT(30)
+#define   SWITCH_ETHER_AFE_PWD		GENMASK(28, 24)
+#define SWITCH_PBUS_PHY_IAC		0x7c20
+#define   SWITCH_PBUS_PHY_START		BIT(31)
+#define   SWITCH_PBUS_PHY_CMD		BIT(30)
+#define   SWITCH_PBUS_PHY_CMD_READ	FIELD_PREP(SWITCH_PBUS_PHY_CMD, 0x0)
+#define   SWITCH_PBUS_PHY_CMD_WRITE	FIELD_PREP(SWITCH_PBUS_PHY_CMD, 0x1)
+#define   SWITCH_PBUS_PHY_PORTADDR	GENMASK(28, 24)
+#define   SWITCH_PBUS_PHY_REGADDR	GENMASK(23, 0)
+#define SWITCH_PBUS_PHY_IAWD		0x7c24
+#define SWITCH_PBUS_PHY_IARD		0x7c28
 
 /* FE */
 #define PSE_BASE			0x0100
@@ -106,6 +134,9 @@
 	 (_n) == 2 ? GDM2_BASE : GDM1_BASE)
 
 #define REG_GDM_FWD_CFG(_n)		GDM_BASE(_n)
+#define REG_GDM_MAC_LSB(_n)		(GDM_BASE(_n) + 0x08)
+#define REG_GDM_MAC_MSB(_n)		(GDM_BASE(_n) + 0x0c)
+#define REG_GDM_RX_LEN_THR(_n)		(GDM_BASE(_n) + 0x14)
 #define GDM_PAD_EN			BIT(28)
 #define GDM_DROP_CRC_ERR		BIT(23)
 #define GDM_IP4_CKSUM			BIT(22)
@@ -116,6 +147,74 @@
 #define GDM_MCFQ_MASK			GENMASK(7, 4)
 #define GDM_OCFQ_MASK			GENMASK(3, 0)
 
+/* EN7512/EN7521 GDM forwarding configuration */
+#define EN751221_GDM_DROP_OVERSIZE	BIT(25)	/* matches en7528 gdm_regs.h */
+#define EN751221_GDM_MYMAC_FPORT	GENMASK(15, 12)
+#define EN751221_GDM_BCAST_FPORT	GENMASK(11, 8)
+#define EN751221_GDM_MCAST_FPORT	GENMASK(7, 4)
+#define EN751221_GDM_DEFAULT_FPORT	GENMASK(3, 0)
+#define EN751221_GDM_OVERSIZE_LEN	GENMASK(31, 16)
+#define EN751221_GDM_RUNT_LEN		GENMASK(15, 0)
+
+/* EN7512/EN7521 QDMA register layout */
+#define EN751221_REG_TX_RING_BASE	0x0008
+#define EN751221_REG_RX_RING_BASE	0x000c
+#define EN751221_REG_TX_CPU_IDX		0x0010
+#define EN751221_REG_TX_DMA_IDX		0x0014
+#define EN751221_REG_RX_CPU_IDX		0x0018
+#define EN751221_REG_RX_DMA_IDX		0x001c
+#define EN751221_REG_FWD_DSCP_BASE	0x0020
+#define EN751221_REG_FWD_BUF_BASE	0x0024
+#define EN751221_REG_FWD_DSCP_CFG	0x0028
+#define EN751221_REG_LMGR_INIT_CFG	0x0030
+#define EN751221_REG_INT_STATUS		0x0050
+#define EN751221_REG_INT_ENABLE		0x0054
+#define EN751221_REG_RX_DELAY_INT	0x005c
+#define EN751221_REG_IRQ_BASE		0x0060
+#define EN751221_REG_IRQ_CFG		0x0064
+#define EN751221_REG_IRQ_CLEAR_LEN	0x0068
+#define EN751221_REG_IRQ_STATUS		0x006c
+#define EN751221_REG_RX_RING_SIZE	0x0100
+#define EN751221_REG_RX_RING_THR		0x0104
+
+#define EN751221_RING_IDX_MASK		GENMASK(11, 0)
+#define EN751221_IRQ_DEPTH_MASK		GENMASK(11, 0)
+#define EN751221_IRQ_THRESHOLD_MASK	GENMASK(27, 16)
+#define EN751221_IRQ_HEAD_IDX_MASK	GENMASK(11, 0)
+#define EN751221_IRQ_ENTRY_LEN_MASK	GENMASK(27, 16)
+#define EN751221_IRQ_CLEAR_LEN_MASK	GENMASK(7, 0)
+#define EN751221_HWFWD_LOW_THR_MASK	GENMASK(12, 0)
+#define EN751221_HWFWD_DESC_NUM_MASK	GENMASK(15, 0)
+#define EN751221_HWFWD_OVERHEAD_MASK	GENMASK(23, 16)
+#define EN751221_HWFWD_OVERHEAD_EN	BIT(24)		/* en7528: stock sets this */
+#define EN751221_GLOBAL_CFG_MSG_WORD_SWAP BIT(28)
+
+#define EN751221_TXMSG_CHANNEL_MASK	GENMASK(10, 3)
+#define EN751221_TXMSG_QUEUE_MASK	GENMASK(2, 0)
+#define EN751221_TXMSG_FPORT_MASK	GENMASK(21, 19)
+#define EN751221_FPORT_GDM1		1
+#define EN751221_HW_DSCP_NUM		256	/* en7528 stock LMGR pool (was 4) */
+#define EN751221_IRQ_QUEUE_LEN		32
+#define EN751221_IRQ_THRESHOLD		4
+/*
+ * SCU reset banks (base 0x1fb00000): kernel clk-en7523.c en751221_rst_ofs[] =
+ * { bank0 = REG_RST_CTRL2 = 0x830, bank1 = REG_RST_CTRL1 = 0x834 }, and
+ * en751221_rst_map[] places the frame-engine domains as (RST_NR_PER_BANK=32):
+ *   FE_QDMA1 "qdma0" = 32+1 -> bank1 bit1     FE_QDMA2 "qdma1" = 32+2 -> bank1 bit2
+ *   FE_RST   "fe"    = 32+21-> bank1 bit21     XPON_MAC = 32+31 -> bank1 bit31
+ *   XPON_PHY "xpon-phy" = 0 -> bank0 bit0
+ * The stock econet-eth eth node resets ALL FIVE as one array pulse.
+ */
+#define EN751221_RESET_CONTROL_B1	0xbfb00834	/* SCU reset bank1 (REG_RST_CTRL1) */
+#define EN751221_RESET_CONTROL_B0	0xbfb00830	/* SCU reset bank0 (REG_RST_CTRL2) */
+#define EN751221_RESET_CONTROL2		EN751221_RESET_CONTROL_B1  /* back-compat alias */
+#define EN751221_FE_SRAM_SEL		0xbfb00958
+#define EN751221_RST_QDMA0		BIT(1)		/* bank1: FE_QDMA1 "qdma0" */
+#define EN751221_RST_QDMA1		BIT(2)		/* bank1: FE_QDMA2 "qdma1" (was missing) */
+#define EN751221_RST_FE			BIT(21)		/* bank1: FE_RST */
+#define EN751221_RST_XPON_MAC		BIT(31)		/* bank1: XPON_MAC (was missing) */
+#define EN751221_RST_XPON_PHY		BIT(0)		/* bank0: XPON_PHY (was missing) */
+
 /* QDMA */
 #define REG_QDMA_GLOBAL_CFG			0x0004
 #define GLOBAL_CFG_RX_2B_OFFSET_MASK		BIT(31)
@@ -290,11 +389,24 @@ struct airoha_qdma_fwd_desc {
 	__le32 rsv1;
 };
 
+struct en751221_qdma_fwd_desc {
+	u32 addr;
+	u32 info;
+	u32 msg0;
+	u32 msg1;
+};
+
 struct airoha_queue {
 	struct airoha_qdma_desc *desc;
 	u16 head;
 
 	int ndesc;
+
+	/* Uncached TX bounce buffer: the same broken-flush issue means a CPU-
+	 * built packet in cached memory is not guaranteed to be in DRAM when the
+	 * QDMA reads it, so copy each TX frame through this uncached buffer. */
+	void *bounce;
+	unsigned long bounce_phys;
 };
 
 struct airoha_tx_irq_queue {
@@ -347,6 +459,7 @@ struct airoha_eth {
 
 struct airoha_eth_soc_data {
 	u32 version;
+	bool econet;
 	int num_xsi_rsts;
 	const char * const *xsi_rsts_names;
 	const char *switch_compatible;
@@ -365,6 +478,12 @@ static const char * const en7581_xsi_rsts_names[] = {
 	"xfp-mac",
 };
 
+static const char * const an7583_xsi_rsts_names[] = {
+	"hsi0-mac",
+	"hsi1-mac",
+	"xfp-mac",
+};
+
 static u32 airoha_rr(void __iomem *base, u32 offset)
 {
 	return readl(base + offset);
@@ -405,8 +524,12 @@ static u32 airoha_rmw(void __iomem *base, u32 offset, u32 mask, u32 val)
 #define airoha_qdma_clear(qdma, offset, val)			\
 	airoha_rmw((qdma)->regs, (offset), (val), 0)
 
+#define airoha_switch_rr(eth, offset)				\
+	airoha_rr((eth)->switch_regs, (offset))
 #define airoha_switch_wr(eth, offset, val)			\
 	airoha_wr((eth)->switch_regs, (offset), (val))
+#define airoha_switch_rmw(eth, offset, mask, val)		\
+	airoha_rmw((eth)->switch_regs, (offset), (mask), (val))
 
 static inline dma_addr_t dma_map_unaligned(void *vaddr, size_t len,
 					   enum dma_data_direction dir)
@@ -429,12 +552,41 @@ static inline void dma_unmap_unaligned(dma_addr_t addr, size_t len,
 	dma_unmap_single(start, end - start, dir);
 }
 
+static void *airoha_dma_alloc_coherent(size_t size, unsigned long *dma_addr)
+{
+#if IS_ENABLED(CONFIG_ARCH_EN75XX)
+	void *buf;
+
+	size = ALIGN(size, ARCH_DMA_MINALIGN);
+	buf = memalign(ARCH_DMA_MINALIGN, size);
+	if (!buf)
+		return NULL;
+
+	*dma_addr = virt_to_phys(buf);
+	/*
+	 * EN7528 CM L2 has no Hit_Writeback_Inv_SD (op 0x17 hangs). DMA-shared
+	 * rings must therefore live in KSEG1 so CPU stores reach DRAM without
+	 * an L2 writeback. QDMA then sees descriptors by construction.
+	 */
+	return (void *)CKSEG1ADDR((unsigned long)buf);
+#else
+	return dma_alloc_coherent(size, dma_addr);
+#endif
+}
+
+static bool airoha_is_en751221(struct airoha_eth *eth)
+{
+	return eth->soc->econet;
+}
+
 static int airoha_get_fe_port(struct airoha_gdm_port *port)
 {
 	struct airoha_qdma *qdma = port->qdma;
 	struct airoha_eth *eth = qdma->eth;
 
 	switch (eth->soc->version) {
+	case 0x7512:
+		return EN751221_FPORT_GDM1;
 	case 0x7523:
 		/* FIXME: GDM1 is the only supported port */
 		return FE_PSE_PORT_GDM1;
@@ -446,6 +598,32 @@ static int airoha_get_fe_port(struct airoha_gdm_port *port)
 
 static void airoha_fe_maccr_init(struct airoha_gdm_port *port)
 {
+	if (airoha_is_en751221(port->qdma->eth)) {
+		u32 fwd_cfg, len_thr;
+
+		/*
+		 * QDMA0 CPU is fport zero: received frames go to the CPU. The
+		 * stock en7528 econet-eth driver sets exactly this (all fport
+		 * classes = CPU) plus drop_oversize. (BIT25 DROP_OVERSIZE is the
+		 * correct en7528 bit per gdm_regs.h — the earlier bits[31:30]
+		 * "forward-enable" guess was wrong: those are VIP/L2LU bits.)
+		 */
+		fwd_cfg = EN751221_GDM_DROP_OVERSIZE |
+			  FIELD_PREP(EN751221_GDM_MYMAC_FPORT, 0) |
+			  FIELD_PREP(EN751221_GDM_BCAST_FPORT, 0) |
+			  FIELD_PREP(EN751221_GDM_MCAST_FPORT, 0) |
+			  FIELD_PREP(EN751221_GDM_DEFAULT_FPORT, 0);
+		airoha_fe_wr(port->qdma->eth, REG_GDM_FWD_CFG(port->id),
+			     fwd_cfg);
+
+		len_thr = FIELD_PREP(EN751221_GDM_OVERSIZE_LEN,
+				     PKTSIZE_ALIGN) |
+			  FIELD_PREP(EN751221_GDM_RUNT_LEN, 60);
+		airoha_fe_wr(port->qdma->eth, REG_GDM_RX_LEN_THR(port->id),
+			     len_thr);
+		return;
+	}
+
 	/*
 	 * Disable any kind of CRC drop or offload.
 	 * Enable padding of short TX packets to 60 bytes.
@@ -460,6 +638,214 @@ static int airoha_fe_init(struct airoha_gdm_port *port)
 	return 0;
 }
 
+static void en751221_qdma_reset_rx_desc(struct airoha_queue *q, int index)
+{
+	struct airoha_qdma_desc *desc = &q->desc[index];
+	uchar *rx_packet = net_rx_packets[index];
+	u32 next;
+
+	dma_map_single(rx_packet, PKTSIZE_ALIGN, DMA_FROM_DEVICE);
+	next = (index + 1) % q->ndesc;
+
+	WRITE_ONCE(desc->rsv, 0);
+	WRITE_ONCE(desc->ctrl,
+		   FIELD_PREP(QDMA_DESC_LEN_MASK, PKTSIZE_ALIGN));
+	WRITE_ONCE(desc->addr, virt_to_phys(rx_packet));
+	WRITE_ONCE(desc->data, next);
+	WRITE_ONCE(desc->msg0, 0);
+	WRITE_ONCE(desc->msg1, 0);
+	WRITE_ONCE(desc->msg2, 0);
+	WRITE_ONCE(desc->msg3, 0);
+
+	dma_map_unaligned(desc, sizeof(*desc), DMA_TO_DEVICE);
+}
+
+static void en751221_qdma_init_rx_desc(struct airoha_qdma *qdma)
+{
+	struct airoha_queue *q = &qdma->q_rx[0];
+	int i;
+
+	for (i = 0; i < q->ndesc; i++)
+		en751221_qdma_reset_rx_desc(q, i);
+
+	/* On EN751221, equal CPU/HW indices mean that the RX ring is full. */
+	q->head = 0;
+	airoha_qdma_wr(qdma, EN751221_REG_RX_CPU_IDX, 0);
+	airoha_qdma_wr(qdma, EN751221_REG_RX_DMA_IDX, 1);
+}
+
+static int en751221_qdma_init_rx(struct airoha_qdma *qdma)
+{
+	struct airoha_queue *q = &qdma->q_rx[0];
+	unsigned long dma_addr;
+
+	q->ndesc = RX_DSCP_NUM;
+	q->desc = airoha_dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
+	if (!q->desc)
+		return -ENOMEM;
+
+	memset(q->desc, 0, q->ndesc * sizeof(*q->desc));
+	airoha_qdma_wr(qdma, EN751221_REG_RX_RING_BASE, dma_addr);
+	airoha_qdma_wr(qdma, EN751221_REG_RX_RING_SIZE, q->ndesc);
+	airoha_qdma_wr(qdma, EN751221_REG_RX_RING_THR, 0);
+
+	en751221_qdma_init_rx_desc(qdma);
+	return 0;
+}
+
+static int en751221_qdma_init_tx(struct airoha_qdma *qdma)
+{
+	struct airoha_queue *q = &qdma->q_tx[0];
+	unsigned long dma_addr;
+
+	q->ndesc = TX_DSCP_NUM;
+	q->head = 0;
+	q->desc = airoha_dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
+	if (!q->desc)
+		return -ENOMEM;
+
+	memset(q->desc, 0, q->ndesc * sizeof(*q->desc));
+	airoha_qdma_wr(qdma, EN751221_REG_TX_RING_BASE, dma_addr);
+	airoha_qdma_wr(qdma, EN751221_REG_TX_CPU_IDX, 0);
+	airoha_qdma_wr(qdma, EN751221_REG_TX_DMA_IDX, 0);
+
+	q->bounce = airoha_dma_alloc_coherent(AIROHA_MAX_PACKET_SIZE,
+					      &q->bounce_phys);
+	if (!q->bounce)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int en751221_qdma_init_tx_irq(struct airoha_qdma *qdma)
+{
+	struct airoha_tx_irq_queue *irq_q = &qdma->q_tx_irq[0];
+	unsigned long dma_addr;
+	u32 cfg;
+
+	irq_q->size = EN751221_IRQ_QUEUE_LEN;
+	irq_q->q = airoha_dma_alloc_coherent(irq_q->size * sizeof(u32),
+					     &dma_addr);
+	if (!irq_q->q)
+		return -ENOMEM;
+
+	irq_q->qdma = qdma;
+	memset(irq_q->q, 0xff, irq_q->size * sizeof(u32));
+	dma_map_single(irq_q->q, irq_q->size * sizeof(u32), DMA_TO_DEVICE);
+
+	airoha_qdma_wr(qdma, EN751221_REG_IRQ_BASE, dma_addr);
+	cfg = FIELD_PREP(EN751221_IRQ_THRESHOLD_MASK,
+			 EN751221_IRQ_THRESHOLD) |
+	      FIELD_PREP(EN751221_IRQ_DEPTH_MASK, irq_q->size);
+	airoha_qdma_wr(qdma, EN751221_REG_IRQ_CFG, cfg);
+
+	return 0;
+}
+
+static void en751221_qdma_reset_tx(struct airoha_qdma *qdma)
+{
+	struct airoha_queue *q = &qdma->q_tx[0];
+
+	memset(q->desc, 0, q->ndesc * sizeof(*q->desc));
+	dma_map_single(q->desc, q->ndesc * sizeof(*q->desc), DMA_TO_DEVICE);
+	q->head = 0;
+	airoha_qdma_wr(qdma, EN751221_REG_TX_CPU_IDX, 0);
+	airoha_qdma_wr(qdma, EN751221_REG_TX_DMA_IDX, 0);
+}
+
+static int en751221_qdma_init_hfwd(struct airoha_qdma *qdma)
+{
+	unsigned long dma_addr;
+	u32 status, val;
+	int size;
+
+	size = EN751221_HW_DSCP_NUM * sizeof(struct en751221_qdma_fwd_desc);
+	qdma->hfwd.desc = airoha_dma_alloc_coherent(size, &dma_addr);
+	if (!qdma->hfwd.desc)
+		return -ENOMEM;
+	memset(qdma->hfwd.desc, 0, size);
+	dma_map_single(qdma->hfwd.desc, size, DMA_TO_DEVICE);
+	airoha_qdma_wr(qdma, EN751221_REG_FWD_DSCP_BASE, dma_addr);
+
+	size = AIROHA_MAX_PACKET_SIZE * EN751221_HW_DSCP_NUM;
+	qdma->hfwd.q = airoha_dma_alloc_coherent(size, &dma_addr);
+	if (!qdma->hfwd.q)
+		return -ENOMEM;
+	memset(qdma->hfwd.q, 0, size);
+	dma_map_single(qdma->hfwd.q, size, DMA_TO_DEVICE);
+	airoha_qdma_wr(qdma, EN751221_REG_FWD_BUF_BASE, dma_addr);
+
+	/* en7528 stock: low_thr = 32 (hwf_cfg = 0x20), not 1. */
+	val = FIELD_PREP(HW_FWD_DSCP_PAYLOAD_SIZE_MASK, 0) |
+	      FIELD_PREP(EN751221_HWFWD_LOW_THR_MASK, 32);
+	airoha_qdma_wr(qdma, EN751221_REG_FWD_DSCP_CFG, val);
+
+	/*
+	 * en7528 stock LMGR init = 0x01140100 (OVERHEAD_EN + overhead 0x14 +
+	 * 256 descriptors). The borrowed value omitted OVERHEAD_EN and used
+	 * only 4 descriptors, starving the PSE store-and-forward pool so
+	 * CPU-injected frames were dropped after QDMA writeback DONE.
+	 */
+	val = LMGR_INIT_START |
+	      EN751221_HWFWD_OVERHEAD_EN |
+	      FIELD_PREP(EN751221_HWFWD_OVERHEAD_MASK, 0x14) |
+	      FIELD_PREP(EN751221_HWFWD_DESC_NUM_MASK,
+			 EN751221_HW_DSCP_NUM);
+	airoha_qdma_wr(qdma, EN751221_REG_LMGR_INIT_CFG, val);
+
+	return read_poll_timeout(airoha_qdma_rr, status,
+				 !(status & LMGR_INIT_START), USEC_PER_MSEC,
+				 30 * USEC_PER_MSEC, qdma,
+				 EN751221_REG_LMGR_INIT_CFG);
+}
+
+static int en751221_qdma_init(struct airoha_qdma *qdma)
+{
+	u32 cfg;
+	int ret;
+
+	/* Stop the engine before replacing all descriptor rings. */
+	airoha_qdma_wr(qdma, REG_QDMA_GLOBAL_CFG, 0);
+	airoha_qdma_wr(qdma, EN751221_REG_INT_ENABLE, 0);
+	airoha_qdma_wr(qdma, EN751221_REG_INT_STATUS, 0xffffffff);
+
+	ret = en751221_qdma_init_rx(qdma);
+	if (ret)
+		return ret;
+
+	ret = en751221_qdma_init_tx(qdma);
+	if (ret)
+		return ret;
+
+	ret = en751221_qdma_init_tx_irq(qdma);
+	if (ret)
+		return ret;
+
+	ret = en751221_qdma_init_hfwd(qdma);
+	if (ret)
+		return ret;
+
+	/*
+	 * EN7528: do NOT set GLOBAL_CFG_DSCP_BYTE_SWAP. The stock en7528
+	 * econet-eth driver programs qdma_cfg = 0x140800f5 (msg_word_swap +
+	 * payload_byte_swap, dscp_byte_swap = soc->dscp_byte_swap = false).
+	 * The mainline-airoha value this port borrowed set dscp_byte_swap,
+	 * byte-swapping the TX descriptor the QDMA reads (addr/msg/FPORT) so
+	 * the frame was routed nowhere while still reporting DONE. Matching
+	 * the stock value restores CPU->GDM1->switch egress.
+	 */
+	cfg = EN751221_GLOBAL_CFG_MSG_WORD_SWAP |
+	      GLOBAL_CFG_PAYLOAD_BYTE_SWAP_MASK |
+	      GLOBAL_CFG_IRQ0_EN_MASK |
+	      GLOBAL_CFG_CHECK_DONE_MASK |
+	      GLOBAL_CFG_TX_WB_DONE_MASK |
+	      FIELD_PREP(GLOBAL_CFG_MAX_ISSUE_NUM_MASK, 3);
+	airoha_qdma_wr(qdma, REG_QDMA_GLOBAL_CFG, cfg);
+	airoha_qdma_wr(qdma, EN751221_REG_RX_DELAY_INT, 0);
+
+	return 0;
+}
+
 static void airoha_qdma_reset_rx_desc(struct airoha_queue *q, int index)
 {
 	struct airoha_qdma_desc *desc;
@@ -501,7 +887,7 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
 	q->ndesc = ndesc;
 	q->head = 0;
 
-	q->desc = dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
+	q->desc = airoha_dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
 	if (!q->desc)
 		return -ENOMEM;
 
@@ -548,7 +934,7 @@ static int airoha_qdma_init_tx_queue(struct airoha_queue *q,
 	q->ndesc = size;
 	q->head = 0;
 
-	q->desc = dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
+	q->desc = airoha_dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
 	if (!q->desc)
 		return -ENOMEM;
 
@@ -570,7 +956,7 @@ static int airoha_qdma_tx_irq_init(struct airoha_tx_irq_queue *irq_q,
 	int id = irq_q - &qdma->q_tx_irq[0];
 	unsigned long dma_addr;
 
-	irq_q->q = dma_alloc_coherent(size * sizeof(u32), &dma_addr);
+	irq_q->q = airoha_dma_alloc_coherent(size * sizeof(u32), &dma_addr);
 	if (!irq_q->q)
 		return -ENOMEM;
 
@@ -615,7 +1001,7 @@ static int airoha_qdma_init_hfwd_queues(struct airoha_qdma *qdma)
 	int size;
 
 	size = HW_DSCP_NUM * sizeof(struct airoha_qdma_fwd_desc);
-	qdma->hfwd.desc = dma_alloc_coherent(size, &dma_addr);
+	qdma->hfwd.desc = airoha_dma_alloc_coherent(size, &dma_addr);
 	if (!qdma->hfwd.desc)
 		return -ENOMEM;
 
@@ -625,7 +1011,7 @@ static int airoha_qdma_init_hfwd_queues(struct airoha_qdma *qdma)
 	airoha_qdma_wr(qdma, REG_FWD_DSCP_BASE, dma_addr);
 
 	size = AIROHA_MAX_PACKET_SIZE * HW_DSCP_NUM;
-	qdma->hfwd.q = dma_alloc_coherent(size, &dma_addr);
+	qdma->hfwd.q = airoha_dma_alloc_coherent(size, &dma_addr);
 	if (!qdma->hfwd.q)
 		return -ENOMEM;
 
@@ -689,6 +1075,8 @@ static int airoha_qdma_init(struct udevice *dev,
 	qdma->regs = dev_remap_addr_name(dev, "qdma0");
 	if (IS_ERR(qdma->regs))
 		return PTR_ERR(qdma->regs);
+	if (airoha_is_en751221(eth))
+		return en751221_qdma_init(qdma);
 
 	err = airoha_qdma_init_rx(qdma);
 	if (err)
@@ -735,11 +1123,51 @@ static int airoha_pcs_init(struct udevice *dev)
 }
 #endif
 
+static int en751221_hw_init(struct udevice *dev, struct airoha_eth *eth)
+{
+	u32 val, val2;
+	int ret;
+
+	/* Expose the frame-engine register window instead of the FE SRAM. */
+	__raw_writel(0, (void __iomem *)EN751221_FE_SRAM_SEL);
+
+	/*
+	 * Reset the SAME FIVE frame-engine domains the stock econet-eth DT eth
+	 * node resets as one array pulse: {fe, qdma0=FE_QDMA1, qdma1=FE_QDMA2,
+	 * xpon-mac, xpon-phy}. The earlier port reset only qdma0(bit1)+fe(bit21)
+	 * and left FE_QDMA2 (bank1 bit2) — the second FE-DMA domain that gates
+	 * the QDMA<->PSE store-and-forward datapath shared by both QDMAs — in its
+	 * bootbase state. That back-pressures qdma0's drain: the first CPU-TX
+	 * latches TX_DMA_BUSY (qdma_cfg .f2) but the frame never reaches
+	 * GDM1/egress and tx_hwi never advances. Assert all five, hold, deassert.
+	 */
+	val = __raw_readl((void __iomem *)EN751221_RESET_CONTROL_B1);
+	val2 = __raw_readl((void __iomem *)EN751221_RESET_CONTROL_B0);
+	__raw_writel(val | EN751221_RST_QDMA0 | EN751221_RST_QDMA1 |
+		     EN751221_RST_FE | EN751221_RST_XPON_MAC,
+		     (void __iomem *)EN751221_RESET_CONTROL_B1);
+	__raw_writel(val2 | EN751221_RST_XPON_PHY,
+		     (void __iomem *)EN751221_RESET_CONTROL_B0);
+	mdelay(20);
+	__raw_writel(val, (void __iomem *)EN751221_RESET_CONTROL_B1);
+	__raw_writel(val2, (void __iomem *)EN751221_RESET_CONTROL_B0);
+	mdelay(20);
+
+	ret = airoha_qdma_init(dev, eth, &eth->qdma[0]);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
 static int airoha_hw_init(struct udevice *dev,
 			  struct airoha_eth *eth)
 {
 	int ret, i;
 
+	if (airoha_is_en751221(eth))
+		return en751221_hw_init(dev, eth);
+
 	/* disable xsi */
 	ret = reset_assert_bulk(&eth->xsi_rsts);
 	if (ret)
@@ -787,6 +1215,86 @@ static int airoha_switch_init(struct udevice *dev, struct airoha_eth *eth)
 
 	/* Switch doesn't have a DEV, gets address and setup Flood and CPU port */
 	eth->switch_regs = map_sysmem(addr, 0);
+	if (data->econet) {
+		u32 pmcr;
+
+		/*
+		 * Soft-reset the integrated MT7530 switch core to a known state
+		 * before (re)configuring it, mirroring mt7531_setup(): the stock
+		 * bootbase leaves it in its own config. SYS_CTRL @0x7000, bits
+		 * SW_SYS_RST|SW_REG_RST self-clear when the reset completes.
+		 */
+		airoha_switch_wr(eth, 0x7000, 0x3);
+		mdelay(100);
+
+		/* Integrated MT7530: flood unknown traffic to all ports and use P6. */
+		airoha_switch_wr(eth, SWITCH_MFC,
+				 SWITCH_BC_FFP | SWITCH_UNM_FFP | SWITCH_UNU_FFP |
+				 SWITCH_CPU_EN | FIELD_PREP(SWITCH_CPU_PORT, 6));
+
+		pmcr = SWITCH_MAC_MODE | SWITCH_FORCE_MODE | SWITCH_MAC_TX_EN |
+		       SWITCH_MAC_RX_EN | SWITCH_BKOFF_EN | SWITCH_BKPR_EN |
+		       SWITCH_FORCE_SPD_1000 | SWITCH_FORCE_DPX |
+		       SWITCH_FORCE_LNK;
+
+		/*
+		 * P5 is the TRGMII cascade to the external MCM switch while P6
+		 * is the CPU port.  The EN751221 OEM setup uses shrink IPG only
+		 * on the cascade and short IPG on the CPU link.
+		 */
+		airoha_switch_wr(eth, SWITCH_PMCR(5),
+				 pmcr | SWITCH_IPG_CFG_SHRINK);
+		airoha_switch_wr(eth, SWITCH_PMCR(6),
+				 pmcr | SWITCH_IPG_CFG_SHORT);
+		airoha_switch_wr(eth, SWITCH_PHY_POLL, 0x7f7f8c08);
+
+		/*
+		 * EN7528 LAN ports 1-4 (internal GPHYs at MDIO 9-12): enable the
+		 * MAC but DO NOT force the link. Leaving FORCE_MODE clear lets the
+		 * switch PHY-poll (SWITCH_PHY_POLL, set just above) drive each port
+		 * MAC to the speed/duplex the GPHY actually negotiates with its
+		 * peer. Force-linking these at 1G silently breaks egress to a
+		 * 100M/10M peer: the MAC<->GPHY line rate mismatches, so a CPU-TX
+		 * frame leaves GDM1 (GDM1 TX MIB increments) but never reaches the
+		 * RJ45. Proven on HW with a 100M host — forced 1G => 0 egress, auto
+		 * => ping/traffic works and PMSR reports the real 100M FDX link.
+		 * (Ports 5/6 keep the forced-1G `pmcr` above: those are the fixed
+		 * internal CPU/cascade links, not GPHY line ports.)
+		 */
+		{
+			u32 lan_pmcr = SWITCH_MAC_MODE | SWITCH_MAC_TX_EN |
+				       SWITCH_MAC_RX_EN | SWITCH_BKOFF_EN |
+				       SWITCH_BKPR_EN | SWITCH_IPG_CFG_NORMAL;
+			int p;
+
+			for (p = 1; p <= 4; p++)
+				airoha_switch_wr(eth, SWITCH_PMCR(p), lan_pmcr);
+		}
+
+		/*
+		 * EN7528: the integrated MT7530 needs its per-port forwarding
+		 * matrix programmed, or no frames pass between ports (the LAN
+		 * GPHYs at MDIO 9-12 are already powered and link fine; the
+		 * en7512 econet stub only set MFC/PMCR and never the matrix).
+		 * Give each user port (0-5) a matrix pointing at the CPU port
+		 * (6) and the CPU port a matrix of all user ports, and mark
+		 * every port a VLAN user port. Mirrors mt753x_port_isolation()
+		 * / mt7988_setup(): PCR = 0x2004 + p*0x100 (matrix at bit 16),
+		 * PVC = 0x2010 + p*0x100 (STAG 0x8100, VLAN_ATTR_USER).
+		 */
+		{
+			int p;
+
+			for (p = 0; p < 7; p++) {
+				airoha_switch_wr(eth, 0x2004 + p * 0x100,
+						 (u32)(p == 6 ? 0x3f : 0x40) << 16);
+				airoha_switch_wr(eth, 0x2010 + p * 0x100,
+						 0x81000000);
+			}
+		}
+
+		return 0;
+	}
 
 	/* Set FLOOD, no CPU switch register */
 	airoha_switch_wr(eth, SWITCH_MFC, SWITCH_BC_FFP | SWITCH_UNM_FFP |
@@ -809,6 +1317,59 @@ static int airoha_switch_init(struct udevice *dev, struct airoha_eth *eth)
 			 FIELD_PREP(SWITCH_PHY_END_ADDR, 0xc) |
 			 FIELD_PREP(SWITCH_PHY_ST_ADDR, 0x8));
 
+	/* AN7583 require tweak to GEPHY_CONN_CFG and clear PHY BMCR_PDOWN */
+	if (!strcmp(data->switch_compatible, "airoha,an7583-switch")) {
+		int i;
+
+		airoha_switch_rmw(eth, SWITCH_GEPHY_CONN_CFG,
+				  SWITCH_DPHY_CKIN_SEL |
+				  SWITCH_PHY_CORE_REG_CLK_SEL |
+				  SWITCH_ETHER_AFE_PWD,
+				  SWITCH_DPHY_CKIN_SEL |
+				  SWITCH_PHY_CORE_REG_CLK_SEL |
+				  FIELD_PREP(SWITCH_ETHER_AFE_PWD, 0));
+
+		/* Disable BMCR_PDOWN for every PHY */
+		for (i = 0; i < AIROHA_MAX_NUM_SWITCH_PORT; i++) {
+			int try;
+			u32 val;
+
+			airoha_switch_wr(eth, SWITCH_PBUS_PHY_IAC,
+					 SWITCH_PBUS_PHY_START |
+					 SWITCH_PBUS_PHY_CMD_READ |
+					 FIELD_PREP(SWITCH_PBUS_PHY_PORTADDR, i) |
+					 FIELD_PREP(SWITCH_PBUS_PHY_REGADDR,
+						    AIROHA_PBUS_C22_MASK | MII_BMCR));
+
+			for (try = 0; try < AIROHA_MAX_PBUS_TRY; try++) {
+				val = airoha_switch_rr(eth, SWITCH_PBUS_PHY_IAC);
+				if (!(val & SWITCH_PBUS_PHY_START))
+					break;
+
+				udelay(AIROHA_PBUS_SLEEP);
+			}
+
+			val = airoha_switch_rr(eth, SWITCH_PBUS_PHY_IARD);
+			val &= ~BMCR_PDOWN;
+
+			airoha_switch_wr(eth, SWITCH_PBUS_PHY_IAWD, val);
+			airoha_switch_wr(eth, SWITCH_PBUS_PHY_IAC,
+					 SWITCH_PBUS_PHY_START |
+					 SWITCH_PBUS_PHY_CMD_WRITE |
+					 FIELD_PREP(SWITCH_PBUS_PHY_PORTADDR, i) |
+					 FIELD_PREP(SWITCH_PBUS_PHY_REGADDR,
+						    AIROHA_PBUS_C22_MASK | MII_BMCR));
+
+			for (try = 0; try < AIROHA_MAX_PBUS_TRY; try++) {
+				val = airoha_switch_rr(eth, SWITCH_PBUS_PHY_IAC);
+				if (!(val & SWITCH_PBUS_PHY_START))
+					break;
+
+				udelay(AIROHA_PBUS_SLEEP);
+			}
+		}
+	}
+
 	return 0;
 }
 
@@ -828,6 +1389,8 @@ static int airoha_alloc_gdm_port(struct udevice *dev, ofnode node)
 	ret = ofnode_read_u32(node, "reg", &id);
 	if (ret)
 		return ret;
+	if (eth->soc->econet)
+		id++;
 
 	if (id > AIROHA_MAX_NUM_GDM_PORTS)
 		return -EINVAL;
@@ -882,56 +1445,65 @@ static int airoha_eth_probe(struct udevice *dev)
 {
 	struct airoha_eth_soc_data *data = (void *)dev_get_driver_data(dev);
 	struct airoha_eth *eth = dev_get_priv(dev);
-	struct regmap *scu_regmap;
 	struct udevice *mdio_dev;
 	ofnode node;
 	int i, ret;
 
-	scu_regmap = airoha_get_scu_regmap();
-	if (IS_ERR(scu_regmap))
-		return PTR_ERR(scu_regmap);
+	eth->soc = data;
+	if (!data->econet) {
+#if IS_ENABLED(CONFIG_ARCH_AIROHA)
+		struct regmap *scu_regmap;
 
-	/* It seems by default the FEMEM_SEL is set to Memory (0x1)
-	 * preventing any access to any QDMA and FrameEngine register
-	 * reporting all 0xdeadbeef (poor cow :( )
-	 */
-	regmap_write(scu_regmap, SCU_SHARE_FEMEM_SEL, 0x0);
+		scu_regmap = airoha_get_scu_regmap();
+		if (IS_ERR(scu_regmap))
+			return PTR_ERR(scu_regmap);
 
-	eth->soc = data;
+		/* FEMEM_SEL=1 hides QDMA/FE behind the shared SRAM window. */
+		regmap_write(scu_regmap, SCU_SHARE_FEMEM_SEL, 0x0);
+#else
+		return -EOPNOTSUPP;
+#endif
+	}
 
 	eth->fe_regs = dev_remap_addr_name(dev, "fe");
 	if (!eth->fe_regs)
 		return -ENOMEM;
 
-	eth->rsts.resets = devm_kcalloc(dev, AIROHA_MAX_NUM_RSTS,
-					sizeof(struct reset_ctl), GFP_KERNEL);
-	if (!eth->rsts.resets)
-		return -ENOMEM;
-	eth->rsts.count = AIROHA_MAX_NUM_RSTS;
+	if (!data->econet) {
+		eth->rsts.resets = devm_kcalloc(dev, AIROHA_MAX_NUM_RSTS,
+						sizeof(struct reset_ctl), GFP_KERNEL);
+		if (!eth->rsts.resets)
+			return -ENOMEM;
+		eth->rsts.count = AIROHA_MAX_NUM_RSTS;
 
-	eth->xsi_rsts.resets = devm_kcalloc(dev, data->num_xsi_rsts,
-					    sizeof(struct reset_ctl), GFP_KERNEL);
-	if (!eth->xsi_rsts.resets)
-		return -ENOMEM;
-	eth->xsi_rsts.count = data->num_xsi_rsts;
+		eth->xsi_rsts.resets = devm_kcalloc(dev, data->num_xsi_rsts,
+						    sizeof(struct reset_ctl), GFP_KERNEL);
+		if (!eth->xsi_rsts.resets)
+			return -ENOMEM;
+		eth->xsi_rsts.count = data->num_xsi_rsts;
 
-	ret = reset_get_by_name(dev, "fe", &eth->rsts.resets[0]);
-	if (ret)
-		return ret;
+		ret = reset_get_by_name(dev, "fe", &eth->rsts.resets[0]);
+		if (ret)
+			return ret;
 
-	ret = reset_get_by_name(dev, "pdma", &eth->rsts.resets[1]);
-	if (ret)
-		return ret;
+		ret = reset_get_by_name(dev, "pdma", &eth->rsts.resets[1]);
+		if (ret)
+			return ret;
 
-	ret = reset_get_by_name(dev, "qdma", &eth->rsts.resets[2]);
-	if (ret)
-		return ret;
+		ret = reset_get_by_name(dev, "qdma", &eth->rsts.resets[2]);
+		if (ret)
+			return ret;
 
-	for (i = 0; i < data->num_xsi_rsts; i++) {
-		ret = reset_get_by_name(dev, data->xsi_rsts_names[i],
-					&eth->xsi_rsts.resets[i]);
+		ret = reset_get_by_name(dev, "switch", &eth->rsts.resets[3]);
 		if (ret)
 			return ret;
+
+		for (i = 0; i < data->num_xsi_rsts; i++) {
+			ret = reset_get_by_name(dev, data->xsi_rsts_names[i],
+						&eth->xsi_rsts.resets[i]);
+			if (ret)
+				return ret;
+		}
 	}
 
 	ret = airoha_hw_init(dev, eth);
@@ -943,12 +1515,15 @@ static int airoha_eth_probe(struct udevice *dev)
 		return ret;
 
 	/* Airoha switch mdio PHYs maybe used by several GDM devices */
-	mdio_dev = airoha_switch_mdio_init(dev);
-	if (!IS_ERR_OR_NULL(mdio_dev))
-		eth->switch_mdio_dev = mdio_dev;
+	if (!data->econet) {
+		mdio_dev = airoha_switch_mdio_init(dev);
+		if (!IS_ERR_OR_NULL(mdio_dev))
+			eth->switch_mdio_dev = mdio_dev;
+	}
 
 	ofnode_for_each_subnode(node, dev_ofnode(dev)) {
-		if (!ofnode_device_is_compatible(node, "airoha,eth-mac"))
+		if (!ofnode_device_is_compatible(node, "airoha,eth-mac") &&
+		    !ofnode_device_is_compatible(node, "econet,eth-mac"))
 			continue;
 
 		if (!ofnode_is_enabled(node))
@@ -964,9 +1539,18 @@ static int airoha_eth_probe(struct udevice *dev)
 
 static int airoha_eth_port_of_to_plat(struct udevice *dev)
 {
+	struct airoha_eth *eth = (void *)dev_get_driver_data(dev);
 	struct airoha_gdm_port *port = dev_get_priv(dev);
+	u32 id;
+	int ret;
+
+	ret = dev_read_u32(dev, "reg", &id);
+	if (ret)
+		return ret;
+
+	port->id = id + (eth->soc->econet ? 1 : 0);
 
-	return dev_read_u32(dev, "reg", &port->id);
+	return 0;
 }
 
 static int airoha_eth_port_probe(struct udevice *dev)
@@ -992,6 +1576,9 @@ static int airoha_eth_port_probe(struct udevice *dev)
 		return -EINVAL;
 #endif
 	} else {
+		if (eth->soc->econet)
+			return 0;
+
 		/*
 		 * GDM1 device connected to airoha switch. Probe airoha switch
 		 * mdio to be able set/query states of corresponding LAN ports.
@@ -1016,7 +1603,12 @@ static int airoha_eth_init(struct udevice *dev)
 	qid = 0;
 	q = &qdma->q_rx[qid];
 
-	airoha_qdma_init_rx_desc(q);
+	if (airoha_is_en751221(qdma->eth))
+		en751221_qdma_init_rx_desc(qdma);
+	else
+		airoha_qdma_init_rx_desc(q);
+	if (airoha_is_en751221(qdma->eth))
+		en751221_qdma_reset_tx(qdma);
 
 	airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
 			GLOBAL_CFG_TX_DMA_EN_MASK |
@@ -1087,6 +1679,129 @@ static void airoha_eth_stop(struct udevice *dev)
 			  GLOBAL_CFG_RX_DMA_EN_MASK);
 }
 
+static int en751221_eth_send(struct udevice *dev, void *packet, int length)
+{
+	struct airoha_gdm_port *port = dev_get_priv(dev);
+	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_queue *q = &qdma->q_tx[0];
+	struct airoha_qdma_desc *desc;
+	u32 index, next, ctrl = 0, irq_status = 0;
+	int i;
+
+	index = q->head;
+	next = (index + 1) % q->ndesc;
+	desc = &q->desc[index];
+
+	/*
+	 * Stage the frame in the KSEG1 bounce buffer (see
+	 * airoha_dma_alloc_coherent). desc and bounce are uncached, so the
+	 * writes land in DRAM; no map/flush.
+	 */
+	memcpy(q->bounce, packet, length);
+
+	WRITE_ONCE(desc->rsv, 0);
+	WRITE_ONCE(desc->ctrl, FIELD_PREP(QDMA_DESC_LEN_MASK, length));
+	WRITE_ONCE(desc->addr, q->bounce_phys);
+	WRITE_ONCE(desc->data, next);
+	WRITE_ONCE(desc->msg0,
+		   FIELD_PREP(EN751221_TXMSG_CHANNEL_MASK, 0) |
+		   FIELD_PREP(EN751221_TXMSG_QUEUE_MASK, 0));
+	WRITE_ONCE(desc->msg1,
+		   FIELD_PREP(EN751221_TXMSG_FPORT_MASK,
+			      EN751221_FPORT_GDM1));
+	WRITE_ONCE(desc->msg2, 0);
+	WRITE_ONCE(desc->msg3, 0);
+
+	airoha_qdma_wr(qdma, EN751221_REG_TX_CPU_IDX, next);
+
+	for (i = 0; i < 10000; i++) {
+		ctrl = READ_ONCE(desc->ctrl);
+		if (ctrl & QDMA_DESC_DONE_MASK)
+			break;
+		udelay(1);
+	}
+
+	if (!(ctrl & QDMA_DESC_DONE_MASK))
+		return -ETIMEDOUT;
+
+	for (i = 0; i < 10000; i++) {
+		irq_status = airoha_qdma_rr(qdma, EN751221_REG_IRQ_STATUS);
+		if (FIELD_GET(EN751221_IRQ_ENTRY_LEN_MASK, irq_status))
+			break;
+		udelay(1);
+	}
+	if (!FIELD_GET(EN751221_IRQ_ENTRY_LEN_MASK, irq_status))
+		return -ETIMEDOUT;
+
+	/*
+	 * QDMA consumes one hardware-forward descriptor for each CPU TX
+	 * context.  Advancing the completion queue returns that descriptor
+	 * to LMGR; without this, the four-entry first-light pool is exhausted
+	 * after four packets even though TX writeback reports DONE.
+	 */
+	airoha_qdma_rmw(qdma, EN751221_REG_IRQ_CLEAR_LEN,
+			EN751221_IRQ_CLEAR_LEN_MASK, 1);
+
+	q->head = next;
+	return 0;
+}
+
+static int en751221_eth_recv(struct udevice *dev, uchar **packetp)
+{
+	struct airoha_gdm_port *port = dev_get_priv(dev);
+	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_queue *q = &qdma->q_rx[0];
+	struct airoha_qdma_desc *desc;
+	uchar *rx_packet;
+	u32 index, hw_index, length, addr;
+
+	index = (q->head + 1) % q->ndesc;
+	hw_index = airoha_qdma_rr(qdma, EN751221_REG_RX_DMA_IDX) &
+		   EN751221_RING_IDX_MASK;
+	if (index == hw_index)
+		return -EAGAIN;
+
+	desc = &q->desc[index];
+	dma_unmap_unaligned((dma_addr_t)desc, sizeof(*desc),
+			    DMA_FROM_DEVICE);
+	length = FIELD_GET(QDMA_DESC_LEN_MASK, READ_ONCE(desc->ctrl));
+	addr = READ_ONCE(desc->addr);
+	if (!length || length > PKTSIZE_ALIGN) {
+		en751221_qdma_reset_rx_desc(q, index);
+		airoha_qdma_wr(qdma, EN751221_REG_RX_CPU_IDX, index);
+		q->head = index;
+		return -EIO;
+	}
+
+	/*
+	 * Hand U-Boot the KSEG1 alias of the received frame. QDMA wrote DRAM
+	 * directly; a cached read can still hit a stale L2 line because this
+	 * CM cannot Hit_Writeback_Inv_SD (see airoha_dma_alloc_coherent).
+	 */
+	rx_packet = (uchar *)CKSEG1ADDR((unsigned long)addr);
+	*packetp = rx_packet;
+
+	return length;
+}
+
+static int en751221_eth_free_pkt(struct udevice *dev, uchar *packet)
+{
+	struct airoha_gdm_port *port = dev_get_priv(dev);
+	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_queue *q = &qdma->q_rx[0];
+	u32 index;
+
+	if (!packet)
+		return 0;
+
+	index = (q->head + 1) % q->ndesc;
+	en751221_qdma_reset_rx_desc(q, index);
+	airoha_qdma_wr(qdma, EN751221_REG_RX_CPU_IDX, index);
+	q->head = index;
+
+	return 0;
+}
+
 static int airoha_eth_send(struct udevice *dev, void *packet, int length)
 {
 	struct airoha_gdm_port *port = dev_get_priv(dev);
@@ -1100,6 +1815,9 @@ static int airoha_eth_send(struct udevice *dev, void *packet, int length)
 	u32 val;
 	int i;
 
+	if (airoha_is_en751221(qdma->eth))
+		return en751221_eth_send(dev, packet, length);
+
 	/*
 	 * There is no need to pad short TX packets to 60 bytes since the
 	 * GDM_PAD_EN bit set in the corresponding REG_GDM_FWD_CFG(n) register.
@@ -1161,6 +1879,9 @@ static int airoha_eth_recv(struct udevice *dev, int flags, uchar **packetp)
 	u16 length;
 	int qid;
 
+	if (airoha_is_en751221(qdma->eth))
+		return en751221_eth_recv(dev, packetp);
+
 	qid = 0;
 	q = &qdma->q_rx[qid];
 	desc = &q->desc[q->head];
@@ -1187,6 +1908,9 @@ static int arht_eth_free_pkt(struct udevice *dev, uchar *packet, int length)
 	struct airoha_queue *q;
 	int qid;
 
+	if (airoha_is_en751221(qdma->eth))
+		return en751221_eth_free_pkt(dev, packet);
+
 	if (!packet)
 		return 0;
 
@@ -1237,6 +1961,11 @@ static int arht_eth_write_hwaddr(struct udevice *dev)
 	macaddr_msb = FIELD_PREP(SMACCR1_MAC1, mac[1]) |
 		      FIELD_PREP(SMACCR1_MAC0, mac[0]);
 
+	if (airoha_is_en751221(qdma->eth)) {
+		airoha_fe_wr(qdma->eth, REG_GDM_MAC_LSB(port->id), macaddr_lsb);
+		airoha_fe_wr(qdma->eth, REG_GDM_MAC_MSB(port->id), macaddr_msb);
+	}
+
 	/* Set MAC for Switch */
 	airoha_switch_wr(qdma->eth, SWITCH_SMACCR0, macaddr_lsb);
 	airoha_switch_wr(qdma->eth, SWITCH_SMACCR1, macaddr_msb);
@@ -1246,11 +1975,20 @@ static int arht_eth_write_hwaddr(struct udevice *dev)
 
 static int airoha_eth_bind(struct udevice *dev)
 {
+	const struct airoha_eth_soc_data *data;
+
+	data = (const void *)dev_get_driver_data(dev);
+
 	/*
-	 * Force Probe as we set the Main ETH driver as misc
-	 * to register multiple eth port for each GDM
+	 * Force probe on Airoha ARM SoCs since the parent is a misc device
+	 * responsible for registering the child GDM Ethernet devices.  Do not
+	 * probe EN751221 here: DM_FLAG_PROBE_AFTER_BIND is handled by
+	 * initr_dm_devices() before serial_initialize(), which makes an early
+	 * QDMA/switch failure look like a silent hang.  The EN751221 board
+	 * probes this parent from board_late_init(), immediately before net init.
 	 */
-	dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
+	if (!data || !data->econet)
+		dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
 
 	return 0;
 }
@@ -1262,6 +2000,12 @@ static const struct airoha_eth_soc_data en7523_data = {
 	.switch_compatible = "airoha,en7523-switch",
 };
 
+static const struct airoha_eth_soc_data en751221_data = {
+	.version = 0x7512,
+	.econet = true,
+	.switch_compatible = "mediatek,mt7530",
+};
+
 static const struct airoha_eth_soc_data en7581_data = {
 	.version = 0x7581,
 	.xsi_rsts_names = en7581_xsi_rsts_names,
@@ -1269,13 +2013,25 @@ static const struct airoha_eth_soc_data en7581_data = {
 	.switch_compatible = "airoha,en7581-switch",
 };
 
+static const struct airoha_eth_soc_data an7583_data = {
+	.xsi_rsts_names = an7583_xsi_rsts_names,
+	.num_xsi_rsts = ARRAY_SIZE(an7583_xsi_rsts_names),
+	.switch_compatible = "airoha,an7583-switch",
+};
+
 static const struct udevice_id airoha_eth_ids[] = {
+	{ .compatible = "econet,en751221-eth",
+	  .data = (ulong)&en751221_data,
+	},
 	{ .compatible = "airoha,en7523-eth",
 	  .data = (ulong)&en7523_data,
 	},
 	{ .compatible = "airoha,en7581-eth",
 	  .data = (ulong)&en7581_data,
 	},
+	{ .compatible = "airoha,an7583-eth",
+	  .data = (ulong)&an7583_data,
+	},
 	{ }
 };
 
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 5c8ec2b1..28909d49 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -272,7 +272,7 @@ config PHY_EXYNOS_USBDRD
 config PHY_MTK_TPHY
 	bool "MediaTek T-PHY Driver"
 	depends on PHY
-	depends on ARCH_MEDIATEK || SOC_MT7621
+	depends on ARCH_MEDIATEK || SOC_MT7621 || ARCH_EN75XX
 	select REGMAP
 	select SYSCON
 	help
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 07/12] mtd: env, cmd: add EN75 NAND remap handling
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (5 preceding siblings ...)
  2026-08-25 22:42   ` [PATCH v1 06/12] net: airoha-eth: add EN7528 Ethernet support AK Sharma
@ 2026-08-25 22:42   ` AK Sharma
  2026-08-25 23:06     ` Tom Rini
  2026-08-25 22:42   ` [PATCH v1 08/12] boot: image: add weak hooks for the decompression buffer and flush AK Sharma
                     ` (6 subsequent siblings)
  13 siblings, 1 reply; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:42 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Honour the EcoNet/Airoha NAND "remap" region attribute when reading and
writing MTD partitions from the env and the mtd command, matching the
vendor block layout.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 cmd/mtd.c   | 30 ++++++++++++++--
 env/Kconfig |  2 +-
 env/mtd.c   | 98 +++++++++++++++++++++++++++++++++--------------------
 3 files changed, 89 insertions(+), 41 deletions(-)

diff --git a/cmd/mtd.c b/cmd/mtd.c
index 7f251440..d7b8fa3b 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -519,6 +519,13 @@ static int do_mtd_io(struct cmd_tbl *cmdtp, int flag, int argc,
 		goto out_put_mtd;
 	}
 
+	if (start_off >= mtd->size) {
+		printf("Offset 0x%llx is past the end of %s (size 0x%llx)\n",
+		       start_off, mtd->name, mtd->size);
+		ret = CMD_RET_FAILURE;
+		goto out_put_mtd;
+	}
+
 	default_len = dump ? mtd->writesize : mtd->size;
 	len = argc > 1 ? hextoul(argv[1], NULL) : default_len;
 	if (!mtd_is_aligned_with_min_io_size(mtd, len)) {
@@ -557,11 +564,21 @@ static int do_mtd_io(struct cmd_tbl *cmdtp, int flag, int argc,
 	io_op.datbuf = buf;
 	io_op.oobbuf = woob ? &buf[len] : NULL;
 
-	/* Search for the first good block after the given offset */
+	/*
+	 * Search for the first good block after the given offset.
+	 * Bounded: mtd_block_isbad() returns a negative errno past the end of
+	 * the device, which is nonzero and would otherwise spin forever.
+	 */
 	off = start_off;
-	while (mtd_block_isbad(mtd, off))
+	while (off < mtd->size && mtd_block_isbad(mtd, off) > 0)
 		off += mtd->erasesize;
 
+	if (off >= mtd->size) {
+		printf("No good block found after offset 0x%llx\n", start_off);
+		ret = CMD_RET_FAILURE;
+		goto out_put_mtd;
+	}
+
 	led_activity_blink();
 
 	if (benchmark)
@@ -569,9 +586,16 @@ static int do_mtd_io(struct cmd_tbl *cmdtp, int flag, int argc,
 
 	/* Loop over the pages to do the actual read/write */
 	while (remaining) {
+		if (off >= mtd->size) {
+			printf("Reached the end of %s before completing\n",
+			       mtd->name);
+			ret = -EIO;
+			break;
+		}
+
 		/* Skip the block if it is bad */
 		if (mtd_is_aligned_with_block_size(mtd, off) &&
-		    mtd_block_isbad(mtd, off)) {
+		    mtd_block_isbad(mtd, off) > 0) {
 			off += mtd->erasesize;
 			continue;
 		}
diff --git a/env/Kconfig b/env/Kconfig
index 7abd82ab..ba45d194 100644
--- a/env/Kconfig
+++ b/env/Kconfig
@@ -652,7 +652,7 @@ config ENV_OFFSET_RELATIVE_END
 config ENV_OFFSET_REDUND
 	hex "Redundant environment offset"
 	depends on (ENV_IS_IN_EEPROM || ENV_IS_IN_MMC || ENV_IS_IN_NAND || \
-		    ENV_IS_IN_SPI_FLASH) && ENV_REDUNDANT
+		    ENV_IS_IN_SPI_FLASH || ENV_IS_IN_MTD) && ENV_REDUNDANT
 	default 0x10C0000 if MICROBLAZE
 	default 0x0
 	help
diff --git a/env/mtd.c b/env/mtd.c
index b26ee809..bf9fa516 100644
--- a/env/mtd.c
+++ b/env/mtd.c
@@ -2,6 +2,7 @@
 /*
  *  Author: Christian Marangi <ansuelsmth@gmail.com>
  */
+#include <env.h>
 #include <env_internal.h>
 #include <errno.h>
 #include <malloc.h>
@@ -9,6 +10,7 @@
 #include <asm/cache.h>
 #include <asm/global_data.h>
 #include <linux/mtd/mtd.h>
+#include <memalign.h>
 #include <u-boot/crc.h>
 
 DECLARE_GLOBAL_DATA_PTR;
@@ -30,6 +32,29 @@ static int setup_mtd_device(struct mtd_info **mtd_env)
 	return 0;
 }
 
+static int env_mtd_read_buf(struct mtd_info *mtd, u32 offset, void *buf, int len)
+{
+	u32 sect_size = mtd->erasesize;
+	size_t ret_len;
+	char *tmp = buf;
+	int remaining = len;
+	int ret;
+
+	while (remaining) {
+		if (!(offset % sect_size) && mtd_block_isbad(mtd, offset)) {
+			offset += sect_size;
+			continue;
+		}
+		ret = mtd_read(mtd, offset, mtd->writesize, &ret_len, tmp);
+		if (ret)
+			return ret;
+		tmp += ret_len;
+		offset += ret_len;
+		remaining -= ret_len;
+	}
+	return 0;
+}
+
 static int env_mtd_save(void)
 {
 	char *saved_buf = NULL, *write_buf, *tmp;
@@ -40,7 +65,7 @@ static int env_mtd_save(void)
 	u32 write_size;
 	env_t env_new;
 	int remaining;
-	u32 offset;
+	u32 offset, save_off;
 	int ret;
 
 	ret = setup_mtd_device(&mtd_env);
@@ -48,6 +73,12 @@ static int env_mtd_save(void)
 		return ret;
 
 	sect_size = mtd_env->erasesize;
+#ifdef CONFIG_ENV_OFFSET_REDUND
+	save_off = (gd->env_valid == ENV_VALID) ?
+		CONFIG_ENV_OFFSET_REDUND : CONFIG_ENV_OFFSET;
+#else
+	save_off = CONFIG_ENV_OFFSET;
+#endif
 
 	/* Is the sector larger than the env (i.e. embedded) */
 	if (sect_size > CONFIG_ENV_SIZE) {
@@ -57,7 +88,7 @@ static int env_mtd_save(void)
 			goto done;
 		}
 
-		offset = CONFIG_ENV_OFFSET;
+		offset = save_off;
 		remaining = sect_size;
 		tmp = saved_buf;
 
@@ -87,7 +118,7 @@ static int env_mtd_save(void)
 	sect_num = DIV_ROUND_UP(CONFIG_ENV_SIZE, sect_size);
 
 	ei.mtd = mtd_env;
-	ei.addr = CONFIG_ENV_OFFSET;
+	ei.addr = save_off;
 	ei.len = sect_num * sect_size;
 
 	puts("Erasing MTD...");
@@ -104,7 +135,7 @@ static int env_mtd_save(void)
 		write_buf = (char *)&env_new;
 	}
 
-	offset = CONFIG_ENV_OFFSET;
+	offset = save_off;
 	remaining = write_size;
 	tmp = write_buf;
 
@@ -129,6 +160,9 @@ static int env_mtd_save(void)
 
 	ret = 0;
 	puts("done\n");
+#ifdef CONFIG_ENV_OFFSET_REDUND
+	gd->env_valid = gd->env_valid == ENV_VALID ? ENV_REDUND : ENV_VALID;
+#endif
 
 done:
 	put_mtd_device(mtd_env);
@@ -142,12 +176,8 @@ done:
 static int env_mtd_load(void)
 {
 	struct mtd_info *mtd_env;
-	char *buf, *tmp;
-	size_t ret_len;
-	int remaining;
-	u32 sect_size;
-	u32 offset;
-	int ret;
+	char *buf, *buf2 = NULL;
+	int ret, read1_fail, read2_fail = 1;
 
 	buf = (char *)memalign(ARCH_DMA_MINALIGN, CONFIG_ENV_SIZE);
 	if (!buf) {
@@ -159,40 +189,34 @@ static int env_mtd_load(void)
 	if (ret)
 		goto out;
 
-	sect_size = mtd_env->erasesize;
-
-	offset = CONFIG_ENV_OFFSET;
-	remaining = CONFIG_ENV_SIZE;
-	tmp = buf;
-
-	while (remaining) {
-		/* Skip the block if it is bad */
-		if (!(offset % sect_size) &&
-		    mtd_block_isbad(mtd_env, offset)) {
-			offset += sect_size;
-			continue;
-		}
-
-		ret = mtd_read(mtd_env, offset, mtd_env->writesize,
-			       &ret_len, tmp);
-		if (ret) {
-			env_set_default("mtd_read() failed", 1);
-			goto out;
-		}
-
-		tmp += ret_len;
-		offset += ret_len;
-		remaining -= ret_len;
+	read1_fail = env_mtd_read_buf(mtd_env, CONFIG_ENV_OFFSET, buf,
+				      CONFIG_ENV_SIZE);
+#ifdef CONFIG_ENV_OFFSET_REDUND
+	buf2 = (char *)memalign(ARCH_DMA_MINALIGN, CONFIG_ENV_SIZE);
+	if (!buf2) {
+		env_set_default("memalign() failed", 0);
+		ret = -EIO;
+		goto out_dev;
+	}
+	read2_fail = env_mtd_read_buf(mtd_env, CONFIG_ENV_OFFSET_REDUND, buf2,
+				      CONFIG_ENV_SIZE);
+	ret = env_import_redund(buf, read1_fail, buf2, read2_fail, H_EXTERNAL);
+#else
+	if (read1_fail) {
+		env_set_default("mtd_read() failed", 1);
+		ret = read1_fail;
+		goto out_dev;
 	}
-
 	ret = env_import(buf, 1, H_EXTERNAL);
 	if (!ret)
 		gd->env_valid = ENV_VALID;
+#endif
 
-out:
+out_dev:
 	put_mtd_device(mtd_env);
-
+out:
 	free(buf);
+	free(buf2);
 
 	return ret;
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 08/12] boot: image: add weak hooks for the decompression buffer and flush
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (6 preceding siblings ...)
  2026-08-25 22:42   ` [PATCH v1 07/12] mtd: env, cmd: add EN75 NAND remap handling AK Sharma
@ 2026-08-25 22:42   ` AK Sharma
  2026-08-25 23:05     ` Tom Rini
  2026-08-25 22:42   ` [PATCH v1 09/12] mips: dts: add EcoNet EN75xx device trees AK Sharma
                     ` (5 subsequent siblings)
  13 siblings, 1 reply; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:42 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Add two weak hooks around the bootm image decompression:
image_get_decomp_buf() lets a platform redirect the decompression
destination (e.g. to an uncached window), and image_decomp_flush() lets
it flush the caches over the decompressed image. Both default to no-ops,
so existing platforms are unaffected. The EN75xx uses them to keep the
kernel coherent on its cache-coherent CM.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 boot/image.c    | 34 +++++++++++++++++++++++++++-------
 include/image.h |  5 +++++
 2 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/boot/image.c b/boot/image.c
index 185d52ba..680a78da 100644
--- a/boot/image.c
+++ b/boot/image.c
@@ -442,15 +442,31 @@ int image_decomp_type(const unsigned char *buf, ulong len)
 	return cmagic->comp_id;
 }
 
+#if !defined(USE_HOSTCC)
+void *__weak image_get_decomp_buf(void *load_buf)
+{
+	return load_buf;
+}
+
+void __weak image_decomp_flush(ulong load, ulong image_len)
+{
+}
+#endif
+
 int image_decomp(int comp, ulong load, ulong image_start, int type,
 		 void *load_buf, void *image_buf, ulong image_len,
 		 uint unc_len, ulong *load_end)
 {
 	int ret = -ENOSYS;
+	void *dst = load_buf;
 
 	*load_end = load;
 	print_decomp_msg(comp, type, load == image_start, load);
 
+#if !defined(USE_HOSTCC)
+	dst = image_get_decomp_buf(load_buf);
+#endif
+
 	/*
 	 * Load the image to the right place, decompressing if needed. After
 	 * this, image_len will be set to the number of uncompressed bytes
@@ -462,13 +478,13 @@ int image_decomp(int comp, ulong load, ulong image_start, int type,
 		if (load == image_start)
 			break;
 		if (image_len <= unc_len)
-			memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);
+			memmove_wd(dst, image_buf, image_len, CHUNKSZ);
 		else
 			ret = -ENOSPC;
 		break;
 	case IH_COMP_GZIP:
 		if (!tools_build() && CONFIG_IS_ENABLED(GZIP))
-			ret = gunzip(load_buf, unc_len, image_buf, &image_len);
+			ret = gunzip(dst, unc_len, image_buf, &image_len);
 		break;
 	case IH_COMP_BZIP2:
 		if (!tools_build() && CONFIG_IS_ENABLED(BZIP2)) {
@@ -479,7 +495,7 @@ int image_decomp(int comp, ulong load, ulong image_start, int type,
 			 * use slower decompression algorithm which requires
 			 * at most 2300 KB of memory.
 			 */
-			ret = BZ2_bzBuffToBuffDecompress(load_buf, &size,
+			ret = BZ2_bzBuffToBuffDecompress(dst, &size,
 				image_buf, image_len, CONSERVE_MEMORY, 0);
 			image_len = size;
 		}
@@ -488,7 +504,7 @@ int image_decomp(int comp, ulong load, ulong image_start, int type,
 		if (!tools_build() && CONFIG_IS_ENABLED(LZMA)) {
 			SizeT lzma_len = unc_len;
 
-			ret = lzmaBuffToBuffDecompress(load_buf, &lzma_len,
+			ret = lzmaBuffToBuffDecompress(dst, &lzma_len,
 						       image_buf, image_len);
 			image_len = lzma_len;
 		}
@@ -497,7 +513,7 @@ int image_decomp(int comp, ulong load, ulong image_start, int type,
 		if (!tools_build() && CONFIG_IS_ENABLED(LZO)) {
 			size_t size = unc_len;
 
-			ret = lzop_decompress(image_buf, image_len, load_buf, &size);
+			ret = lzop_decompress(image_buf, image_len, dst, &size);
 			image_len = size;
 		}
 		break;
@@ -505,7 +521,7 @@ int image_decomp(int comp, ulong load, ulong image_start, int type,
 		if (!tools_build() && CONFIG_IS_ENABLED(LZ4)) {
 			size_t size = unc_len;
 
-			ret = ulz4fn(image_buf, image_len, load_buf, &size);
+			ret = ulz4fn(image_buf, image_len, dst, &size);
 			image_len = size;
 		}
 		break;
@@ -514,7 +530,7 @@ int image_decomp(int comp, ulong load, ulong image_start, int type,
 			struct abuf in, out;
 
 			abuf_init_set(&in, image_buf, image_len);
-			abuf_init_set(&out, load_buf, unc_len);
+			abuf_init_set(&out, dst, unc_len);
 			ret = zstd_decompress(&in, &out);
 			if (ret >= 0) {
 				image_len = ret;
@@ -532,6 +548,10 @@ int image_decomp(int comp, ulong load, ulong image_start, int type,
 	if (ret)
 		return ret;
 
+#if !defined(USE_HOSTCC)
+	image_decomp_flush(load, image_len);
+#endif
+
 	return 0;
 }
 
diff --git a/include/image.h b/include/image.h
index 34efac60..b2a6c536 100644
--- a/include/image.h
+++ b/include/image.h
@@ -1014,6 +1014,11 @@ int image_decomp(int comp, ulong load, ulong image_start, int type,
 		 void *load_buf, void *image_buf, ulong image_len,
 		 uint unc_len, ulong *load_end);
 
+#ifndef USE_HOSTCC
+void *image_get_decomp_buf(void *load_buf);
+void image_decomp_flush(ulong load, ulong image_len);
+#endif
+
 /**
  * Set up properties in the FDT
  *
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 09/12] mips: dts: add EcoNet EN75xx device trees
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (7 preceding siblings ...)
  2026-08-25 22:42   ` [PATCH v1 08/12] boot: image: add weak hooks for the decompression buffer and flush AK Sharma
@ 2026-08-25 22:42   ` AK Sharma
  2026-08-25 22:55     ` Tom Rini
  2026-08-25 22:42   ` [PATCH v1 10/12] board: econet: add EN7512 reference board AK Sharma
                     ` (4 subsequent siblings)
  13 siblings, 1 reply; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:42 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Add the device trees for the EN7512 and EN7528 reference boards and the
EN7528 RAM-boot variant.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 arch/mips/dts/Makefile                        |   2 +
 .../dts/econet,en7512-reference-u-boot.dtsi   |  58 +++++++
 arch/mips/dts/econet,en7512-reference.dts     |  27 +++
 arch/mips/dts/econet,en7512.dtsi              |  88 ++++++++++
 arch/mips/dts/econet,en7528-ram-u-boot.dtsi   |   6 +
 arch/mips/dts/econet,en7528-ram.dts           |  72 ++++++++
 .../dts/econet,en7528-reference-u-boot.dtsi   |  62 +++++++
 arch/mips/dts/econet,en7528-reference.dts     |  25 +++
 arch/mips/dts/econet,en7528.dtsi              | 164 ++++++++++++++++++
 9 files changed, 504 insertions(+)
 create mode 100644 arch/mips/dts/econet,en7512-reference-u-boot.dtsi
 create mode 100644 arch/mips/dts/econet,en7512-reference.dts
 create mode 100644 arch/mips/dts/econet,en7512.dtsi
 create mode 100644 arch/mips/dts/econet,en7528-ram-u-boot.dtsi
 create mode 100644 arch/mips/dts/econet,en7528-ram.dts
 create mode 100644 arch/mips/dts/econet,en7528-reference-u-boot.dtsi
 create mode 100644 arch/mips/dts/econet,en7528-reference.dts
 create mode 100644 arch/mips/dts/econet,en7528.dtsi

diff --git a/arch/mips/dts/Makefile b/arch/mips/dts/Makefile
index 7c4ee8b6..3e0347dd 100644
--- a/arch/mips/dts/Makefile
+++ b/arch/mips/dts/Makefile
@@ -6,6 +6,8 @@ dtb-$(CONFIG_TARGET_AP152) += ap152.dtb
 dtb-$(CONFIG_TARGET_MALTA) += mti,malta.dtb
 dtb-$(CONFIG_TARGET_PIC32MZDASK) += pic32mzda_sk.dtb
 dtb-$(CONFIG_TARGET_XILFPGA) += nexys4ddr.dtb
+dtb-$(CONFIG_ARCH_EN75XX) += econet,en7512-reference.dtb \
+	econet,en7528-reference.dtb econet,en7528-ram.dtb
 dtb-$(CONFIG_BOARD_BROADCOM_BCM968380GERG) += brcm,bcm968380gerg.dtb
 dtb-$(CONFIG_BOARD_COMTREND_AR5315U) += comtrend,ar-5315u.dtb
 dtb-$(CONFIG_BOARD_COMTREND_AR5387UN) += comtrend,ar-5387un.dtb
diff --git a/arch/mips/dts/econet,en7512-reference-u-boot.dtsi b/arch/mips/dts/econet,en7512-reference-u-boot.dtsi
new file mode 100644
index 00000000..36d97c26
--- /dev/null
+++ b/arch/mips/dts/econet,en7512-reference-u-boot.dtsi
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/stringify.h>
+
+/ {
+	binman: binman {
+		multiple-images;
+	};
+};
+
+&uart0 {
+	bootph-all;
+};
+
+&binman {
+	spl-img {
+		filename = "u-boot-spl.img";
+
+		mkimage {
+			args = "-A", "mips", "-T", "standalone", "-O", "u-boot",
+			       "-C", "none", "-n", "EN7512 U-Boot SPL",
+			       "-a", __stringify(CONFIG_SPL_TEXT_BASE),
+			       "-e", __stringify(CONFIG_SPL_TEXT_BASE);
+
+			u-boot-spl {
+			};
+		};
+	};
+
+	en7512-uboot {
+		filename = "u-boot-en7512.bin";
+		pad-byte = <0xff>;
+
+		u-boot-tpl {
+			offset = <0x00000000>;
+			size = <0x00008000>;
+		};
+
+		ddr-blob {
+			type = "blob-ext";
+			filename = "arch/mips/mach-en75xx/en7512_ddr.bin";
+			offset = <0x00008000>;
+			size = <0x00004f70>;
+		};
+
+		spl {
+			type = "blob";
+			filename = "u-boot-spl.img";
+			offset = <0x00010000>;
+			size = <0x00040000>;
+		};
+
+		u-boot {
+			type = "blob";
+			filename = "u-boot.img";
+			offset = <0x00050000>;
+		};
+	};
+};
diff --git a/arch/mips/dts/econet,en7512-reference.dts b/arch/mips/dts/econet,en7512-reference.dts
new file mode 100644
index 00000000..b8a3aba5
--- /dev/null
+++ b/arch/mips/dts/econet,en7512-reference.dts
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0+
+/dts-v1/;
+
+#include "econet,en7512.dtsi"
+
+/ {
+	model = "EcoNet EN7512/EN7521 reference board";
+	compatible = "econet,en7512-reference", "econet,en751221",
+		     "econet,en7512";
+
+	aliases {
+		serial0 = &uart0;
+		ethernet0 = &gmac0;
+	};
+
+	chosen {
+		stdout-path = "serial0:115200n8";
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&sfc {
+	status = "okay";
+};
diff --git a/arch/mips/dts/econet,en7512.dtsi b/arch/mips/dts/econet,en7512.dtsi
new file mode 100644
index 00000000..0f13159d
--- /dev/null
+++ b/arch/mips/dts/econet,en7512.dtsi
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		cpu@0 {
+			compatible = "mips,mips34Kc";
+			device_type = "cpu";
+			reg = <0>;
+		};
+	};
+
+	memory@0 {
+		device_type = "memory";
+		reg = <0x00000000 0x08000000>;
+	};
+
+	soc {
+		compatible = "simple-bus";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		ranges;
+
+		syscon: syscon@1fb00000 {
+			compatible = "econet,en7512-syscon", "syscon";
+			reg = <0x1fb00000 0x1000>;
+		};
+
+		sfc: spi@1fa10000 {
+			compatible = "airoha,en7523-spi";
+			reg = <0x1fa10000 0x140>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+
+			spi_nand: nand@0 {
+				compatible = "spi-nand";
+				reg = <0>;
+				spi-max-frequency = <20000000>;
+				spi-tx-bus-width = <1>;
+				spi-rx-bus-width = <2>;
+			};
+		};
+
+		sram@1fa30000 {
+			compatible = "mmio-sram";
+			reg = <0x1fa30000 0xc000>;
+		};
+
+		dramc: memory-controller@1fb20000 {
+			compatible = "econet,en7512-dramc";
+			reg = <0x1fb20000 0x1000>;
+		};
+
+		ethernet: ethernet@1fb50000 {
+			compatible = "econet,en751221-eth";
+			reg = <0x1fb50000 0x4000>,
+			      <0x1fb54000 0x1000>;
+			reg-names = "fe", "qdma0";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			gmac0: mac@0 {
+				compatible = "econet,eth-mac";
+				reg = <0>;
+				phy-mode = "trgmii";
+			};
+		};
+
+		switch0: switch@1fb58000 {
+			compatible = "mediatek,mt7530";
+			reg = <0x1fb58000 0x8000>;
+		};
+
+		uart0: serial@1fbf0000 {
+			compatible = "econet,en7512-uart";
+			reg = <0x1fbf0000 0x30>;
+			clock-frequency = <20000000>;
+			current-speed = <115200>;
+			status = "disabled";
+		};
+	};
+};
diff --git a/arch/mips/dts/econet,en7528-ram-u-boot.dtsi b/arch/mips/dts/econet,en7528-ram-u-boot.dtsi
new file mode 100644
index 00000000..94512540
--- /dev/null
+++ b/arch/mips/dts/econet,en7528-ram-u-boot.dtsi
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* RAM-boot: no binman image; just make the console available pre-relocation. */
+
+&uart0 {
+	bootph-all;
+};
diff --git a/arch/mips/dts/econet,en7528-ram.dts b/arch/mips/dts/econet,en7528-ram.dts
new file mode 100644
index 00000000..0c5a59fb
--- /dev/null
+++ b/arch/mips/dts/econet,en7528-ram.dts
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0+
+/dts-v1/;
+
+#include "econet,en7528.dtsi"
+
+/ {
+	model = "EcoNet EN7528 RAM-boot (bootbase-loaded)";
+	compatible = "econet,en7528-ram", "econet,en7528";
+
+	aliases {
+		serial0 = &uart0;
+		ethernet0 = &gmac0;
+	};
+
+	chosen {
+		stdout-path = "serial0:115200n8";
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&sfc {
+	status = "okay";
+};
+
+&ethernet {
+	status = "okay";
+};
+
+&switch0 {
+	status = "okay";
+};
+
+&usb {
+	status = "okay";
+};
+
+/* R3-hook map (harmless on RAM-boot: extra labels on spi-nand0). */
+&spi_nand {
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "bootloader";
+			reg = <0x0 0x40000>;
+		};
+		partition@40000 {
+			label = "uboot";
+			reg = <0x40000 0x80000>;
+		};
+		partition@c0000 {
+			label = "ubootenv";
+			reg = <0xc0000 0x20000>;
+		};
+		partition@e0000 {
+			label = "ubootenv2";
+			reg = <0xe0000 0x20000>;
+		};
+		partition@100000 {
+			label = "kernel";
+			reg = <0x100000 0x1000000>;
+		};
+		partition@1100000 {
+			label = "rootfs";
+			reg = <0x1100000 0xda80000>;
+		};
+	};
+};
diff --git a/arch/mips/dts/econet,en7528-reference-u-boot.dtsi b/arch/mips/dts/econet,en7528-reference-u-boot.dtsi
new file mode 100644
index 00000000..f0ded2b0
--- /dev/null
+++ b/arch/mips/dts/econet,en7528-reference-u-boot.dtsi
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/stringify.h>
+
+/ {
+	binman: binman {
+		multiple-images;
+	};
+};
+
+&uart0 {
+	bootph-all;
+};
+
+&binman {
+	spl-img {
+		filename = "u-boot-spl.img";
+
+		mkimage {
+			args = "-A", "mips", "-T", "standalone", "-O", "u-boot",
+			       "-C", "none", "-n", "EN7528 U-Boot SPL",
+			       "-a", __stringify(CONFIG_SPL_TEXT_BASE),
+			       "-e", __stringify(CONFIG_SPL_TEXT_BASE);
+
+			u-boot-spl {
+			};
+		};
+	};
+
+	en7528-uboot {
+		filename = "u-boot-en7528.bin";
+		pad-byte = <0xff>;
+
+		u-boot-tpl {
+			offset = <0x00000000>;
+			size = <0x00008000>;
+		};
+
+		/*
+		 * en7528_ddr.bin is the 0x5a70-byte FE-SRAM DDR3 stage (JCOW407 V1.6) lifted
+		 * from the stock bootbase (see doc/board/econet/en7528-porting.md).
+		 */
+		ddr-blob {
+			type = "blob-ext";
+			filename = "arch/mips/mach-en75xx/en7528_ddr.bin";
+			offset = <0x00008000>;
+			size = <0x00005a70>;
+		};
+
+		spl {
+			type = "blob";
+			filename = "u-boot-spl.img";
+			offset = <0x00020000>;
+			size = <0x00030000>;
+		};
+
+		u-boot {
+			type = "blob";
+			filename = "u-boot.img";
+			offset = <0x00050000>;
+		};
+	};
+};
diff --git a/arch/mips/dts/econet,en7528-reference.dts b/arch/mips/dts/econet,en7528-reference.dts
new file mode 100644
index 00000000..d029f41c
--- /dev/null
+++ b/arch/mips/dts/econet,en7528-reference.dts
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0+
+/dts-v1/;
+
+#include "econet,en7528.dtsi"
+
+/ {
+	model = "EcoNet EN7528 reference board (DASAN H660GM-A)";
+	compatible = "econet,en7528-reference", "econet,en7528";
+
+	aliases {
+		serial0 = &uart0;
+	};
+
+	chosen {
+		stdout-path = "serial0:115200n8";
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&sfc {
+	status = "okay";
+};
diff --git a/arch/mips/dts/econet,en7528.dtsi b/arch/mips/dts/econet,en7528.dtsi
new file mode 100644
index 00000000..b8b4668b
--- /dev/null
+++ b/arch/mips/dts/econet,en7528.dtsi
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* EcoNet/Airoha EN7528 (MIPS 1004Kc, little-endian) */
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		cpu@0 {
+			compatible = "mips,mips1004Kc";
+			device_type = "cpu";
+			reg = <0>;
+		};
+
+		cpu@1 {
+			compatible = "mips,mips1004Kc";
+			device_type = "cpu";
+			reg = <1>;
+		};
+	};
+
+	memory@0 {
+		device_type = "memory";
+		/* Capped below the 0x1fxxxxxx MMIO island; DDR stage
+		 * publishes the true size at runtime (dram_init). */
+		reg = <0x00000000 0x10000000>;
+	};
+
+	/* Dummy clock/phy so xhci-mtk's mandatory clk_get_bulk()/
+	 * generic_phy_get_bulk() succeed without an en7528 SCU clock or
+	 * T-PHY driver; the real USB clock is left ungated by the bootbase
+	 * and the xHCI IPPC block does the port power-up. */
+	usb_clk: usb-clk {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <24000000>;
+	};
+
+	usb_phy: usb-phy {
+		compatible = "nop-phy";
+		#phy-cells = <0>;
+		clocks = <&usb_clk>;	/* nop_phy_probe() calls clk_get_bulk() */
+	};
+
+	soc {
+		compatible = "simple-bus";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		ranges;
+
+		syscon: syscon@1fb00000 {
+			compatible = "econet,en7528-syscon", "syscon";
+			reg = <0x1fb00000 0x1000>;
+		};
+
+		sfc: spi@1fa10000 {
+			compatible = "airoha,en7581-snand";
+			reg = <0x1fa10000 0x140>,
+			      <0x1fa11000 0x1000>;	/* NFI: driver uses up to 0x55c;
+							 * U-Boot regmap range-checks (Linux
+							 * gets a full page from ioremap). */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+
+			spi_nand: nand@0 {
+				compatible = "spi-nand";
+				reg = <0>;
+				spi-max-frequency = <20000000>;
+				spi-tx-bus-width = <1>;
+				spi-rx-bus-width = <2>;
+
+				/* Vendor bad-block remapping. bbt-table-size is a
+				 * per-board constant: EN7528 uses 250, not the 1000
+				 * default, and a wrong value fails the BBT checksum.
+				 * assert-reserve-size must match the pool size the
+				 * bootbase prints ("bmt pool size: 163").
+				 */
+				econet,bmt;
+				econet,bbt-table-size = <250>;
+				econet,assert-reserve-size = <163>;
+			};
+		};
+
+		sram@1fa30000 {
+			compatible = "mmio-sram";
+			reg = <0x1fa30000 0x20000>;
+		};
+
+		dramc: memory-controller@1fb20000 {
+			compatible = "econet,en7528-dramc";
+			reg = <0x1fb20000 0x1000>;
+		};
+
+		/*
+		 * EN7528 Ethernet = same EN751221-family FE/QDMA as the EN7512,
+		 * feeding an MT7530-family DSA switch (GMAC0 -> switch CPU port).
+		 * The airoha_eth U-Boot driver binds "econet,en751221-eth" and
+		 * self-initializes the "mediatek,mt7530" switch.
+		 */
+		ethernet: ethernet@1fb50000 {
+			compatible = "econet,en751221-eth";
+			reg = <0x1fb50000 0x4000>,
+			      <0x1fb54000 0x1000>;
+			reg-names = "fe", "qdma0";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+
+			gmac0: mac@0 {
+				compatible = "econet,eth-mac";
+				reg = <0>;
+				phy-mode = "trgmii";
+			};
+		};
+
+		switch0: switch@1fb58000 {
+			compatible = "mediatek,mt7530";
+			reg = <0x1fb58000 0x8000>;
+			status = "disabled";
+		};
+
+		/*
+		 * USB3 host = MediaTek xHCI (same IP as mt8173), driven by
+		 * U-Boot's xhci-mtk. "mac" is the xHCI MMIO, "ippc" the IP port
+		 * control block (a sub-region of the USB T-PHY window). The
+		 * en7528 USB clock is ungated by the bootbase (the kernel node
+		 * carries no clocks either) so &usb_clk is a dummy fixed-clock
+		 * to satisfy clk_get_bulk(); &usb_phy is a nop-phy (the xHCI
+		 * IPPC block powers the ports up itself). USB reset/PHY-reset are
+		 * released by a direct SCU poke in board_late_init (there is no
+		 * DM reset provider in this port — see board.c).
+		 */
+		usb: usb@1fb90000 {
+			compatible = "mediatek,mtk-xhci";
+			reg = <0x1fb90000 0x4000>,
+			      <0x1fa80700 0x100>;
+			reg-names = "mac", "ippc";
+			clocks = <&usb_clk>;
+			phys = <&usb_phy>;
+			/*
+			 * USB2-only: the U3/SerDes path needs SCU/serdes config
+			 * we don't do, and leaving U3 enabled makes xhci-mtk stall
+			 * on an unstable U3 clock (per the Airoha AN7581 USB PHY
+			 * commit). Disable both U3 ports so only USB2 is brought up.
+			 */
+			mediatek,u3p-dis-msk = <0x3>;
+			status = "disabled";
+		};
+
+		uart0: serial@1fbf0000 {
+			compatible = "econet,en7528-uart";
+			reg = <0x1fbf0000 0x30>;
+			reg-io-width = <4>;
+			reg-shift = <2>;
+			clock-frequency = <2000000>;
+			current-speed = <115200>;
+			status = "disabled";
+		};
+	};
+};
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 10/12] board: econet: add EN7512 reference board
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (8 preceding siblings ...)
  2026-08-25 22:42   ` [PATCH v1 09/12] mips: dts: add EcoNet EN75xx device trees AK Sharma
@ 2026-08-25 22:42   ` AK Sharma
  2026-08-25 22:42   ` [PATCH v1 11/12] board: econet: add EN7528 board support AK Sharma
                     ` (3 subsequent siblings)
  13 siblings, 0 replies; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:42 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Add the EcoNet EN7512 reference board: board glue, defconfig, board
header and documentation.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 board/econet/en7512/MAINTAINERS    |  8 +++
 board/econet/en7512/Makefile       |  2 +
 board/econet/en7512/board.c        | 34 +++++++++++
 configs/en7512_reference_defconfig | 93 ++++++++++++++++++++++++++++++
 doc/board/econet/en7512.rst        | 70 ++++++++++++++++++++++
 drivers/usb/host/Kconfig           |  2 +-
 include/configs/en7512.h           |  8 +++
 7 files changed, 216 insertions(+), 1 deletion(-)
 create mode 100644 board/econet/en7512/MAINTAINERS
 create mode 100644 board/econet/en7512/Makefile
 create mode 100644 board/econet/en7512/board.c
 create mode 100644 configs/en7512_reference_defconfig
 create mode 100644 doc/board/econet/en7512.rst
 create mode 100644 include/configs/en7512.h

diff --git a/board/econet/en7512/MAINTAINERS b/board/econet/en7512/MAINTAINERS
new file mode 100644
index 00000000..0c1b931a
--- /dev/null
+++ b/board/econet/en7512/MAINTAINERS
@@ -0,0 +1,8 @@
+ECONET EN7512 REFERENCE BOARD
+M:	AK Sharma <maskachoska@yahoo.com>
+S:	Maintained
+F:	arch/mips/dts/econet,en7512*
+F:	board/econet/en7512/
+F:	configs/en7512_reference_defconfig
+F:	doc/board/econet/en7512.rst
+F:	include/configs/en7512.h
diff --git a/board/econet/en7512/Makefile b/board/econet/en7512/Makefile
new file mode 100644
index 00000000..bcca1a9f
--- /dev/null
+++ b/board/econet/en7512/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0+
+obj-y += board.o
diff --git a/board/econet/en7512/board.c b/board/econet/en7512/board.c
new file mode 100644
index 00000000..c20597e8
--- /dev/null
+++ b/board/econet/en7512/board.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <dm.h>
+#include <init.h>
+#include <linux/errno.h>
+#include <stdio.h>
+
+extern U_BOOT_DRIVER(airoha_eth);
+
+int board_init(void)
+{
+	return 0;
+}
+
+int board_late_init(void)
+{
+	struct udevice *dev;
+	int ret;
+
+	if (!IS_ENABLED(CONFIG_AIROHA_ETH))
+		return 0;
+
+	/*
+	 * The EN751221 Ethernet parent is intentionally not probed while the
+	 * driver model is being initialized. Probe it after the console is
+	 * usable and just before initr_net() enumerates UCLASS_ETH devices.
+	 */
+	ret = uclass_get_device_by_driver(UCLASS_MISC,
+					  DM_DRIVER_GET(airoha_eth), &dev);
+	if (ret && ret != -ENODEV)
+		printf("EN751221 Ethernet probe failed: %d\n", ret);
+
+	return 0;
+}
diff --git a/configs/en7512_reference_defconfig b/configs/en7512_reference_defconfig
new file mode 100644
index 00000000..ebf5dbf0
--- /dev/null
+++ b/configs/en7512_reference_defconfig
@@ -0,0 +1,93 @@
+CONFIG_MIPS=y
+CONFIG_SYS_BIG_ENDIAN=y
+CONFIG_SKIP_LOWLEVEL_INIT=y
+CONFIG_SYS_MALLOC_LEN=0x100000
+CONFIG_SPL_LIBCOMMON_SUPPORT=y
+CONFIG_SPL_LIBGENERIC_SUPPORT=y
+CONFIG_NR_DRAM_BANKS=1
+CONFIG_ENV_SIZE=0x1000
+CONFIG_DM_GPIO=y
+CONFIG_DEFAULT_DEVICE_TREE="econet,en7512-reference"
+CONFIG_SPL_SYS_MALLOC_F_LEN=0x10000
+CONFIG_SPL_SERIAL=y
+CONFIG_TPL_SYS_MALLOC_F_LEN=0x1000
+CONFIG_SPL_BSS_START_ADDR=0x80080000
+CONFIG_SPL_BSS_MAX_SIZE=0x10000
+CONFIG_SYS_LOAD_ADDR=0x81800000
+CONFIG_SPL=y
+CONFIG_ARCH_EN75XX=y
+CONFIG_BUILD_TARGET="u-boot.bin"
+CONFIG_BOARD_EN7512_REFERENCE=y
+CONFIG_SYS_MIPS_TIMER_FREQ=150000000
+CONFIG_MIPS_CACHE_SETUP=y
+CONFIG_MIPS_CACHE_DISABLE=y
+CONFIG_RESTORE_EXCEPTION_VECTOR_BASE=y
+CONFIG_MIPS_BOOT_FDT=y
+CONFIG_FIT=y
+CONFIG_FIT_VERBOSE=y
+CONFIG_BOOTDELAY=3
+CONFIG_OF_STDOUT_VIA_ALIAS=y
+CONFIG_DEFAULT_FDT_FILE="en751221_ref"
+CONFIG_SYS_PBSIZE=1049
+CONFIG_SYS_CONSOLE_IS_IN_ENV=y
+CONFIG_DISPLAY_CPUINFO=y
+# CONFIG_DISPLAY_BOARDINFO is not set
+CONFIG_BOARD_LATE_INIT=y
+CONFIG_SPL_MAX_SIZE=0x30000
+CONFIG_SPL_SYS_MALLOC_SIMPLE=y
+CONFIG_TPL=y
+# CONFIG_TPL_FRAMEWORK is not set
+CONFIG_HUSH_PARSER=y
+CONFIG_SYS_PROMPT="U-Boot> "
+CONFIG_SYS_MAXARGS=8
+CONFIG_CMD_BOOTZ=y
+CONFIG_CMD_BOOTMENU=y
+# CONFIG_CMD_ELF is not set
+# CONFIG_CMD_XIMG is not set
+CONFIG_CMD_BIN2MAC=y
+CONFIG_CMD_MEMINFO=y
+CONFIG_CMD_BIND=y
+CONFIG_CMD_GPIO=y
+CONFIG_CMD_MTD=y
+# CONFIG_CMD_SETEXPR is not set
+CONFIG_CMD_DHCP=y
+CONFIG_CMD_MII=y
+CONFIG_CMD_PING=y
+CONFIG_CMD_EXT4=y
+CONFIG_CMD_FAT=y
+CONFIG_CMD_FS_GENERIC=y
+CONFIG_CMD_MTDPARTS=y
+CONFIG_CMD_LOG=y
+CONFIG_CMD_UBI=y
+CONFIG_SPL_OF_CONTROL=y
+CONFIG_ENV_OVERWRITE=y
+CONFIG_ENV_IS_NOWHERE=y
+CONFIG_ENV_IS_IN_UBI=y
+CONFIG_ENV_REDUNDANT=y
+CONFIG_ENV_UBI_PART="ubi"
+CONFIG_ENV_UBI_VOLUME="ubootenv"
+CONFIG_ENV_UBI_VOLUME_REDUND="ubootenv2"
+CONFIG_ENV_RELOC_GD_ENV_ADDR=y
+CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
+CONFIG_NET_RANDOM_ETHADDR=y
+CONFIG_SYS_RX_ETH_BUFFER=8
+CONFIG_SPL_DM=y
+CONFIG_CLK=y
+CONFIG_DMA=y
+CONFIG_LED=y
+CONFIG_MTD=y
+CONFIG_DM_MTD=y
+CONFIG_MTD_SPI_NAND=y
+CONFIG_UBI_BLOCK=y
+CONFIG_DM_MDIO=y
+CONFIG_AIROHA_ETH=y
+CONFIG_PHY=y
+CONFIG_PINCTRL=y
+CONFIG_RAM=y
+CONFIG_SYS_NS16550=y
+CONFIG_SPI=y
+CONFIG_DM_SPI=y
+CONFIG_AIROHA_SNFI_SPI=y
+# CONFIG_UBIFS_SILENCE_MSG is not set
+# CONFIG_BINMAN_FDT is not set
+CONFIG_SHA512=y
diff --git a/doc/board/econet/en7512.rst b/doc/board/econet/en7512.rst
new file mode 100644
index 00000000..379ae0cb
--- /dev/null
+++ b/doc/board/econet/en7512.rst
@@ -0,0 +1,70 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+EN7512/EN7521 reference board
+=============================
+
+This target provides the initial U-Boot port for the MIPS-based EcoNet/Airoha
+EN7512 and EN7521 SoCs. It replaces the proprietary multi-stage TCBoot flow
+with U-Boot TPL, SPL and U-Boot proper stages.
+
+Boot flow
+---------
+
+The boot ROM starts the TPL from the uncached flash window at ``0xbfc00000``.
+The TPL enables the Front-End SRAM, initializes the serial port and reads the
+DDR calibration image and SPL through the early SFC reader. After DDR
+initialization, SPL loads ``u-boot.img`` into DRAM.
+
+The generated ``u-boot-en7512.bin`` has the following fixed layout:
+
+==================  ===========  ========================================
+Offset              Maximum size Contents
+==================  ===========  ========================================
+``0x00000000``      ``0x8000``   TPL and TCBoot image descriptor
+``0x00008000``      ``0x4f70``   Vendor DDR calibration image
+``0x0000ff00``      ``0x100``    Board manufacturing information
+``0x00010000``      ``0x40000``  Legacy-header SPL image
+``0x00050000``      remaining    Legacy ``u-boot.img``
+==================  ===========  ========================================
+
+The combined image leaves the manufacturing block erased. Before replacing a
+stock bootloader, preserve the board-specific block from the original image::
+
+    tools/en7512_image.py --image u-boot-en7512.bin \
+        --stock tcboot-stock.bin --output u-boot-en7512-board.bin
+
+The block contains data such as the MAC address, model and board GPIO values.
+Do not flash the unmerged combined image over a device whose board data has not
+been backed up.
+
+Build
+-----
+
+Use a big-endian MIPS cross compiler::
+
+    make en7512_reference_defconfig
+    make CROSS_COMPILE=mips-linux-gnu-
+
+The flashable combined image is ``u-boot-en7512.bin``.
+
+Current limitations
+-------------------
+
+* DDR initialization temporarily uses the original vendor calibration image,
+  linked for execution at ``0x9fa32800``. Its source code was not included in
+  the SDK and it must eventually be replaced by a native driver.
+* The SFC implementation used by TPL and SPL is read-only. U-Boot proper uses
+  the Airoha EN7523 SPI-MEM driver with the EN751221 double chip-select quirk.
+  Flash erase/write must not be used until the board's BBT/BMT layout has been
+  verified.
+* Ethernet provides a polling-only U-Boot path through EN751221 QDMA0, GDM1
+  and the integrated MT7530. It deliberately preserves switch/PHY reset state
+  during chainloading. Cold-boot PHY initialization and multi-port control
+  still need board-specific validation.
+* U-Boot proper has been chainloaded and validated on physical hardware. The
+  complete BootROM -> TPL -> SPL -> U-Boot cold-boot path remains untested.
+
+TPL reports early failures with one character followed by a newline:
+``I`` for SFC initialization, ``F`` for DDR image reading, ``H`` for an
+invalid SPL header, ``S`` for SPL loading and ``L`` for a load address that
+cannot be reached through KSEG0/KSEG1.
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index d75883e2..fbd6690a 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -54,7 +54,7 @@ config USB_XHCI_EXYNOS
 
 config USB_XHCI_MTK
 	bool "Support for MediaTek on-chip xHCI USB controller"
-	depends on ARCH_MEDIATEK || SOC_MT7621
+	depends on ARCH_MEDIATEK || SOC_MT7621 || ARCH_EN75XX
 	help
 	  Enables support for the on-chip xHCI controller on MediaTek SoCs.
 
diff --git a/include/configs/en7512.h b/include/configs/en7512.h
new file mode 100644
index 00000000..93e1c485
--- /dev/null
+++ b/include/configs/en7512.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef __CONFIG_EN7512_H
+#define __CONFIG_EN7512_H
+
+#define CFG_SYS_SDRAM_BASE	0x80000000
+#define CFG_SYS_INIT_SP_OFFSET	0x00800000
+
+#endif
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 11/12] board: econet: add EN7528 board support
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (9 preceding siblings ...)
  2026-08-25 22:42   ` [PATCH v1 10/12] board: econet: add EN7512 reference board AK Sharma
@ 2026-08-25 22:42   ` AK Sharma
  2026-08-25 22:57     ` Tom Rini
  2026-08-25 23:00     ` Tom Rini
  2026-08-25 22:42   ` [PATCH v1 12/12] doc: board: econet: add EN75xx documentation AK Sharma
                     ` (2 subsequent siblings)
  13 siblings, 2 replies; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:42 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Add the EcoNet EN7528 board: board glue, the reference/RAM/tclinux and
JCOW414 R3-hook defconfigs, board header and default environment.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 board/econet/en7528/MAINTAINERS         |  13 +++
 board/econet/en7528/Makefile            |   2 +
 board/econet/en7528/board.c             | 110 +++++++++++++++++++++++
 board/econet/en7528/mk_tclinux_uboot.py | 112 ++++++++++++++++++++++++
 board/econet/en7528/tclinux_stub.S      |  65 ++++++++++++++
 configs/en7528_jcow414_r3hook_defconfig |  76 ++++++++++++++++
 configs/en7528_ram_defconfig            |  62 +++++++++++++
 configs/en7528_reference_defconfig      |  40 +++++++++
 defenvs/en7528_jcow414_r3hook_env       |   5 ++
 include/configs/en7528.h                |   9 ++
 10 files changed, 494 insertions(+)
 create mode 100644 board/econet/en7528/MAINTAINERS
 create mode 100644 board/econet/en7528/Makefile
 create mode 100644 board/econet/en7528/board.c
 create mode 100644 board/econet/en7528/mk_tclinux_uboot.py
 create mode 100644 board/econet/en7528/tclinux_stub.S
 create mode 100644 configs/en7528_jcow414_r3hook_defconfig
 create mode 100644 configs/en7528_ram_defconfig
 create mode 100644 configs/en7528_reference_defconfig
 create mode 100644 defenvs/en7528_jcow414_r3hook_env
 create mode 100644 include/configs/en7528.h

diff --git a/board/econet/en7528/MAINTAINERS b/board/econet/en7528/MAINTAINERS
new file mode 100644
index 00000000..69cefe4a
--- /dev/null
+++ b/board/econet/en7528/MAINTAINERS
@@ -0,0 +1,13 @@
+ECONET/AIROHA EN75XX
+M:	AK Sharma <maskachoska@yahoo.com>
+S:	Maintained
+F:	arch/mips/mach-en75xx/
+F:	arch/mips/dts/econet,en7528*
+F:	board/econet/en7528/
+F:	configs/en7528_*_defconfig
+F:	defenvs/en7528_jcow414_r3hook_env
+F:	doc/board/econet/en7528.rst
+F:	doc/board/econet/index.rst
+F:	drivers/mtd/nand/spi/en75_bmt.*
+F:	drivers/serial/serial_en75xx.c
+F:	include/configs/en7528.h
diff --git a/board/econet/en7528/Makefile b/board/econet/en7528/Makefile
new file mode 100644
index 00000000..bcca1a9f
--- /dev/null
+++ b/board/econet/en7528/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0+
+obj-y += board.o
diff --git a/board/econet/en7528/board.c b/board/econet/en7528/board.c
new file mode 100644
index 00000000..5708781e
--- /dev/null
+++ b/board/econet/en7528/board.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <dm.h>
+#include <init.h>
+#include <asm/io.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <stdio.h>
+
+#if CONFIG_IS_ENABLED(AIROHA_ETH)
+extern U_BOOT_DRIVER(airoha_eth);
+#endif
+
+#if defined(CONFIG_USB_XHCI_MTK)
+/*
+ * Release the USB host + T-PHY reset domains. The SCU reset controller is not
+ * modelled as a DM reset provider in this port (the ethernet driver pokes it
+ * directly too), so pulse the reset bits by hand. Banks/bits verified against
+ * the kernel clk-en7523 en751221 reset table: USB_HOST_P0 = RST_CTRL1(0x834)
+ * bit22; USB_PHY_P0/P1 = RST_USB(0x0ec) bits 6/7. set = assert/hold in reset,
+ * clear = release.
+ */
+#define EN7528_SCU_RST_CTRL1	0xbfb00834
+#define EN7528_SCU_RST_USB	0xbfb000ec
+#define EN7528_RST_USB_HOST_P0	BIT(22)		/* in RST_CTRL1 */
+#define EN7528_RST_USB_PHY_P0	BIT(6)		/* in RST_USB */
+#define EN7528_RST_USB_PHY_P1	BIT(7)		/* in RST_USB */
+
+/*
+ * USB2 T-PHY port enable. The nop-phy placeholder does nothing, so bring the
+ * two USB2 PHY ports up by hand the way the kernel en7528 USB PHY driver's
+ * .init does: write ACR3_ENABLE to each U2 port block. PHY MMIO base
+ * 0x1fa80000; U2 port bases 0x300 / 0x1300; ACR3 at +0x1c.
+ */
+#define EN7528_USB_PHY_BASE	0xbfa80000
+#define EN7528_USB_PHY_U2_P0	0x300
+#define EN7528_USB_PHY_U2_P1	0x1300
+#define EN7528_USB_PHY_ACR3	0x1c
+#define EN7528_USB_PHY_ACR3_ENABLE	0xc0240000
+
+#define SCU_R(off)	__raw_readl((void __iomem *)(0xbfb00000 + (off)))
+#define SCU_W(off, v)	__raw_writel((v), (void __iomem *)(0xbfb00000 + (off)))
+
+static void en7528_usb_init(void)
+{
+	u32 v;
+
+	/*
+	 * SoC-level USB clock/PLL + XSI mux + reset, reverse-engineered from the
+	 * stock JCOW407 vendor kernel (MU3H driver, routine at 0x56f170). This is
+	 * what the mainline OpenWrt kernel relies on the bootloader to have done,
+	 * and which the bootbase does NOT do in our ramboot path — so the xHCI MAC
+	 * (0x1fb90000) comes up unclocked without it.
+	 */
+	v = SCU_R(0x90);			/* USB clock/PLL enable: bits[30:29]=11 */
+	SCU_W(0x90, (v & 0x9fffffff) | 0x60000000);
+	mdelay(10);				/* PLL settle */
+	v = SCU_R(0xa8); SCU_W(0xa8, v | 0x600);
+	v = SCU_R(0xac); SCU_W(0xac, v & ~0x30000000);
+	v = SCU_R(0xa8); SCU_W(0xa8, v | 0x6000);
+	v = SCU_R(0xa8); SCU_W(0xa8, v & ~0x1800);
+	v = SCU_R(0xac); SCU_W(0xac, v & ~0x08000000);
+	v = SCU_R(0xec); SCU_W(0xec, v & ~0x80000000);	/* RST_USB assert */
+	mdelay(1);
+	v = SCU_R(0xec); SCU_W(0xec, v | 0x80000000);	/* RST_USB deassert */
+	mdelay(6);
+
+	/* legacy per-domain reset release (harmless, kept) */
+	v = SCU_R(0x834); SCU_W(0x834, v & ~EN7528_RST_USB_HOST_P0);
+	v = SCU_R(0xec);  SCU_W(0xec, v & ~(EN7528_RST_USB_PHY_P0 | EN7528_RST_USB_PHY_P1));
+	mdelay(1);
+
+	/* enable both USB2 T-PHY ports (kernel en7528 phy .init) */
+	__raw_writel(EN7528_USB_PHY_ACR3_ENABLE,
+		     (void __iomem *)(EN7528_USB_PHY_BASE +
+				      EN7528_USB_PHY_U2_P0 + EN7528_USB_PHY_ACR3));
+	__raw_writel(EN7528_USB_PHY_ACR3_ENABLE,
+		     (void __iomem *)(EN7528_USB_PHY_BASE +
+				      EN7528_USB_PHY_U2_P1 + EN7528_USB_PHY_ACR3));
+	mdelay(1);
+}
+#endif
+
+int board_init(void)
+{
+	return 0;
+}
+
+int board_late_init(void)
+{
+#if defined(CONFIG_USB_XHCI_MTK)
+	en7528_usb_init();
+#endif
+#if CONFIG_IS_ENABLED(AIROHA_ETH)
+	struct udevice *dev;
+	int ret;
+
+	/*
+	 * Probe the EN751221-family Ethernet parent after the console is up and
+	 * just before initr_net() enumerates UCLASS_ETH devices (mirrors the
+	 * EN7512 reference board).
+	 */
+	ret = uclass_get_device_by_driver(UCLASS_MISC,
+					  DM_DRIVER_GET(airoha_eth), &dev);
+	if (ret && ret != -ENODEV)
+		printf("EN7528 Ethernet probe failed: %d\n", ret);
+#endif
+	return 0;
+}
diff --git a/board/econet/en7528/mk_tclinux_uboot.py b/board/econet/en7528/mk_tclinux_uboot.py
new file mode 100644
index 00000000..d63f46a3
--- /dev/null
+++ b/board/econet/en7528/mk_tclinux_uboot.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+"""
+Package U-Boot as an EcoNet/TrendChip "tclinux" HDR2 image, so the stock
+bootbase will load and run it exactly the way it loads the stock kernel.
+
+The bootbase decompresses a slot's LZMA payload to a fixed 0x80002000 and jumps
+there. U-Boot cannot be linked at that address (MIPS relocation needs the
+relocation delta 64 KiB aligned, and reserve_uboot() force-aligns relocaddr to
+64 KiB, so a TEXT_BASE of 0x80002000 always panics "Mis-aligned relocation").
+So the payload is a small relocator stub followed by a U-Boot linked at
+0x81000000; the stub copies it up and jumps.
+
+  payload = [stub @0x80002000][pad to 0x100][u-boot.bin linked @0x81000000]
+  image   = [HDR2 header 0x100][LZMA(payload)]
+
+Header layout, confirmed against the stock image on a live board
+(0x100 + kernel_len + rootfs_len == total, exactly):
+
+  0x00  "HDR2"
+  0x08  u32 LE  total size, including the 0x100 header
+  0x0c  u32 LE  JAMCRC of everything after the header
+  0x10  version string
+  0x50  u32 LE  kernel length
+  0x54  u32 LE  rootfs length
+  0x100         LZMA kernel, then the rootfs
+
+Usage: mk_tclinux_uboot.py <stub.bin> <u-boot.bin> <out.trx> [version]
+"""
+import struct
+import subprocess
+import sys
+import tempfile
+import zlib
+
+PAYLOAD_OFF = 0x100
+HDR_SIZE = 0x100
+DEFAULT_VERSION = 'UBOOT_EN7528'
+
+
+def lzma_alone(data):
+    """LZMA 'alone' stream, the format the bootbase decompressor expects.
+
+    xz streams and therefore stamps the uncompressed-size field as unknown; the
+    vendor encoder records the real size and some decoders rely on it, so the
+    field is filled in afterwards. Both are valid LZMA-alone.
+    """
+    out = subprocess.run(
+        ['xz', '--format=lzma', '--stdout', '-9', '--lzma1=preset=9,lc=1,lp=2,pb=2'],
+        input=data, check=True, capture_output=True).stdout
+
+    # xz streams, so it stamps the size field "unknown" (all 0xff). The vendor
+    # encoder (lzma_alone) records the real size and some decoders rely on it,
+    # so fill it in. Header is props(1) + dictsize(4) + uncompressed size(8).
+    out = bytearray(out)
+    struct.pack_into('<Q', out, 5, len(data))
+    return bytes(out)
+
+
+def main():
+    if len(sys.argv) < 4:
+        sys.exit(__doc__)
+
+    stub = open(sys.argv[1], 'rb').read()
+    uboot = open(sys.argv[2], 'rb').read()
+    outpath = sys.argv[3]
+    version = sys.argv[4] if len(sys.argv) > 4 else DEFAULT_VERSION
+
+    if len(stub) > PAYLOAD_OFF:
+        sys.exit('stub is %d bytes, must fit in %#x' % (len(stub), PAYLOAD_OFF))
+
+    payload = stub + b'\x00' * (PAYLOAD_OFF - len(stub)) + uboot
+    comp = lzma_alone(payload)
+
+    kernel_len = len(comp)
+    rootfs_len = 0
+    total = HDR_SIZE + kernel_len + rootfs_len
+
+    hdr = bytearray(b'\xff' * HDR_SIZE)
+    hdr[0x00:0x04] = b'HDR2'
+    hdr[0x04:0x08] = bytes([0x00, 0x01, 0x00, 0x00])   # as seen in stock images
+    struct.pack_into('<I', hdr, 0x08, total)
+    # 0x0c filled in below
+    ver = version.encode()[:0x1f] + b'\n'
+    hdr[0x10:0x10 + len(ver)] = ver
+    hdr[0x10 + len(ver):0x30] = b'\x00' * (0x30 - 0x10 - len(ver))
+    hdr[0x30] = 0x0a                                    # as seen in stock images
+    hdr[0x31:0x50] = b'\x00' * (0x50 - 0x31)
+    struct.pack_into('<I', hdr, 0x50, kernel_len)
+    struct.pack_into('<I', hdr, 0x54, rootfs_len)
+    hdr[0x58:0x100] = b'\x00' * (0x100 - 0x58)
+
+    body = comp
+    jamcrc = (zlib.crc32(body) ^ 0xFFFFFFFF) & 0xFFFFFFFF
+    struct.pack_into('<I', hdr, 0x0c, jamcrc)
+
+    image = bytes(hdr) + body
+    open(outpath, 'wb').write(image)
+
+    print('stub        : %d B' % len(stub))
+    print('u-boot      : %d B (linked 0x81000000)' % len(uboot))
+    print('payload     : %d B (entry 0x80002000)' % len(payload))
+    print('lzma        : %d B' % kernel_len)
+    print('version     : %s' % version)
+    print('JAMCRC      : 0x%08x' % jamcrc)
+    print('total       : %d B (0x%x)  [header 0x100 + kernel + rootfs]' % (total, total))
+    print('image       : %s  %d B' % (outpath, len(image)))
+    assert len(image) == total, (len(image), total)
+    print('consistency : 0x100 + %d + %d == %d  OK' % (kernel_len, rootfs_len, total))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/board/econet/en7528/tclinux_stub.S b/board/econet/en7528/tclinux_stub.S
new file mode 100644
index 00000000..4bd032d4
--- /dev/null
+++ b/board/econet/en7528/tclinux_stub.S
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Relocator stub for booting U-Boot as an EcoNet "tclinux" image.
+ *
+ * The vendor bootbase decompresses a firmware image to a fixed 0x80002000 and
+ * jumps there. U-Boot cannot be linked at that address: MIPS relocation
+ * requires (relocaddr - TEXT_BASE) to be 64 KiB aligned, and reserve_uboot()
+ * force-aligns relocaddr to 64 KiB, so a TEXT_BASE of 0x80002000 always
+ * panics with "Mis-aligned relocation".
+ *
+ * So this stub takes the entry instead: it copies the U-Boot image that
+ * follows it up to a 64 KiB aligned address and jumps there.
+ *
+ * The source is read through the cached KSEG0 alias, because the loader wrote
+ * it that way and the bytes may still only exist in cache. The destination is
+ * written through the uncached KSEG1 alias so it is guaranteed to reach DRAM
+ * without depending on this SoC's unreliable cache maintenance (see
+ * airoha_eth / en75_bmt), and nothing has cached that region before.
+ *
+ * Layout of the payload this stub heads:
+ *   +0x0000  this stub
+ *   +0x0100  u-boot.bin, linked at 0x81000000
+ */
+
+#define STUB_ENTRY	0x80002000
+#define PAYLOAD_OFF	0x100
+#define DEST		0x81000000
+/* Fixed generous span; U-Boot is ~0.5 MiB and trailing junk is harmless. */
+#define COPY_LEN	0x00100000
+
+	.set	noreorder
+	.set	noat
+	.globl	_start
+	.text
+_start:
+	/*
+	 * t0 = CACHED source = KSEG0(STUB_ENTRY + PAYLOAD_OFF).
+	 * Whoever loaded us (the bootbase decompressor, or tftpboot when
+	 * testing) wrote this image through the cache, so it must be read back
+	 * the same way - reading the uncached alias returns stale DRAM.
+	 */
+	lui	$t0, 0x8000
+	ori	$t0, $t0, (PAYLOAD_OFF + 0x2000)
+
+	/* t1 = uncached destination = KSEG1(DEST) */
+	lui	$t1, 0xa100
+
+	/* t2 = byte count */
+	lui	$t2, (COPY_LEN >> 16)
+	ori	$t2, $t2, (COPY_LEN & 0xffff)
+
+1:
+	lw	$t3, 0($t0)
+	sw	$t3, 0($t1)
+	addiu	$t0, $t0, 4
+	addiu	$t1, $t1, 4
+	addiu	$t2, $t2, -4
+	bgtz	$t2, 1b
+	nop
+
+	/* Enter U-Boot at its link address (cached KSEG0). */
+	lui	$t9, (DEST >> 16)
+	ori	$t9, $t9, (DEST & 0xffff)
+	jr	$t9
+	nop
diff --git a/configs/en7528_jcow414_r3hook_defconfig b/configs/en7528_jcow414_r3hook_defconfig
new file mode 100644
index 00000000..f847dc42
--- /dev/null
+++ b/configs/en7528_jcow414_r3hook_defconfig
@@ -0,0 +1,76 @@
+CONFIG_MIPS=y
+CONFIG_SYS_LITTLE_ENDIAN=y
+CONFIG_SKIP_LOWLEVEL_INIT=y
+CONFIG_SYS_MALLOC_LEN=0x400000
+CONFIG_SYS_MALLOC_F_LEN=0x10000
+CONFIG_NR_DRAM_BANKS=1
+CONFIG_ENV_SIZE=0x20000
+CONFIG_ENV_OFFSET=0xc0000
+CONFIG_ENV_OFFSET_REDUND=0xe0000
+CONFIG_DEFAULT_DEVICE_TREE="econet,en7528-ram"
+CONFIG_SYS_LOAD_ADDR=0x82000000
+CONFIG_ARCH_EN75XX=y
+CONFIG_BUILD_TARGET="u-boot.bin"
+CONFIG_BOARD_EN7528_R3HOOK=y
+CONFIG_TEXT_BASE=0x81E00000
+CONFIG_SYS_MIPS_TIMER_FREQ=200000000
+# CONFIG_MIPS_CACHE_SETUP is not set
+# CONFIG_MIPS_CACHE_DISABLE is not set
+# CONFIG_RESTORE_EXCEPTION_VECTOR_BASE is not set
+CONFIG_MIPS_BOOT_FDT=y
+CONFIG_FIT=y
+CONFIG_FIT_VERBOSE=y
+CONFIG_SYS_BOOTM_LEN=0x2000000
+CONFIG_BOOTDELAY=2
+CONFIG_DISPLAY_CPUINFO=y
+# CONFIG_DISPLAY_BOARDINFO is not set
+CONFIG_HUSH_PARSER=y
+CONFIG_SYS_PROMPT="EN7528> "
+CONFIG_SYS_MAXARGS=16
+CONFIG_CMD_MEMINFO=y
+CONFIG_CMD_BOOTM=y
+CONFIG_CMD_SAVEENV=y
+CONFIG_CMD_MTD=y
+# CONFIG_CMD_SETEXPR is not set
+# CONFIG_ENV_IS_NOWHERE is not set
+CONFIG_ENV_IS_IN_MTD=y
+CONFIG_ENV_MTD_DEV="spi-nand0"
+CONFIG_ENV_REDUNDANT=y
+CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE=y
+CONFIG_ENV_DEFAULT_ENV_TEXT_FILE="defenvs/en7528_jcow414_r3hook_env"
+CONFIG_BOARD_LATE_INIT=y
+CONFIG_NET=y
+CONFIG_NETDEVICES=y
+CONFIG_CMD_NET=y
+CONFIG_CMD_DHCP=y
+CONFIG_CMD_PING=y
+CONFIG_CMD_MII=y
+CONFIG_NET_RANDOM_ETHADDR=y
+CONFIG_DM_MDIO=y
+CONFIG_PHY=y
+CONFIG_NOP_PHY=y
+CONFIG_CLK=y
+CONFIG_DMA=y
+CONFIG_AIROHA_ETH=y
+CONFIG_USB=y
+CONFIG_DM_USB=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_MTK=y
+CONFIG_USB_STORAGE=y
+CONFIG_CMD_USB=y
+CONFIG_BLK=y
+CONFIG_PARTITIONS=y
+CONFIG_DOS_PARTITION=y
+CONFIG_EFI_PARTITION=y
+CONFIG_FS_FAT=y
+CONFIG_CMD_FAT=y
+CONFIG_CMD_FS_GENERIC=y
+CONFIG_SPI=y
+CONFIG_DM_SPI=y
+CONFIG_AIROHA_SNFI_SPI=y
+CONFIG_MTD=y
+CONFIG_DM_MTD=y
+CONFIG_MTD_SPI_NAND=y
+CONFIG_CMD_DM=y
+CONFIG_MTD_EN75_BMT=y
+CONFIG_LZMA=y
diff --git a/configs/en7528_ram_defconfig b/configs/en7528_ram_defconfig
new file mode 100644
index 00000000..55ec279c
--- /dev/null
+++ b/configs/en7528_ram_defconfig
@@ -0,0 +1,62 @@
+CONFIG_MIPS=y
+CONFIG_SYS_LITTLE_ENDIAN=y
+CONFIG_SKIP_LOWLEVEL_INIT=y
+CONFIG_SYS_MALLOC_LEN=0x100000
+CONFIG_SYS_MALLOC_F_LEN=0x10000
+CONFIG_NR_DRAM_BANKS=1
+CONFIG_ENV_SIZE=0x1000
+CONFIG_DEFAULT_DEVICE_TREE="econet,en7528-ram"
+CONFIG_SYS_LOAD_ADDR=0x81800000
+CONFIG_ARCH_EN75XX=y
+CONFIG_BUILD_TARGET="u-boot.bin"
+CONFIG_BOARD_EN7528_RAM=y
+CONFIG_SYS_MIPS_TIMER_FREQ=200000000
+# CONFIG_MIPS_CACHE_SETUP is not set
+# CONFIG_MIPS_CACHE_DISABLE is not set
+# CONFIG_RESTORE_EXCEPTION_VECTOR_BASE is not set
+CONFIG_BOOTDELAY=3
+CONFIG_DISPLAY_CPUINFO=y
+# CONFIG_DISPLAY_BOARDINFO is not set
+CONFIG_HUSH_PARSER=y
+CONFIG_SYS_PROMPT="EN7528> "
+CONFIG_SYS_MAXARGS=16
+CONFIG_CMD_MEMINFO=y
+# CONFIG_CMD_SETEXPR is not set
+CONFIG_ENV_IS_NOWHERE=y
+CONFIG_BOARD_LATE_INIT=y
+CONFIG_NET=y
+CONFIG_NETDEVICES=y
+CONFIG_CMD_NET=y
+CONFIG_CMD_DHCP=y
+CONFIG_CMD_PING=y
+CONFIG_CMD_MII=y
+CONFIG_NET_RANDOM_ETHADDR=y
+CONFIG_DM_MDIO=y
+CONFIG_PHY=y
+CONFIG_NOP_PHY=y
+CONFIG_CLK=y
+CONFIG_DMA=y
+CONFIG_AIROHA_ETH=y
+CONFIG_USB=y
+CONFIG_DM_USB=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_MTK=y
+CONFIG_USB_STORAGE=y
+CONFIG_CMD_USB=y
+CONFIG_BLK=y
+CONFIG_PARTITIONS=y
+CONFIG_DOS_PARTITION=y
+CONFIG_EFI_PARTITION=y
+CONFIG_FS_FAT=y
+CONFIG_CMD_FAT=y
+CONFIG_CMD_FS_GENERIC=y
+CONFIG_SPI=y
+CONFIG_DM_SPI=y
+CONFIG_AIROHA_SNFI_SPI=y
+CONFIG_MTD=y
+CONFIG_DM_MTD=y
+CONFIG_MTD_SPI_NAND=y
+CONFIG_CMD_MTD=y
+CONFIG_CMD_DM=y
+CONFIG_MTD_EN75_BMT=y
+CONFIG_LZMA=y
diff --git a/configs/en7528_reference_defconfig b/configs/en7528_reference_defconfig
new file mode 100644
index 00000000..6811c7e3
--- /dev/null
+++ b/configs/en7528_reference_defconfig
@@ -0,0 +1,40 @@
+CONFIG_MIPS=y
+CONFIG_SYS_LITTLE_ENDIAN=y
+CONFIG_SKIP_LOWLEVEL_INIT=y
+CONFIG_SYS_MALLOC_LEN=0x100000
+CONFIG_SPL_LIBCOMMON_SUPPORT=y
+CONFIG_SPL_LIBGENERIC_SUPPORT=y
+CONFIG_NR_DRAM_BANKS=1
+CONFIG_ENV_SIZE=0x1000
+CONFIG_DEFAULT_DEVICE_TREE="econet,en7528-reference"
+CONFIG_SPL_SYS_MALLOC_F_LEN=0x10000
+CONFIG_SPL_SERIAL=y
+CONFIG_TPL_SYS_MALLOC_F_LEN=0x1000
+CONFIG_SPL_BSS_START_ADDR=0x80080000
+CONFIG_SPL_BSS_MAX_SIZE=0x10000
+CONFIG_SYS_LOAD_ADDR=0x81800000
+CONFIG_SPL=y
+CONFIG_ARCH_EN75XX=y
+CONFIG_BUILD_TARGET="u-boot.bin"
+CONFIG_BOARD_EN7528_REFERENCE=y
+CONFIG_SYS_MIPS_TIMER_FREQ=150000000
+CONFIG_MIPS_CACHE_SETUP=y
+CONFIG_MIPS_CACHE_DISABLE=y
+CONFIG_RESTORE_EXCEPTION_VECTOR_BASE=y
+CONFIG_MIPS_BOOT_FDT=y
+CONFIG_FIT=y
+CONFIG_BOOTDELAY=3
+CONFIG_DISPLAY_CPUINFO=y
+# CONFIG_DISPLAY_BOARDINFO is not set
+CONFIG_SPL_MAX_SIZE=0x30000
+CONFIG_SPL_SYS_MALLOC_SIMPLE=y
+CONFIG_TPL=y
+# CONFIG_TPL_FRAMEWORK is not set
+CONFIG_HUSH_PARSER=y
+CONFIG_SYS_PROMPT="EN7528> "
+CONFIG_SYS_MAXARGS=8
+CONFIG_CMD_MEMINFO=y
+# CONFIG_CMD_SETEXPR is not set
+CONFIG_ENV_IS_NOWHERE=y
+CONFIG_SPL_OF_CONTROL=y
+CONFIG_SPL_DM=y
diff --git a/defenvs/en7528_jcow414_r3hook_env b/defenvs/en7528_jcow414_r3hook_env
new file mode 100644
index 00000000..4bc9e278
--- /dev/null
+++ b/defenvs/en7528_jcow414_r3hook_env
@@ -0,0 +1,5 @@
+ipaddr=192.168.1.111
+serverip=192.168.1.166
+loadaddr=0x82000000
+bootdelay=2
+bootcmd=mtd read spi-nand0 ${loadaddr} 0x100000 0x1000000; bootm ${loadaddr}
diff --git a/include/configs/en7528.h b/include/configs/en7528.h
new file mode 100644
index 00000000..b8dc2410
--- /dev/null
+++ b/include/configs/en7528.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef __CONFIG_EN7528_H
+#define __CONFIG_EN7528_H
+
+#define CFG_SYS_SDRAM_BASE	0x80000000
+/* Above FIT scratch (0x82000000) and kernel load (0x80020000, up to ~32 MiB). */
+#define CFG_SYS_INIT_SP_OFFSET	0x04000000
+
+#endif
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v1 12/12] doc: board: econet: add EN75xx documentation
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (10 preceding siblings ...)
  2026-08-25 22:42   ` [PATCH v1 11/12] board: econet: add EN7528 board support AK Sharma
@ 2026-08-25 22:42   ` AK Sharma
  2026-08-25 23:02   ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support Tom Rini
  2026-08-26 14:26   ` Matheus Sampaio Queiroga
  13 siblings, 0 replies; 26+ messages in thread
From: AK Sharma @ 2026-08-25 22:42 UTC (permalink / raw)
  To: u-boot
  Cc: Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi, Tom Rini, AK Sharma

Add the EcoNet vendor index and the EN7528 porting notes.

Signed-off-by: AK Sharma <maskachoska@yahoo.com>
---
 doc/board/econet/en7528.rst | 39 +++++++++++++++++++++++++++++++++++++
 doc/board/econet/index.rst  | 10 ++++++++++
 doc/board/index.rst         |  1 +
 3 files changed, 50 insertions(+)
 create mode 100644 doc/board/econet/en7528.rst
 create mode 100644 doc/board/econet/index.rst

diff --git a/doc/board/econet/en7528.rst b/doc/board/econet/en7528.rst
new file mode 100644
index 00000000..fc4eaf2e
--- /dev/null
+++ b/doc/board/econet/en7528.rst
@@ -0,0 +1,39 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+EN7528 reference board
+======================
+
+This target provides the U-Boot port for the MIPS-based EcoNet/Airoha
+EN7528 SoC (MIPS 1004Kc, little-endian), used in many GPON/XPON ONT home
+gateways. It replaces the proprietary multi-stage TCBoot flow with the
+U-Boot TPL, SPL and U-Boot proper stages.
+
+Boot flow
+---------
+
+The boot ROM starts the TPL from the uncached flash window at
+``0xbfc00000``. The TPL enables the Front-End SRAM, initializes the serial
+port and reads the DDR calibration image and SPL through the early SFC
+reader. After DDR initialization, SPL loads ``u-boot.img`` into DRAM.
+
+Build
+-----
+
+.. code-block:: bash
+
+    make en7528_reference_defconfig
+    make CROSS_COMPILE=mipsel-linux-
+
+Defconfigs
+----------
+
+- ``en7528_reference_defconfig`` - reference board (DASAN H660GM-A)
+- ``en7528_ram_defconfig`` - RAM / chainloaded build
+- ``en7528_jcow414_r3hook_defconfig`` - JioFiber JCOW414 R3-hook variant
+
+DDR calibration blob
+--------------------
+
+The TPL loads a vendor DDR calibration stage from flash; it is not part of
+the U-Boot source. See ``arch/mips/mach-en75xx/README.ddr-blob`` for how to
+extract it from the vendor bootloader and where to flash it.
diff --git a/doc/board/econet/index.rst b/doc/board/econet/index.rst
new file mode 100644
index 00000000..d691db07
--- /dev/null
+++ b/doc/board/econet/index.rst
@@ -0,0 +1,10 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+EcoNet/Airoha
+=============
+
+.. toctree::
+   :maxdepth: 2
+
+   en7512
+   en7528
diff --git a/doc/board/index.rst b/doc/board/index.rst
index fcb4224b..60d75003 100644
--- a/doc/board/index.rst
+++ b/doc/board/index.rst
@@ -29,6 +29,7 @@ Board-specific doc
    coreboot/index
    emcraft/index
    emulation/index
+   econet/index
    gateworks/index
    google/index
    highbank/index
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 02/12] mips: en75xx: add TPL and SPL early boot stages
  2026-08-25 22:41   ` [PATCH v1 02/12] mips: en75xx: add TPL and SPL early boot stages AK Sharma
@ 2026-08-25 22:53     ` Tom Rini
  0 siblings, 0 replies; 26+ messages in thread
From: Tom Rini @ 2026-08-25 22:53 UTC (permalink / raw)
  To: AK Sharma
  Cc: u-boot, Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi

[-- Attachment #1: Type: text/plain, Size: 727 bytes --]

On Wed, Aug 26, 2026 at 04:11:57AM +0530, AK Sharma wrote:
> Add the TPL and SPL stages for the EN75xx. The TPL brings up the cache
> and SoC straps, runs the vendor DDR calibration stage from flash via the
> early SFC (serial-flash controller) reader, then the SPL loads U-Boot
> proper.
> 
> The DDR calibration stage is a vendor artifact read from flash at boot
> and is not shipped in-tree; see arch/mips/mach-en75xx/README.ddr-blob for
> how to obtain it and its checksums.
> 
> Signed-off-by: AK Sharma <maskachoska@yahoo.com>
> ---
>  arch/mips/mach-en75xx/README.ddr-blob |  21 ++

Documentation needs to be in rST format and under doc/ (and then make
KDOC_WERROR=1 htmldocs must be clean).

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 09/12] mips: dts: add EcoNet EN75xx device trees
  2026-08-25 22:42   ` [PATCH v1 09/12] mips: dts: add EcoNet EN75xx device trees AK Sharma
@ 2026-08-25 22:55     ` Tom Rini
  0 siblings, 0 replies; 26+ messages in thread
From: Tom Rini @ 2026-08-25 22:55 UTC (permalink / raw)
  To: AK Sharma
  Cc: u-boot, Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi

[-- Attachment #1: Type: text/plain, Size: 1433 bytes --]

On Wed, Aug 26, 2026 at 04:12:04AM +0530, AK Sharma wrote:

> Add the device trees for the EN7512 and EN7528 reference boards and the
> EN7528 RAM-boot variant.
> 
> Signed-off-by: AK Sharma <maskachoska@yahoo.com>
> ---
>  arch/mips/dts/Makefile                        |   2 +
>  .../dts/econet,en7512-reference-u-boot.dtsi   |  58 +++++++
>  arch/mips/dts/econet,en7512-reference.dts     |  27 +++
>  arch/mips/dts/econet,en7512.dtsi              |  88 ++++++++++
>  arch/mips/dts/econet,en7528-ram-u-boot.dtsi   |   6 +
>  arch/mips/dts/econet,en7528-ram.dts           |  72 ++++++++
>  .../dts/econet,en7528-reference-u-boot.dtsi   |  62 +++++++
>  arch/mips/dts/econet,en7528-reference.dts     |  25 +++
>  arch/mips/dts/econet,en7528.dtsi              | 164 ++++++++++++++++++
>  9 files changed, 504 insertions(+)
>  create mode 100644 arch/mips/dts/econet,en7512-reference-u-boot.dtsi
>  create mode 100644 arch/mips/dts/econet,en7512-reference.dts
>  create mode 100644 arch/mips/dts/econet,en7512.dtsi
>  create mode 100644 arch/mips/dts/econet,en7528-ram-u-boot.dtsi
>  create mode 100644 arch/mips/dts/econet,en7528-ram.dts
>  create mode 100644 arch/mips/dts/econet,en7528-reference-u-boot.dtsi
>  create mode 100644 arch/mips/dts/econet,en7528-reference.dts
>  create mode 100644 arch/mips/dts/econet,en7528.dtsi

What is the status of upstreaming all of these dts files? Thanks.

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 11/12] board: econet: add EN7528 board support
  2026-08-25 22:42   ` [PATCH v1 11/12] board: econet: add EN7528 board support AK Sharma
@ 2026-08-25 22:57     ` Tom Rini
  2026-08-25 23:00     ` Tom Rini
  1 sibling, 0 replies; 26+ messages in thread
From: Tom Rini @ 2026-08-25 22:57 UTC (permalink / raw)
  To: AK Sharma
  Cc: u-boot, Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi

[-- Attachment #1: Type: text/plain, Size: 2611 bytes --]

On Wed, Aug 26, 2026 at 04:12:06AM +0530, AK Sharma wrote:

> Add the EcoNet EN7528 board: board glue, the reference/RAM/tclinux and
> JCOW414 R3-hook defconfigs, board header and default environment.
> 
> Signed-off-by: AK Sharma <maskachoska@yahoo.com>
[snip]
> diff --git a/board/econet/en7528/MAINTAINERS b/board/econet/en7528/MAINTAINERS
> new file mode 100644
> index 00000000..69cefe4a
> --- /dev/null
> +++ b/board/econet/en7528/MAINTAINERS
> @@ -0,0 +1,13 @@
> +ECONET/AIROHA EN75XX
> +M:	AK Sharma <maskachoska@yahoo.com>
> +S:	Maintained
> +F:	arch/mips/mach-en75xx/
> +F:	arch/mips/dts/econet,en7528*
> +F:	board/econet/en7528/
> +F:	configs/en7528_*_defconfig
> +F:	defenvs/en7528_jcow414_r3hook_env
> +F:	doc/board/econet/en7528.rst
> +F:	doc/board/econet/index.rst
> +F:	drivers/mtd/nand/spi/en75_bmt.*
> +F:	drivers/serial/serial_en75xx.c
> +F:	include/configs/en7528.h

You should make use of the "N:" format to match on things like en75xx or
en7528 and so forth.

[snip]
> diff --git a/board/econet/en7528/mk_tclinux_uboot.py b/board/econet/en7528/mk_tclinux_uboot.py
> new file mode 100644
> index 00000000..d63f46a3
> --- /dev/null
> +++ b/board/econet/en7528/mk_tclinux_uboot.py
> @@ -0,0 +1,112 @@
> +#!/usr/bin/env python3
> +"""
> +Package U-Boot as an EcoNet/TrendChip "tclinux" HDR2 image, so the stock
> +bootbase will load and run it exactly the way it loads the stock kernel.
> +
> +The bootbase decompresses a slot's LZMA payload to a fixed 0x80002000 and jumps
> +there. U-Boot cannot be linked at that address (MIPS relocation needs the
> +relocation delta 64 KiB aligned, and reserve_uboot() force-aligns relocaddr to
> +64 KiB, so a TEXT_BASE of 0x80002000 always panics "Mis-aligned relocation").
> +So the payload is a small relocator stub followed by a U-Boot linked at
> +0x81000000; the stub copies it up and jumps.
> +
> +  payload = [stub @0x80002000][pad to 0x100][u-boot.bin linked @0x81000000]
> +  image   = [HDR2 header 0x100][LZMA(payload)]
> +
> +Header layout, confirmed against the stock image on a live board
> +(0x100 + kernel_len + rootfs_len == total, exactly):
> +
> +  0x00  "HDR2"
> +  0x08  u32 LE  total size, including the 0x100 header
> +  0x0c  u32 LE  JAMCRC of everything after the header
> +  0x10  version string
> +  0x50  u32 LE  kernel length
> +  0x54  u32 LE  rootfs length
> +  0x100         LZMA kernel, then the rootfs
> +
> +Usage: mk_tclinux_uboot.py <stub.bin> <u-boot.bin> <out.trx> [version]
> +"""

This should be part of the binman support I believe.

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 05/12] mtd: spi-nand: add EN75 BMT bad-block management support
  2026-08-25 22:42   ` [PATCH v1 05/12] mtd: spi-nand: add EN75 BMT bad-block management support AK Sharma
@ 2026-08-25 22:59     ` Tom Rini
  2026-08-26  6:47       ` Frieder Schrempf
  0 siblings, 1 reply; 26+ messages in thread
From: Tom Rini @ 2026-08-25 22:59 UTC (permalink / raw)
  To: AK Sharma
  Cc: u-boot, Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi

[-- Attachment #1: Type: text/plain, Size: 2914 bytes --]

On Wed, Aug 26, 2026 at 04:12:00AM +0530, AK Sharma wrote:

> Add read support for the EcoNet/Airoha BMT/BBT (bad-block management
> table) layout used by the EN75xx vendor bootloaders, so U-Boot maps the
> SPI-NAND logical blocks the same way as the vendor firmware.
> 
> Signed-off-by: AK Sharma <maskachoska@yahoo.com>
> ---
>  drivers/mtd/nand/spi/Kconfig    |   8 +
>  drivers/mtd/nand/spi/Makefile   |   1 +
>  drivers/mtd/nand/spi/core.c     |   9 +-
>  drivers/mtd/nand/spi/en75_bmt.c | 665 ++++++++++++++++++++++++++++++++
>  drivers/mtd/nand/spi/en75_bmt.h |  24 ++
>  5 files changed, 706 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/mtd/nand/spi/en75_bmt.c
>  create mode 100644 drivers/mtd/nand/spi/en75_bmt.h
> 
> diff --git a/drivers/mtd/nand/spi/Kconfig b/drivers/mtd/nand/spi/Kconfig
> index 1124dada..09531914 100644
> --- a/drivers/mtd/nand/spi/Kconfig
> +++ b/drivers/mtd/nand/spi/Kconfig
> @@ -6,3 +6,11 @@ menuconfig MTD_SPI_NAND
>  	select SPI_MEM
>  	help
>  	  This is the framework for the SPI NAND device drivers.
> +
> +config MTD_EN75_BMT
> +	bool "EcoNet EN75xx vendor BMT/BBT translation (read-only)"
> +	depends on MTD_SPI_NAND
> +	help
> +	  Honour the EcoNet/Airoha factory BBT and BMT so logical NAND
> +	  addresses match the bootbase and Linux. Read-only: never rewrite
> +	  the tables.
> diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
> index a7a0b2cb..20aa718d 100644
> --- a/drivers/mtd/nand/spi/Makefile
> +++ b/drivers/mtd/nand/spi/Makefile
> @@ -1,6 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0
>  
>  spinand-objs := core.o otp.o
> +spinand-objs += $(if $(CONFIG_MTD_EN75_BMT),en75_bmt.o)
>  spinand-objs += alliancememory.o ato.o esmt.o fmsh.o foresee.o gigadevice.o macronix.o
>  spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
>  obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
> diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
> index 14af4264..29fdefe2 100644
> --- a/drivers/mtd/nand/spi/core.c
> +++ b/drivers/mtd/nand/spi/core.c
> @@ -33,6 +33,7 @@
>  #include <linux/mtd/spinand.h>
>  #include <linux/printk.h>
>  #include <linux/delay.h>
> +#include "en75_bmt.h"
>  #endif
>  
>  struct spinand_plat {
> @@ -1703,15 +1704,21 @@ static int spinand_probe(struct udevice *dev)
>  #ifndef __UBOOT__
>  	ret = mtd_device_register(mtd, NULL, 0);
>  #else
> +	ret = en75_bmt_attach(mtd);
> +	if (ret)
> +		goto err_spinand_cleanup;
> +
>  	ret = add_mtd_device(mtd);
>  #endif
>  	if (ret)
> -		goto err_spinand_cleanup;
> +		goto err_bmt_detach;
>  
>  	plat->mtd = mtd;
>  
>  	return 0;
>  
> +err_bmt_detach:
> +	en75_bmt_detach(mtd);
>  err_spinand_cleanup:
>  	spinand_cleanup(spinand);

I am concerned about generic code path changes here, how is this handled
in the upstream kernel?

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 11/12] board: econet: add EN7528 board support
  2026-08-25 22:42   ` [PATCH v1 11/12] board: econet: add EN7528 board support AK Sharma
  2026-08-25 22:57     ` Tom Rini
@ 2026-08-25 23:00     ` Tom Rini
  1 sibling, 0 replies; 26+ messages in thread
From: Tom Rini @ 2026-08-25 23:00 UTC (permalink / raw)
  To: AK Sharma
  Cc: u-boot, Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi

[-- Attachment #1: Type: text/plain, Size: 740 bytes --]

On Wed, Aug 26, 2026 at 04:12:06AM +0530, AK Sharma wrote:

> Add the EcoNet EN7528 board: board glue, the reference/RAM/tclinux and
> JCOW414 R3-hook defconfigs, board header and default environment.
> 
> Signed-off-by: AK Sharma <maskachoska@yahoo.com>
[snip]
> diff --git a/defenvs/en7528_jcow414_r3hook_env b/defenvs/en7528_jcow414_r3hook_env
> new file mode 100644
> index 00000000..4bc9e278
> --- /dev/null
> +++ b/defenvs/en7528_jcow414_r3hook_env
> @@ -0,0 +1,5 @@
> +ipaddr=192.168.1.111
> +serverip=192.168.1.166
> +loadaddr=0x82000000
> +bootdelay=2
> +bootcmd=mtd read spi-nand0 ${loadaddr} 0x100000 0x1000000; bootm ${loadaddr}

This should just be in the board directory as other platforms do.

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (11 preceding siblings ...)
  2026-08-25 22:42   ` [PATCH v1 12/12] doc: board: econet: add EN75xx documentation AK Sharma
@ 2026-08-25 23:02   ` Tom Rini
  2026-08-26 14:26   ` Matheus Sampaio Queiroga
  13 siblings, 0 replies; 26+ messages in thread
From: Tom Rini @ 2026-08-25 23:02 UTC (permalink / raw)
  To: AK Sharma
  Cc: u-boot, Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi

[-- Attachment #1: Type: text/plain, Size: 1515 bytes --]

On Wed, Aug 26, 2026 at 04:11:55AM +0530, AK Sharma wrote:

> This series adds U-Boot support for the EcoNet/Airoha EN75xx MIPS SoC
> family: the big-endian MIPS34Kc EN7512/EN7521 and the little-endian
> MIPS1004Kc EN7528, used in many GPON/XPON ONT home gateways. It extends
> the existing Airoha SNFI SPI-NAND and GDM Ethernet drivers.
> 
> It brings up the SoC (TPL/SPL, cache/CM init), and adds the UART, the
> EN75 BMT SPI-NAND bad-block mapping, weak bootm decompression hooks for
> the coherent CM, and Ethernet. Reference boards for the EN7512 and EN7528
> are included, plus the EN7528 R3-hook boot variant.
> 
> The DDR calibration stage is a vendor artifact and is not shipped
> in-tree. The reference board packages it as a binman "blob-ext"; build
> those configs with BINMAN_ALLOW_MISSING=1 and supply the blob to run
> (see arch/mips/mach-en75xx/README.ddr-blob). A native DDR-init
> implementation is a known TODO.
> 
> Tested on a Nokia G-2425G-A (EN7528, MT29F2G01 SPI-NAND): boots to the
> prompt with correct 256 MiB DRAM, working Ethernet/TFTP and SPI-NAND.

I am glad to see this effort. I am, after reviewing a few of the
patches, worried that this is still too much of a vendor port rather
than re-working the vendor port to match upstream best practices.
Perhaps one thing that would help is to keep in mind what the minimum
set of additions required are, to get a good amount of functionality.
And then follow-up patches once the core is in. Thanks.

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 08/12] boot: image: add weak hooks for the decompression buffer and flush
  2026-08-25 22:42   ` [PATCH v1 08/12] boot: image: add weak hooks for the decompression buffer and flush AK Sharma
@ 2026-08-25 23:05     ` Tom Rini
  0 siblings, 0 replies; 26+ messages in thread
From: Tom Rini @ 2026-08-25 23:05 UTC (permalink / raw)
  To: AK Sharma
  Cc: u-boot, Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi

[-- Attachment #1: Type: text/plain, Size: 854 bytes --]

On Wed, Aug 26, 2026 at 04:12:03AM +0530, AK Sharma wrote:

> Add two weak hooks around the bootm image decompression:
> image_get_decomp_buf() lets a platform redirect the decompression
> destination (e.g. to an uncached window), and image_decomp_flush() lets
> it flush the caches over the decompressed image. Both default to no-ops,
> so existing platforms are unaffected. The EN75xx uses them to keep the
> kernel coherent on its cache-coherent CM.
> 
> Signed-off-by: AK Sharma <maskachoska@yahoo.com>
> ---
>  boot/image.c    | 34 +++++++++++++++++++++++++++-------
>  include/image.h |  5 +++++
>  2 files changed, 32 insertions(+), 7 deletions(-)

This seems like something that should be possible with the normal
environment-based restrictions documented in doc/usage/environment.rst
around bootm_size and similar.

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 07/12] mtd: env, cmd: add EN75 NAND remap handling
  2026-08-25 22:42   ` [PATCH v1 07/12] mtd: env, cmd: add EN75 NAND remap handling AK Sharma
@ 2026-08-25 23:06     ` Tom Rini
  0 siblings, 0 replies; 26+ messages in thread
From: Tom Rini @ 2026-08-25 23:06 UTC (permalink / raw)
  To: AK Sharma
  Cc: u-boot, Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Frieder Schrempf, Jerome Forissier, Marek Vasut,
	Michael Trimarchi

[-- Attachment #1: Type: text/plain, Size: 564 bytes --]

On Wed, Aug 26, 2026 at 04:12:02AM +0530, AK Sharma wrote:

> Honour the EcoNet/Airoha NAND "remap" region attribute when reading and
> writing MTD partitions from the env and the mtd command, matching the
> vendor block layout.
> 
> Signed-off-by: AK Sharma <maskachoska@yahoo.com>
> ---
>  cmd/mtd.c   | 30 ++++++++++++++--
>  env/Kconfig |  2 +-
>  env/mtd.c   | 98 +++++++++++++++++++++++++++++++++--------------------

This is another place where I am concerned about a lot of changes to
generic code to support a specific platform.

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 05/12] mtd: spi-nand: add EN75 BMT bad-block management support
  2026-08-25 22:59     ` Tom Rini
@ 2026-08-26  6:47       ` Frieder Schrempf
  0 siblings, 0 replies; 26+ messages in thread
From: Frieder Schrempf @ 2026-08-26  6:47 UTC (permalink / raw)
  To: Tom Rini, AK Sharma
  Cc: u-boot, Christian Marangi, Daniel Schwierzeck, Dario Binacchi,
	Jerome Forissier, Marek Vasut, Michael Trimarchi

On 26.08.26 00:59, Tom Rini wrote:
> On Wed, Aug 26, 2026 at 04:12:00AM +0530, AK Sharma wrote:
> 
>> Add read support for the EcoNet/Airoha BMT/BBT (bad-block management
>> table) layout used by the EN75xx vendor bootloaders, so U-Boot maps the
>> SPI-NAND logical blocks the same way as the vendor firmware.
>>
>> Signed-off-by: AK Sharma <maskachoska@yahoo.com>
>> ---
>>  drivers/mtd/nand/spi/Kconfig    |   8 +
>>  drivers/mtd/nand/spi/Makefile   |   1 +
>>  drivers/mtd/nand/spi/core.c     |   9 +-
>>  drivers/mtd/nand/spi/en75_bmt.c | 665 ++++++++++++++++++++++++++++++++
>>  drivers/mtd/nand/spi/en75_bmt.h |  24 ++
>>  5 files changed, 706 insertions(+), 1 deletion(-)
>>  create mode 100644 drivers/mtd/nand/spi/en75_bmt.c
>>  create mode 100644 drivers/mtd/nand/spi/en75_bmt.h
>>
>> diff --git a/drivers/mtd/nand/spi/Kconfig b/drivers/mtd/nand/spi/Kconfig
>> index 1124dada..09531914 100644
>> --- a/drivers/mtd/nand/spi/Kconfig
>> +++ b/drivers/mtd/nand/spi/Kconfig
>> @@ -6,3 +6,11 @@ menuconfig MTD_SPI_NAND
>>  	select SPI_MEM
>>  	help
>>  	  This is the framework for the SPI NAND device drivers.
>> +
>> +config MTD_EN75_BMT
>> +	bool "EcoNet EN75xx vendor BMT/BBT translation (read-only)"
>> +	depends on MTD_SPI_NAND
>> +	help
>> +	  Honour the EcoNet/Airoha factory BBT and BMT so logical NAND
>> +	  addresses match the bootbase and Linux. Read-only: never rewrite
>> +	  the tables.
>> diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
>> index a7a0b2cb..20aa718d 100644
>> --- a/drivers/mtd/nand/spi/Makefile
>> +++ b/drivers/mtd/nand/spi/Makefile
>> @@ -1,6 +1,7 @@
>>  # SPDX-License-Identifier: GPL-2.0
>>  
>>  spinand-objs := core.o otp.o
>> +spinand-objs += $(if $(CONFIG_MTD_EN75_BMT),en75_bmt.o)
>>  spinand-objs += alliancememory.o ato.o esmt.o fmsh.o foresee.o gigadevice.o macronix.o
>>  spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
>>  obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
>> diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
>> index 14af4264..29fdefe2 100644
>> --- a/drivers/mtd/nand/spi/core.c
>> +++ b/drivers/mtd/nand/spi/core.c
>> @@ -33,6 +33,7 @@
>>  #include <linux/mtd/spinand.h>
>>  #include <linux/printk.h>
>>  #include <linux/delay.h>
>> +#include "en75_bmt.h"
>>  #endif
>>  
>>  struct spinand_plat {
>> @@ -1703,15 +1704,21 @@ static int spinand_probe(struct udevice *dev)
>>  #ifndef __UBOOT__
>>  	ret = mtd_device_register(mtd, NULL, 0);
>>  #else
>> +	ret = en75_bmt_attach(mtd);
>> +	if (ret)
>> +		goto err_spinand_cleanup;
>> +
>>  	ret = add_mtd_device(mtd);
>>  #endif
>>  	if (ret)
>> -		goto err_spinand_cleanup;
>> +		goto err_bmt_detach;
>>  
>>  	plat->mtd = mtd;
>>  
>>  	return 0;
>>  
>> +err_bmt_detach:
>> +	en75_bmt_detach(mtd);
>>  err_spinand_cleanup:
>>  	spinand_cleanup(spinand);
> 
> I am concerned about generic code path changes here, how is this handled
> in the upstream kernel?

I agree. I don't see any justification or explanation for adding
chip-specific code to the spinand core.

Also, would it be possible for you to upstream the support to the Linux
kernel first and then sync back to U-Boot?

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support
  2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
                     ` (12 preceding siblings ...)
  2026-08-25 23:02   ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support Tom Rini
@ 2026-08-26 14:26   ` Matheus Sampaio Queiroga
  2026-08-26 15:05     ` Tom Rini
  13 siblings, 1 reply; 26+ messages in thread
From: Matheus Sampaio Queiroga @ 2026-08-26 14:26 UTC (permalink / raw)
  To: maskachoska
  Cc: ansuelsmth, daniel.schwierzeck, dario.binacchi, frieder.schrempf,
	jerome.forissier, marex, michael, trini, u-boot

Dude, if you're going to get other people's patchers and not even mention that someone else used the base, don't even send the patchers, I just made a draft for the SoC en751221 and en7528, on the openwrt forum itself I said that the boot was unstable, especially the ethernet of the en7528 and the spi0



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support
  2026-08-26 14:26   ` Matheus Sampaio Queiroga
@ 2026-08-26 15:05     ` Tom Rini
  2026-08-26 15:16       ` AK Sharma
  0 siblings, 1 reply; 26+ messages in thread
From: Tom Rini @ 2026-08-26 15:05 UTC (permalink / raw)
  To: Matheus Sampaio Queiroga, maskachoska
  Cc: ansuelsmth, daniel.schwierzeck, dario.binacchi, frieder.schrempf,
	jerome.forissier, marex, michael, u-boot

[-- Attachment #1: Type: text/plain, Size: 600 bytes --]

On Wed, Aug 26, 2026 at 11:26:18AM -0300, Matheus Sampaio Queiroga wrote:

> Dude, if you're going to get other people's patchers and not even mention that someone else used the base, don't even send the patchers, I just made a draft for the SoC en751221 and en7528, on the openwrt forum itself I said that the boot was unstable, especially the ethernet of the en7528 and the spi0

Posting someone elses work violates the rules in
https://docs.u-boot.org/en/latest/develop/sending_patches.html#attributing-code-copyrights-signing
and specifically https://developercertificate.org/

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support
  2026-08-26 15:05     ` Tom Rini
@ 2026-08-26 15:16       ` AK Sharma
  2026-08-26 15:17         ` Tom Rini
  0 siblings, 1 reply; 26+ messages in thread
From: AK Sharma @ 2026-08-26 15:16 UTC (permalink / raw)
  To: Tom Rini
  Cc: Matheus Sampaio Queiroga, ansuelsmth, daniel.schwierzeck,
	dario.binacchi, frieder.schrempf, jerome.forissier, marex,
	michael, u-boot

Please drop this series — do not apply it. It is derived from Matheus Sampaio Queiroga's GPL-2.0+ work in github.com/sirherobrine23/en7523_u-boot (the en751221 branch), which I submitted without crediting him and without his agreement; it was also an early draft, not ready for mainline. I apologize to Matheus and to the list. If a corrected series is sent later it will carry proper authorship and his sign-off. Sorry for the noise.

Regards,
Ak

> On 26 Aug 2026, at 8:35 PM, Tom Rini <trini@konsulko.com> wrote:
> 
> On Wed, Aug 26, 2026 at 11:26:18AM -0300, Matheus Sampaio Queiroga wrote:
> 
>> Dude, if you're going to get other people's patchers and not even mention that someone else used the base, don't even send the patchers, I just made a draft for the SoC en751221 and en7528, on the openwrt forum itself I said that the boot was unstable, especially the ethernet of the en7528 and the spi0
> 
> Posting someone elses work violates the rules in
> https://docs.u-boot.org/en/latest/develop/sending_patches.html#attributing-code-copyrights-signing
> and specifically https://developercertificate.org/
> 
> -- 
> Tom


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support
  2026-08-26 15:16       ` AK Sharma
@ 2026-08-26 15:17         ` Tom Rini
  0 siblings, 0 replies; 26+ messages in thread
From: Tom Rini @ 2026-08-26 15:17 UTC (permalink / raw)
  To: AK Sharma
  Cc: Matheus Sampaio Queiroga, ansuelsmth, daniel.schwierzeck,
	dario.binacchi, frieder.schrempf, jerome.forissier, marex,
	michael, u-boot

[-- Attachment #1: Type: text/plain, Size: 558 bytes --]

On Wed, Aug 26, 2026 at 08:46:00PM +0530, AK Sharma wrote:

> Please drop this series — do not apply it. It is derived from Matheus Sampaio Queiroga's GPL-2.0+ work in github.com/sirherobrine23/en7523_u-boot (the en751221 branch), which I submitted without crediting him and without his agreement; it was also an early draft, not ready for mainline. I apologize to Matheus and to the list. If a corrected series is sent later it will carry proper authorship and his sign-off. Sorry for the noise.

Thank you for the responsible follow-up.

-- 
Tom

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2026-08-26 15:17 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20260825224330.713151-1-maskachoska.ref@yahoo.com>
2026-08-25 22:41 ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support AK Sharma
2026-08-25 22:41   ` [PATCH v1 01/12] mips: en75xx: add EcoNet/Airoha EN75xx " AK Sharma
2026-08-25 22:41   ` [PATCH v1 02/12] mips: en75xx: add TPL and SPL early boot stages AK Sharma
2026-08-25 22:53     ` Tom Rini
2026-08-25 22:41   ` [PATCH v1 03/12] serial: en75xx: add EcoNet/Airoha EN75xx UART driver AK Sharma
2026-08-25 22:41   ` [PATCH v1 04/12] spi: airoha-snfi: add EN7528 SPI-NAND controller support AK Sharma
2026-08-25 22:42   ` [PATCH v1 05/12] mtd: spi-nand: add EN75 BMT bad-block management support AK Sharma
2026-08-25 22:59     ` Tom Rini
2026-08-26  6:47       ` Frieder Schrempf
2026-08-25 22:42   ` [PATCH v1 06/12] net: airoha-eth: add EN7528 Ethernet support AK Sharma
2026-08-25 22:42   ` [PATCH v1 07/12] mtd: env, cmd: add EN75 NAND remap handling AK Sharma
2026-08-25 23:06     ` Tom Rini
2026-08-25 22:42   ` [PATCH v1 08/12] boot: image: add weak hooks for the decompression buffer and flush AK Sharma
2026-08-25 23:05     ` Tom Rini
2026-08-25 22:42   ` [PATCH v1 09/12] mips: dts: add EcoNet EN75xx device trees AK Sharma
2026-08-25 22:55     ` Tom Rini
2026-08-25 22:42   ` [PATCH v1 10/12] board: econet: add EN7512 reference board AK Sharma
2026-08-25 22:42   ` [PATCH v1 11/12] board: econet: add EN7528 board support AK Sharma
2026-08-25 22:57     ` Tom Rini
2026-08-25 23:00     ` Tom Rini
2026-08-25 22:42   ` [PATCH v1 12/12] doc: board: econet: add EN75xx documentation AK Sharma
2026-08-25 23:02   ` [PATCH v1 00/12] mips: add EcoNet/Airoha EN75xx (EN7512/EN7528) SoC support Tom Rini
2026-08-26 14:26   ` Matheus Sampaio Queiroga
2026-08-26 15:05     ` Tom Rini
2026-08-26 15:16       ` AK Sharma
2026-08-26 15:17         ` Tom Rini

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.