From 1dc6b007b81954c5edc4e5b8106298158163c20b Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 18:24:04 +0200 Subject: [PATCH 01/22] ARM: hisilicon: add Hi3520DV200 SoC support (DT + mach-hibvt) Single Cortex-A9 NVR/DVR SoC. Boots on real hardware (XiongMai NBD6808T-PL) from the stock 2010 U-Boot with an appended DTB, to an OpenIPC userspace shell on ttyAMA0. Addresses and interrupts follow the vendor 3.0.8 BSP. Details that differ from the existing QEMU-only 4.9 port: - The UARTs run from bus/4 (82.5 MHz with the APB source the bootloader selects in PERI_CRG57), not from the 24 MHz crystal. - 0x20700000 is HiSilicon's own L2 controller (vendor "hil2v200"), not a PL310: its ID register reads 0 and treating it as a PL310 hangs the boot. The bootloader leaves it off; no driver yet. - The SP805 watchdog at 0x20040000 runs at 3 MHz (measured). Clocks are fixed-clocks until there is a CRG driver. --- arch/arm/boot/dts/hisilicon/Makefile | 2 + .../dts/hisilicon/hi3520dv200-nbd6808t.dts | 25 ++++ arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi | 139 ++++++++++++++++++ arch/arm/mach-hibvt/Kconfig | 13 ++ arch/arm/mach-hibvt/Makefile | 1 + arch/arm/mach-hibvt/mach-hi3520dv200.c | 18 +++ 6 files changed, 198 insertions(+) create mode 100644 arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts create mode 100644 arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi create mode 100644 arch/arm/mach-hibvt/mach-hi3520dv200.c diff --git a/arch/arm/boot/dts/hisilicon/Makefile b/arch/arm/boot/dts/hisilicon/Makefile index 5ac6b100566..c191a45f8e4 100644 --- a/arch/arm/boot/dts/hisilicon/Makefile +++ b/arch/arm/boot/dts/hisilicon/Makefile @@ -26,5 +26,7 @@ dtb-$(CONFIG_ARCH_HI3516CV100) += \ hi3516cv100-demb.dtb dtb-$(CONFIG_ARCH_HI3516AV100) += \ hi3516av100-demb.dtb +dtb-$(CONFIG_ARCH_HI3520DV200) += \ + hi3520dv200-nbd6808t.dtb dtb-$(CONFIG_ARCH_HI3519V101) += \ hi3519v101-demb.dtb diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts new file mode 100644 index 00000000000..a0ac4b6b90c --- /dev/null +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR MIT) +/* + * XiongMai NBD6808T-PL NVR (MBD0157 board), Hi3520DV200, 8 MB SPI NOR. + */ + +/dts-v1/; +#include "hi3520dv200.dtsi" + +/ { + model = "XiongMai NBD6808T-PL (Hi3520DV200)"; + compatible = "xiongmai,nbd6808t-pl", "hisilicon,hi3520dv200"; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x80000000 0x08000000>; + }; +}; + +&uart0 { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi b/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi new file mode 100644 index 00000000000..d8e3d0166fe --- /dev/null +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR MIT) +/* + * HiSilicon Hi3520DV200 NVR/DVR SoC: a single Cortex-A9 with GIC, SP804 + * timers and a HiSilicon (not PL310) L2 cache controller. + * + * Addresses and interrupt numbers follow the vendor 3.0.8 BSP + * (arch/arm/mach-hi3520d: platform.h, irqs.h). There is no clock driver + * yet: the rates below are fixed-clocks matching what the vendor bootloader + * programs (24 MHz crystal, 330 MHz bus). The SP804s and the PL011s both + * run from bus/4; the vendor kernel selects the APB source for the UARTs + * in PERI_CRG57 (0x200300e4), which the bootloader already leaves set. + */ + +#include +#include + +/ { + #address-cells = <1>; + #size-cells = <1>; + interrupt-parent = <&gic>; + + aliases { + serial0 = &uart0; + serial1 = &uart1; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <0>; + }; + }; + + xtal: clock-24m { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "xtal"; + }; + + /* APB/bus clock; U-Boot leaves the PLL at 1320 MHz, bus = PLL/4. */ + busclk: clock-bus { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <330000000>; + clock-output-names = "busclk"; + }; + + /* bus/4: feeds the SP804 timers and, with the APB source selected, the UARTs. */ + timerclk: clock-timer { + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clocks = <&busclk>; + clock-div = <4>; + clock-mult = <1>; + clock-output-names = "timerclk"; + }; + + /* SP805 reference clock, measured: LOAD 0x10000000 resets after ~179 s. */ + wdtclk: clock-3m { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <3000000>; + clock-output-names = "wdtclk"; + }; + + soc { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + scu@20300000 { + compatible = "arm,cortex-a9-scu"; + reg = <0x20300000 0x100>; + }; + + gic: interrupt-controller@20301000 { + compatible = "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x20301000 0x1000>, <0x20300100 0x100>; + }; + + /* + * 0x20700000 is HiSilicon's own L2 cache controller (the vendor + * BSP's "hil2v200"), not an ARM PL310: its ID register reads 0. + * The bootloader leaves it disabled and there is no driver for + * it here yet, so the system runs without L2 for now. + */ + + timer0: timer@20000000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x20000000 0x1000>; + interrupts = ; + clocks = <&timerclk>, <&timerclk>, <&busclk>; + clock-names = "timer0clk", "timer1clk", "apb_pclk"; + }; + + timer1: timer@20010000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x20010000 0x1000>; + interrupts = ; + clocks = <&timerclk>, <&timerclk>, <&busclk>; + clock-names = "timer0clk", "timer1clk", "apb_pclk"; + status = "disabled"; + }; + + watchdog@20040000 { + compatible = "arm,sp805", "arm,primecell"; + reg = <0x20040000 0x1000>; + clocks = <&wdtclk>, <&busclk>; + clock-names = "wdog_clk", "apb_pclk"; + }; + + uart0: serial@20080000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x20080000 0x1000>; + interrupts = ; + clocks = <&timerclk>, <&busclk>; + clock-names = "uartclk", "apb_pclk"; + status = "disabled"; + }; + + uart1: serial@20090000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x20090000 0x1000>; + interrupts = ; + clocks = <&timerclk>, <&busclk>; + clock-names = "uartclk", "apb_pclk"; + status = "disabled"; + }; + }; +}; diff --git a/arch/arm/mach-hibvt/Kconfig b/arch/arm/mach-hibvt/Kconfig index 2ae8e548e1e..f4f67ade321 100644 --- a/arch/arm/mach-hibvt/Kconfig +++ b/arch/arm/mach-hibvt/Kconfig @@ -173,6 +173,19 @@ config ARCH_HI3518EV20X help Support for Hisilicon Hi3518ev20x Soc family. +config ARCH_HI3520DV200 + bool "Hisilicon Hi3520DV200 Cortex-A9 family" + depends on ARCH_MULTI_V7 + select ARM_GIC + select HAVE_ARM_SCU + select ARM_TIMER_SP804 + help + Support for the Hisilicon Hi3520DV200, a single Cortex-A9 SoC + used in NVR and DVR boards. It has a GIC, SP804 timers, an + SP805 watchdog, HiSilicon's own L2 cache controller, a SATA + controller, USB 2.0, a switch-fabric Ethernet MAC and an + SFC v350 SPI-NOR controller. + config ARCH_HI3536DV100 bool "Hisilicon Hi3536DV100 Cortex-A7(Single) family" depends on ARCH_MULTI_V7 diff --git a/arch/arm/mach-hibvt/Makefile b/arch/arm/mach-hibvt/Makefile index 51770f67f47..efc252e8a2e 100644 --- a/arch/arm/mach-hibvt/Makefile +++ b/arch/arm/mach-hibvt/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_ARCH_HI3556V200) += mach-hi3556v200.o obj-$(CONFIG_ARCH_HI3559V200) += mach-hi3559v200.o obj-$(CONFIG_ARCH_HI3518EV20X) += mach-hi3518ev20x.o obj-$(CONFIG_ARCH_HI3516CV100) += mach-hi3518ev20x.o +obj-$(CONFIG_ARCH_HI3520DV200) += mach-hi3520dv200.o obj-$(CONFIG_ARCH_HI3536DV100) += mach-hi3536dv100.o obj-$(CONFIG_ARCH_HI3521A) += mach-hi3521a.o obj-$(CONFIG_ARCH_HI3531A) += mach-hi3531a.o diff --git a/arch/arm/mach-hibvt/mach-hi3520dv200.c b/arch/arm/mach-hibvt/mach-hi3520dv200.c new file mode 100644 index 00000000000..7d61cee6395 --- /dev/null +++ b/arch/arm/mach-hibvt/mach-hi3520dv200.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hi3520DV200 (single Cortex-A9 NVR/DVR SoC). + * + * Everything the kernel needs comes from the device tree; the GIC and the + * SP804 timers have generic drivers. + */ + +#include + +static const char *const hi3520dv200_compat[] __initconst = { + "hisilicon,hi3520dv200", + NULL, +}; + +DT_MACHINE_START(HI3520DV200_DT, "Hisilicon Hi3520DV200 (Flattened Device Tree)") + .dt_compat = hi3520dv200_compat, +MACHINE_END From a28b53c98e7cae6ba3302c7ef35ab480a91ba171 Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 18:31:27 +0200 Subject: [PATCH 02/22] net: hisilicon: hisi_femac: make the MAC core reset optional The MAC reset line is only asserted and released around probe, and the reset API treats a missing optional reset as a no-op. SoCs without a reset controller driver yet (Hi3520DV200, whose "hieth-sf" up port is register-compatible with this MAC) can then probe the driver, as the PHY reset already allows. --- drivers/net/ethernet/hisilicon/hisi_femac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/hisilicon/hisi_femac.c b/drivers/net/ethernet/hisilicon/hisi_femac.c index 77be7684006..89826068e42 100644 --- a/drivers/net/ethernet/hisilicon/hisi_femac.c +++ b/drivers/net/ethernet/hisilicon/hisi_femac.c @@ -811,7 +811,7 @@ static int hisi_femac_drv_probe(struct platform_device *pdev) goto out_free_netdev; } - priv->mac_rst = devm_reset_control_get(dev, "mac"); + priv->mac_rst = devm_reset_control_get_optional_exclusive(dev, "mac"); if (IS_ERR(priv->mac_rst)) { ret = PTR_ERR(priv->mac_rst); goto out_disable_clk; From c5fbf1fd85f87a96074a7062f0ecce3d3d073ecc Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Sun, 18 Oct 2026 16:03:15 +0200 Subject: [PATCH 03/22] dt-bindings: net: hisilicon-femac-mdio: add the Hi3520DV200 The Hi3520DV200's internal PHY answers at the address held in the low five bits of a system-control register, which reads 0 after power-on. Add a compatible for it, with that register as a second reg entry. --- .../devicetree/bindings/net/hisilicon-femac-mdio.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/net/hisilicon-femac-mdio.txt b/Documentation/devicetree/bindings/net/hisilicon-femac-mdio.txt index 23a39a309d1..f7e3749e60d 100644 --- a/Documentation/devicetree/bindings/net/hisilicon-femac-mdio.txt +++ b/Documentation/devicetree/bindings/net/hisilicon-femac-mdio.txt @@ -1,8 +1,11 @@ Hisilicon Fast Ethernet MDIO Controller interface Required properties: -- compatible: should be "hisilicon,hisi-femac-mdio". -- reg: address and length of the register set for the device. +- compatible: should be "hisilicon,hisi-femac-mdio", or + "hisilicon,hi3520dv200-femac-mdio" followed by "hisilicon,hisi-femac-mdio". +- reg: address and length of the register set for the device. On + "hisilicon,hi3520dv200-femac-mdio" a second entry gives the internal PHY + control register, whose low five bits set the address the PHY answers at. - clocks: A phandle to the reference clock for this device. - PHY subnode: inherits from phy binding [1] From f71602a2e3d4e0b7c8b22c26bfbcad678a91469f Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Sun, 18 Oct 2026 16:03:16 +0200 Subject: [PATCH 04/22] net: mdio: hisi-femac: set the Hi3520DV200 internal PHY address The Hi3520DV200's internal PHY answers at the address in bits 4:0 of SYS_CTRL PERI24 (0x20050094). The register is 0 after power-on and only the bootloader's network code sets it, so a kernel booted straight from flash finds nothing at the address in the DT ("MDIO device at address 3 is missing", no eth0). Booting after a TFTP transfer hid this. The existing internal-PHY path writes the address too, but then runs the festa PHY trim, and this SoC has no trim data and a different PHY (id 0x001cc816). Its compatible therefore takes an address-only path: with the register as the second reg entry, the driver writes the PHY address from the DT before the bus is scanned. Tested on an NBD6808T-PL by clearing the register in U-Boot before bootm: the PHY is found at address 3 and the link comes up at 100 Mbps. --- drivers/net/mdio/mdio-hisi-femac.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/net/mdio/mdio-hisi-femac.c b/drivers/net/mdio/mdio-hisi-femac.c index 582bcf7dcc2..378c5f4c28d 100644 --- a/drivers/net/mdio/mdio-hisi-femac.c +++ b/drivers/net/mdio/mdio-hisi-femac.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #define MDIO_RWCTRL 0x00 @@ -80,6 +81,7 @@ struct hisi_femac_mdio_data { void __iomem *fephy_trim_iobase; struct mii_bus *bus; u32 phy_addr; + bool fephy_addr_only; }; static int hisi_femac_mdio_wait_ready(struct hisi_femac_mdio_data *data) @@ -321,6 +323,22 @@ static void hisi_femac_fephy_reset_and_trim(struct hisi_femac_mdio_data *data) hisi_femac_fephy_trim(data); } +/* + * The Hi3520DV200 internal PHY answers at the address in bits 4:0 of its + * control register, which is 0 after power-on. Only the bootloader's network + * code sets it, so a kernel booted without it finds no PHY. This SoC has no + * festa trim data, and its PHY is not a festa, so only the address is set. + */ +static void hisi_femac_fephy_write_addr(struct hisi_femac_mdio_data *data) +{ + u32 val; + + val = readl(data->fephy_iobase); + val &= ~BIT_MASK_FEPHY_ADDR; + val |= data->phy_addr; + writel(val, data->fephy_iobase); +} + static void hisi_femac_fephy_set_phy_addr(struct hisi_femac_mdio_data *data) { u32 val; @@ -424,6 +442,8 @@ static int hisi_femac_mdio_probe(struct platform_device *pdev) } hisi_femac_get_phy_addr(data, np); + data->fephy_addr_only = data->fephy_iobase && + device_get_match_data(&pdev->dev); dev_info(&pdev->dev, "fephy_iobase=%p fephy_rst=%p fephy_clk=%p use_fephy=%d phy_addr=%d\n", data->fephy_iobase, data->fephy_rst, data->fephy_clk, hisi_femac_use_fephy(data), data->phy_addr); @@ -431,7 +451,9 @@ static int hisi_femac_mdio_probe(struct platform_device *pdev) dev_info(&pdev->dev, "FEPHY ctrl reg=0x%08x\n", readl(data->fephy_iobase)); - if (hisi_femac_use_fephy(data)) + if (data->fephy_addr_only) + hisi_femac_fephy_write_addr(data); + else if (hisi_femac_use_fephy(data)) hisi_femac_fephy_reset_and_trim(data); else hisi_femac_fephy_set_phy_addr(data); @@ -465,6 +487,7 @@ static void hisi_femac_mdio_remove(struct platform_device *pdev) } static const struct of_device_id hisi_femac_mdio_dt_ids[] = { + { .compatible = "hisilicon,hi3520dv200-femac-mdio", .data = (void *)true }, { .compatible = "hisilicon,hisi-femac-mdio" }, { } }; From 42d3b0ca3ff17417ea7fc3f83020889651a57316 Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 18:31:27 +0200 Subject: [PATCH 05/22] ARM: dts: hi3520dv200: add Ethernet (hisi-femac on the up port) The Hi3520DV200 "hieth-sf" switch-fabric MAC has an up port at the block base and a down port at +0x2000, sharing global registers at +0x1300 and an MDIO controller at +0x1100. The up port's registers and bit layout match hisi-femac (MAC_PORTSEL/PORTSET/SET, the queue registers, and the up-port bits in FWCTRL, MACTCTRL, IRQ_ENA and the MAC filter), so it is described as a hisi-femac with the global block as its second region. On the NBD6808T-PL the RJ45 is on the up port through the SoC's internal PHY at MDIO address 3 (id 0x001cc816). Link comes up at 100 Mbps full duplex, with ping and bulk TCP transfers working. --- .../dts/hisilicon/hi3520dv200-nbd6808t.dts | 15 +++++++++++ arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi | 27 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts index a0ac4b6b90c..d5ff48ea55c 100644 --- a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts @@ -23,3 +23,18 @@ &uart0 { status = "okay"; }; + +/* The RJ45 is on the up port, through the SoC's internal PHY at address 3. */ +&mdio { + status = "okay"; + + phy_up: ethernet-phy@3 { + reg = <3>; + }; +}; + +ð_up { + phy-handle = <&phy_up>; + phy-mode = "mii"; + status = "okay"; +}; diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi b/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi index d8e3d0166fe..c0ee00d4806 100644 --- a/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi @@ -118,6 +118,33 @@ clock-names = "wdog_clk", "apb_pclk"; }; + /* + * The "hieth-sf" switch-fabric MAC: two ports, up (0x0000) and + * down (+0x2000), sharing global registers at +0x1300 and an + * MDIO controller at +0x1100. The up port's registers and bits + * are those of hisi-femac, which drives it with the global block + * passed as its second region. + */ + mdio: mdio@10091100 { + compatible = "hisilicon,hi3520dv200-femac-mdio", + "hisilicon,hisi-femac-mdio"; + /* MDIO, then the internal PHY control (SYS_CTRL PERI24) */ + reg = <0x10091100 0x10>, <0x20050094 0x4>; + clocks = <&busclk>; + clock-names = "mdio"; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + eth_up: ethernet@10090000 { + compatible = "hisilicon,hisi-femac-v1"; + reg = <0x10090000 0x1000>, <0x10091300 0x200>; + interrupts = ; + clocks = <&busclk>; + status = "disabled"; + }; + uart0: serial@20080000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x20080000 0x1000>; From e94c5ceb2a9a5634cc2225d95f7aec3cc7f16fcb Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Mon, 9 Nov 2026 21:19:44 +0100 Subject: [PATCH 06/22] dt-bindings: spi: add the HiSilicon Hi3520DV200 SFC The SPI-NOR controller of the Hi3520DV200 (hardware version 0x350) has the register layout of the v3xx controllers driven by spi-hisi-sfc-v3xx. Document its compatible. The flash on the boards sits on CS1, so num-cs is usually 2; without an interrupt the driver polls. --- .../spi/hisilicon,hi3520dv200-sfc.yaml | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/hisilicon,hi3520dv200-sfc.yaml diff --git a/Documentation/devicetree/bindings/spi/hisilicon,hi3520dv200-sfc.yaml b/Documentation/devicetree/bindings/spi/hisilicon,hi3520dv200-sfc.yaml new file mode 100644 index 00000000000..6caacf87985 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/hisilicon,hi3520dv200-sfc.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/hisilicon,hi3520dv200-sfc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: HiSilicon Hi3520DV200 SPI flash controller (SFC v350) + +maintainers: + - Bjorn Stout + +description: + The SPI-NOR controller of the Hi3520DV200 (hardware version 0x350), with + the register layout of the v3xx controllers handled by spi-hisi-sfc-v3xx. + Without an interrupt the driver polls for completion. + +allOf: + - $ref: spi-controller.yaml# + +properties: + compatible: + const: hisilicon,hi3520dv200-sfc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + num-cs: + description: + Number of chip selects. The flash on Hi3520DV200 boards is on CS1. + maximum: 2 + default: 1 + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + spi@10010000 { + compatible = "hisilicon,hi3520dv200-sfc"; + reg = <0x10010000 0x1000>; + num-cs = <2>; + #address-cells = <1>; + #size-cells = <0>; + + flash@1 { + compatible = "jedec,spi-nor"; + reg = <1>; + label = "hi_sfc"; + spi-max-frequency = <50000000>; + }; + }; From 2863f1ffdcbb606f993c58b91576590f044b047b Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 19:20:34 +0200 Subject: [PATCH 07/22] spi: hisi-sfc-v3xx: support the Hi3520DV200 SFC v350 via device tree The SPI-NOR controller in HiSilicon's Hi3520DV200 (hardware version 0x350) has the register layout of the v3xx parts this driver handles: GLB_CFG at 0x100 with the address-mode bit, interrupts at 0x120-0x12c, CMD_CFG/INS/ADDR at 0x300-0x30c with the same CMD_CFG fields, and the data buffer at 0x400 (16 words, which the driver already picks for versions below 0x351). Add an OF match for it, let it build on ARCH_HISI_BVT, and take the number of chip selects from the generic "num-cs" property: the ACPI parts expose one, while the Hi3520DV200 boards put their flash on CS1. Tested on a XiongMai NBD6808T-PL (8 MB MX25L6406E on CS1): reading every partition back gives the same bytes as a dump taken with the vendor bootloader; a full 8 MB read takes about 4.4 s in polling mode. --- drivers/spi/Kconfig | 2 +- drivers/spi/spi-hisi-sfc-v3xx.c | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index c3b2f02f591..cdb77018bb5 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -457,7 +457,7 @@ config SPI_HISI_KUNPENG config SPI_HISI_SFC_V3XX tristate "HiSilicon SPI NOR Flash Controller for Hi16XX chipsets" - depends on (ARM64 && ACPI) || COMPILE_TEST + depends on (ARM64 && ACPI) || ARCH_HISI_BVT || COMPILE_TEST depends on HAS_IOMEM help This enables support for HiSilicon v3xx SPI NOR flash controller diff --git a/drivers/spi/spi-hisi-sfc-v3xx.c b/drivers/spi/spi-hisi-sfc-v3xx.c index b2af2eed197..bbab1fe66c6 100644 --- a/drivers/spi/spi-hisi-sfc-v3xx.c +++ b/drivers/spi/spi-hisi-sfc-v3xx.c @@ -433,7 +433,7 @@ static int hisi_sfc_v3xx_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct hisi_sfc_v3xx_host *host; struct spi_controller *ctlr; - u32 version, glb_config; + u32 version, glb_config, num_cs; int ret; ctlr = spi_alloc_host(&pdev->dev, sizeof(*host)); @@ -477,7 +477,10 @@ static int hisi_sfc_v3xx_probe(struct platform_device *pdev) } ctlr->bus_num = -1; - ctlr->num_chipselect = 1; + /* The ACPI parts expose one chip select; the Hi3520DV200 flash is on CS1. */ + if (device_property_read_u32(dev, "num-cs", &num_cs)) + num_cs = 1; + ctlr->num_chipselect = num_cs; ctlr->mem_ops = &hisi_sfc_v3xx_mem_ops; /* @@ -519,10 +522,22 @@ static const struct acpi_device_id hisi_sfc_v3xx_acpi_ids[] = { }; MODULE_DEVICE_TABLE(acpi, hisi_sfc_v3xx_acpi_ids); +/* + * The SFC v350 in HiSilicon's Hi3520DV200 has the register layout of the + * v3xx parts (GLB_CFG 0x100, interrupts 0x120-0x12c, CMD_CFG/INS/ADDR + * 0x300-0x30c, a 16-word data buffer at 0x400). + */ +static const struct of_device_id hisi_sfc_v3xx_of_ids[] = { + { .compatible = "hisilicon,hi3520dv200-sfc" }, + {} +}; +MODULE_DEVICE_TABLE(of, hisi_sfc_v3xx_of_ids); + static struct platform_driver hisi_sfc_v3xx_spi_driver = { .driver = { .name = "hisi-sfc-v3xx", .acpi_match_table = hisi_sfc_v3xx_acpi_ids, + .of_match_table = hisi_sfc_v3xx_of_ids, }, .probe = hisi_sfc_v3xx_probe, }; From 94039f3899ca6049ce58d933ad0bfdbe54c7026a Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 19:20:34 +0200 Subject: [PATCH 08/22] ARM: dts: hi3520dv200: add the SPI-NOR controller and NBD6808T-PL flash Describe the SFC v350 at 0x10010000 (driven by spi-hisi-sfc-v3xx) and, on the NBD6808T-PL, the 8 MB MX25L6406E on chip select 1 with the stock firmware's partition layout. All partitions are read-only while the board still carries its vendor firmware. --- .../dts/hisilicon/hi3520dv200-nbd6808t.dts | 57 +++++++++++++++++++ arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi | 10 ++++ 2 files changed, 67 insertions(+) diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts index d5ff48ea55c..18e59d85c65 100644 --- a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts @@ -38,3 +38,60 @@ phy-mode = "mii"; status = "okay"; }; + +/* + * 8 MB MX25L6406E on CS1. Read-only for now: the stock firmware is still on + * it, laid out as the vendor bootargs describe. + */ +&sfc { + status = "okay"; + + flash@1 { + compatible = "jedec,spi-nor"; + reg = <1>; + spi-max-frequency = <50000000>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + partition@0 { + label = "boot"; + reg = <0x000000 0x030000>; + read-only; + }; + partition@30000 { + label = "romfs"; + reg = <0x030000 0x1f0000>; + read-only; + }; + partition@220000 { + label = "usr"; + reg = <0x220000 0x3b0000>; + read-only; + }; + partition@5d0000 { + label = "custom"; + reg = <0x5d0000 0x1a0000>; + read-only; + }; + partition@770000 { + label = "logo"; + reg = <0x770000 0x010000>; + read-only; + }; + partition@780000 { + label = "mtd"; + reg = <0x780000 0x080000>; + read-only; + }; + /* the whole chip, for verifying it against a backup */ + partition@0-all { + label = "all"; + reg = <0x000000 0x800000>; + read-only; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi b/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi index c0ee00d4806..5e52f6dc42e 100644 --- a/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi @@ -118,6 +118,16 @@ clock-names = "wdog_clk", "apb_pclk"; }; + /* SFC v350: the register layout of the v3xx SPI-NOR controller. */ + sfc: spi@10010000 { + compatible = "hisilicon,hi3520dv200-sfc"; + reg = <0x10010000 0x1000>; + num-cs = <2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + /* * The "hieth-sf" switch-fabric MAC: two ports, up (0x0000) and * down (+0x2000), sharing global registers at +0x1300 and an From 064a516c1454780a1c4f2775a0ca04e87761ffc3 Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Mon, 9 Nov 2026 21:19:45 +0100 Subject: [PATCH 09/22] dt-bindings: ata: add the HiSilicon Hi3520DV200 AHCI controller The Hi3520DV200's two-port AHCI controller needs its PHYs clocked, reset and programmed through the SoC's SATA clock/reset register (PERI_CRG45), given as a second reg entry. The port PHY value differs between boards: add "hisilicon,phy-config" for it, defaulting to HiSilicon's Hi3520D reference value. --- .../ata/hisilicon,hi3520dv200-ahci.yaml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Documentation/devicetree/bindings/ata/hisilicon,hi3520dv200-ahci.yaml diff --git a/Documentation/devicetree/bindings/ata/hisilicon,hi3520dv200-ahci.yaml b/Documentation/devicetree/bindings/ata/hisilicon,hi3520dv200-ahci.yaml new file mode 100644 index 00000000000..04899855850 --- /dev/null +++ b/Documentation/devicetree/bindings/ata/hisilicon,hi3520dv200-ahci.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/ata/hisilicon,hi3520dv200-ahci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: HiSilicon Hi3520DV200 AHCI SATA controller + +maintainers: + - Bjorn Stout + +description: + The Hi3520DV200's two-port AHCI controller. Its PHYs are clocked, taken + out of reset and programmed by the driver through the SoC's SATA + clock/reset register (PERI_CRG45) before the AHCI core is started. + +properties: + compatible: + const: hisilicon,hi3520dv200-ahci + + reg: + items: + - description: AHCI registers, including the vendor PHY registers + - description: PERI_CRG45, the SATA clock and reset register + + interrupts: + maxItems: 1 + + hisilicon,phy-config: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Value for the per-port PHY control register (port offset 0x74). + HiSilicon's Hi3520D reference value, 0x0e276756, is the default. + XiongMai boards need 0x0e262709, the value their vendor firmware + writes; with the default their link keeps resetting. + default: 0x0e276756 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + #include + + sata@10080000 { + compatible = "hisilicon,hi3520dv200-ahci"; + reg = <0x10080000 0x10000>, <0x200300b4 0x4>; + interrupts = ; + hisilicon,phy-config = <0x0e262709>; + }; From 8e2ee787851cc011515cee136664a638a426f14e Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 19:35:09 +0200 Subject: [PATCH 10/22] ata: add AHCI glue for the HiSilicon Hi3520DV200 The Hi3520DV200's SATA controller is a standard two-port AHCI block, but its PHYs have to be clocked, released from reset and programmed before the AHCI core sees a link. Port the vendor 3.0.8 BSP's sequence (CONFIG_HI_SATA, 1.5 Gbps mode): clocks and the per-port bug-bypass setting, bus and PHY out of reset, PHY control/OOB values, the port PHY configuration, a PHY reset pulse and the core resets. The port PHY value defaults to HiSilicon's Hi3520D reference value and can be overridden with "hisilicon,phy-config"; XiongMai boards need the Hi3531/Hi3521 value their vendor firmware writes, without which the link keeps resetting. Tested on a XiongMai NBD6808T-PL with a 2.5" 320 GB disk: detected on the first attempt at boot, 63 MB/s writing and 75 MB/s reading 256 MB through ext4; with no disk both ports report link down within a second. --- drivers/ata/Kconfig | 12 ++ drivers/ata/Makefile | 1 + drivers/ata/ahci_hi3520dv200.c | 196 +++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 drivers/ata/ahci_hi3520dv200.c diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig index 2349bca136e..42f3af04d33 100644 --- a/drivers/ata/Kconfig +++ b/drivers/ata/Kconfig @@ -288,6 +288,18 @@ config AHCI_XGENE help This option enables support for APM X-Gene SoC SATA host controller. +config AHCI_HI3520DV200 + tristate "HiSilicon Hi3520DV200 AHCI SATA support" + depends on ARCH_HISI_BVT || COMPILE_TEST + select SATA_HOST + help + This option enables support for the on-chip SATA controller of the + HiSilicon Hi3520DV200 NVR/DVR SoC: a two-port AHCI block whose + PHYs this glue clocks, resets and programs before handing the + controller to the generic AHCI platform code. + + If unsure, say N. + config AHCI_QORIQ tristate "Freescale QorIQ AHCI SATA support" depends on OF diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile index 20e6645ab73..f79ac48e4b6 100644 --- a/drivers/ata/Makefile +++ b/drivers/ata/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_AHCI_SUNXI) += ahci_sunxi.o libahci.o libahci_platform.o obj-$(CONFIG_AHCI_ST) += ahci_st.o libahci.o libahci_platform.o obj-$(CONFIG_AHCI_TEGRA) += ahci_tegra.o libahci.o libahci_platform.o obj-$(CONFIG_AHCI_XGENE) += ahci_xgene.o libahci.o libahci_platform.o +obj-$(CONFIG_AHCI_HI3520DV200) += ahci_hi3520dv200.o libahci.o libahci_platform.o obj-$(CONFIG_AHCI_QORIQ) += ahci_qoriq.o libahci.o libahci_platform.o # SFF w/ custom DMA diff --git a/drivers/ata/ahci_hi3520dv200.c b/drivers/ata/ahci_hi3520dv200.c new file mode 100644 index 00000000000..00692fc78aa --- /dev/null +++ b/drivers/ata/ahci_hi3520dv200.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AHCI glue for the HiSilicon Hi3520DV200 on-chip SATA controller. + * + * The controller is a standard AHCI block, but its two-port PHY needs to be + * clocked, released from reset and programmed before the AHCI core sees a + * link. The sequence, register values and the per-port bug-bypass setting + * follow the vendor 3.0.8 BSP (drivers/ata/ahci_sys.c and + * ahci_sys_hi3520d_defconfig.c, CONFIG_HI_SATA, 1.5 Gbps mode). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ahci.h" + +#define DRV_NAME "ahci-hi3520dv200" + +/* PERI_CRG45 (0x200300b4): SATA clocks and resets */ +#define CRG_PHY_RST BIT(0) +#define CRG_HRST BIT(1) +#define CRG_RST BIT(2) +#define CRG_TX0_RST BIT(3) +#define CRG_TX1_RST BIT(4) +#define CRG_RX0_RST BIT(5) +#define CRG_RX1_RST BIT(6) +#define CRG_ALIVE_RST BIT(7) +#define CRG_PORT1_CKEN BIT(8) +#define CRG_PORT0_CKEN BIT(9) +#define CRG_CKEN BIT(10) +#define CRG_CLK_SEL BIT(11) +#define CRG_CLK_ALIVE BIT(12) +#define CRG_CORE_RESETS (CRG_RST | CRG_TX0_RST | CRG_TX1_RST | \ + CRG_RX0_RST | CRG_RX1_RST | CRG_ALIVE_RST) + +/* Vendor registers in the AHCI block */ +#define PHY0_CTLL 0x54 +#define PHY0_CTLH 0x58 +#define PHY1_CTLL 0x60 +#define PHY1_CTLH 0x64 +#define PHY_CTLH_DIS_CLK BIT(12) +#define OOB_CTL 0x6c +#define PORT_BASE(i) (0x100 + (i) * 0x80) +#define PORT_BUG_BYPASS 0x44 +#define PORT_PHYCTL 0x74 + +/* 1.5 Gbps PHY settings from the vendor BSP */ +#define PHY_CTLL_1G5 0x880ec788 +#define PHY_CTLH 0x00002521 +#define OOB_CTL_VAL 0x84060c15 +/* + * Data-transmit-control, record, PM-reset and fetch bypasses plus the + * Seagate R_ERR workaround (bits 13..9), which the vendor sets on every port + * "due to bugs of sata controller". + */ +#define PORT_BUG_BYPASS_VAL 0x3f24 +/* + * Port PHY control. HiSilicon's Hi3520D reference value is 0x0e276756; the + * vendor firmware on XiongMai boards writes 0x0e262709 (the Hi3531/Hi3521 + * value), without which their link keeps resetting. Boards may override it + * with "hisilicon,phy-config". + */ +#define PORT_PHYCTL_DEFAULT 0x0e276756 +#define NR_PORTS 2 + +struct hi3520dv200_sata { + void __iomem *crg; + u32 phy_config; +}; + +static void crg_update(void __iomem *crg, u32 clear, u32 set) +{ + writel((readl(crg) & ~clear) | set, crg); +} + +static void hi3520dv200_sata_phy_init(struct ahci_host_priv *hpriv) +{ + struct hi3520dv200_sata *priv = hpriv->plat_data; + void __iomem *mmio = hpriv->mmio; + void __iomem *crg = priv->crg; + int i; + + /* + * Clocks on, internal PHY clock, per-port bug bypasses. The vendor + * BSP writes the bypasses here, before releasing CRG_HRST (in + * hi_sata_phy_clk_sel(), ahead of hi_sata_unreset()). That works + * because CRG_HRST is clear at power-on (PERI_CRG45 reads 0): only + * the clocks are off, and the values read back after a cold boot. + */ + crg_update(crg, 0, CRG_CKEN | CRG_PORT0_CKEN | CRG_PORT1_CKEN); + crg_update(crg, CRG_CLK_SEL | CRG_CLK_ALIVE, 0); + for (i = 0; i < NR_PORTS; i++) + writel(PORT_BUG_BYPASS_VAL, mmio + PORT_BASE(i) + PORT_BUG_BYPASS); + + /* bus interface, then PHY out of reset */ + crg_update(crg, CRG_HRST, 0); + msleep(20); + crg_update(crg, CRG_PHY_RST, 0); + msleep(20); + + /* program both PHYs with their clocks gated */ + writel(readl(mmio + PHY0_CTLH) | PHY_CTLH_DIS_CLK, mmio + PHY0_CTLH); + writel(readl(mmio + PHY1_CTLH) | PHY_CTLH_DIS_CLK, mmio + PHY1_CTLH); + writel(PHY_CTLL_1G5, mmio + PHY0_CTLL); + writel(PHY_CTLH, mmio + PHY0_CTLH); + writel(PHY_CTLL_1G5, mmio + PHY1_CTLL); + writel(PHY_CTLH, mmio + PHY1_CTLH); + writel(OOB_CTL_VAL, mmio + OOB_CTL); + for (i = 0; i < NR_PORTS; i++) + writel(priv->phy_config, mmio + PORT_BASE(i) + PORT_PHYCTL); + + /* pulse the PHY reset so the settings take, then release the core */ + crg_update(crg, 0, CRG_PHY_RST); + msleep(20); + crg_update(crg, CRG_PHY_RST, 0); + msleep(20); + crg_update(crg, CRG_CORE_RESETS, 0); + msleep(20); +} + +static const struct ata_port_info ahci_hi3520dv200_port_info = { + .flags = AHCI_FLAG_COMMON, + .pio_mask = ATA_PIO4, + .udma_mask = ATA_UDMA6, + .port_ops = &ahci_platform_ops, +}; + +static const struct scsi_host_template ahci_hi3520dv200_sht = { + AHCI_SHT(DRV_NAME), +}; + +static int ahci_hi3520dv200_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hi3520dv200_sata *priv; + struct ahci_host_priv *hpriv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->crg = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(priv->crg)) + return PTR_ERR(priv->crg); + + if (of_property_read_u32(dev->of_node, "hisilicon,phy-config", + &priv->phy_config)) + priv->phy_config = PORT_PHYCTL_DEFAULT; + + hpriv = ahci_platform_get_resources(pdev, 0); + if (IS_ERR(hpriv)) + return PTR_ERR(hpriv); + hpriv->plat_data = priv; + + ret = ahci_platform_enable_resources(hpriv); + if (ret) + return ret; + + hi3520dv200_sata_phy_init(hpriv); + + ret = ahci_platform_init_host(pdev, hpriv, &ahci_hi3520dv200_port_info, + &ahci_hi3520dv200_sht); + if (ret) { + ahci_platform_disable_resources(hpriv); + return ret; + } + + dev_info(dev, "PHY config %#010x\n", priv->phy_config); + return 0; +} + +static const struct of_device_id ahci_hi3520dv200_of_match[] = { + { .compatible = "hisilicon,hi3520dv200-ahci" }, + {} +}; +MODULE_DEVICE_TABLE(of, ahci_hi3520dv200_of_match); + +static struct platform_driver ahci_hi3520dv200_driver = { + .probe = ahci_hi3520dv200_probe, + .remove = ata_platform_remove_one, + .driver = { + .name = DRV_NAME, + .of_match_table = ahci_hi3520dv200_of_match, + }, +}; +module_platform_driver(ahci_hi3520dv200_driver); + +MODULE_DESCRIPTION("HiSilicon Hi3520DV200 AHCI SATA driver"); +MODULE_LICENSE("GPL"); From d43290517d0778a361e6e5054158675beb6512ac Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 19:35:10 +0200 Subject: [PATCH 11/22] ARM: dts: hi3520dv200: add SATA and USB 2.0 host SATA at 0x10080000 with PERI_CRG45 as its clock/reset register, driven by ahci-hi3520dv200; the NBD6808T-PL uses the PHY value its vendor firmware writes. EHCI and OHCI at 0x100b0000/0x100a0000 are generic platform controllers; the bootloader already enables their clock and PHY (PERI_CRG46, PERI_USB) exactly as the vendor kernel does. --- .../dts/hisilicon/hi3520dv200-nbd6808t.dts | 17 ++++++++++++ arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi | 27 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts index 18e59d85c65..cc81ff3d307 100644 --- a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts @@ -95,3 +95,20 @@ }; }; }; + +/* + * The vendor firmware's PHY value; with the Hi3520D reference one this + * board's link keeps resetting. + */ +&sata { + hisilicon,phy-config = <0x0e262709>; + status = "okay"; +}; + +&ohci { + status = "okay"; +}; + +&ehci { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi b/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi index 5e52f6dc42e..97f7d0a3c7e 100644 --- a/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi @@ -128,6 +128,33 @@ status = "disabled"; }; + /* reg 1 is PERI_CRG45, the SATA clock/reset register */ + sata: sata@10080000 { + compatible = "hisilicon,hi3520dv200-ahci"; + reg = <0x10080000 0x10000>, <0x200300b4 0x4>; + interrupts = ; + status = "disabled"; + }; + + /* + * USB 2.0 host. The bootloader enables its clock and PHY + * (PERI_CRG46 0x200300b8, PERI_USB 0x20050080) when it scans + * the bus. + */ + ohci: usb@100a0000 { + compatible = "generic-ohci"; + reg = <0x100a0000 0x10000>; + interrupts = ; + status = "disabled"; + }; + + ehci: usb@100b0000 { + compatible = "generic-ehci"; + reg = <0x100b0000 0x10000>; + interrupts = ; + status = "disabled"; + }; + /* * The "hieth-sf" switch-fabric MAC: two ports, up (0x0000) and * down (+0x2000), sharing global registers at +0x1300 and an From a70daff82d6238a4d4934a4612581ed6bc1d9d3e Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Mon, 9 Nov 2026 21:19:47 +0100 Subject: [PATCH 12/22] dt-bindings: cache: add the HiSilicon Hi3520DV200 L2 cache controller The Hi3520DV200's L2 cache is HiSilicon's own controller ("hil2v200"), not a PL310: its ID register reads 0 and it has its own maintenance registers. Document its compatible. --- .../cache/hisilicon,hi3520dv200-l2cache.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Documentation/devicetree/bindings/cache/hisilicon,hi3520dv200-l2cache.yaml diff --git a/Documentation/devicetree/bindings/cache/hisilicon,hi3520dv200-l2cache.yaml b/Documentation/devicetree/bindings/cache/hisilicon,hi3520dv200-l2cache.yaml new file mode 100644 index 00000000000..2625f7ad8db --- /dev/null +++ b/Documentation/devicetree/bindings/cache/hisilicon,hi3520dv200-l2cache.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/cache/hisilicon,hi3520dv200-l2cache.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: HiSilicon Hi3520DV200 L2 cache controller (hil2v200) + +maintainers: + - Bjorn Stout + +description: + HiSilicon's own outer cache controller in the Hi3520DV200, 128 KB with + 8 ways and 32-byte lines. It is not an ARM PL310 (its ID register reads 0) + and is maintained by address, by way and set, or a whole way at a time. + +allOf: + - $ref: /schemas/cache-controller.yaml# + +properties: + compatible: + const: hisilicon,hi3520dv200-l2cache + + reg: + maxItems: 1 + + interrupts: + items: + - description: check 0 (error) interrupt + - description: check 1 (error) interrupt + - description: combined interrupt + + cache-unified: true + + cache-level: + const: 2 + +required: + - compatible + - reg + - cache-unified + - cache-level + +unevaluatedProperties: false + +examples: + - | + #include + + cache-controller@20700000 { + compatible = "hisilicon,hi3520dv200-l2cache"; + reg = <0x20700000 0x10000>; + interrupts = , + , + ; + cache-unified; + cache-level = <2>; + }; From 61d570b461288a71e323d542331ef2497c83d18e Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 19:45:31 +0200 Subject: [PATCH 13/22] ARM: hisilicon: add the hil2v200 L2 cache controller for Hi3520DV200 The Hi3520DV200's L2 cache at 0x20700000 is HiSilicon's own controller (the vendor BSP's "hil2v200"), not a PL310: lines are maintained through INVALID/CLEAN registers, by physical address or by way and set, whole ways through an AUTO register, and a read of SYNC drains it. Port the vendor 3.0.8 driver as an outer cache (128 KB, 8 ways, 32-byte lines), brought up from the machine's init_irq where the core would initialise a PL310. "nol2" on the command line leaves it off. Unlike the vendor driver: - an invalidate cleans the partial lines at either end of its range first, so dirty data next to a DMA buffer is not lost; - flush_all cleans and invalidates line by line, by way and set: the controller has no combined clean+invalidate of a way, and a clean of all ways followed by an invalidate of all ways drops whatever the CPU evicts into the L2 in between. Flush ranges always go line by line; only large clean ranges use whole-way cleans; - range operations take the lock in 4 KB blocks, to bound the time with interrupts off; - disable turns the L2 off before cleaning and invalidating it, as the vendor's suspend path does; - INTCLR is cleared at init by writing back RINT: it is write-1-to-clear (measured), so the vendor's write of 0 leaves a stale AUTO_END. On a XiongMai NBD6808T-PL each maintenance operation was checked through an uncached alias of the same page (by-address clean and invalidate, by way and set, partial-line invalidate, flush_all, which takes 130 us). With the L2 on, DMA data stays intact (TFTP download, a random file written to SATA and read back after dropping caches, SPI-NOR reads and TCP transfers both ways all match), and compared to nol2 on the same kernel gzip of 8 MB takes 4.99 s instead of 8.40 s and 256 MB ext4 writes run at 73 instead of 53 MB/s. reboot -f still resets the board. --- arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi | 15 +- .../arm/include/asm/hardware/cache-hil2v200.h | 14 + arch/arm/mach-hibvt/mach-hi3520dv200.c | 13 + arch/arm/mm/Kconfig | 13 + arch/arm/mm/Makefile | 1 + arch/arm/mm/cache-hil2v200.c | 255 ++++++++++++++++++ 6 files changed, 307 insertions(+), 4 deletions(-) create mode 100644 arch/arm/include/asm/hardware/cache-hil2v200.h create mode 100644 arch/arm/mm/cache-hil2v200.c diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi b/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi index 97f7d0a3c7e..ce9fcb82492 100644 --- a/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200.dtsi @@ -88,11 +88,18 @@ }; /* - * 0x20700000 is HiSilicon's own L2 cache controller (the vendor - * BSP's "hil2v200"), not an ARM PL310: its ID register reads 0. - * The bootloader leaves it disabled and there is no driver for - * it here yet, so the system runs without L2 for now. + * HiSilicon's own L2 controller ("hil2v200"), not a PL310: its + * ID register reads 0. 128 KB, 8 ways, 32-byte lines. */ + l2: cache-controller@20700000 { + compatible = "hisilicon,hi3520dv200-l2cache"; + reg = <0x20700000 0x10000>; + interrupts = , + , + ; + cache-unified; + cache-level = <2>; + }; timer0: timer@20000000 { compatible = "arm,sp804", "arm,primecell"; diff --git a/arch/arm/include/asm/hardware/cache-hil2v200.h b/arch/arm/include/asm/hardware/cache-hil2v200.h new file mode 100644 index 00000000000..d51917e1527 --- /dev/null +++ b/arch/arm/include/asm/hardware/cache-hil2v200.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __ASM_ARM_HARDWARE_CACHE_HIL2V200_H +#define __ASM_ARM_HARDWARE_CACHE_HIL2V200_H + +#include +#include + +#ifdef CONFIG_CACHE_HIL2V200 +int __init hil2v200_of_init(void); +#else +static inline int hil2v200_of_init(void) { return -ENODEV; } +#endif + +#endif diff --git a/arch/arm/mach-hibvt/mach-hi3520dv200.c b/arch/arm/mach-hibvt/mach-hi3520dv200.c index 7d61cee6395..a1f53e711a5 100644 --- a/arch/arm/mach-hibvt/mach-hi3520dv200.c +++ b/arch/arm/mach-hibvt/mach-hi3520dv200.c @@ -6,6 +6,8 @@ * SP804 timers have generic drivers. */ +#include +#include #include static const char *const hi3520dv200_compat[] __initconst = { @@ -13,6 +15,17 @@ static const char *const hi3520dv200_compat[] __initconst = { NULL, }; +/* + * The L2 is HiSilicon's own controller, so it is brought up here, at the + * point where the core would initialise a PL310. + */ +static void __init hi3520dv200_init_irq(void) +{ + irqchip_init(); + hil2v200_of_init(); +} + DT_MACHINE_START(HI3520DV200_DT, "Hisilicon Hi3520DV200 (Flattened Device Tree)") .dt_compat = hi3520dv200_compat, + .init_irq = hi3520dv200_init_irq, MACHINE_END diff --git a/arch/arm/mm/Kconfig b/arch/arm/mm/Kconfig index 7b27ee9482b..ebe12782eb0 100644 --- a/arch/arm/mm/Kconfig +++ b/arch/arm/mm/Kconfig @@ -984,6 +984,19 @@ config MIGHT_HAVE_CACHE_L2X0 instead of this option, thus preventing the user from inadvertently configuring a broken kernel. +config CACHE_HIL2V200 + bool "Enable the HiSilicon L2 cache controller v200" + depends on ARCH_HI3520DV200 + default y + select OUTER_CACHE + select OUTER_CACHE_SYNC + help + This option enables the HiSilicon "hil2v200" L2 cache controller + found in the Hi3520DV200 (128 KB, 8 ways). It is not a PL310: + lines are maintained by physical address through its own + registers, and it is driven as an outer cache. Without it the + SoC runs with its L2 disabled. + config CACHE_L2X0 bool "Enable the L2x0 outer cache controller" if MIGHT_HAVE_CACHE_L2X0 default MIGHT_HAVE_CACHE_L2X0 diff --git a/arch/arm/mm/Makefile b/arch/arm/mm/Makefile index 1e220101337..64dbd206404 100644 --- a/arch/arm/mm/Makefile +++ b/arch/arm/mm/Makefile @@ -94,6 +94,7 @@ obj-$(CONFIG_CFI) += proc.o obj-$(CONFIG_OUTER_CACHE) += l2c-common.o obj-$(CONFIG_CACHE_B15_RAC) += cache-b15-rac.o obj-$(CONFIG_CACHE_FEROCEON_L2) += cache-feroceon-l2.o +obj-$(CONFIG_CACHE_HIL2V200) += cache-hil2v200.o obj-$(CONFIG_CACHE_L2X0) += cache-l2x0.o l2c-l2x0-resume.o obj-$(CONFIG_CACHE_L2X0_PMU) += cache-l2x0-pmu.o obj-$(CONFIG_CACHE_XSC3L2) += cache-xsc3l2.o diff --git a/arch/arm/mm/cache-hil2v200.c b/arch/arm/mm/cache-hil2v200.c new file mode 100644 index 00000000000..ab4d061edd9 --- /dev/null +++ b/arch/arm/mm/cache-hil2v200.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HiSilicon L2 cache controller v200 ("hil2v200"), as found in the + * Hi3520DV200. It is not an ARM PL310: maintenance is done a line at a time + * through the INVALID/CLEAN registers, by physical address or by way and + * set, or a whole way at a time through the AUTO register, and a read of + * SYNC drains the controller. + * + * Register layout and enable sequence follow the hil2v200 driver in the + * vendor's 3.0.8 BSP. + */ + +#define pr_fmt(fmt) "hil2v200: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#define L2_CTRL 0x000 +#define L2_AUCTRL 0x004 +#define AUCTRL_EVENT_BUS_EN BIT(12) +#define AUCTRL_MONITOR_EN BIT(13) +#define L2_INTMASK 0x100 +#define L2_RINT 0x108 +#define RINT_AUTO_END BIT(14) +#define L2_INTCLR 0x10c +#define L2_SYNC 0x200 +#define L2_MAINT_AUTO 0x20c +#define MAINT_AUTO_START BIT(0) +#define MAINT_AUTO_CLEAN BIT(1) +#define MAINT_AUTO_WAY_SHIFT 2 +#define L2_INVALID 0x210 +#define L2_CLEAN 0x214 +/* INVALID/CLEAN take a physical line address, or a way and set index */ +#define OP_BY_ADDRESS BIT(0) +#define OP_WAY_SHIFT 29 +#define OP_SET_SHIFT 5 +#define L2_SPECIAL_CHECK0 0x804 +#define L2_SPECIAL_CHECK1 0x808 + +#define L2_LINE_SIZE 32 +#define L2_WAYS 8 +#define L2_SIZE SZ_128K /* Hi3520DV200 */ +#define L2_SETS (L2_SIZE / L2_WAYS / L2_LINE_SIZE) +/* bytes maintained per hold of the lock, to bound the time with IRQs off */ +#define L2_RANGE_BLOCK SZ_4K + +static void __iomem *l2_base; +static DEFINE_RAW_SPINLOCK(l2_lock); +static bool l2_off __initdata; + +/* "nol2": leave the L2 disabled, for comparisons or as an escape hatch. */ +static int __init hil2_nol2(char *unused) +{ + l2_off = true; + return 0; +} +early_param("nol2", hil2_nol2); + +/* Clean or invalidate one whole way and wait for the controller to finish. */ +static void hil2_way_op(unsigned int way, bool clean) +{ + u32 cmd = (way << MAINT_AUTO_WAY_SHIFT) | MAINT_AUTO_START; + + if (clean) + cmd |= MAINT_AUTO_CLEAN; + writel_relaxed(cmd, l2_base + L2_MAINT_AUTO); + while (!(readl_relaxed(l2_base + L2_RINT) & RINT_AUTO_END)) + cpu_relax(); + writel_relaxed(readl_relaxed(l2_base + L2_RINT), l2_base + L2_INTCLR); +} + +static void __hil2_inv_all(void) +{ + unsigned int way; + + for (way = 0; way < L2_WAYS; way++) + hil2_way_op(way, false); +} + +/* + * Clean and invalidate one line at a time, by way and set index. A clean of + * all ways followed by an invalidate of all ways would drop any line the + * CPU evicts into the L2 in between; there is no combined clean+invalidate + * of a way. + */ +static void hil2_flush_all(void) +{ + unsigned int way, set; + unsigned long flags; + u32 op; + + for (way = 0; way < L2_WAYS; way++) { + raw_spin_lock_irqsave(&l2_lock, flags); + for (set = 0; set < L2_SETS; set++) { + op = (way << OP_WAY_SHIFT) | (set << OP_SET_SHIFT); + writel_relaxed(op, l2_base + L2_CLEAN); + writel_relaxed(op, l2_base + L2_INVALID); + } + raw_spin_unlock_irqrestore(&l2_lock, flags); + } +} + +static void __hil2_flush_line(unsigned long addr) +{ + writel_relaxed(addr | OP_BY_ADDRESS, l2_base + L2_CLEAN); + writel_relaxed(addr | OP_BY_ADDRESS, l2_base + L2_INVALID); +} + +/* Clean (L2_CLEAN), invalidate (L2_INVALID) or both (0) every line in range. */ +static void hil2_range_op(unsigned long start, unsigned long end, + unsigned int reg) +{ + unsigned long addr, blk_end, flags; + + start &= ~(L2_LINE_SIZE - 1); + while (start < end) { + blk_end = start + min(end - start, (unsigned long)L2_RANGE_BLOCK); + + raw_spin_lock_irqsave(&l2_lock, flags); + for (addr = start; addr < blk_end; addr += L2_LINE_SIZE) { + if (reg) + writel_relaxed(addr | OP_BY_ADDRESS, l2_base + reg); + else + __hil2_flush_line(addr); + } + raw_spin_unlock_irqrestore(&l2_lock, flags); + start = blk_end; + } +} + +static void hil2_inv_range(unsigned long start, unsigned long end) +{ + unsigned long flags; + + /* + * Lines only partly in the range may hold dirty data from outside + * it: clean those before invalidating them. + */ + if (start & (L2_LINE_SIZE - 1)) { + start &= ~(L2_LINE_SIZE - 1); + raw_spin_lock_irqsave(&l2_lock, flags); + __hil2_flush_line(start); + raw_spin_unlock_irqrestore(&l2_lock, flags); + start += L2_LINE_SIZE; + } + if (start < end && (end & (L2_LINE_SIZE - 1))) { + end &= ~(L2_LINE_SIZE - 1); + raw_spin_lock_irqsave(&l2_lock, flags); + __hil2_flush_line(end); + raw_spin_unlock_irqrestore(&l2_lock, flags); + } + /* never widened to the whole cache: that would drop other dirty lines */ + hil2_range_op(start, end, L2_INVALID); +} + +static void hil2_clean_range(unsigned long start, unsigned long end) +{ + unsigned long flags; + unsigned int way; + + /* cleaning never loses data, so a whole-way clean is safe */ + if (end - start >= L2_SIZE) { + raw_spin_lock_irqsave(&l2_lock, flags); + for (way = 0; way < L2_WAYS; way++) + hil2_way_op(way, true); + raw_spin_unlock_irqrestore(&l2_lock, flags); + return; + } + hil2_range_op(start, end, L2_CLEAN); +} + +static void hil2_flush_range(unsigned long start, unsigned long end) +{ + hil2_range_op(start, end, 0); +} + +static void hil2_sync(void) +{ + readl_relaxed(l2_base + L2_SYNC); +} + +/* + * Turn the L2 off first, so that nothing is allocated into it any more and + * lines the CPU evicts go straight to memory, then clean and invalidate it. + * The vendor's suspend path does the same. + */ +static void hil2_disable(void) +{ + unsigned long flags; + unsigned int way; + + raw_spin_lock_irqsave(&l2_lock, flags); + writel_relaxed(0, l2_base + L2_CTRL); + for (way = 0; way < L2_WAYS; way++) + hil2_way_op(way, true); + __hil2_inv_all(); + raw_spin_unlock_irqrestore(&l2_lock, flags); +} + +int __init hil2v200_of_init(void) +{ + struct device_node *np; + u32 aux; + + if (l2_off) { + pr_info("disabled on the command line (nol2)\n"); + return -ENODEV; + } + + np = of_find_compatible_node(NULL, NULL, "hisilicon,hi3520dv200-l2cache"); + if (!np || !of_device_is_available(np)) { + of_node_put(np); + return -ENODEV; + } + l2_base = of_iomap(np, 0); + of_node_put(np); + if (!l2_base) + return -ENOMEM; + + writel_relaxed(0, l2_base + L2_CTRL); + + aux = readl_relaxed(l2_base + L2_AUCTRL); + writel_relaxed(aux | AUCTRL_EVENT_BUS_EN | AUCTRL_MONITOR_EN, + l2_base + L2_AUCTRL); + + /* + * Clear stale state. INTCLR is write-1-to-clear: a stale AUTO_END + * would make the first way operation look finished at once. The + * vendor enables all interrupts but AUTO_END. + */ + writel_relaxed(readl_relaxed(l2_base + L2_RINT), l2_base + L2_INTCLR); + writel_relaxed(0, l2_base + L2_SPECIAL_CHECK0); + writel_relaxed(0, l2_base + L2_SPECIAL_CHECK1); + writel_relaxed(0x3fff, l2_base + L2_INTMASK); + + __hil2_inv_all(); + writel_relaxed(1, l2_base + L2_CTRL); + + outer_cache.inv_range = hil2_inv_range; + outer_cache.clean_range = hil2_clean_range; + outer_cache.flush_range = hil2_flush_range; + outer_cache.flush_all = hil2_flush_all; + outer_cache.sync = hil2_sync; + outer_cache.disable = hil2_disable; + + pr_info("%u KB, %u ways, %u B lines, enabled\n", + L2_SIZE / SZ_1K, L2_WAYS, L2_LINE_SIZE); + return 0; +} From 6faf791611b3af972fd5ef8eaefc9d1e0fe42fb4 Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 19:54:46 +0200 Subject: [PATCH 14/22] watchdog: sp805: restart with a load of 1, not 0 The restart handler loads 0 to make the watchdog fire at once. The SP805 in HiSilicon's Hi3520DV200 never resets the system with a load of 0: reboot ends in "Reboot failed -- System halted", and the board then stays hung even though a timeout had been running before the restart handler reprogrammed it. Loaded with 1 (LOAD_MIN, the smallest value wdt_setload() itself would program), the same watchdog resets the board immediately. Tested on a XiongMai NBD6808T-PL: "reboot -f" and a sysrq-triggered panic with panic=20 both come back to the bootloader. --- drivers/watchdog/sp805_wdt.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c index c2125f204a1..569e28d0c15 100644 --- a/drivers/watchdog/sp805_wdt.c +++ b/drivers/watchdog/sp805_wdt.c @@ -140,7 +140,13 @@ wdt_restart(struct watchdog_device *wdd, unsigned long mode, void *cmd) writel_relaxed(UNLOCK, wdt->base + WDTLOCK); writel_relaxed(0, wdt->base + WDTCONTROL); - writel_relaxed(0, wdt->base + WDTLOAD); + /* + * The smallest load that counts. The SP805 in HiSilicon's Hi3520DV200 + * never resets the system with a load of 0, and stays stuck there, so + * the restart (and any running timeout) silently fails; with 1 it + * resets at once, as a 0 does on other implementations. + */ + writel_relaxed(LOAD_MIN, wdt->base + WDTLOAD); writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL); /* Flush posted writes. */ From 831895d8da9d9e402da5a9a807df10127d44399c Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 20:03:39 +0200 Subject: [PATCH 15/22] ARM: configs: add hi3520dv200_defconfig A configuration for Hi3520DV200 NVR boards that fits the 2048 KB kernel partition of an 8 MB NOR layout (1537 KB zImage, xz, DTB appended for bootloaders without DT support): serial console, Ethernet, SPI-NOR with JFFS2/squashfs/overlayfs, SATA and USB storage with ext4/vfat, the hil2v200 L2 cache and the SP805 watchdog, with debugging aids, IPv6, VT and input left out. xz is 88 KB smaller than LZMA here and takes the same ~1 s to unpack. --- arch/arm/configs/hi3520dv200_defconfig | 201 +++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 arch/arm/configs/hi3520dv200_defconfig diff --git a/arch/arm/configs/hi3520dv200_defconfig b/arch/arm/configs/hi3520dv200_defconfig new file mode 100644 index 00000000000..cdae811e1c5 --- /dev/null +++ b/arch/arm/configs/hi3520dv200_defconfig @@ -0,0 +1,201 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_KERNEL_XZ=y +CONFIG_SYSVIPC=y +# CONFIG_CROSS_MEMORY_ATTACH is not set +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BLK_DEV_INITRD=y +# CONFIG_RD_GZIP is not set +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_XZ is not set +# CONFIG_RD_LZO is not set +# CONFIG_RD_LZ4 is not set +# CONFIG_RD_ZSTD is not set +# CONFIG_INITRAMFS_PRESERVE_MTIME is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_EXPERT=y +# CONFIG_UID16 is not set +# CONFIG_FHANDLE is not set +# CONFIG_BUG is not set +# CONFIG_AIO is not set +# CONFIG_IO_URING is not set +# CONFIG_ADVISE_SYSCALLS is not set +# CONFIG_MEMBARRIER is not set +# CONFIG_RSEQ is not set +# CONFIG_CACHESTAT_SYSCALL is not set +# CONFIG_KALLSYMS is not set +CONFIG_ARCH_HISI_BVT=y +CONFIG_ARCH_HI3520DV200=y +# CONFIG_HARDEN_BRANCH_PREDICTOR is not set +# CONFIG_HARDEN_BRANCH_HISTORY is not set +# CONFIG_KUSER_HELPERS is not set +# CONFIG_CACHE_L2X0 is not set +CONFIG_ARM_ERRATA_754322=y +# CONFIG_ARM_PATCH_IDIV is not set +# CONFIG_ARM_PAN is not set +CONFIG_ARM_APPENDED_DTB=y +CONFIG_ARM_ATAG_DTB_COMPAT=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_SUSPEND is not set +# CONFIG_SECCOMP is not set +# CONFIG_STACKPROTECTOR is not set +# CONFIG_VMAP_STACK is not set +# CONFIG_STRICT_KERNEL_RWX is not set +# CONFIG_GCC_PLUGINS is not set +# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set +CONFIG_PARTITION_ADVANCED=y +# CONFIG_EFI_PARTITION is not set +# CONFIG_MQ_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_KYBER is not set +# CONFIG_COREDUMP is not set +# CONFIG_SWAP is not set +# CONFIG_SLAB_MERGE_DEFAULT is not set +# CONFIG_COMPAT_BRK is not set +# CONFIG_COMPACTION is not set +# CONFIG_VM_EVENT_COUNTERS is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +# CONFIG_AF_UNIX_OOB is not set +CONFIG_INET=y +CONFIG_IP_PNP=y +# CONFIG_INET_DIAG is not set +# CONFIG_IPV6 is not set +# CONFIG_BQL is not set +# CONFIG_WIRELESS is not set +# CONFIG_ETHTOOL_NETLINK is not set +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_STANDALONE is not set +# CONFIG_PREVENT_FIRMWARE_BUILD is not set +# CONFIG_FW_LOADER is not set +# CONFIG_ALLOW_DEV_COREDUMP is not set +CONFIG_MTD=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_SPI_NOR=y +# CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=16384 +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_SCSI_LOWLEVEL is not set +CONFIG_ATA=y +# CONFIG_ATA_VERBOSE_ERROR is not set +# CONFIG_ATA_FORCE is not set +# CONFIG_SATA_PMP is not set +CONFIG_AHCI_HI3520DV200=y +# CONFIG_ATA_SFF is not set +CONFIG_NETDEVICES=y +# CONFIG_NET_CORE is not set +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CIRRUS is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_DAVICOM is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_FARADAY is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_GOOGLE is not set +CONFIG_HISI_FEMAC=y +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_ADI is not set +# CONFIG_NET_VENDOR_LITEX is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_META is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_MUCSE is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_PENSANDO is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +# CONFIG_NET_VENDOR_STMICRO is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_XILINX is not set +CONFIG_MDIO_HISI_FEMAC=y +# CONFIG_USB_NET_DRIVERS is not set +# CONFIG_WLAN is not set +# CONFIG_INPUT is not set +# CONFIG_SERIO is not set +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_LEGACY_TIOCSTI is not set +# CONFIG_LDISC_AUTOLOAD is not set +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +# CONFIG_HW_RANDOM is not set +# CONFIG_DEVPORT is not set +CONFIG_SPI=y +CONFIG_SPI_HISI_SFC_V3XX=y +# CONFIG_PTP_1588_CLOCK is not set +# CONFIG_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_ARM_SP805_WATCHDOG=y +CONFIG_USB=y +# CONFIG_USB_DEFAULT_PERSIST is not set +CONFIG_USB_EHCI_HCD=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_OHCI_HCD_PLATFORM=y +CONFIG_USB_STORAGE=y +# CONFIG_VIRTIO_MENU is not set +# CONFIG_VHOST_MENU is not set +# CONFIG_IOMMU_SUPPORT is not set +# CONFIG_NVMEM is not set +CONFIG_EXT4_FS=y +# CONFIG_EXT4_USE_FOR_EXT2 is not set +# CONFIG_DNOTIFY is not set +CONFIG_OVERLAY_FS=y +# CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW is not set +CONFIG_VFAT_FS=y +# CONFIG_PROC_SYSCTL is not set +# CONFIG_PROC_PAGE_MONITOR is not set +CONFIG_TMPFS=y +CONFIG_TMPFS_XATTR=y +CONFIG_JFFS2_FS=y +# CONFIG_JFFS2_FS_WRITEBUFFER is not set +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_XZ=y +# CONFIG_NETWORK_FILESYSTEMS is not set +# CONFIG_CRC_OPTIMIZATIONS is not set +# CONFIG_XZ_DEC_X86 is not set +# CONFIG_XZ_DEC_POWERPC is not set +# CONFIG_XZ_DEC_ARM is not set +# CONFIG_XZ_DEC_ARMTHUMB is not set +# CONFIG_XZ_DEC_ARM64 is not set +# CONFIG_XZ_DEC_SPARC is not set +# CONFIG_XZ_DEC_RISCV is not set +# CONFIG_SYMBOLIC_ERRNAME is not set +# CONFIG_DEBUG_MISC is not set +# CONFIG_SECTION_MISMATCH_WARN_ONLY is not set +# CONFIG_SLUB_DEBUG is not set +# CONFIG_FTRACE is not set +# CONFIG_RUNTIME_TESTING_MENU is not set From df9250bd02a1c01a66dca0db9ea41b3a2b945b10 Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 21:17:31 +0200 Subject: [PATCH 16/22] ARM: dts: hi3520dv200: nbd6808t: VGA through a simple-framebuffer The vendor bootloader can drive the display controller: "startvo 0 4 10" opens VGA at 1080p60 and "startgx 0 0x8f000000 3840 0 0 1920 1080" scans graphics layer 0 out of a buffer at 0x8f000000, as ARGB1555 with the alpha bit ignored. Describe that buffer as a simple-framebuffer (x1r5g5b5) and reserve it, so Linux can draw on the VGA output without a display driver. Without those bootloader commands the buffer is ordinary reserved memory and nothing is shown. Tested on the NBD6808T-PL: fbcon shows the kernel log at 240x67 on a VGA monitor and data written to /dev/fb0 appears on screen. --- .../dts/hisilicon/hi3520dv200-nbd6808t.dts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts index cc81ff3d307..b123d58f498 100644 --- a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts @@ -18,6 +18,34 @@ device_type = "memory"; reg = <0x80000000 0x08000000>; }; + + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + vga_fb_mem: framebuffer@8f000000 { + reg = <0x8f000000 0x400000>; + no-map; + }; + }; + + /* + * VGA, 1920x1080@60. The vendor bootloader drives the display + * controller ("startvo 0 4 10; startgx 0 0x8f000000 3840 0 0 1920 + * 1080" opens VGA at 1080p60 and graphics layer 0 at this buffer, + * scanned out as ARGB1555 with the alpha bit ignored); Linux only + * draws into it. Without those bootloader commands nothing is shown. + */ + framebuffer@8f000000 { + compatible = "simple-framebuffer"; + reg = <0x8f000000 0x3f4800>; + memory-region = <&vga_fb_mem>; + width = <1920>; + height = <1080>; + stride = <3840>; + format = "x1r5g5b5"; + }; }; &uart0 { From 7256183dfd6c0ef03e8d9838b47ca5014519049a Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Tue, 13 Oct 2026 21:17:33 +0200 Subject: [PATCH 17/22] ARM: configs: hi3520dv200: add the simple-framebuffer and fbcon Enable simplefb with /dev/fb0, the VT and the framebuffer console, so a monitor on the VGA output shows the console when the bootloader has set the display up. 107 KB more: the zImage is 1644 KB of the 2048 KB partition. --- arch/arm/configs/hi3520dv200_defconfig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/arm/configs/hi3520dv200_defconfig b/arch/arm/configs/hi3520dv200_defconfig index cdae811e1c5..6c51f532251 100644 --- a/arch/arm/configs/hi3520dv200_defconfig +++ b/arch/arm/configs/hi3520dv200_defconfig @@ -142,9 +142,6 @@ CONFIG_HISI_FEMAC=y CONFIG_MDIO_HISI_FEMAC=y # CONFIG_USB_NET_DRIVERS is not set # CONFIG_WLAN is not set -# CONFIG_INPUT is not set -# CONFIG_SERIO is not set -# CONFIG_VT is not set # CONFIG_LEGACY_PTYS is not set # CONFIG_LEGACY_TIOCSTI is not set # CONFIG_LDISC_AUTOLOAD is not set @@ -158,6 +155,9 @@ CONFIG_SPI_HISI_SFC_V3XX=y # CONFIG_HWMON is not set CONFIG_WATCHDOG=y CONFIG_ARM_SP805_WATCHDOG=y +CONFIG_FB=y +CONFIG_FB_SIMPLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_USB=y # CONFIG_USB_DEFAULT_PERSIST is not set CONFIG_USB_EHCI_HCD=y @@ -193,6 +193,7 @@ CONFIG_SQUASHFS_XZ=y # CONFIG_XZ_DEC_ARM64 is not set # CONFIG_XZ_DEC_SPARC is not set # CONFIG_XZ_DEC_RISCV is not set +CONFIG_FONTS=y # CONFIG_SYMBOLIC_ERRNAME is not set # CONFIG_DEBUG_MISC is not set # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set From 47918c94f4ccfcb9eb6e779eecd7badf91ce1a44 Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Thu, 15 Oct 2026 20:34:13 +0200 Subject: [PATCH 18/22] spi: hisi-sfc-v3xx: name the flash after its DT label Bootloaders on Hi3520DV200 boards (the vendor's and OpenIPC's) pass the flash layout as "mtdparts=hi_sfc:...", which the command-line partition parser only applies to an MTD device of that name. Through spi-mem the flash is named after the SPI device instead. Provide a get_name hook that returns the flash node's "label" when it has one, and the default name otherwise, so ACPI parts are unaffected. --- drivers/spi/spi-hisi-sfc-v3xx.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/spi/spi-hisi-sfc-v3xx.c b/drivers/spi/spi-hisi-sfc-v3xx.c index bbab1fe66c6..200155fc06a 100644 --- a/drivers/spi/spi-hisi-sfc-v3xx.c +++ b/drivers/spi/spi-hisi-sfc-v3xx.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -368,7 +369,24 @@ static int hisi_sfc_v3xx_exec_op(struct spi_mem *mem, return hisi_sfc_v3xx_generic_exec_op(host, op, chip_select); } +/* + * Name the flash after its DT "label" when it has one. Bootloaders on + * Hi3520DV200 boards pass "mtdparts=hi_sfc:...", which only matches an MTD + * device of that name; without a label the default (the SPI device name) is + * kept. + */ +static const char *hisi_sfc_v3xx_get_name(struct spi_mem *mem) +{ + const char *name; + + if (!of_property_read_string(mem->spi->dev.of_node, "label", &name)) + return name; + + return dev_name(&mem->spi->dev); +} + static const struct spi_controller_mem_ops hisi_sfc_v3xx_mem_ops = { + .get_name = hisi_sfc_v3xx_get_name, .adjust_op_size = hisi_sfc_v3xx_adjust_op_size, .supports_op = hisi_sfc_v3xx_supports_op, .exec_op = hisi_sfc_v3xx_exec_op, From acc638303d4e37e88261720973527b76d8813575 Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Thu, 15 Oct 2026 20:34:13 +0200 Subject: [PATCH 19/22] ARM: dts: hi3520dv200: nbd6808t: label the flash hi_sfc Both the vendor and the OpenIPC bootloader describe this board's flash as "mtdparts=hi_sfc:...". With the label the command-line layout applies; the stock partitions in the DT remain as the fallback when the bootloader passes none. --- arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts index b123d58f498..1df008cbd14 100644 --- a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts @@ -68,8 +68,9 @@ }; /* - * 8 MB MX25L6406E on CS1. Read-only for now: the stock firmware is still on - * it, laid out as the vendor bootargs describe. + * 8 MB MX25L6406E on CS1. The partitions below are the stock firmware's + * layout, read-only; they only apply when the bootloader passes no + * "mtdparts=hi_sfc:..." of its own. */ &sfc { status = "okay"; @@ -77,6 +78,8 @@ flash@1 { compatible = "jedec,spi-nor"; reg = <1>; + /* the name both the vendor and OpenIPC bootloaders use in mtdparts= */ + label = "hi_sfc"; spi-max-frequency = <50000000>; partitions { From db99f911eaed1372794bc9aa8ec85f65df29117a Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Thu, 15 Oct 2026 20:34:13 +0200 Subject: [PATCH 20/22] ARM: configs: hi3520dv200: command-line partitions and WireGuard Enable MTD_CMDLINE_PARTS so the bootloader's "mtdparts=" layout is used (the flash images OpenIPC builds rely on it), and WireGuard for remote access without exposing other services. WireGuard is a module, as on the other OpenIPC boards, so their userspace (which runs "modprobe wireguard") works unchanged; that needs MODULES and MODULE_UNLOAD. The zImage is 1712 KB of the 2048 KB partition, with 133 KB of modules on the root filesystem. --- arch/arm/configs/hi3520dv200_defconfig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/arm/configs/hi3520dv200_defconfig b/arch/arm/configs/hi3520dv200_defconfig index 6c51f532251..62b1f73151d 100644 --- a/arch/arm/configs/hi3520dv200_defconfig +++ b/arch/arm/configs/hi3520dv200_defconfig @@ -44,6 +44,8 @@ CONFIG_NEON=y # CONFIG_VMAP_STACK is not set # CONFIG_STRICT_KERNEL_RWX is not set # CONFIG_GCC_PLUGINS is not set +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y # CONFIG_BLOCK_LEGACY_AUTOLOAD is not set CONFIG_PARTITION_ADVANCED=y # CONFIG_EFI_PARTITION is not set @@ -74,6 +76,7 @@ CONFIG_DEVTMPFS_MOUNT=y # CONFIG_FW_LOADER is not set # CONFIG_ALLOW_DEV_COREDUMP is not set CONFIG_MTD=y +CONFIG_MTD_CMDLINE_PARTS=y CONFIG_MTD_BLOCK=y CONFIG_MTD_SPI_NOR=y # CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set @@ -91,7 +94,7 @@ CONFIG_ATA=y CONFIG_AHCI_HI3520DV200=y # CONFIG_ATA_SFF is not set CONFIG_NETDEVICES=y -# CONFIG_NET_CORE is not set +CONFIG_WIREGUARD=m # CONFIG_NET_VENDOR_ALACRITECH is not set # CONFIG_NET_VENDOR_AMAZON is not set # CONFIG_NET_VENDOR_AQUANTIA is not set From b1c03feedf053a93b2dc58fe38ab23c9ae755c6b Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Sun, 18 Oct 2026 16:30:55 +0200 Subject: [PATCH 21/22] ARM: configs: hi3520dv200: enable sysctl PROC_SYSCTL went with the other EXPERT trims, which left no /proc/sys: OpenIPC's sysctl start-up failed ("Running sysctl: FAIL"), mdev could not register as the hotplug helper, and nothing could set net.ipv4.ip_forward, which routing over WireGuard needs. 18 KB: the zImage is 1731 KB of the 2048 KB partition. --- arch/arm/configs/hi3520dv200_defconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm/configs/hi3520dv200_defconfig b/arch/arm/configs/hi3520dv200_defconfig index 62b1f73151d..76f1833c4f1 100644 --- a/arch/arm/configs/hi3520dv200_defconfig +++ b/arch/arm/configs/hi3520dv200_defconfig @@ -179,7 +179,6 @@ CONFIG_EXT4_FS=y CONFIG_OVERLAY_FS=y # CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW is not set CONFIG_VFAT_FS=y -# CONFIG_PROC_SYSCTL is not set # CONFIG_PROC_PAGE_MONITOR is not set CONFIG_TMPFS=y CONFIG_TMPFS_XATTR=y From 42fc53fcb815876266c9dce2c0acedd0f8a106e5 Mon Sep 17 00:00:00 2001 From: Bjorn Stout Date: Sun, 18 Oct 2026 18:17:48 +0200 Subject: [PATCH 22/22] ARM: dts: hi3520dv200: nbd6808t: describe all 256 MB of DDR The board has 256 MB. The vendor firmware gave Linux the first 128 MB (mem=132M) and the rest to the MPP's media memory zone, and the DT copied that split. Without the MPP all of it is ordinary memory, apart from the VGA framebuffer at 0x8f000000, which is already reserved no-map. With 128 MB, eight camera streams through go2rtc left the system out of memory. With 256 MB, Linux reports 249560K available; 150 MB of random data in tmpfs reads back identical, and the framebuffer still registers. --- arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts index 1df008cbd14..3c5661f2a7c 100644 --- a/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts +++ b/arch/arm/boot/dts/hisilicon/hi3520dv200-nbd6808t.dts @@ -14,9 +14,14 @@ stdout-path = "serial0:115200n8"; }; + /* + * 256 MB of DDR. The vendor firmware gave Linux the first 128 MB and + * the rest to the media memory zone; without the MPP all of it is + * ordinary memory except the VGA framebuffer reserved below. + */ memory@80000000 { device_type = "memory"; - reg = <0x80000000 0x08000000>; + reg = <0x80000000 0x10000000>; }; reserved-memory {