From: Jonas Jelonek Date: Mon, 6 Jul 2026 15:15:59 +0000 (+0000) Subject: realtek: add support for Ubiquiti UniFi USW Pro Max 24 PoE X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b519bc3b76517ce5b54db78b02536d139b620dcf;p=thirdparty%2Fopenwrt.git realtek: add support for Ubiquiti UniFi USW Pro Max 24 PoE Add support for RTL9302B-based Ubiquiti UniFi USW Pro Max 24 PoE switch with 16x GbE and 8x 2.5G RJ45 ports, 2x SFP+, and a front display. Hardware ======== - RTL9302B switch SoC - 512 MiB RAM - 32 MiB SPI-NOR flash - 16x 100M/1G RJ45 ports via 2x RTL8218E - 8x 100M/1G/2.5G RJ45 ports via 2x RTL8224 - PoE: - 400W total budget - 8x 802.3at, 32W per port (ports 1-8) - 16x 802.3bt, 60W per port (ports 9-24) - 2x SFP+ ports - Buttons: 1x Reset - LEDs: RGBW LED per port (Etherlighting) - Front touch display via USB ACM (see below) - Console: TTL 3.3V, 115200 8N1 (internal pin header close to SoC; layout front to back: VCC RX TX GND) - Etherlighting feature (lighting patterns and color control) - Vendor firmware: U-Boot + LEDE-based Ubiquiti OS MAC address =========== Single MAC address in EEPROM partition, applied to all ports. Front touch display =================== The unit has a touch-capable front display, driven by a dedicated STM32-based MCU. Unlike other Ubiquiti switches where the MCU is connected to the SoC via UART directly, here it is exposed as a USB CDC-ACM serial device through an on-board Genesys Logic GL850G USB hub. The MCU runs Ubiquiti's LCM firmware and exposes a high-level JSON protocol (page selection, button-press events, etc.); arbitrary pixel-level control is not possible without replacing the MCU firmware. Display support therefore depends on both USB host support and a driver for the LCM protocol, neither of which is currently available. Known issues ============ - PoE not available, depends on WIP Realtek PSE MCU driver - Etherlighting not controllable, driver WIP. Port LEDs for link work though. By default, the controller keeps the LEDs in a breathing state, gated by the link state delivered by the Realtek SoC. Disclaimer ========== Stock firmware uses a dual-bank layout (kernel0/kernel1, ~15 MiB each). OpenWrt replaces both banks with a single contiguous firmware partition. Flashing OpenWrt overwrites both stock kernel slots; U-Boot remains intact and can be used for recovery. Installation ============ 1. Enable SSH on the stock UniFi OS and log in with user account. 2. Copy the OpenWrt sysupgrade image to /tmp on the switch (e.g. via scp). 3. Adjust IMG below to point at the copied file, then run the block as a whole. It writes kernel0, splits into kernel1 if the image is larger than that slot (otherwise invalidates kernel1 so U-Boot cannot pick a stale bank), and reboots: IMG=/tmp/openwrt-realtek-rtl930x-ubnt_usw-pro-max-24-poe-squashfs-sysupgrade.bin K0_BLOCKS=$((0xec0000 / 0x10000)) dd if="$IMG" of=/dev/mtdblock2 bs=64k count=$K0_BLOCKS conv=fsync if [ "$(wc -c < "$IMG")" -gt $((0xec0000)) ]; then dd if="$IMG" of=/dev/mtdblock3 bs=64k skip=$K0_BLOCKS conv=fsync else dd if=/dev/zero of=/dev/mtdblock3 bs=64k count=1 conv=fsync fi sync reboot The switch comes up in OpenWrt after reboot. 4. It is recommended to modify the bootcmd to speed up the boot and prevent any issues due to the dual-boot selection. Since U-Boot by default uses bootubnt which does a lot of (unneeded) RTK initialization, quite some time passes until Linux is started. Additionally, the U-Boot logic fiddles with some bits on flash which causes JFFS2 errors in OpenWrt. While this doesn't seem to cause issues yet, be defensive and set the bootcmd to: bootm 0xb4150000 This directly boots the uImage from flash, without doing all the initialization. OpenWrt is able to bootstrap the networking completely on its own. It does not matter which bank stock booted from when the dd block runs: both banks are touched in the same pass (kernel0 written, kernel1 either written or invalidated). With kernel1 invalidated, U-Boot's internal fallback kicks in and permanently switches to kernel0 on the next boot, so the device stays on OpenWrt as long as kernel0 is bootable. Recovery ======== Since the installation procedure invalidates or partially overwrites the second bank, recovery requires serial console access (see Hardware above for pinout). 1. Interrupt U-Boot autoboot by spamming a key during early boot to drop into the U-Boot prompt. 2. Bring up networking: rtk network on 3. Transfer an OpenWrt initramfs image via TFTP and boot it: tftpboot 0x82000000 : bootm 0x82000000 4. From the running initramfs OpenWrt, do a sysupgrade to reflash OpenWrt or whatever you want to recover. There is no need for the complicated procedure from installation since OpenWrt sees the firmware partition already as a whole. Return to stock firmware ======================== There is no fully-supported revert path. The stock firmware blob is a Ubiquiti UBNT archive (header + parts, see firmware-utils' fw.h) that embeds a u-boot and a kernel0 uImage payload; only the latter is relevant when writing back to the kernel partitions. The snippet below extracts the kernel0 uImage from such a blob by locating the uImage magic and using the size carried in the uImage header itself, without parsing any UBNT framing. It is provided as a best-effort starting point; verify the result before flashing, otherwise you're on your own: BLOB= OFF=$(grep -aboF $'\x27\x05\x19\x56' "$BLOB" | head -1 | cut -d: -f1) SIZE=$(( $(dd if="$BLOB" bs=1 skip=$((OFF + 12)) count=4 2>/dev/null \ | hexdump -e '1/4 "%u"') + 64 )) dd if="$BLOB" of=kernel0.uImage bs=1 skip="$OFF" count="$SIZE" Once you have a clean uImage, write it to the kernel partition from within OpenWrt. If you adjusted the bootcmd during installation, make sure to restore it to the default "bootcmd=bootubnt". After a reboot, Ubiquiti's firmware should boot. Or, if you made backups of the flash before installation, just write the backup back to flash. Link: https://github.com/openwrt/openwrt/pull/24110 Signed-off-by: Jonas Jelonek --- diff --git a/target/linux/realtek/base-files/etc/board.d/02_network b/target/linux/realtek/base-files/etc/board.d/02_network index 899f3f117d6..c9d542f2222 100644 --- a/target/linux/realtek/base-files/etc/board.d/02_network +++ b/target/linux/realtek/base-files/etc/board.d/02_network @@ -96,6 +96,7 @@ realtek_setup_macs() plasmacloud,psx28|\ sirivision,sr-st3408f|\ ubnt,usw-pro-xg-8-poe|\ + ubnt,usw-pro-max-24-poe|\ zyxel,gs1920-24hp-v2) lan_mac="$(get_mac_label)" ;; diff --git a/target/linux/realtek/dts/rtl9302_ubnt_usw-pro-max-24-poe.dts b/target/linux/realtek/dts/rtl9302_ubnt_usw-pro-max-24-poe.dts new file mode 100644 index 00000000000..04fd710a5b2 --- /dev/null +++ b/target/linux/realtek/dts/rtl9302_ubnt_usw-pro-max-24-poe.dts @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/dts-v1/; + +#include "rtl930x.dtsi" + +#include +#include +#include +#include +#include + +/ { + compatible = "ubnt,usw-pro-max-24-poe", "realtek,rtl9302b-soc"; + model = "UniFi USW Pro Max 24 PoE"; + + aliases { + label-mac-device = ðernet0; + }; + + chosen { + bootargs = "console=ttyS0,115200 earlycon"; + stdout-path = "serial0:115200n8"; + }; + + memory@0 { + device_type = "memory"; + reg = <0x00000000 0x10000000>, /* first 256 MiB */ + <0x20000000 0x10000000>; /* remaining 256 MiB */ + }; + + keys { + compatible = "gpio-keys"; + + key-reset { + label = "reset"; + gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + }; + + led_set: led_set@0 { + compatible = "realtek,rtl9300-leds"; + clock-frequency = <1250000>; + active-low; + + /* + * selects all speed modes to trigger a LED, two slots. This doesn't correspond + * fully to actual LED behavior. The serial stream is fed into the Etherlighting + * MCU which translates that into the LEDs, managing color, behavior etc. in + * addition. + */ + led_set0 = <(RTL93XX_LED_SET_10G | RTL93XX_LED_SET_5G | RTL93XX_LED_SET_2P5G | + RTL93XX_LED_SET_1G | RTL93XX_LED_SET_100M | RTL93XX_LED_SET_10M | + RTL93XX_LED_SET_LINK | RTL93XX_LED_SET_ACT)>; + }; + + sfp1: sfp-p1 { + compatible = "sff,sfp"; + i2c-bus = <&i2c_sfp1>; + los-gpio = <&gpio1 0 GPIO_ACTIVE_HIGH>; + mod-def0-gpio = <&gpio1 2 GPIO_ACTIVE_LOW>; + tx-disable-gpio = <&gpio1 3 GPIO_ACTIVE_HIGH>; + tx-fault-gpio = <&gpio1 1 GPIO_ACTIVE_HIGH>; + }; + + sfp2: sfp-p2 { + compatible = "sff,sfp"; + i2c-bus = <&i2c_sfp2>; + los-gpio = <&gpio1 4 GPIO_ACTIVE_HIGH>; + mod-def0-gpio = <&gpio1 6 GPIO_ACTIVE_LOW>; + tx-disable-gpio = <&gpio1 7 GPIO_ACTIVE_HIGH>; + tx-fault-gpio = <&gpio1 5 GPIO_ACTIVE_HIGH>; + }; +}; + +&spi0 { + status = "okay"; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + partition@0 { + label = "u-boot"; + reg = <0x0 0x140000>; + read-only; + }; + + partition@140000 { + label = "u-boot-env"; + reg = <0x140000 0x10000>; + }; + + /* + * Vendor layout has two kernel partitions: + * (1) <0x150000 0xec0000> = "kernel0" + * (2) <0x1010000 0xed0000> = "kernel1" + */ + partition@150000 { + label = "firmware"; + reg = <0x150000 0x1d90000>; + compatible = "openwrt,uimage", "denx,uimage"; + }; + + partition@1ee0000 { + label = "cdata"; + reg = <0x1ee0000 0x10000>; + }; + + partition@1ef0000 { + label = "cfg"; + reg = <0x1ef0000 0x100000>; + }; + + partition@1ff0000 { + label = "EEPROM"; + reg = <0x1ff0000 0x10000>; + + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + factory_macaddr: macaddr@0 { + compatible = "mac-base"; + reg = <0x0 0x6>; + #nvmem-cell-cells = <1>; + }; + }; + }; + }; + }; +}; + +ðernet0 { + nvmem-cells = <&factory_macaddr 0>; + nvmem-cell-names = "mac-address"; +}; + +&gpio0 { + /* + * GPIO 1 is the global reset pin shared by all PHYs across all MDIO + * buses. It is intentionally not declared as reset-gpios on any bus: + * the MDIO driver / phylink only support a reset GPIO per bus, not on + * the parent controller. Attaching it to a single bus would still reset + * the PHYs on the other buses as a side effect, leaving their software + * state out of sync with the hardware and likely breaking them. + */ + phy_reset_hog { + gpio-hog; + gpios = <1 GPIO_ACTIVE_LOW>; + output-low; + line-name = "phy-reset"; + }; +}; + +&i2c_mst1 { + status = "okay"; + + i2c1: i2c@1 { + reg = <1>; + + adt7475@2e { + compatible = "adi,adt7475"; + reg = <0x2e>; + }; + }; + + i2c_sfp1: i2c@2 { reg = <2>; }; + i2c_sfp2: i2c@3 { reg = <3>; }; + i2c4: i2c@4 { reg = <4>; }; /* Etherlighting MCU at 0x66 */ + + i2c6: i2c@6 { + reg = <6>; + + gpio1: gpio@25 { + compatible = "nxp,pca9555"; + reg = <0x25>; + gpio-controller; + #gpio-cells = <2>; + }; + }; +}; + +&i2c_mst2 { + status = "okay"; + + i2c0: i2c@0 { reg = <0>; }; /* PoE MCU at 0x20 */ + + i2c7: i2c@7 { + reg = <7>; + + gpio2: gpio@22 { + compatible = "nxp,pca9555"; + reg = <0x22>; + gpio-controller; + #gpio-cells = <2>; + }; + }; +}; + +&mdio_bus0 { + /* RTL8218E */ + ethernet-phy-package@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + PHY_C22(0, 0) + PHY_C22(1, 1) + PHY_C22(2, 2) + PHY_C22(3, 3) + PHY_C22(4, 4) + PHY_C22(5, 5) + PHY_C22(6, 6) + PHY_C22(7, 7) + }; +}; + +&mdio_bus1 { + /* RTL8218E */ + ethernet-phy-package@8 { + #address-cells = <1>; + #size-cells = <0>; + reg = <8>; + + PHY_C22(8, 8) + PHY_C22(9, 9) + PHY_C22(10, 10) + PHY_C22(11, 11) + PHY_C22(12, 12) + PHY_C22(13, 13) + PHY_C22(14, 14) + PHY_C22(15, 15) + }; +}; + +&mdio_bus2 { + /* RTL8224 */ + ethernet-phy-package@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + + PHY_C45(16, 0) + PHY_C45(17, 1) + PHY_C45(18, 2) + PHY_C45(19, 3) + }; + + /* RTL8224 */ + ethernet-phy-package@4 { + reg = <4>; + #address-cells = <1>; + #size-cells = <0>; + + PHY_C45(20, 4) + PHY_C45(21, 5) + PHY_C45(22, 6) + PHY_C45(23, 7) + }; +}; + +&switch0 { + ethernet-ports { + #address-cells = <1>; + #size-cells = <0>; + + SWITCH_PORT_LED(0, 1, 2, 0, 0, usxgmii) + SWITCH_PORT_LED(1, 2, 2, 1, 0, usxgmii) + SWITCH_PORT_LED(2, 3, 2, 2, 0, usxgmii) + SWITCH_PORT_LED(3, 4, 2, 3, 0, usxgmii) + SWITCH_PORT_LED(4, 5, 2, 4, 0, usxgmii) + SWITCH_PORT_LED(5, 6, 2, 5, 0, usxgmii) + SWITCH_PORT_LED(6, 7, 2, 6, 0, usxgmii) + SWITCH_PORT_LED(7, 8, 2, 7, 0, usxgmii) + + SWITCH_PORT_LED(8, 9, 3, 0, 0, usxgmii) + SWITCH_PORT_LED(9, 10, 3, 1, 0, usxgmii) + SWITCH_PORT_LED(10, 11, 3, 2, 0, usxgmii) + SWITCH_PORT_LED(11, 12, 3, 3, 0, usxgmii) + SWITCH_PORT_LED(12, 13, 3, 4, 0, usxgmii) + SWITCH_PORT_LED(13, 14, 3, 5, 0, usxgmii) + SWITCH_PORT_LED(14, 15, 3, 6, 0, usxgmii) + SWITCH_PORT_LED(15, 16, 3, 7, 0, usxgmii) + + SWITCH_PORT_LED(16, 17, 4, 0, 0, 10g-qxgmii) + SWITCH_PORT_LED(17, 18, 4, 1, 0, 10g-qxgmii) + SWITCH_PORT_LED(18, 19, 4, 2, 0, 10g-qxgmii) + SWITCH_PORT_LED(19, 20, 4, 3, 0, 10g-qxgmii) + + SWITCH_PORT_LED(20, 21, 5, 0, 0, 10g-qxgmii) + SWITCH_PORT_LED(21, 22, 5, 1, 0, 10g-qxgmii) + SWITCH_PORT_LED(22, 23, 5, 2, 0, 10g-qxgmii) + SWITCH_PORT_LED(23, 24, 5, 3, 0, 10g-qxgmii) + + SWITCH_PORT_SFP(24, 25, 6, 0, 1) + SWITCH_PORT_SFP(25, 26, 7, 0, 2) + + /* CPU port */ + port@28 { + ethernet = <ðernet0>; + reg = <28>; + phy-mode = "internal"; + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + }; +}; + diff --git a/target/linux/realtek/image/rtl930x.mk b/target/linux/realtek/image/rtl930x.mk index 53f3d32d693..a1def7b0a1c 100644 --- a/target/linux/realtek/image/rtl930x.mk +++ b/target/linux/realtek/image/rtl930x.mk @@ -162,6 +162,16 @@ define Device/ubnt_usw-aggregation endef TARGET_DEVICES += ubnt_usw-aggregation +define Device/ubnt_usw-pro-max-24-poe + SOC := rtl9302 + DEVICE_VENDOR := Ubiquiti + DEVICE_MODEL := UniFi USW Pro Max 24 PoE + IMAGE_SIZE := 30272k + DEVICE_PACKAGES := kmod-hwmon-adt7475 + $(Device/kernel-lzma) +endef +TARGET_DEVICES += ubnt_usw-pro-max-24-poe + define Device/vimin_vm-s100-0800ms SOC := rtl9303 UIMAGE_MAGIC := 0x93000000