diff --git a/.github/workflows/build-boot.yml b/.github/workflows/build-boot.yml
index e84e582af..4cd741e21 100644
--- a/.github/workflows/build-boot.yml
+++ b/.github/workflows/build-boot.yml
@@ -19,6 +19,8 @@ jobs:
- aarch64_qemu_boot
- bpi_r3_sd_boot
- bpi_r3_emmc_boot
+ - bpi_r4_sd_boot
+ - bpi_r4_emmc_boot
- bpi_r64_sd_boot
- bpi_r64_emmc_boot
- cn9130_crb_boot
diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml
index 497ef794f..16c17b7de 100644
--- a/.github/workflows/build-image.yml
+++ b/.github/workflows/build-image.yml
@@ -11,6 +11,7 @@ on:
- raspberrypi-rpi2
- raspberrypi-rpi64
- bananapi-bpi-r3
+ - bananapi-bpi-r4
- bananapi-bpi-r64
- friendlyarm-nanopi-r2s
- microchip-sama7g54-ek
@@ -68,6 +69,12 @@ jobs:
echo "ARCH=aarch64" >> $GITHUB_ENV
echo "BUILD_EMMC=true" >> $GITHUB_ENV
;;
+ bananapi-bpi-r4)
+ echo "BOOTLOADER_SD=bpi-r4-sd-boot" >> $GITHUB_ENV
+ echo "BOOTLOADER_EMMC=bpi-r4-emmc-boot" >> $GITHUB_ENV
+ echo "ARCH=aarch64" >> $GITHUB_ENV
+ echo "BUILD_EMMC=true" >> $GITHUB_ENV
+ ;;
bananapi-bpi-r64)
echo "BOOTLOADER_SD=bpi-r64-sd-boot" >> $GITHUB_ENV
echo "BOOTLOADER_EMMC=bpi-r64-emmc-boot" >> $GITHUB_ENV
diff --git a/board/aarch64/Config.in b/board/aarch64/Config.in
index a27864e8c..20acf6824 100644
--- a/board/aarch64/Config.in
+++ b/board/aarch64/Config.in
@@ -2,6 +2,7 @@ if BR2_aarch64
source "$BR2_EXTERNAL_INFIX_PATH/board/aarch64/alder-alder/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/aarch64/bananapi-bpi-r3/Config.in"
+source "$BR2_EXTERNAL_INFIX_PATH/board/aarch64/bananapi-bpi-r4/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/aarch64/bananapi-bpi-r64/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/aarch64/freescale-imx8mp-evk/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/board/aarch64/friendlyarm-nanopi-r2s/Config.in"
diff --git a/board/aarch64/README.md b/board/aarch64/README.md
index 074cfab48..61dca726b 100644
--- a/board/aarch64/README.md
+++ b/board/aarch64/README.md
@@ -4,9 +4,10 @@ aarch64
Board Specific Documentation
----------------------------
-- [Banana Pi BPi-R3](banana-pi-r3/)
-- [Banana Pi BPi-R64](banana-pi-r64/)
-- [Marvell CN9130-CRB](cn9130-crb/)
-- [Microchip SparX-5i PCB135 (eMMC)](sparx5-pcb135/)
-- [NanoPi R2S](r2s/)
-- [Raspberry Pi 64-bit](raspberry-pi64/)
+- [Banana Pi BPi-R3](bananapi-bpi-r3/)
+- [Banana Pi BPi-R4](bananapi-bpi-r4/)
+- [Banana Pi BPi-R64](bananapi-bpi-r64/)
+- [Marvell CN9130-CRB](marvell-cn9130-crb/)
+- [Microchip SparX-5i PCB135 (eMMC)](microchip-sparx5-pcb135/)
+- [NanoPi R2S](friendlyarm-nanopi-r2s/)
+- [Raspberry Pi 64-bit](raspberrypi-rpi64/)
diff --git a/board/aarch64/alder-alder/alder-alder.hash b/board/aarch64/alder-alder/alder-alder.hash
new file mode 100644
index 000000000..2809a5eed
--- /dev/null
+++ b/board/aarch64/alder-alder/alder-alder.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
diff --git a/board/aarch64/bananapi-bpi-r3/bananapi-bpi-r3.hash b/board/aarch64/bananapi-bpi-r3/bananapi-bpi-r3.hash
new file mode 100644
index 000000000..d1a69bc36
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r3/bananapi-bpi-r3.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 dbe4eae8debbba8135297e15f24aeefef0b4c03781f3f9328db4398d58a728b3 LICENSE
diff --git a/board/aarch64/bananapi-bpi-r4/Config.in b/board/aarch64/bananapi-bpi-r4/Config.in
new file mode 100644
index 000000000..cfb7fc863
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/Config.in
@@ -0,0 +1,11 @@
+config BR2_PACKAGE_BANANAPI_BPI_R4
+ bool "Banana Pi R4"
+ depends on BR2_aarch64
+ select BR2_PACKAGE_FEATURE_WIFI
+ select BR2_PACKAGE_LINUX_FIRMWARE
+ select BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK
+ select BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7988
+ select BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7996
+ select SDCARD_AUX
+ help
+ Build Banana PI R4 support
diff --git a/board/aarch64/bananapi-bpi-r4/LICENSE b/board/aarch64/bananapi-bpi-r4/LICENSE
new file mode 100644
index 000000000..8cdb30a3a
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2026 The KernelKit Authors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/board/aarch64/bananapi-bpi-r4/README.md b/board/aarch64/bananapi-bpi-r4/README.md
new file mode 100644
index 000000000..d9cb6eda7
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/README.md
@@ -0,0 +1,360 @@
+# Banana Pi BPI-R4 / BPI-R4-2g5 / BPI-R4P
+
+
+
+## Overview
+
+The Banana Pi BPI-R4 is a high-performance networking board built around the
+MediaTek MT7988A SoC (Filogic 880). It is a successor to the BPI-R3 and
+represents a significant step up: the CPU switches from in-order Cortex-A53
+cores to out-of-order Cortex-A73 cores, and the MT7531 switch is now part of
+the SOC. The base model comes with two SFP+ cages supporting 1.25/2.5/5/10
+Gbps, and the R4P model comes with one SFP+ cage and one 2.5 Gbps RJ45 jack,
+which with an optional PoE board means the board can act as PD. Unlike the
+BPi-R3, however, there is no on-board WiFi support, that requires an external
+tri-band WiFi 7 (IEEE 802.11be) card.
+
+### Variants
+
+| **Feature** | **BPI-R4** | **BPI-R4-2g5** | **BPI-R4P** |
+|------------------|------------------|-------------------|---------------------------|
+| 1G switch ports | wan, lan1–lan3 | lan0–lan3 | lan0–lan3 |
+| 2.5G RJ45 (WAN) | — | wan | wan (PoE input) |
+| SFP+ port(s) | sfp1, sfp2 (10G) | sfp1 (10G) | sfp1 (10G) |
+| Linux DT | bpi-r4 | bpi-r4-2g5 | bpi-r4-2g5 (same) |
+
+On the standard BPI-R4 the four 1G switch ports are labeled `wan`, `lan1`, `lan2`,
+and `lan3`. On the R4-2g5 and R4P, the port that would otherwise be `wan` on the
+switch is relabeled `lan0` via a device tree overlay, since the actual WAN port on
+those variants is the dedicated 2.5G internal PHY on the separate RJ45 jack.
+
+The BPI-R4P is mechanically and electrically identical to the BPI-R4-2g5
+with an additional PoE daughterboard header. From a Linux kernel and
+Infix perspective the two boards are treated identically.
+
+### SoC: MediaTek MT7988A vs MT7986 (BPI-R3)
+
+The MT7988A (Filogic 880) and MT7986 (Filogic 820) may look similar from
+their part numbers, but they are built on entirely different silicon with
+distinct system architectures:
+
+| **Aspect** | **MT7988A (BPI-R4)** | **MT7986 (BPI-R3)** |
+|------------------|--------------------------|---------------------------|
+| CPU cores | 4x Cortex-A73 @ 1.8 GHz | 4x Cortex-A53 @ 2.0 GHz |
+| CPU architecture | ARMv8-A, out-of-order | ARMv8-A, in-order |
+| Internal switch | 1 Gbps per port | 1 Gbps per port |
+| Uplinks | USXGMII (native 10 Gbps) | USXGMII (native 2.5 Gbps) |
+| WiFi | MT7996E PCIe module | MT7915E (built-in) |
+| WiFi standard | WiFi 7 (802.11be) | WiFi 6 (802.11ax) |
+| WiFi bands | 2.4 / 5 / 6 GHz | 2.4 / 5 GHz |
+| Hardware crypto | EIP-197 NPU | EIP-93 |
+| PCIe slots | 4 (2x mPCIe, 2x M.2) | 1 mPCIe |
+| Boot ROM offset | Sector 1024 (0x80000) | Sector 1024 (0x80000) |
+
+### Hardware Features
+
+- **SoC:** MediaTek MT7988A (Filogic 880)
+- **CPU:** Quad-core ARM Cortex-A73, up to 1.8 GHz
+- **RAM:** 4 GB DDR4
+- **Storage:** 8 GB eMMC, microSD card slot, SPI NAND flash
+- **Ethernet switch:** 4-port 1 GbE (10/100/1000) internal DSA switch
+- **SFP+ ports:** 2x 10 Gbps USXGMII (standard R4) or 1x SFP+ (R4-2g5/R4P)
+- **WiFi:** optional MediaTek MT7996E PCIe module — tri-band WiFi 7 (2.4/5/6 GHz)
+- **USB:** 1x USB 3.0 (xHCI)
+- **PCIe:** 2x mPCIe (SIM2/SIM3), 1x M.2 Key-B (SIM1), 1x M.2 Key-M (SSD)
+- **RTC:** PCF8563 on I2C
+- **Fan:** PWM-controlled cooling with thermal management
+- **Console:** UART at 115200 8N1 (3.3 V, USB-to-serial adapter required)
+
+### Default Network Configuration
+
+Infix ships with the following factory defaults.
+
+**Standard BPI-R4:**
+
+- **LAN bridge** (`br0`, 192.168.0.1/24): `lan1`, `lan2`, `lan3` (1G switch ports)
+- **WAN port** (`wan`): DHCP client, used for internet uplink (1G switch port)
+- **SFP+ ports** (`sfp1`, `sfp2`): Present but unconfigured
+
+**BPI-R4-2g5 and BPI-R4P:**
+
+- **LAN bridge** (`br0`, 192.168.0.1/24): `lan0`, `lan1`, `lan2`, `lan3` (1G switch ports)
+- **WAN port** (`wan`): DHCP client, used for internet uplink (2.5G internal PHY)
+- **SFP+ port** (`sfp1`): Present but unconfigured
+
+> [!NOTE]
+> If an optional WiFi 7 (MT7996E) PCIe card is installed, the radio interfaces
+> are bridged into the LAN as access points. WiFi is not included with the board
+> and is not part of the factory default configuration.
+
+
+## Getting Started
+
+
+
+### Quick Start with SD Card
+
+1. **Download the SD card image:** [infix-bpi-r4-sdcard.img][2]
+2. **Flash the image to an SD card:** see [this guide][0]
+3. **Set boot switches to SD card mode** (see Boot Switch Reference below)
+4. **Insert the SD card, connect power and a serial console (115200 8N1)**
+5. Default login: `admin` / `admin`
+
+### Boot Switch Reference
+
+
+
+The BPI-R4 has a 2-position DIP switch (SW3) that selects the boot media.
+Switch positions are printed on the board near the SD card slot.
+
+| A | B | Boot media (SW3) |
+|-----|-----|------------------|
+| OFF | ON | SPI NAND |
+| ON | OFF | eMMC |
+| ON | ON | SD card |
+
+> [!NOTE]
+> "OFF" = switch in the UP position = logical 0.
+
+## Installing to eMMC
+
+For production use or better reliability, install Infix to the internal
+eMMC storage.
+
+> [!IMPORTANT]
+> The MT7988A has a single MMC controller that can only operate in one mode
+> (SD or eMMC) per boot session. The SD card U-Boot cannot switch to eMMC
+> mode mid-session, so an intermediate NAND bootloader step is required to
+> write the eMMC — the same approach as BPi-R3.
+>
+> The MT7988A boot chain is: **BROM → BL2 → FIP (BL31 + U-Boot)**. The FIP
+> (Firmware Image Package) bundles ARM Trusted Firmware (BL31) and U-Boot
+> together; it is not just a U-Boot binary. When BL2 runs, it loads the FIP
+> from a fixed offset and hands off to BL31, which then jumps into U-Boot.
+>
+> The factory SPI NAND contains a minimal recovery U-Boot that only supports
+> TFTP — it has no USB command. You cannot use it to load files from a USB
+> drive. Similarly, the factory eMMC ships with a basic OpenWRT image.
+> You must first flash a full-featured U-Boot (from Frank-W) to SPI NAND via
+> the SD card U-Boot before you can write the eMMC.
+>
+> This process involves three boot mode changes and multiple flash operations.
+> Take your time and verify each step carefully.
+
+### Prerequisites
+
+- USB-to-serial adapter (3.3 V) for console access
+- USB flash drive (FAT32 formatted)
+- microSD card with Infix SD image, for initial boot
+- Downloaded files (see below)
+
+### Required Files
+
+Place these files on a FAT32-formatted USB drive:
+
+1. **Infix eMMC image:** [infix-bpi-r4-emmc.img][3]
+2. **eMMC bootloader** (from): [bpi-r4-emmc-boot-2026.01-latest.tar.gz][4]
+ - Extract `bl2.img` from the tarball to your USB drive
+3. **Intermediate NAND bootloader** from Frank-W's U-Boot (for BPI-R4, SPI NAND):
+ - [bpi-r4_spim-nand_bl2.img][13] — BL2 first-stage loader
+ - [bpi-r4_spim-nand_fip.bin][14] — FIP image (BL31 + U-Boot)
+
+### Step 1: Boot from SD card
+
+1. Set boot switches to **SD card mode** (SW3: A=ON, B=ON)
+2. Insert the SD card with the Infix image
+3. Power on and break into U-Boot (press Ctrl-C during countdown)
+
+### Step 2: Flash intermediate NAND bootloader
+
+This installs a full-featured U-Boot to SPI NAND so the board can load files
+from USB in the next step. From the SD card U-Boot prompt:
+
+```
+usb start
+mtd erase spi-nand0
+fatload usb 0:1 0x50000000 bpi-r4_spim-nand_bl2.img
+mtd write spi-nand0 0x50000000 0x0 0x100000
+fatload usb 0:1 0x50000000 bpi-r4_spim-nand_fip.bin
+mtd write spi-nand0 0x50000000 0x580000 0x200000
+```
+
+### Step 3: Boot from NAND
+
+1. Power off the board
+2. Set boot switches to **SPI NAND mode** (SW3: A=OFF, B=ON)
+3. Power on — you should boot into U-Boot again
+
+### Step 4: Write Infix image to eMMC
+
+From the U-Boot prompt:
+
+```
+usb start
+fatload usb 0:1 0x50000000 infix-bpi-r4-emmc.img
+setexpr blocks ${filesize} / 0x200
+mmc write 0x50000000 0x0 ${blocks}
+```
+
+### Step 5: Configure eMMC boot partition
+
+Write the BL2 bootloader to the eMMC boot partition:
+
+```
+mmc partconf 0 1 1 1
+mmc erase 0x0 0x400
+fatload usb 0:1 0x50000000 bl2.img
+mmc write 0x50000000 0x0 0x400
+mmc partconf 0 1 1 0
+```
+
+### Step 6: Boot from eMMC
+
+1. Power off the board
+2. Set boot switches to **eMMC mode** (SW3: A=ON, B=OFF)
+3. Remove the SD card (optional, recommended to verify eMMC boot)
+4. Power on
+
+Your BPI-R4 should now boot Infix from internal eMMC storage.
+
+## Troubleshooting
+
+### Board won't boot
+
+- Verify boot switch positions — double-check against the wiki
+- Ensure the power supply provides adequate current (12 V / 3 A recommended)
+- Check the serial console output at 115200 8N1
+
+### Can't break into U-Boot
+
+- Connect the serial console before powering on
+- Press Ctrl-C immediately when boot messages appear
+- Try power cycling and pressing Ctrl-C repeatedly during the countdown
+
+### eMMC boot fails after installation
+
+- Boot from NAND (SW3=ON) and verify the eMMC write completed without errors
+- Re-run the `mmc partconf` sequence — a missed step is the most common
+ cause of failure
+- Use `mmc info` to confirm the eMMC is detected
+
+### No network connectivity
+
+The interface names on BPI-R4 differ from BPI-R3. Confirm what Linux
+has created with `ip link` and compare with the factory configuration:
+
+- `wan`, `lan1`–`lan3`: 1G switch ports (standard R4)
+- `wan`, `lan0`–`lan3`: on R4-2g5/R4P, `wan` is the 2.5G internal PHY; `lan0`–`lan3` are 1G switch ports
+- `sfp1` (and `sfp2` on standard R4): 10G SFP+ cage(s)
+
+If interface renames look wrong, check `dmesg | grep renamed` and if
+you spot any errors, please report as an issue to the Infix tracker
+on GitHub.
+
+## Board Variants
+
+### BPI-R4
+
+The base model BPi-R4 ports look like this, the `wan` port is the same physical
+switch port as `lan0` on the R4-2g5/R4P, but here it serves as the WAN port in
+the factory default configuration.
+
+```
+.-.
+| | .-----.-----.-----.-----.
+| | _______ _______ | | | | | .---.
+| | | | | | | | | | | | | .-----.
+'-' '-------' '-------' '-----'-----'-----'-----' '---' '-----'
+USB1 sfp1 sfp2 wan lan1 lan2 lan3 DC12V PD20V
+```
+
+### BPI-R4-2g5 and BPI-R4P
+
+These variants substitute one SFP+ cage for an internal 2.5 Gbps PHY
+connected to a standard RJ45 jack. From a kernel perspective they use
+the `mt7988a-bananapi-bpi-r4-2g5` device tree.
+
+The BPI-R4P additionally supports an optional PoE input daughterboard on
+the 2.5 Gbps RJ45 port. The PoE circuitry is passive with respect to
+Linux — no special kernel driver is required for basic operation.
+
+For these board variants, the WAN role moves to the dedicated 2.5G internal PHY,
+so all four switch ports become LAN ports (`lan0`–`lan3`) instead of three:
+
+```
+.-.
+| | .-----. .-----.-----.-----.-----.
+| | _______ | | | | | | | .---.
+| | | | | | | | | | | | | .-----.
+'-' '-------' '-----' '-----'-----'-----'-----' '---' '-----'
+USB1 sfp1 wan lan0 lan1 lan2 lan3 DC12V PD20V
+```
+
+### Selecting the Board Variant
+
+U-Boot cannot automatically distinguish the standard R4 from the R4-2g5/R4P at
+boot time (the on-board EEPROM is not programmed from factory). Instead, the
+variant is read from the persistent U-Boot environment on the `aux` partition,
+and you have to set it manually using `fw_setenv` from a UNIX shell:
+
+```bash
+# For BPI-R4-2g5 or BPI-R4P (1x SFP+ + 1x 2.5 Gbps RJ45):
+sudo fw_setenv BOARD_VARIANT 2g5
+
+# To revert to the standard BPI-R4 (2x SFP+) clear the setting:
+sudo fw_setenv BOARD_VARIANT
+```
+
+> [!IMPORTANT]
+> The change takes effect on the next reboot. No re-flashing is required, but
+> you may want to do a factory reset to activate the changes in your system
+> `startup-config`: `sudo factory -y` from shell, or the CLI.
+
+`fw_setenv` writes to the `uboot.env` file on the aux partition. U-Boot
+reads this at every boot (before loading the kernel) and selects the
+matching device tree:
+
+| `BOARD_VARIANT` | Device tree loaded |
+|-----------------|-----------------------------------|
+| *(unset)* | `mt7988a-bananapi-bpi-r4.dtb` |
+| `2g5` | `mt7988a-bananapi-bpi-r4-2g5.dtb` |
+
+## Additional Resources
+
+- [Infix Documentation][1]
+- [Official BPI-R4 Page][7]
+- [BPI-R4 Forum][8]
+- [Frank-W's site][9]
+- [Release Downloads][10]
+- [Bootloader Builds][11]
+
+## Building Custom Images
+
+```bash
+# Build bootloaders for SD and eMMC
+make x-bpi-r4-sd-boot
+make x-bpi-r4-emmc-boot
+
+# Build main system
+make aarch64
+
+# Create SD card image
+./utils/mkimage.sh -od bananapi-bpi-r4
+
+# Create eMMC image
+./utils/mkimage.sh -odt emmc bananapi-bpi-r4
+```
+
+[0]: https://www.kernelkit.org/posts/flashing-sdcard/
+[1]: https://www.kernelkit.org/infix/latest/
+[2]: https://github.com/kernelkit/infix/releases/download/latest-boot/infix-bpi-r4-sdcard.img
+[3]: https://github.com/kernelkit/infix/releases/download/latest-boot/infix-bpi-r4-emmc.img
+[4]: https://github.com/kernelkit/infix/releases/download/latest-boot/bpi-r4-emmc-boot-2026.01-latest.tar.gz
+[7]: https://docs.banana-pi.org/en/BPI-R4/BananaPi_BPI-R4
+[8]: https://forum.banana-pi.org/
+[9]: https://wiki.fw-web.de/doku.php?id=en:bpi-r4:start
+[10]: https://github.com/kernelkit/infix/releases/tag/latest
+[11]: https://github.com/kernelkit/infix/releases/tag/latest-boot
+[12]: https://github.com/frank-w/u-boot/releases
+[13]: https://github.com/frank-w/u-boot/releases/download/CI-BUILD-2026-01-bpi-2026.01-2026-01-15_2013/bpi-r4_spim-nand_bl2.img
+[14]: https://github.com/frank-w/u-boot/releases/download/CI-BUILD-2026-01-bpi-2026.01-2026-01-15_2013/bpi-r4_spim-nand_fip.bin
diff --git a/board/aarch64/bananapi-bpi-r4/banana_pi_bpi-r4_poe.jpg b/board/aarch64/bananapi-bpi-r4/banana_pi_bpi-r4_poe.jpg
new file mode 100644
index 000000000..d035eb553
Binary files /dev/null and b/board/aarch64/bananapi-bpi-r4/banana_pi_bpi-r4_poe.jpg differ
diff --git a/board/aarch64/bananapi-bpi-r4/bananapi-bpi-r4.hash b/board/aarch64/bananapi-bpi-r4/bananapi-bpi-r4.hash
new file mode 100644
index 000000000..2b1e74432
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/bananapi-bpi-r4.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 d48246c717b505cc11df95171f2fd548b389e1a463f1af4c68d0b69fe0d1009b LICENSE
diff --git a/board/aarch64/bananapi-bpi-r4/bananapi-bpi-r4.mk b/board/aarch64/bananapi-bpi-r4/bananapi-bpi-r4.mk
new file mode 100644
index 000000000..645a037a1
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/bananapi-bpi-r4.mk
@@ -0,0 +1,49 @@
+define BANANAPI_BPI_R4_LINUX_CONFIG_FIXUPS
+ $(call KCONFIG_ENABLE_OPT,CONFIG_ARCH_MEDIATEK)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_MACH_MT7988)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_PINCTRL_MT7988)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_SERIAL_8250_MT6577)
+ $(call KCONFIG_SET_OPT,CONFIG_I2C_GPIO,y)
+ $(call KCONFIG_SET_OPT,CONFIG_MTK_THERMAL,m)
+ $(call KCONFIG_SET_OPT,CONFIG_MTK_LVTS_THERMAL,m)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_MTK_UART)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_MEDIATEK_WATCHDOG)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_MEDIATEK_GE_PHY)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_MEDIATEK_GE_SOC_PHY)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_NET_VENDOR_MEDIATEK)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_NET_MEDIATEK_SOC)
+ $(call KCONFIG_SET_OPT,CONFIG_NET_DSA_MT7530,m)
+ $(call KCONFIG_SET_OPT,CONFIG_MT7996E,m)
+ $(call KCONFIG_SET_OPT,CONFIG_PCIE_MEDIATEK_GEN3,m)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_MTK_SCPSYS)
+ $(call KCONFIG_ENABLE_OPT,CONFIG_MMC_MTK)
+ $(call KCONFIG_SET_OPT,CONFIG_I2C_MT65XX,m)
+ $(call KCONFIG_SET_OPT,CONFIG_USB_XHCI_MTK,m)
+ $(call KCONFIG_SET_OPT,CONFIG_PHY_MTK_TPHY,m)
+ $(call KCONFIG_SET_OPT,CONFIG_PHY_MTK_XSPHY,m)
+ $(call KCONFIG_SET_OPT,CONFIG_PHY_MTK_XFI_TPHY,m)
+ $(call KCONFIG_SET_OPT,CONFIG_PCS_STANDALONE,y)
+ $(call KCONFIG_SET_OPT,CONFIG_PCS_MTK_USXGMII,y)
+ $(call KCONFIG_SET_OPT,CONFIG_PWM_MEDIATEK,m)
+ $(call KCONFIG_SET_OPT,CONFIG_SENSORS_PWM_FAN,m)
+ $(call KCONFIG_SET_OPT,CONFIG_NVMEM_MTK_EFUSE,m)
+ $(call KCONFIG_SET_OPT,CONFIG_MEDIATEK_2P5GE_PHY,m)
+ $(call KCONFIG_SET_OPT,CONFIG_CRYPTO_DEV_SAFEXCEL,m)
+endef
+
+LINUX_DTS_MT7988 = $(LINUX_DIR)/arch/arm64/boot/dts/mediatek
+
+define BANANAPI_BPI_R4_KERNEL_DTBS_INSTALL_TARGET
+ @$(call MESSAGE,"Installing kernel DTBs and DTBOs for BPI-R4")
+ $(foreach f, \
+ mt7988a-bananapi-bpi-r4.dtb \
+ mt7988a-bananapi-bpi-r4-2g5.dtb \
+ mt7988a-bananapi-bpi-r4-sd.dtbo \
+ mt7988a-bananapi-bpi-r4-emmc.dtbo, \
+ $(INSTALL) -D $(LINUX_DTS_MT7988)/$(f) \
+ $(TARGET_DIR)/boot/mediatek/$(f)$(sep))
+endef
+BANANAPI_BPI_R4_POST_INSTALL_TARGET_HOOKS += BANANAPI_BPI_R4_KERNEL_DTBS_INSTALL_TARGET
+
+$(eval $(ix-board))
+$(eval $(generic-package))
diff --git a/board/aarch64/bananapi-bpi-r4/bootstrap-sw3.png b/board/aarch64/bananapi-bpi-r4/bootstrap-sw3.png
new file mode 100644
index 000000000..a76d17152
Binary files /dev/null and b/board/aarch64/bananapi-bpi-r4/bootstrap-sw3.png differ
diff --git a/board/aarch64/bananapi-bpi-r4/bootstrap-switch.jpg b/board/aarch64/bananapi-bpi-r4/bootstrap-switch.jpg
new file mode 100644
index 000000000..70c3a3d91
Binary files /dev/null and b/board/aarch64/bananapi-bpi-r4/bootstrap-switch.jpg differ
diff --git a/board/aarch64/bananapi-bpi-r4/console-header.png b/board/aarch64/bananapi-bpi-r4/console-header.png
new file mode 100644
index 000000000..2f2bb246e
Binary files /dev/null and b/board/aarch64/bananapi-bpi-r4/console-header.png differ
diff --git a/board/aarch64/bananapi-bpi-r4/dts/Makefile b/board/aarch64/bananapi-bpi-r4/dts/Makefile
new file mode 100644
index 000000000..a3cd484c7
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/dts/Makefile
@@ -0,0 +1,2 @@
+dtb-y += infix/bananapi,bpi-r4.dtbo
+dtb-y += infix/bananapi,bpi-r4-2g5.dtbo
diff --git a/board/aarch64/bananapi-bpi-r4/dts/infix/bananapi,bpi-r4-2g5.dtso b/board/aarch64/bananapi-bpi-r4/dts/infix/bananapi,bpi-r4-2g5.dtso
new file mode 100644
index 000000000..286553f75
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/dts/infix/bananapi,bpi-r4-2g5.dtso
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/dts-v1/;
+/plugin/;
+
+/ {
+ compatible = "bananapi,bpi-r4-2g5";
+};
+
+/*
+ * On the 2g5 variant, GMAC1 uses the internal 2.5G PHY (eth1 renamed to
+ * "wan" by udev). The switch's port0, which is labeled "wan" in the base
+ * DTS, would cause a name collision when the switch driver registers it.
+ * Rename it to "lan0" here so the kernel names it correctly from the start.
+ */
+&gsw_port0 {
+ label = "lan0";
+};
+
+/*
+ * The upstream mt7988a.dtsi sets compatible = "ethernet-phy-ieee802.3-c45"
+ * on the internal 2.5G PHY node. That gives genphy_c45 (built-in) OF-match
+ * priority over the PHY-ID-based mtk-2p5ge driver, leaving the PHY powered
+ * down and unclaimed. Replace it with the standard ethernet-phy-idXXXX.XXXX
+ * form so of_get_phy_id() registers the PHY as C22 with explicit ID 0x00339c11.
+ * mtk-2p5ge then matches via PHY_ID_MATCH_VENDOR(0x00339c00) instead of
+ * genphy_c45 winning via OF-match.
+ * Note: /delete-property/ is not used because U-Boot fdt apply does not
+ * implement property deletion in overlays.
+ */
+&int_2p5g_phy {
+ compatible = "ethernet-phy-id0033.9c11";
+};
diff --git a/board/aarch64/bananapi-bpi-r4/dts/infix/bananapi,bpi-r4.dtso b/board/aarch64/bananapi-bpi-r4/dts/infix/bananapi,bpi-r4.dtso
new file mode 100644
index 000000000..7ea10d03b
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/dts/infix/bananapi,bpi-r4.dtso
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/dts-v1/;
+/plugin/;
+
+/ {
+ compatible = "bananapi,bpi-r4", "mediatek,mt7988a";
+};
+
+/*
+ * The upstream mt7988a-bananapi-bpi-r4.dtsi sets stdout-path = "serial0:..."
+ * but the aliases node there only has ethernet entries. Linux resolves
+ * stdout-path by looking in /aliases, so we must add serial0 here.
+ * Path references in overlay aliases cannot use &label, so use the full path.
+ */
+&{/} {
+ aliases {
+ serial0 = "/soc/serial@11000000";
+ };
+
+ chosen {
+ infix {
+ /* Default admin user password: 'admin' */
+ factory-password-hash = "$5$mI/zpOAqZYKLC2WU$i7iPzZiIjOjrBF3NyftS9CCq8dfYwHwrmUK097Jca9A";
+ usb-ports = <&ssusb1>;
+ usb-port-names = "USB";
+ };
+ };
+};
diff --git a/board/aarch64/bananapi-bpi-r4/genimage.cfg.in b/board/aarch64/bananapi-bpi-r4/genimage.cfg.in
new file mode 100644
index 000000000..51bda51da
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/genimage.cfg.in
@@ -0,0 +1,81 @@
+image cfg.ext4 {
+ empty = true
+ temporary = true
+ size = 128M
+ ext4 {
+ label = "cfg"
+ use-mke2fs = true
+ features = "uninit_bg"
+ extraargs = "-m 0 -i 4096"
+ }
+}
+
+# The /var partition will be expanded automatically at first boot
+# to use the full size of the SD-card or eMMC media.
+image var.ext4 {
+ empty = true
+ temporary = true
+ size = 128M
+ ext4 {
+ label = "var"
+ use-mke2fs = true
+ features = "uninit_bg"
+ extraargs = "-m 0 -i 4096"
+ }
+}
+
+image #INFIX_ID##VERSION#-bpi-r4-#TARGET#.img {
+ hdimage {
+ partition-table-type = "gpt"
+ gpt-no-backup = true
+ }
+ # BL2 bootloader partition (MediaTek official location)
+ partition bl2 {
+ image = "bl2.img"
+ offset = 1024s # 0x80000 = sector 1024
+ size = 4M # 0x400000
+ bootable = true
+ }
+
+ # Factory/calibration data (sectors 9216-13311)
+ partition factory {
+ offset = 4608K # 0x480000
+ size = 2M # 0x200000
+ }
+
+ # FIP partition - BL31 + U-Boot (sectors 13312-17407)
+ partition fip {
+ image = "fip.bin"
+ offset = 13312s
+ size = 4096s
+ }
+
+ partition aux {
+ partition-uuid = D4EF35A0-0652-45A1-B3DE-D63339C82035
+ image = "aux.ext4"
+ }
+
+ partition primary {
+ partition-type-uuid = 0FC63DAF-8483-4772-8E79-3D69D8477DE4
+ bootable = true
+ size = 250M
+ image = "rootfs.squashfs"
+ }
+
+ partition secondary {
+ partition-type-uuid = 0FC63DAF-8483-4772-8E79-3D69D8477DE4
+ bootable = true
+ size = 250M
+ image = "rootfs.squashfs"
+ }
+
+ partition cfg {
+ partition-uuid = 7aa497f0-73b5-47e5-b2ab-8752d8a48105
+ image = "cfg.ext4"
+ }
+
+ partition var {
+ partition-uuid = 8046A06A-E45A-4A14-A6AD-6684704A393F
+ image = "var.ext4"
+ }
+}
diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-2g5-emmc-syslinux.conf b/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-2g5-emmc-syslinux.conf
new file mode 100644
index 000000000..f74dc1094
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-2g5-emmc-syslinux.conf
@@ -0,0 +1,5 @@
+label Infix (aarch64)
+ kernel /boot/Image
+ fdtdir /boot
+ fdtoverlays /boot/mediatek/mt7988a-bananapi-bpi-r4-emmc.dtbo /boot/infix/bananapi,bpi-r4.dtbo /boot/infix/bananapi,bpi-r4-2g5.dtbo
+ append ${bootargs_root} ${bootargs_log} usbcore.authorized_default=2 -- ${bootargs_user}
diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-2g5-sdmmc-syslinux.conf b/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-2g5-sdmmc-syslinux.conf
new file mode 100644
index 000000000..4d198362d
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-2g5-sdmmc-syslinux.conf
@@ -0,0 +1,5 @@
+label Infix (aarch64)
+ kernel /boot/Image
+ fdtdir /boot
+ fdtoverlays /boot/mediatek/mt7988a-bananapi-bpi-r4-sd.dtbo /boot/infix/bananapi,bpi-r4.dtbo /boot/infix/bananapi,bpi-r4-2g5.dtbo
+ append ${bootargs_root} ${bootargs_log} usbcore.authorized_default=2 -- ${bootargs_user}
diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-emmc-syslinux.conf b/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-emmc-syslinux.conf
new file mode 100644
index 000000000..079c9a262
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-emmc-syslinux.conf
@@ -0,0 +1,5 @@
+label Infix (aarch64)
+ kernel /boot/Image
+ fdtdir /boot
+ fdtoverlays /boot/mediatek/mt7988a-bananapi-bpi-r4-emmc.dtbo /boot/infix/bananapi,bpi-r4.dtbo
+ append ${bootargs_root} ${bootargs_log} usbcore.authorized_default=2 -- ${bootargs_user}
diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-sdmmc-syslinux.conf b/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-sdmmc-syslinux.conf
new file mode 100644
index 000000000..b3734a23d
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/rootfs/boot/syslinux/bananapi,bpi-r4-sdmmc-syslinux.conf
@@ -0,0 +1,5 @@
+label Infix (aarch64)
+ kernel /boot/Image
+ fdtdir /boot
+ fdtoverlays /boot/mediatek/mt7988a-bananapi-bpi-r4-sd.dtbo /boot/infix/bananapi,bpi-r4.dtbo
+ append ${bootargs_root} ${bootargs_log} usbcore.authorized_default=2 -- ${bootargs_user}
diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/factory-config.cfg
new file mode 100644
index 000000000..5571d3200
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/factory-config.cfg
@@ -0,0 +1,404 @@
+{
+ "ieee802-dot1ab-lldp:lldp": {
+ "infix-lldp:enabled": true
+ },
+ "ietf-hardware:hardware": {
+ "component": [
+ {
+ "name": "USB",
+ "class": "infix-hardware:usb",
+ "state": {
+ "admin-state": "unlocked"
+ }
+ }
+ ]
+ },
+ "ietf-interfaces:interfaces": {
+ "interface": [
+ {
+ "name": "br0",
+ "type": "infix-if-type:bridge",
+ "ietf-ip:ipv4": {
+ "address": [
+ {
+ "ip": "192.168.0.1",
+ "prefix-length": 24
+ }
+ ]
+ }
+ },
+ {
+ "name": "lan0",
+ "type": "infix-if-type:ethernet",
+ "ietf-ip:ipv6": {},
+ "infix-interfaces:bridge-port": {
+ "bridge": "br0"
+ }
+ },
+ {
+ "name": "lan1",
+ "type": "infix-if-type:ethernet",
+ "ietf-ip:ipv6": {},
+ "infix-interfaces:bridge-port": {
+ "bridge": "br0"
+ }
+ },
+ {
+ "name": "lan2",
+ "type": "infix-if-type:ethernet",
+ "ietf-ip:ipv6": {},
+ "infix-interfaces:bridge-port": {
+ "bridge": "br0"
+ }
+ },
+ {
+ "name": "lan3",
+ "type": "infix-if-type:ethernet",
+ "ietf-ip:ipv6": {},
+ "infix-interfaces:bridge-port": {
+ "bridge": "br0"
+ }
+ },
+ {
+ "name": "lo",
+ "type": "infix-if-type:loopback",
+ "ietf-ip:ipv4": {
+ "address": [
+ {
+ "ip": "127.0.0.1",
+ "prefix-length": 8
+ }
+ ]
+ },
+ "ietf-ip:ipv6": {
+ "address": [
+ {
+ "ip": "::1",
+ "prefix-length": 128
+ }
+ ]
+ }
+ },
+ {
+ "name": "wan",
+ "type": "infix-if-type:ethernet",
+ "ietf-ip:ipv4": {
+ "infix-dhcp-client:dhcp": {
+ "option": [
+ {
+ "id": "ntp-server"
+ },
+ {
+ "id": "broadcast"
+ },
+ {
+ "id": "domain"
+ },
+ {
+ "id": "hostname",
+ "value": "auto"
+ },
+ {
+ "id": "dns-server"
+ },
+ {
+ "id": "router"
+ },
+ {
+ "id": "netmask"
+ },
+ {
+ "id": "vendor-class",
+ "value": "Banana Pi BPI-R4"
+ }
+ ]
+ }
+ },
+ "ietf-ip:ipv6": {
+ "infix-dhcpv6-client:dhcp": {
+ "option": [
+ {
+ "id": "ntp-server"
+ },
+ {
+ "id": "client-fqdn"
+ },
+ {
+ "id": "domain-search"
+ },
+ {
+ "id": "dns-server"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "ietf-keystore:keystore": {
+ "asymmetric-keys": {
+ "asymmetric-key": [
+ {
+ "name": "genkey",
+ "public-key-format": "infix-crypto-types:ssh-public-key-format",
+ "public-key": "",
+ "private-key-format": "infix-crypto-types:rsa-private-key-format",
+ "cleartext-private-key": "",
+ "certificates": {}
+ }
+ ]
+ }
+ },
+ "ietf-netconf-acm:nacm": {
+ "enable-nacm": true,
+ "read-default": "permit",
+ "write-default": "permit",
+ "exec-default": "permit",
+ "groups": {
+ "group": [
+ {
+ "name": "admin",
+ "user-name": [
+ "admin"
+ ]
+ },
+ {
+ "name": "operator",
+ "user-name": []
+ },
+ {
+ "name": "guest",
+ "user-name": []
+ }
+ ]
+ },
+ "rule-list": [
+ {
+ "name": "admin-acl",
+ "group": [
+ "admin"
+ ],
+ "rule": [
+ {
+ "name": "permit-all",
+ "module-name": "*",
+ "access-operations": "*",
+ "action": "permit",
+ "comment": "Allow 'admin' group complete access to all operations and data."
+ }
+ ]
+ },
+ {
+ "name": "operator-acl",
+ "group": [
+ "operator"
+ ],
+ "rule": [
+ {
+ "name": "permit-system-rpcs",
+ "module-name": "ietf-system",
+ "rpc-name": "*",
+ "access-operations": "exec",
+ "action": "permit",
+ "comment": "Operators can reboot, shutdown, and set system time."
+ }
+ ]
+ },
+ {
+ "name": "guest-acl",
+ "group": [
+ "guest"
+ ],
+ "rule": [
+ {
+ "name": "deny-all-write+exec",
+ "module-name": "*",
+ "access-operations": "create update delete exec",
+ "action": "deny",
+ "comment": "Guests cannot change anything or exec rpcs."
+ }
+ ]
+ },
+ {
+ "name": "default-deny-all",
+ "group": [
+ "*"
+ ],
+ "rule": [
+ {
+ "name": "deny-password-access",
+ "path": "/ietf-system:system/authentication/user/password",
+ "access-operations": "*",
+ "action": "deny",
+ "comment": "No user except admins can access password hashes."
+ },
+ {
+ "name": "deny-keystore-access",
+ "module-name": "ietf-keystore",
+ "access-operations": "*",
+ "action": "deny",
+ "comment": "No user except admins can access cryptographic keys."
+ },
+ {
+ "name": "deny-truststore-access",
+ "module-name": "ietf-truststore",
+ "access-operations": "*",
+ "action": "deny",
+ "comment": "No user except admins can access trust store."
+ }
+ ]
+ }
+ ]
+ },
+ "ietf-netconf-server:netconf-server": {
+ "listen": {
+ "endpoints": {
+ "endpoint": [
+ {
+ "name": "default-ssh",
+ "ssh": {
+ "tcp-server-parameters": {
+ "local-bind": [
+ {
+ "local-address": "::"
+ }
+ ]
+ },
+ "ssh-server-parameters": {
+ "server-identity": {
+ "host-key": [
+ {
+ "name": "default-key",
+ "public-key": {
+ "central-keystore-reference": "genkey"
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ietf-system:system": {
+ "hostname": "bpi-%m",
+ "ntp": {
+ "server": [
+ {
+ "name": "default",
+ "udp": {
+ "address": "pool.ntp.org"
+ }
+ }
+ ]
+ },
+ "authentication": {
+ "user": [
+ {
+ "name": "admin",
+ "password": "$factory$",
+ "infix-system:shell": "bash"
+ }
+ ]
+ },
+ "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo="
+ },
+ "infix-dhcp-server:dhcp-server": {
+ "option": [
+ {
+ "id": "ntp-server",
+ "address": "auto"
+ },
+ {
+ "id": "dns-server",
+ "address": "auto"
+ },
+ {
+ "id": "router",
+ "address": "auto"
+ }
+ ],
+ "subnet": [
+ {
+ "subnet": "192.168.0.0/24",
+ "pool": {
+ "start-address": "192.168.0.100",
+ "end-address": "192.168.0.250"
+ }
+ }
+ ]
+ },
+ "infix-firewall:firewall": {
+ "default": "wan",
+ "zone": [
+ {
+ "name": "lan",
+ "action": "accept",
+ "interface": [
+ "br0"
+ ]
+ },
+ {
+ "name": "wan",
+ "action": "drop",
+ "interface": [
+ "wan"
+ ],
+ "service": [
+ "dhcpv6-client"
+ ]
+ }
+ ],
+ "policy": [
+ {
+ "name": "lan-to-wan",
+ "action": "accept",
+ "ingress": [
+ "lan"
+ ],
+ "egress": [
+ "wan"
+ ],
+ "masquerade": true
+ }
+ ]
+ },
+ "infix-meta:meta": {
+ "version": "1.7"
+ },
+ "infix-services:mdns": {
+ "enabled": true
+ },
+ "infix-services:ssh": {
+ "enabled": true,
+ "hostkey": [
+ "genkey"
+ ],
+ "listen": [
+ {
+ "name": "ipv4",
+ "address": "0.0.0.0",
+ "port": 22
+ },
+ {
+ "name": "ipv6",
+ "address": "::",
+ "port": 22
+ }
+ ]
+ },
+ "infix-services:web": {
+ "enabled": true,
+ "console": {
+ "enabled": true
+ },
+ "netbrowse": {
+ "enabled": true
+ },
+ "restconf": {
+ "enabled": true
+ }
+ }
+}
diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/udev/rules.d/60-mtk-2p5ge-phy.rules b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/udev/rules.d/60-mtk-2p5ge-phy.rules
new file mode 100644
index 000000000..e4ec55b32
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/udev/rules.d/60-mtk-2p5ge-phy.rules
@@ -0,0 +1,5 @@
+# The mtk-2p5ge PHY driver uses MDIO PHY ID matching, not OF compatible
+# matching. udev auto-loading is based on MODALIAS, which for DT-described
+# devices is "of:N...C" — a format that never matches the driver's
+# "mdio:..." module alias. Match on OF_COMPATIBLE_0 explicitly instead.
+ACTION=="add", SUBSYSTEM=="mdio_bus", ENV{OF_COMPATIBLE_0}=="ethernet-phy-id0033.9c11", RUN+="/sbin/modprobe -b mtk-2p5ge"
diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/udev/rules.d/90-bpi-r4-rename-ifaces.rules b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/udev/rules.d/90-bpi-r4-rename-ifaces.rules
new file mode 100644
index 000000000..20d58c4bd
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/udev/rules.d/90-bpi-r4-rename-ifaces.rules
@@ -0,0 +1,2 @@
+ACTION=="add", SUBSYSTEM=="net", DEVPATH=="/devices/platform/soc/15100000.ethernet/net/eth1", NAME="wan"
+ACTION=="add", SUBSYSTEM=="net", DEVPATH=="/devices/platform/soc/15100000.ethernet/net/eth2", NAME="sfp1"
diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4/etc/factory-config.cfg
new file mode 100644
index 000000000..13a22c9ba
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4/etc/factory-config.cfg
@@ -0,0 +1,396 @@
+{
+ "ieee802-dot1ab-lldp:lldp": {
+ "infix-lldp:enabled": true
+ },
+ "ietf-hardware:hardware": {
+ "component": [
+ {
+ "name": "USB",
+ "class": "infix-hardware:usb",
+ "state": {
+ "admin-state": "unlocked"
+ }
+ }
+ ]
+ },
+ "ietf-interfaces:interfaces": {
+ "interface": [
+ {
+ "name": "br0",
+ "type": "infix-if-type:bridge",
+ "ietf-ip:ipv4": {
+ "address": [
+ {
+ "ip": "192.168.0.1",
+ "prefix-length": 24
+ }
+ ]
+ }
+ },
+ {
+ "name": "lan1",
+ "type": "infix-if-type:ethernet",
+ "ietf-ip:ipv6": {},
+ "infix-interfaces:bridge-port": {
+ "bridge": "br0"
+ }
+ },
+ {
+ "name": "lan2",
+ "type": "infix-if-type:ethernet",
+ "ietf-ip:ipv6": {},
+ "infix-interfaces:bridge-port": {
+ "bridge": "br0"
+ }
+ },
+ {
+ "name": "lan3",
+ "type": "infix-if-type:ethernet",
+ "ietf-ip:ipv6": {},
+ "infix-interfaces:bridge-port": {
+ "bridge": "br0"
+ }
+ },
+ {
+ "name": "lo",
+ "type": "infix-if-type:loopback",
+ "ietf-ip:ipv4": {
+ "address": [
+ {
+ "ip": "127.0.0.1",
+ "prefix-length": 8
+ }
+ ]
+ },
+ "ietf-ip:ipv6": {
+ "address": [
+ {
+ "ip": "::1",
+ "prefix-length": 128
+ }
+ ]
+ }
+ },
+ {
+ "name": "wan",
+ "type": "infix-if-type:ethernet",
+ "ietf-ip:ipv4": {
+ "infix-dhcp-client:dhcp": {
+ "option": [
+ {
+ "id": "ntp-server"
+ },
+ {
+ "id": "broadcast"
+ },
+ {
+ "id": "domain"
+ },
+ {
+ "id": "hostname",
+ "value": "auto"
+ },
+ {
+ "id": "dns-server"
+ },
+ {
+ "id": "router"
+ },
+ {
+ "id": "netmask"
+ },
+ {
+ "id": "vendor-class",
+ "value": "Banana Pi BPI-R4"
+ }
+ ]
+ }
+ },
+ "ietf-ip:ipv6": {
+ "infix-dhcpv6-client:dhcp": {
+ "option": [
+ {
+ "id": "ntp-server"
+ },
+ {
+ "id": "client-fqdn"
+ },
+ {
+ "id": "domain-search"
+ },
+ {
+ "id": "dns-server"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "ietf-keystore:keystore": {
+ "asymmetric-keys": {
+ "asymmetric-key": [
+ {
+ "name": "genkey",
+ "public-key-format": "infix-crypto-types:ssh-public-key-format",
+ "public-key": "",
+ "private-key-format": "infix-crypto-types:rsa-private-key-format",
+ "cleartext-private-key": "",
+ "certificates": {}
+ }
+ ]
+ }
+ },
+ "ietf-netconf-acm:nacm": {
+ "enable-nacm": true,
+ "read-default": "permit",
+ "write-default": "permit",
+ "exec-default": "permit",
+ "groups": {
+ "group": [
+ {
+ "name": "admin",
+ "user-name": [
+ "admin"
+ ]
+ },
+ {
+ "name": "operator",
+ "user-name": []
+ },
+ {
+ "name": "guest",
+ "user-name": []
+ }
+ ]
+ },
+ "rule-list": [
+ {
+ "name": "admin-acl",
+ "group": [
+ "admin"
+ ],
+ "rule": [
+ {
+ "name": "permit-all",
+ "module-name": "*",
+ "access-operations": "*",
+ "action": "permit",
+ "comment": "Allow 'admin' group complete access to all operations and data."
+ }
+ ]
+ },
+ {
+ "name": "operator-acl",
+ "group": [
+ "operator"
+ ],
+ "rule": [
+ {
+ "name": "permit-system-rpcs",
+ "module-name": "ietf-system",
+ "rpc-name": "*",
+ "access-operations": "exec",
+ "action": "permit",
+ "comment": "Operators can reboot, shutdown, and set system time."
+ }
+ ]
+ },
+ {
+ "name": "guest-acl",
+ "group": [
+ "guest"
+ ],
+ "rule": [
+ {
+ "name": "deny-all-write+exec",
+ "module-name": "*",
+ "access-operations": "create update delete exec",
+ "action": "deny",
+ "comment": "Guests cannot change anything or exec rpcs."
+ }
+ ]
+ },
+ {
+ "name": "default-deny-all",
+ "group": [
+ "*"
+ ],
+ "rule": [
+ {
+ "name": "deny-password-access",
+ "path": "/ietf-system:system/authentication/user/password",
+ "access-operations": "*",
+ "action": "deny",
+ "comment": "No user except admins can access password hashes."
+ },
+ {
+ "name": "deny-keystore-access",
+ "module-name": "ietf-keystore",
+ "access-operations": "*",
+ "action": "deny",
+ "comment": "No user except admins can access cryptographic keys."
+ },
+ {
+ "name": "deny-truststore-access",
+ "module-name": "ietf-truststore",
+ "access-operations": "*",
+ "action": "deny",
+ "comment": "No user except admins can access trust store."
+ }
+ ]
+ }
+ ]
+ },
+ "ietf-netconf-server:netconf-server": {
+ "listen": {
+ "endpoints": {
+ "endpoint": [
+ {
+ "name": "default-ssh",
+ "ssh": {
+ "tcp-server-parameters": {
+ "local-bind": [
+ {
+ "local-address": "::"
+ }
+ ]
+ },
+ "ssh-server-parameters": {
+ "server-identity": {
+ "host-key": [
+ {
+ "name": "default-key",
+ "public-key": {
+ "central-keystore-reference": "genkey"
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ietf-system:system": {
+ "hostname": "bpi-%m",
+ "ntp": {
+ "server": [
+ {
+ "name": "default",
+ "udp": {
+ "address": "pool.ntp.org"
+ }
+ }
+ ]
+ },
+ "authentication": {
+ "user": [
+ {
+ "name": "admin",
+ "password": "$factory$",
+ "infix-system:shell": "bash"
+ }
+ ]
+ },
+ "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo="
+ },
+ "infix-dhcp-server:dhcp-server": {
+ "option": [
+ {
+ "id": "ntp-server",
+ "address": "auto"
+ },
+ {
+ "id": "dns-server",
+ "address": "auto"
+ },
+ {
+ "id": "router",
+ "address": "auto"
+ }
+ ],
+ "subnet": [
+ {
+ "subnet": "192.168.0.0/24",
+ "pool": {
+ "start-address": "192.168.0.100",
+ "end-address": "192.168.0.250"
+ }
+ }
+ ]
+ },
+ "infix-firewall:firewall": {
+ "default": "wan",
+ "zone": [
+ {
+ "name": "lan",
+ "action": "accept",
+ "interface": [
+ "br0"
+ ]
+ },
+ {
+ "name": "wan",
+ "action": "drop",
+ "interface": [
+ "wan"
+ ],
+ "service": [
+ "dhcpv6-client"
+ ]
+ }
+ ],
+ "policy": [
+ {
+ "name": "lan-to-wan",
+ "action": "accept",
+ "ingress": [
+ "lan"
+ ],
+ "egress": [
+ "wan"
+ ],
+ "masquerade": true
+ }
+ ]
+ },
+ "infix-meta:meta": {
+ "version": "1.7"
+ },
+ "infix-services:mdns": {
+ "enabled": true
+ },
+ "infix-services:ssh": {
+ "enabled": true,
+ "hostkey": [
+ "genkey"
+ ],
+ "listen": [
+ {
+ "name": "ipv4",
+ "address": "0.0.0.0",
+ "port": 22
+ },
+ {
+ "name": "ipv6",
+ "address": "::",
+ "port": 22
+ }
+ ]
+ },
+ "infix-services:web": {
+ "enabled": true,
+ "console": {
+ "enabled": true
+ },
+ "netbrowse": {
+ "enabled": true
+ },
+ "restconf": {
+ "enabled": true
+ }
+ }
+}
diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4/etc/udev/rules.d/90-bpi-r4-rename-ifaces.rules b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4/etc/udev/rules.d/90-bpi-r4-rename-ifaces.rules
new file mode 100644
index 000000000..33a17bea8
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4/etc/udev/rules.d/90-bpi-r4-rename-ifaces.rules
@@ -0,0 +1,2 @@
+ACTION=="add", SUBSYSTEM=="net", DEVPATH=="/devices/platform/soc/15100000.ethernet/net/eth1", NAME="sfp2"
+ACTION=="add", SUBSYSTEM=="net", DEVPATH=="/devices/platform/soc/15100000.ethernet/net/eth2", NAME="sfp1"
diff --git a/board/aarch64/bananapi-bpi-r4/uboot/emmc-extras.config b/board/aarch64/bananapi-bpi-r4/uboot/emmc-extras.config
new file mode 100644
index 000000000..89aa7e09f
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/uboot/emmc-extras.config
@@ -0,0 +1 @@
+CONFIG_DEVICE_TREE_INCLUDES="infix-env.dtsi infix-key.dtsi mt7988-emmc-env.dtsi"
diff --git a/board/aarch64/bananapi-bpi-r4/uboot/extras.config b/board/aarch64/bananapi-bpi-r4/uboot/extras.config
new file mode 100644
index 000000000..bf7d6676c
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/uboot/extras.config
@@ -0,0 +1,40 @@
+CONFIG_AUTOBOOT=y
+CONFIG_BOOTDELAY=2
+# CONFIG_MMC_PCI is not set
+CONFIG_ENV_IS_NOWHERE=y
+# CONFIG_ENV_IS_IN_MMC is not set
+CONFIG_MULTI_DTB_FIT=y
+CONFIG_OF_LIST="mt7988a-bananapi-bpi-r4 mt7988a-bananapi-bpi-r4-2g5"
+
+CONFIG_USB=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_MTK=y
+CONFIG_USB_MTU3=y
+CONFIG_PHY=y
+CONFIG_PHY_MTK_TPHY=y
+
+CONFIG_MTK_SPIM=y
+CONFIG_SPI=y
+CONFIG_DM_SPI=y
+CONFIG_SPI_FLASH=y
+CONFIG_DM_SPI_FLASH=y
+CONFIG_SPI_FLASH_MTD=y
+CONFIG_SPI_FLASH_MACRONIX=y
+CONFIG_SPI_FLASH_WINBOND=y
+CONFIG_SPI_FLASH_GIGADEVICE=y
+CONFIG_MTD=y
+CONFIG_DM_MTD=y
+CONFIG_MTD_PARTITIONS=y
+CONFIG_MTD_SPI_NAND=y
+CONFIG_MTK_SPIM=y
+CONFIG_MTK_SNOR=y
+
+CONFIG_BUTTON=y
+CONFIG_BUTTON_CMD=y
+CONFIG_BUTTON_GPIO=y
+
+CONFIG_CMD_SF=y
+CONFIG_CMD_USB=y
+CONFIG_CMD_MTD=y
+CONFIG_CMD_MTDPARTS=y
+CONFIG_CMD_DM=y
diff --git a/board/aarch64/bananapi-bpi-r4/uboot/mt7988-emmc-env.dtsi b/board/aarch64/bananapi-bpi-r4/uboot/mt7988-emmc-env.dtsi
new file mode 100644
index 000000000..4b2946a22
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/uboot/mt7988-emmc-env.dtsi
@@ -0,0 +1,7 @@
+#include
+
+&env {
+ fdtfile = "mediatek/mt7988a-bananapi-bpi-r4.dtb";
+ board = "bananapi,bpi-r4-emmc";
+ ixvariant = "if test \"${BOARD_VARIANT}\" = \"2g5\"; then setenv fdtfile mediatek/mt7988a-bananapi-bpi-r4-2g5.dtb; setenv board bananapi,bpi-r4-2g5-emmc; fi";
+};
diff --git a/board/aarch64/bananapi-bpi-r4/uboot/mt7988-env.dtsi b/board/aarch64/bananapi-bpi-r4/uboot/mt7988-env.dtsi
new file mode 100644
index 000000000..b3ffc7c44
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/uboot/mt7988-env.dtsi
@@ -0,0 +1,27 @@
+/ {
+ config {
+ env: environment {
+ bootcmd = "run ixboot";
+ boot_targets = "mmc0";
+ ethprime = "eth0";
+ fdt_addr_r = "0x43f00000";
+ kernel_addr_r = "0x44000000";
+ fdtoverlay_addr_r = "0x47f00000";
+ scriptaddr = "0x48000000";
+ ramdisk_addr_r = "0x4A000000";
+
+ /*
+ * Apply BOARD_VARIANT from aux uboot.env.
+ * Infix writes BOARD_VARIANT=2g5 via fw_setenv on
+ * BPI-R4-2g5 / BPI-R4P boards to select the correct
+ * device tree. Unset = standard R4 (2x SFP+).
+ */
+ ixvariant = "if test \"${BOARD_VARIANT}\" = \"2g5\"; then setenv fdtfile mediatek/mt7988a-bananapi-bpi-r4-2g5.dtb; fi";
+
+ /* This is a development platform, keep
+ * developer mode statically enabled.
+ */
+ ixbtn-devmode = "setenv dev_mode yes; echo Enabled";
+ };
+ };
+};
diff --git a/board/aarch64/bananapi-bpi-r4/uboot/mt7988-sd-env.dtsi b/board/aarch64/bananapi-bpi-r4/uboot/mt7988-sd-env.dtsi
new file mode 100644
index 000000000..214e67b0e
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/uboot/mt7988-sd-env.dtsi
@@ -0,0 +1,7 @@
+#include
+
+&env {
+ fdtfile = "mediatek/mt7988a-bananapi-bpi-r4.dtb";
+ board = "bananapi,bpi-r4-sdmmc";
+ ixvariant = "if test \"${BOARD_VARIANT}\" = \"2g5\"; then setenv fdtfile mediatek/mt7988a-bananapi-bpi-r4-2g5.dtb; setenv board bananapi,bpi-r4-2g5-sdmmc; fi";
+};
diff --git a/board/aarch64/bananapi-bpi-r4/uboot/sd-extras.config b/board/aarch64/bananapi-bpi-r4/uboot/sd-extras.config
new file mode 100644
index 000000000..58e915f13
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r4/uboot/sd-extras.config
@@ -0,0 +1,2 @@
+CONFIG_DEVICE_TREE_INCLUDES="infix-env.dtsi infix-key.dtsi mt7988-sd-env.dtsi"
+CONFIG_OF_LIST="mt7988a-bananapi-bpi-r4-sd"
diff --git a/board/aarch64/bananapi-bpi-r64/README.md b/board/aarch64/bananapi-bpi-r64/README.md
index 9db6728b1..e4b1ee42d 100644
--- a/board/aarch64/bananapi-bpi-r64/README.md
+++ b/board/aarch64/bananapi-bpi-r64/README.md
@@ -30,20 +30,15 @@ Infix comes preconfigured with:
-The BPI-R64 uses a 2-position DIP switch (SW1) to select the boot device
-order. The MT7622 Boot ROM tries devices in the order listed and falls
-back to the next if no valid BL2 is found at the expected location.
+The BPI-R64 has a 2-position DIP switch (SW1) for selecting the boot device.
+The MT7622 Boot ROM always tries SD first if a card is present, so you can
+leave SW1 in the OFF (eMMC) position and simply insert or remove an SD card
+to control boot device selection.
-| SW1 | Boot device | Use case |
-|-----|-------------|----------------------------|
-| OFF | eMMC | Production eMMC boot |
-| ON | SD card | SD card boot / development |
-
-> [!NOTE]
-> SinoVoip has exposed only one bit of the MT7622's two-bit `BOOT_SEL[1:0]`
-> strapping field via SW1, with `BOOT_SEL[1]` hardwired high. This limits the
-> board to eMMC (`10b`) and SD (`11b`) boot; the SPI-NOR and SPI-NAND modes
-> available on the MT7622 reference board (`00b`, `01b`) are not selectable.
+| SW1 | Boot device |
+|-----|-------------|
+| OFF | eMMC |
+| ON | SD card |
## Getting Started
@@ -58,11 +53,10 @@ back to the next if no valid BL2 is found at the expected location.
dd if=infix-*-bpi-r64-sdcard.img of=/dev/sdX bs=4M status=progress
```
-2. **Set boot switch:** SW1 ON (SD card boot)
-3. **Insert SD card and power on**
-4. **Connect console:** 115200 8N1 — use the dedicated Debug UART header
+2. **Insert SD card and power on**
+3. **Connect console:** 115200 8N1 — use the dedicated Debug UART header
just below the 40-pin GPIO header; pins are labeled GND, RX, TX on the board
-5. **Default login:** `admin` / `admin`
+4. **Default login:** `admin` / `admin`
## Installing to eMMC
@@ -84,9 +78,8 @@ drive.
#### Step 1: Boot from SD card
-1. Set SW1 to ON (SD boot)
-2. Insert SD card with Infix
-3. Power on and break into U-Boot (press Ctrl-C during boot)
+1. Insert SD card with Infix
+2. Power on and break into U-Boot (press Ctrl-C during boot)
#### Step 2: Write the eMMC image from U-Boot
@@ -115,9 +108,8 @@ mmc partconf 0 1 1 0
#### Step 4: Boot from eMMC
1. Power off the board
-2. Set SW1 to OFF (eMMC boot)
-3. Remove SD card and USB drive
-4. Power on
+2. Remove SD card and USB drive
+3. Power on
## Platform Notes
diff --git a/board/aarch64/bananapi-bpi-r64/bananapi-bpi-r64.hash b/board/aarch64/bananapi-bpi-r64/bananapi-bpi-r64.hash
new file mode 100644
index 000000000..2b1e74432
--- /dev/null
+++ b/board/aarch64/bananapi-bpi-r64/bananapi-bpi-r64.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 d48246c717b505cc11df95171f2fd548b389e1a463f1af4c68d0b69fe0d1009b LICENSE
diff --git a/board/aarch64/bananapi-bpi-r64/uboot/emmc-extras.config b/board/aarch64/bananapi-bpi-r64/uboot/emmc-extras.config
index 9dff8112a..896f66474 100644
--- a/board/aarch64/bananapi-bpi-r64/uboot/emmc-extras.config
+++ b/board/aarch64/bananapi-bpi-r64/uboot/emmc-extras.config
@@ -1,2 +1,3 @@
CONFIG_DEVICE_TREE_INCLUDES="infix-env.dtsi infix-key.dtsi mt7622-emmc-env.dtsi"
CONFIG_SUPPORT_EMMC_BOOT=y
+CONFIG_CMD_MMC_PARTCONF=y
diff --git a/board/aarch64/bananapi-bpi-r64/uboot/sd-extras.config b/board/aarch64/bananapi-bpi-r64/uboot/sd-extras.config
index 9502bd1d1..dc1466de0 100644
--- a/board/aarch64/bananapi-bpi-r64/uboot/sd-extras.config
+++ b/board/aarch64/bananapi-bpi-r64/uboot/sd-extras.config
@@ -1 +1,3 @@
CONFIG_DEVICE_TREE_INCLUDES="infix-env.dtsi infix-key.dtsi mt7622-sd-env.dtsi"
+CONFIG_SUPPORT_EMMC_BOOT=y
+CONFIG_CMD_MMC_PARTCONF=y
diff --git a/board/aarch64/freescale-imx8mp-evk/freescale-imx8mp-evk.hash b/board/aarch64/freescale-imx8mp-evk/freescale-imx8mp-evk.hash
new file mode 100644
index 000000000..2809a5eed
--- /dev/null
+++ b/board/aarch64/freescale-imx8mp-evk/freescale-imx8mp-evk.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
diff --git a/board/aarch64/friendlyarm-nanopi-r2s/friendlyarm-nanopi-r2s.hash b/board/aarch64/friendlyarm-nanopi-r2s/friendlyarm-nanopi-r2s.hash
new file mode 100644
index 000000000..d1a69bc36
--- /dev/null
+++ b/board/aarch64/friendlyarm-nanopi-r2s/friendlyarm-nanopi-r2s.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 dbe4eae8debbba8135297e15f24aeefef0b4c03781f3f9328db4398d58a728b3 LICENSE
diff --git a/board/aarch64/marvell-cn9130-crb/marvell-cn9130-crb.hash b/board/aarch64/marvell-cn9130-crb/marvell-cn9130-crb.hash
new file mode 100644
index 000000000..2809a5eed
--- /dev/null
+++ b/board/aarch64/marvell-cn9130-crb/marvell-cn9130-crb.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
diff --git a/board/aarch64/marvell-espressobin/marvell-espressobin.hash b/board/aarch64/marvell-espressobin/marvell-espressobin.hash
new file mode 100644
index 000000000..2809a5eed
--- /dev/null
+++ b/board/aarch64/marvell-espressobin/marvell-espressobin.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
diff --git a/board/aarch64/microchip-sparx5-pcb135/microchip-sparx5-pcb135.hash b/board/aarch64/microchip-sparx5-pcb135/microchip-sparx5-pcb135.hash
new file mode 100644
index 000000000..2809a5eed
--- /dev/null
+++ b/board/aarch64/microchip-sparx5-pcb135/microchip-sparx5-pcb135.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
diff --git a/board/aarch64/raspberrypi-rpi64/raspberrypi-rpi64.hash b/board/aarch64/raspberrypi-rpi64/raspberrypi-rpi64.hash
new file mode 100644
index 000000000..d1a69bc36
--- /dev/null
+++ b/board/aarch64/raspberrypi-rpi64/raspberrypi-rpi64.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 dbe4eae8debbba8135297e15f24aeefef0b4c03781f3f9328db4398d58a728b3 LICENSE
diff --git a/board/aarch64/styx-dcp-sc-28p/styx-dcp-sc-28p.hash b/board/aarch64/styx-dcp-sc-28p/styx-dcp-sc-28p.hash
new file mode 100644
index 000000000..2809a5eed
--- /dev/null
+++ b/board/aarch64/styx-dcp-sc-28p/styx-dcp-sc-28p.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
diff --git a/board/arm/microchip-sama7g54-ek/microchip-sama7g54-ek.hash b/board/arm/microchip-sama7g54-ek/microchip-sama7g54-ek.hash
new file mode 100644
index 000000000..2b1e74432
--- /dev/null
+++ b/board/arm/microchip-sama7g54-ek/microchip-sama7g54-ek.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 d48246c717b505cc11df95171f2fd548b389e1a463f1af4c68d0b69fe0d1009b LICENSE
diff --git a/board/arm/raspberrypi-rpi2/raspberrypi-rpi2.hash b/board/arm/raspberrypi-rpi2/raspberrypi-rpi2.hash
new file mode 100644
index 000000000..d1a69bc36
--- /dev/null
+++ b/board/arm/raspberrypi-rpi2/raspberrypi-rpi2.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 dbe4eae8debbba8135297e15f24aeefef0b4c03781f3f9328db4398d58a728b3 LICENSE
diff --git a/board/common/uboot/env.dtsi b/board/common/uboot/env.dtsi
index 750e83114..f484b5174 100644
--- a/board/common/uboot/env.dtsi
+++ b/board/common/uboot/env.dtsi
@@ -14,6 +14,7 @@
boot_targets = "virtio mmc";
bootcmd = "run ixboot";
+ ixvariant = "";
ixpreboot = /incbin/("scripts/ixpreboot.sh");
ixbtn-devmode = /incbin/("scripts/ixbtn-devmode.sh");
ixbtn-factory = /incbin/("scripts/ixbtn-factory.sh");
diff --git a/board/common/uboot/scripts/ixpreboot.sh b/board/common/uboot/scripts/ixpreboot.sh
index 71eb9f935..bfe61a899 100644
--- a/board/common/uboot/scripts/ixpreboot.sh
+++ b/board/common/uboot/scripts/ixpreboot.sh
@@ -33,7 +33,8 @@ for tgt in "${boot_targets}"; do
setexpr ixmenu_n ${ixmenu_n} + 1
if load ${devtype} ${devnum}:${auxpart} ${loadaddr} /uboot.env; then
- env import -b ${loadaddr} ${filesize} BOOT_ORDER DEBUG ethact
+ env import -b ${loadaddr} ${filesize} BOOT_ORDER DEBUG ethact BOARD_VARIANT
+ run ixvariant
fi
if test -n "${DEBUG}"; then
diff --git a/board/dtb-inst.makefile b/board/dtb-inst.makefile
index 62b0f0595..60590e066 100644
--- a/board/dtb-inst.makefile
+++ b/board/dtb-inst.makefile
@@ -6,4 +6,8 @@ $(DESTDIR)/boot/%.dtb: %.dtb
@echo " DTB-INSTALL $<"
@install -D $< $@
+$(DESTDIR)/boot/%.dtbo: %.dtbo
+ @echo " DTBO-INSTALL $<"
+ @install -D $< $@
+
.PHONY: install
diff --git a/buildroot b/buildroot
index 79bfb2231..362f1511b 160000
--- a/buildroot
+++ b/buildroot
@@ -1 +1 @@
-Subproject commit 79bfb2231d12d7528288a24839c4e9793ac3f733
+Subproject commit 362f1511ba061bd710b159f21993db96e49dbdff
diff --git a/configs/aarch64_defconfig b/configs/aarch64_defconfig
index ccf13f122..f19df0578 100644
--- a/configs/aarch64_defconfig
+++ b/configs/aarch64_defconfig
@@ -134,6 +134,7 @@ BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
BR2_PACKAGE_ALDER_ALDER=y
BR2_PACKAGE_BANANAPI_BPI_R3=y
+BR2_PACKAGE_BANANAPI_BPI_R4=y
BR2_PACKAGE_BANANAPI_BPI_R64=y
BR2_PACKAGE_FRIENDLYARM_NANOPI_R2S=y
BR2_PACKAGE_MARVELL_CN9130_CRB=y
diff --git a/configs/aarch64_minimal_defconfig b/configs/aarch64_minimal_defconfig
index 83e8eb226..2961d48ea 100644
--- a/configs/aarch64_minimal_defconfig
+++ b/configs/aarch64_minimal_defconfig
@@ -113,6 +113,7 @@ BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
BR2_PACKAGE_ALDER_ALDER=y
BR2_PACKAGE_BANANAPI_BPI_R3=y
+BR2_PACKAGE_BANANAPI_BPI_R4=y
BR2_PACKAGE_BANANAPI_BPI_R64=y
BR2_PACKAGE_FRIENDLYARM_NANOPI_R2S=y
BR2_PACKAGE_MARVELL_CN9130_CRB=y
diff --git a/configs/bpi_r4_emmc_boot_defconfig b/configs/bpi_r4_emmc_boot_defconfig
new file mode 100644
index 000000000..2ae3342aa
--- /dev/null
+++ b/configs/bpi_r4_emmc_boot_defconfig
@@ -0,0 +1,42 @@
+BR2_aarch64=y
+BR2_TOOLCHAIN_EXTERNAL=y
+BR2_TOOLCHAIN_EXTERNAL_BOOTLIN=y
+BR2_DL_DIR="$(BR2_EXTERNAL_INFIX_PATH)/dl"
+BR2_CCACHE=y
+BR2_CCACHE_DIR="$(BR2_EXTERNAL_INFIX_PATH)/.ccache"
+BR2_ENABLE_DEBUG=y
+BR2_PACKAGE_OVERRIDE_FILE="$(BR2_EXTERNAL_INFIX_PATH)/local.mk"
+BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_INFIX_PATH)/patches"
+BR2_SSP_NONE=y
+BR2_INIT_NONE=y
+BR2_SYSTEM_BIN_SH_NONE=y
+# BR2_PACKAGE_BUSYBOX is not set
+# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
+# BR2_TARGET_ROOTFS_TAR is not set
+BR2_TARGET_ARM_TRUSTED_FIRMWARE=y
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_GIT=y
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_REPO_URL="https://github.com/mtk-openwrt/arm-trusted-firmware-mtk.git"
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_REPO_VERSION="e06f258664198a901ff1c7c0c87802a115179451"
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_PLATFORM="mt7988"
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_FIP=y
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_UBOOT_AS_BL33=y
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_ADDITIONAL_VARIABLES="BOOT_DEVICE=emmc DRAM_USE_COMB=1 DDR4_4BG_MODE=1 BOARD_BGA=1 HAVE_DRAM_OBJ_FILE=yes USE_MKIMAGE=1 MKIMAGE=$(HOST_DIR)/bin/mkimage"
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_IMAGES="*.img *.bin"
+BR2_TARGET_UBOOT=y
+BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y
+BR2_TARGET_UBOOT_CUSTOM_VERSION=y
+BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2026.01"
+BR2_TARGET_UBOOT_BOARD_DEFCONFIG="mt7988a_bpir4"
+BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES="${BR2_EXTERNAL_INFIX_PATH}/board/common/uboot/extras.config ${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/bananapi-bpi-r4/uboot/extras.config ${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/bananapi-bpi-r4/uboot/emmc-extras.config"
+BR2_TARGET_UBOOT_NEEDS_DTC=y
+BR2_TARGET_UBOOT_FORMAT_DTB=y
+BR2_TARGET_UBOOT_CUSTOM_DTS_PATH="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/bananapi-bpi-r4/uboot/*.dtsi"
+BR2_PACKAGE_HOST_BMAP_TOOLS=y
+BR2_PACKAGE_HOST_GENIMAGE=y
+BR2_PACKAGE_HOST_RAUC=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
+TRUSTED_KEYS=y
+TRUSTED_KEYS_DEVELOPMENT=y
diff --git a/configs/bpi_r4_sd_boot_defconfig b/configs/bpi_r4_sd_boot_defconfig
new file mode 100644
index 000000000..9f2a63e96
--- /dev/null
+++ b/configs/bpi_r4_sd_boot_defconfig
@@ -0,0 +1,42 @@
+BR2_aarch64=y
+BR2_TOOLCHAIN_EXTERNAL=y
+BR2_TOOLCHAIN_EXTERNAL_BOOTLIN=y
+BR2_DL_DIR="$(BR2_EXTERNAL_INFIX_PATH)/dl"
+BR2_CCACHE=y
+BR2_CCACHE_DIR="$(BR2_EXTERNAL_INFIX_PATH)/.ccache"
+BR2_ENABLE_DEBUG=y
+BR2_PACKAGE_OVERRIDE_FILE="$(BR2_EXTERNAL_INFIX_PATH)/local.mk"
+BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_INFIX_PATH)/patches"
+BR2_SSP_NONE=y
+BR2_INIT_NONE=y
+BR2_SYSTEM_BIN_SH_NONE=y
+# BR2_PACKAGE_BUSYBOX is not set
+# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
+# BR2_TARGET_ROOTFS_TAR is not set
+BR2_TARGET_ARM_TRUSTED_FIRMWARE=y
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_GIT=y
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_REPO_URL="https://github.com/mtk-openwrt/arm-trusted-firmware-mtk.git"
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_REPO_VERSION="e06f258664198a901ff1c7c0c87802a115179451"
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_PLATFORM="mt7988"
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_FIP=y
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_UBOOT_AS_BL33=y
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_ADDITIONAL_VARIABLES="BOOT_DEVICE=sdmmc DRAM_USE_COMB=1 DDR4_4BG_MODE=1 BOARD_BGA=1 HAVE_DRAM_OBJ_FILE=yes USE_MKIMAGE=1 MKIMAGE=$(HOST_DIR)/bin/mkimage"
+BR2_TARGET_ARM_TRUSTED_FIRMWARE_IMAGES="*.img *.bin"
+BR2_TARGET_UBOOT=y
+BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y
+BR2_TARGET_UBOOT_CUSTOM_VERSION=y
+BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2026.01"
+BR2_TARGET_UBOOT_BOARD_DEFCONFIG="mt7988a_bpir4_sd"
+BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES="${BR2_EXTERNAL_INFIX_PATH}/board/common/uboot/extras.config ${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/bananapi-bpi-r4/uboot/extras.config ${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/bananapi-bpi-r4/uboot/sd-extras.config"
+BR2_TARGET_UBOOT_NEEDS_DTC=y
+BR2_TARGET_UBOOT_FORMAT_DTB=y
+BR2_TARGET_UBOOT_CUSTOM_DTS_PATH="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/bananapi-bpi-r4/uboot/*.dtsi"
+BR2_PACKAGE_HOST_BMAP_TOOLS=y
+BR2_PACKAGE_HOST_GENIMAGE=y
+BR2_PACKAGE_HOST_RAUC=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
+TRUSTED_KEYS=y
+TRUSTED_KEYS_DEVELOPMENT=y
diff --git a/configs/sama7g54_ek_emmc_boot_defconfig b/configs/sama7g54_ek_emmc_boot_defconfig
index 8fefffd1b..5c76540cc 100644
--- a/configs/sama7g54_ek_emmc_boot_defconfig
+++ b/configs/sama7g54_ek_emmc_boot_defconfig
@@ -20,6 +20,7 @@ BR2_TARGET_AT91BOOTSTRAP3_CUSTOM_TARBALL=y
BR2_TARGET_AT91BOOTSTRAP3_CUSTOM_TARBALL_LOCATION="$(call github,linux4sam,at91bootstrap,v4.0.9)/at91bootstrap-v4.0.9.tar.gz"
BR2_TARGET_AT91BOOTSTRAP3_DEFCONFIG="sama7g5ekemmc_uboot"
BR2_TARGET_UBOOT=y
+BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y
BR2_TARGET_UBOOT_CUSTOM_VERSION=y
BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2025.01"
BR2_TARGET_UBOOT_BOARD_DEFCONFIG="sama7g5ek_mmc"
diff --git a/configs/sama7g54_ek_sd_boot_defconfig b/configs/sama7g54_ek_sd_boot_defconfig
index ddb575781..dc2f94560 100644
--- a/configs/sama7g54_ek_sd_boot_defconfig
+++ b/configs/sama7g54_ek_sd_boot_defconfig
@@ -20,6 +20,7 @@ BR2_TARGET_AT91BOOTSTRAP3_CUSTOM_TARBALL=y
BR2_TARGET_AT91BOOTSTRAP3_CUSTOM_TARBALL_LOCATION="$(call github,linux4sam,at91bootstrap,v4.0.9)/at91bootstrap-v4.0.9.tar.gz"
BR2_TARGET_AT91BOOTSTRAP3_DEFCONFIG="sama7g5eksd_uboot"
BR2_TARGET_UBOOT=y
+BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y
BR2_TARGET_UBOOT_CUSTOM_VERSION=y
BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2025.01"
BR2_TARGET_UBOOT_BOARD_DEFCONFIG="sama7g5ek_mmc1"
diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md
index b5f7af78a..f7b69aef3 100644
--- a/doc/ChangeLog.md
+++ b/doc/ChangeLog.md
@@ -9,9 +9,15 @@ All notable changes to the project are documented in this file.
### Changes
- Upgrade Linux kernel to 6.18.21 (LTS)
+- Add support for [Banana Pi BPI-R4][BPI-R4], quad-core Cortex-A73 router with
+ 4x 2.5 GbE switching, dual 10 GbE SFP+. Variants BPI-R4-2g5 and BPI-R4P have
+ one SFP+ replaced by a 2.5 GbE RJ45, with optional PoE on the R4P
### Fixes
+- N/A
+
+[BPI-R4]: https://docs.banana-pi.org/en/BPI-R4/BananaPi_BPI-R4
[v26.03.0][] - 2026-03-31
-------------------------
diff --git a/package/confd-test-mode/confd-test-mode.hash b/package/confd-test-mode/confd-test-mode.hash
new file mode 100644
index 000000000..0b2c589c7
--- /dev/null
+++ b/package/confd-test-mode/confd-test-mode.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 360336cbf0f228b12b7ca0996b33e442d97f496edcf2ec31b655e63631e7f96f LICENSE
diff --git a/package/date-cpp/date.hash b/package/date-cpp/date-cpp.hash
similarity index 100%
rename from package/date-cpp/date.hash
rename to package/date-cpp/date-cpp.hash
diff --git a/package/date-cpp/date.mk b/package/date-cpp/date-cpp.mk
similarity index 86%
rename from package/date-cpp/date.mk
rename to package/date-cpp/date-cpp.mk
index c181fe33a..c6c9a104f 100644
--- a/package/date-cpp/date.mk
+++ b/package/date-cpp/date-cpp.mk
@@ -1,4 +1,5 @@
DATE_CPP_VERSION = 3.0.1
+DATE_CPP_SOURCE = date-$(DATE_CPP_VERSION).tar.gz
DATE_CPP_SITE = $(call github,HowardHinnant,date,v$(DATE_CPP_VERSION))
DATE_CPP_INSTALL_STAGING = YES
DATE_CPP_LICENSE = MIT
diff --git a/package/netd/netd.hash b/package/netd/netd.hash
new file mode 100644
index 000000000..9c49868ea
--- /dev/null
+++ b/package/netd/netd.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 2f44058572d6e56953b0928a24dc7f816f35d33fb16097e97dfcd5b000bcc140 LICENSE
diff --git a/package/onieprom/onieprom.hash b/package/onieprom/onieprom.hash
new file mode 100644
index 000000000..704aa616d
--- /dev/null
+++ b/package/onieprom/onieprom.hash
@@ -0,0 +1,2 @@
+# Locally calculated
+sha256 ab15fd526bd8dd18a9e77ebc139656bf4d33e97fc7238cd11bf60e2b9b8666c6 COPYING
diff --git a/patches/linux/6.18.21/0042-net-pcs-add-standalone-PCS-registration-infrastructu.patch b/patches/linux/6.18.21/0042-net-pcs-add-standalone-PCS-registration-infrastructu.patch
new file mode 100644
index 000000000..d80ae0ae5
--- /dev/null
+++ b/patches/linux/6.18.21/0042-net-pcs-add-standalone-PCS-registration-infrastructu.patch
@@ -0,0 +1,189 @@
+From 8d2aec5525d60e643f85d68d62186cae530f17fc Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Sun, 5 Apr 2026 11:33:00 +0200
+Subject: [PATCH 42/47] net/pcs: add standalone PCS registration infrastructure
+Organization: Wires
+
+Add a simple registration mechanism that allows platform PCS drivers to
+register their phylink_pcs instances, and consumers (e.g. Ethernet MAC
+drivers) to look them up via a DT phandle using devm_of_pcs_get().
+
+When the pcs-handle property is present but the PCS device is not yet
+registered, devm_of_pcs_get() returns -ENODEV to trigger deferred probe
+in the consumer driver.
+
+Based on work by Daniel Golle .
+
+Signed-off-by: Joachim Wiberg
+---
+ drivers/net/pcs/Kconfig | 4 ++
+ drivers/net/pcs/Makefile | 1 +
+ drivers/net/pcs/pcs-standalone.c | 95 ++++++++++++++++++++++++++++++
+ include/linux/pcs/pcs-standalone.h | 26 ++++++++
+ 4 files changed, 126 insertions(+)
+ create mode 100644 drivers/net/pcs/pcs-standalone.c
+ create mode 100644 include/linux/pcs/pcs-standalone.h
+
+diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
+index ecbc3530e780..f6b4de7f972c 100644
+--- a/drivers/net/pcs/Kconfig
++++ b/drivers/net/pcs/Kconfig
+@@ -5,6 +5,10 @@
+
+ menu "PCS device drivers"
+
++config PCS_STANDALONE
++ tristate
++ select PHYLINK
++
+ config PCS_XPCS
+ tristate "Synopsys DesignWare Ethernet XPCS"
+ select PHYLINK
+diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
+index 4f7920618b90..0cb0057f2b8e 100644
+--- a/drivers/net/pcs/Makefile
++++ b/drivers/net/pcs/Makefile
+@@ -4,6 +4,7 @@
+ pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o \
+ pcs-xpcs-nxp.o pcs-xpcs-wx.o
+
++obj-$(CONFIG_PCS_STANDALONE) += pcs-standalone.o
+ obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
+ obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
+ obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
+diff --git a/drivers/net/pcs/pcs-standalone.c b/drivers/net/pcs/pcs-standalone.c
+new file mode 100644
+index 000000000000..f7b46de59636
+--- /dev/null
++++ b/drivers/net/pcs/pcs-standalone.c
+@@ -0,0 +1,95 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Helpers for standalone PCS drivers
++ *
++ * Copyright (C) 2024 Daniel Golle
++ */
++
++#include
++#include
++
++static LIST_HEAD(pcs_list);
++static DEFINE_MUTEX(pcs_mutex);
++
++struct pcs_standalone {
++ struct device *dev;
++ struct phylink_pcs *pcs;
++ struct list_head list;
++};
++
++static void devm_pcs_provider_release(struct device *dev, void *res)
++{
++ struct pcs_standalone *pcssa = (struct pcs_standalone *)res;
++
++ mutex_lock(&pcs_mutex);
++ list_del(&pcssa->list);
++ mutex_unlock(&pcs_mutex);
++}
++
++int devm_pcs_register(struct device *dev, struct phylink_pcs *pcs)
++{
++ struct pcs_standalone *pcssa;
++
++ pcssa = devres_alloc(devm_pcs_provider_release, sizeof(*pcssa),
++ GFP_KERNEL);
++ if (!pcssa)
++ return -ENOMEM;
++
++ devres_add(dev, pcssa);
++ pcssa->pcs = pcs;
++ pcssa->dev = dev;
++
++ mutex_lock(&pcs_mutex);
++ list_add_tail(&pcssa->list, &pcs_list);
++ mutex_unlock(&pcs_mutex);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(devm_pcs_register);
++
++static struct pcs_standalone *of_pcs_locate(const struct device_node *_np, u32 index)
++{
++ struct device_node *np;
++ struct pcs_standalone *iter, *pcssa = NULL;
++
++ if (!_np)
++ return NULL;
++
++ np = of_parse_phandle(_np, "pcs-handle", index);
++ if (!np)
++ return NULL;
++
++ mutex_lock(&pcs_mutex);
++ list_for_each_entry(iter, &pcs_list, list) {
++ if (iter->dev->of_node != np)
++ continue;
++
++ pcssa = iter;
++ break;
++ }
++ mutex_unlock(&pcs_mutex);
++
++ of_node_put(np);
++
++ return pcssa ?: ERR_PTR(-ENODEV);
++}
++
++struct phylink_pcs *devm_of_pcs_get(struct device *dev,
++ const struct device_node *np,
++ unsigned int index)
++{
++ struct pcs_standalone *pcssa;
++
++ pcssa = of_pcs_locate(np ?: dev->of_node, index);
++ if (IS_ERR_OR_NULL(pcssa))
++ return ERR_PTR(PTR_ERR(pcssa));
++
++ device_link_add(dev, pcssa->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
++
++ return pcssa->pcs;
++}
++EXPORT_SYMBOL_GPL(devm_of_pcs_get);
++
++MODULE_DESCRIPTION("Helper for standalone PCS drivers");
++MODULE_AUTHOR("Daniel Golle ");
++MODULE_LICENSE("GPL");
+diff --git a/include/linux/pcs/pcs-standalone.h b/include/linux/pcs/pcs-standalone.h
+new file mode 100644
+index 000000000000..520835cdeee8
+--- /dev/null
++++ b/include/linux/pcs/pcs-standalone.h
+@@ -0,0 +1,26 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++#ifndef __LINUX_PCS_STANDALONE_H
++#define __LINUX_PCS_STANDALONE_H
++
++#include
++#include
++#include
++#include
++
++#if IS_ENABLED(CONFIG_PCS_STANDALONE)
++int devm_pcs_register(struct device *dev, struct phylink_pcs *pcs);
++struct phylink_pcs *devm_of_pcs_get(struct device *dev,
++ const struct device_node *np, unsigned int index);
++#else
++static inline int devm_pcs_register(struct device *dev, struct phylink_pcs *pcs)
++{
++ return -ENOTSUPP;
++}
++static inline struct phylink_pcs *devm_of_pcs_get(struct device *dev,
++ const struct device_node *np,
++ unsigned int index)
++{
++ return ERR_PTR(-ENOTSUPP);
++}
++#endif /* CONFIG_PCS_STANDALONE */
++#endif /* __LINUX_PCS_STANDALONE_H */
+--
+2.43.0
+
diff --git a/patches/linux/6.18.21/0043-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch b/patches/linux/6.18.21/0043-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch
new file mode 100644
index 000000000..b81370597
--- /dev/null
+++ b/patches/linux/6.18.21/0043-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch
@@ -0,0 +1,465 @@
+From a11a254420cf8777497b7c027208b382c49964e7 Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Mon, 6 Apr 2026 14:14:23 +0200
+Subject: [PATCH 43/47] net/pcs: add MediaTek MT7988 USXGMII PCS driver
+Organization: Wires
+
+Add a PCS driver for the USXGMII subsystem found in the MediaTek MT7988
+SoC (usxgmiisys0 at 0x10080000, usxgmiisys1 at 0x10081000). The hardware
+supports USXGMII (10G with in-band AN), 10GBase-R and 5GBase-R interface
+modes.
+
+The driver:
+ - Registers via devm_pcs_register() so the Ethernet MAC driver can find
+ it through a DT pcs-handle phandle
+ - Delegates SerDes initialisation to the xfi_tphy PHY driver
+ - Sets up PCS speed/AN control registers and handles link state polling
+ - Reports link state from pcs_get_state without re-running pcs_config;
+ the 10G SerDes needs several seconds to complete CDR lock after a reset
+ and must not be restarted on every poll cycle before it can lock
+
+Based on work by Henry Yen and
+Daniel Golle .
+
+Signed-off-by: Joachim Wiberg
+---
+ drivers/net/pcs/Kconfig | 10 +
+ drivers/net/pcs/Makefile | 1 +
+ drivers/net/pcs/pcs-mtk-usxgmii.c | 394 ++++++++++++++++++++++++++++++
+ 3 files changed, 405 insertions(+)
+ create mode 100644 drivers/net/pcs/pcs-mtk-usxgmii.c
+
+diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
+index f6b4de7f972c..563c12d6aa9c 100644
+--- a/drivers/net/pcs/Kconfig
++++ b/drivers/net/pcs/Kconfig
+@@ -29,6 +29,16 @@ config PCS_MTK_LYNXI
+ This module provides helpers to phylink for managing the LynxI PCS
+ which is part of MediaTek's SoC and Ethernet switch ICs.
+
++config PCS_MTK_USXGMII
++ tristate "MediaTek USXGMII PCS"
++ select PCS_STANDALONE
++ select PHY_MTK_XFI_TPHY
++ select PHYLINK
++ help
++ This module provides a driver for MediaTek's USXGMII PCS found in
++ the MT7988 SoC, supporting USXGMII, 10GBase-R and 5GBase-R interface
++ modes.
++
+ config PCS_RZN1_MIIC
+ tristate "Renesas RZ/N1, RZ/N2H, RZ/T2H MII converter"
+ depends on OF
+diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
+index 0cb0057f2b8e..b876cb2157b1 100644
+--- a/drivers/net/pcs/Makefile
++++ b/drivers/net/pcs/Makefile
+@@ -8,4 +8,5 @@ obj-$(CONFIG_PCS_STANDALONE) += pcs-standalone.o
+ obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
+ obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
+ obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
++obj-$(CONFIG_PCS_MTK_USXGMII) += pcs-mtk-usxgmii.o
+ obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
+diff --git a/drivers/net/pcs/pcs-mtk-usxgmii.c b/drivers/net/pcs/pcs-mtk-usxgmii.c
+new file mode 100644
+index 000000000000..f0e7d418db38
+--- /dev/null
++++ b/drivers/net/pcs/pcs-mtk-usxgmii.c
+@@ -0,0 +1,394 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2023 MediaTek Inc.
++ * Author: Henry Yen
++ * Daniel Golle
++ */
++
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++/* USXGMII subsystem config registers */
++/* Register to control speed */
++#define RG_PHY_TOP_SPEED_CTRL1 0x80c
++#define USXGMII_RATE_UPDATE_MODE BIT(31)
++#define USXGMII_MAC_CK_GATED BIT(29)
++#define USXGMII_IF_FORCE_EN BIT(28)
++#define USXGMII_RATE_ADAPT_MODE GENMASK(10, 8)
++#define USXGMII_RATE_ADAPT_MODE_X1 0
++#define USXGMII_RATE_ADAPT_MODE_X2 1
++#define USXGMII_RATE_ADAPT_MODE_X4 2
++#define USXGMII_RATE_ADAPT_MODE_X10 3
++#define USXGMII_RATE_ADAPT_MODE_X100 4
++#define USXGMII_RATE_ADAPT_MODE_X5 5
++#define USXGMII_RATE_ADAPT_MODE_X50 6
++#define USXGMII_XFI_RX_MODE GENMASK(6, 4)
++#define USXGMII_XFI_TX_MODE GENMASK(2, 0)
++#define USXGMII_XFI_MODE_10G 0
++#define USXGMII_XFI_MODE_5G 1
++#define USXGMII_XFI_MODE_2P5G 3
++
++/* Register to control PCS AN */
++#define RG_PCS_AN_CTRL0 0x810
++#define USXGMII_AN_RESTART BIT(31)
++#define USXGMII_AN_SYNC_CNT GENMASK(30, 11)
++#define USXGMII_AN_ENABLE BIT(0)
++
++#define RG_PCS_AN_CTRL2 0x818
++#define USXGMII_LINK_TIMER_IDLE_DETECT GENMASK(29, 20)
++#define USXGMII_LINK_TIMER_COMP_ACK_DETECT GENMASK(19, 10)
++#define USXGMII_LINK_TIMER_AN_RESTART GENMASK(9, 0)
++
++/* Register to read PCS AN status */
++#define RG_PCS_AN_STS0 0x81c
++#define USXGMII_LPA GENMASK(15, 0)
++#define USXGMII_LPA_LATCH BIT(31)
++
++/* Register to read PCS link status */
++#define RG_PCS_RX_STATUS0 0x904
++#define RG_PCS_RX_STATUS_UPDATE BIT(16)
++#define RG_PCS_RX_LINK_STATUS BIT(2)
++
++/* struct mtk_usxgmii_pcs - This structure holds each usxgmii PCS
++ * @pcs: Phylink PCS structure
++ * @dev: Pointer to device structure
++ * @base: IO memory to access PCS hardware
++ * @clk: Pointer to USXGMII clk
++ * @reset: Pointer to USXGMII reset control
++ * @xfi_tphy: Pointer to XFI transceiver PHY
++ * @interface: Currently selected interface mode
++ * @node: List node
++ */
++struct mtk_usxgmii_pcs {
++ struct phylink_pcs pcs;
++ struct device *dev;
++ void __iomem *base;
++ struct clk *clk;
++ struct reset_control *reset;
++ struct phy *xfi_tphy;
++ phy_interface_t interface;
++ struct list_head node;
++};
++
++static u32 mtk_r32(struct mtk_usxgmii_pcs *mpcs, unsigned int reg)
++{
++ return ioread32(mpcs->base + reg);
++}
++
++static void mtk_m32(struct mtk_usxgmii_pcs *mpcs, unsigned int reg, u32 mask, u32 set)
++{
++ u32 val;
++
++ val = ioread32(mpcs->base + reg);
++ val &= ~mask;
++ val |= set;
++ iowrite32(val, mpcs->base + reg);
++}
++
++static struct mtk_usxgmii_pcs *pcs_to_mtk_usxgmii_pcs(struct phylink_pcs *pcs)
++{
++ return container_of(pcs, struct mtk_usxgmii_pcs, pcs);
++}
++
++static void mtk_usxgmii_reset(struct mtk_usxgmii_pcs *mpcs)
++{
++ reset_control_assert(mpcs->reset);
++ udelay(100);
++ reset_control_deassert(mpcs->reset);
++
++ mdelay(10);
++}
++
++static int mtk_usxgmii_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
++ phy_interface_t interface,
++ const unsigned long *advertising,
++ bool permit_pause_to_mac)
++{
++ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
++ unsigned int an_ctrl = 0, link_timer = 0, xfi_mode = 0, adapt_mode = 0;
++ bool mode_changed = false;
++
++ if (interface == PHY_INTERFACE_MODE_USXGMII) {
++ an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0x1FF) | USXGMII_AN_ENABLE;
++ link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x7B) |
++ FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x7B) |
++ FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x7B);
++ xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_MODE_10G) |
++ FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_MODE_10G);
++ } else if (interface == PHY_INTERFACE_MODE_10GBASER) {
++ an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0x1FF);
++ link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x7B) |
++ FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x7B) |
++ FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x7B);
++ xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_MODE_10G) |
++ FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_MODE_10G);
++ adapt_mode = USXGMII_RATE_UPDATE_MODE;
++ } else if (interface == PHY_INTERFACE_MODE_5GBASER) {
++ an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0xFF);
++ link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x3D) |
++ FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x3D) |
++ FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x3D);
++ xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_MODE_5G) |
++ FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_MODE_5G);
++ adapt_mode = USXGMII_RATE_UPDATE_MODE;
++ } else {
++ return -EINVAL;
++ }
++
++ adapt_mode |= FIELD_PREP(USXGMII_RATE_ADAPT_MODE, USXGMII_RATE_ADAPT_MODE_X1);
++
++ if (mpcs->interface != interface) {
++ mpcs->interface = interface;
++ mode_changed = true;
++ }
++
++ phy_reset(mpcs->xfi_tphy);
++ mtk_usxgmii_reset(mpcs);
++
++ /* Setup USXGMII AN ctrl */
++ mtk_m32(mpcs, RG_PCS_AN_CTRL0,
++ USXGMII_AN_SYNC_CNT | USXGMII_AN_ENABLE,
++ an_ctrl);
++
++ mtk_m32(mpcs, RG_PCS_AN_CTRL2,
++ USXGMII_LINK_TIMER_IDLE_DETECT |
++ USXGMII_LINK_TIMER_COMP_ACK_DETECT |
++ USXGMII_LINK_TIMER_AN_RESTART,
++ link_timer);
++
++ /* Gated MAC CK */
++ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
++ USXGMII_MAC_CK_GATED, USXGMII_MAC_CK_GATED);
++
++ /* Enable interface force mode */
++ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
++ USXGMII_IF_FORCE_EN, USXGMII_IF_FORCE_EN);
++
++ /* Setup USXGMII adapt mode */
++ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
++ USXGMII_RATE_UPDATE_MODE | USXGMII_RATE_ADAPT_MODE,
++ adapt_mode);
++
++ /* Setup USXGMII speed */
++ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1,
++ USXGMII_XFI_RX_MODE | USXGMII_XFI_TX_MODE,
++ xfi_mode);
++
++ usleep_range(1, 10);
++
++ /* Un-gated MAC CK */
++ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1, USXGMII_MAC_CK_GATED, 0);
++
++ usleep_range(1, 10);
++
++ /* Disable interface force mode for the AN mode */
++ if (an_ctrl & USXGMII_AN_ENABLE)
++ mtk_m32(mpcs, RG_PHY_TOP_SPEED_CTRL1, USXGMII_IF_FORCE_EN, 0);
++
++ /* Setup PMA/PMD */
++ phy_set_mode_ext(mpcs->xfi_tphy, PHY_MODE_ETHERNET, interface);
++
++ return mode_changed;
++}
++
++static void mtk_usxgmii_pcs_get_fixed_speed(struct mtk_usxgmii_pcs *mpcs,
++ struct phylink_link_state *state)
++{
++ u32 val = mtk_r32(mpcs, RG_PHY_TOP_SPEED_CTRL1);
++ int speed;
++
++ /* Calculate speed from interface speed and rate adapt mode */
++ switch (FIELD_GET(USXGMII_XFI_RX_MODE, val)) {
++ case USXGMII_XFI_MODE_10G:
++ speed = 10000;
++ break;
++ case USXGMII_XFI_MODE_5G:
++ speed = 5000;
++ break;
++ case USXGMII_XFI_MODE_2P5G:
++ speed = 2500;
++ break;
++ default:
++ state->speed = SPEED_UNKNOWN;
++ return;
++ }
++
++ switch (FIELD_GET(USXGMII_RATE_ADAPT_MODE, val)) {
++ case USXGMII_RATE_ADAPT_MODE_X100:
++ speed /= 100;
++ break;
++ case USXGMII_RATE_ADAPT_MODE_X50:
++ speed /= 50;
++ break;
++ case USXGMII_RATE_ADAPT_MODE_X10:
++ speed /= 10;
++ break;
++ case USXGMII_RATE_ADAPT_MODE_X5:
++ speed /= 5;
++ break;
++ case USXGMII_RATE_ADAPT_MODE_X4:
++ speed /= 4;
++ break;
++ case USXGMII_RATE_ADAPT_MODE_X2:
++ speed /= 2;
++ break;
++ case USXGMII_RATE_ADAPT_MODE_X1:
++ break;
++ default:
++ state->speed = SPEED_UNKNOWN;
++ return;
++ }
++
++ state->speed = speed;
++ state->duplex = DUPLEX_FULL;
++}
++
++static void mtk_usxgmii_pcs_get_an_state(struct mtk_usxgmii_pcs *mpcs,
++ struct phylink_link_state *state)
++{
++ u16 lpa;
++
++ /* Refresh LPA by toggling LPA_LATCH */
++ mtk_m32(mpcs, RG_PCS_AN_STS0, USXGMII_LPA_LATCH, USXGMII_LPA_LATCH);
++ ndelay(1020);
++ mtk_m32(mpcs, RG_PCS_AN_STS0, USXGMII_LPA_LATCH, 0);
++ ndelay(1020);
++ lpa = FIELD_GET(USXGMII_LPA, mtk_r32(mpcs, RG_PCS_AN_STS0));
++
++ phylink_decode_usxgmii_word(state, lpa);
++}
++
++static void mtk_usxgmii_pcs_get_state(struct phylink_pcs *pcs,
++ unsigned int neg_mode,
++ struct phylink_link_state *state)
++{
++ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
++
++ /* Refresh USXGMII link status by toggling RG_PCS_AN_STATUS_UPDATE */
++ mtk_m32(mpcs, RG_PCS_RX_STATUS0, RG_PCS_RX_STATUS_UPDATE,
++ RG_PCS_RX_STATUS_UPDATE);
++ ndelay(1020);
++ mtk_m32(mpcs, RG_PCS_RX_STATUS0, RG_PCS_RX_STATUS_UPDATE, 0);
++ ndelay(1020);
++
++ /* Read USXGMII link status */
++ state->link = FIELD_GET(RG_PCS_RX_LINK_STATUS,
++ mtk_r32(mpcs, RG_PCS_RX_STATUS0));
++
++ if (!state->link)
++ return;
++
++ if (FIELD_GET(USXGMII_AN_ENABLE, mtk_r32(mpcs, RG_PCS_AN_CTRL0)))
++ mtk_usxgmii_pcs_get_an_state(mpcs, state);
++ else
++ mtk_usxgmii_pcs_get_fixed_speed(mpcs, state);
++}
++
++static void mtk_usxgmii_pcs_restart_an(struct phylink_pcs *pcs)
++{
++ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
++
++ mtk_m32(mpcs, RG_PCS_AN_CTRL0, USXGMII_AN_RESTART, USXGMII_AN_RESTART);
++}
++
++static void mtk_usxgmii_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
++ phy_interface_t interface,
++ int speed, int duplex)
++{
++ /* Reconfiguring USXGMII to ensure the quality of the RX signal
++ * after the line side link up.
++ */
++ mtk_usxgmii_pcs_config(pcs, neg_mode, interface, NULL, false);
++}
++
++static int mtk_usxgmii_pcs_enable(struct phylink_pcs *pcs)
++{
++ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
++
++ phy_power_on(mpcs->xfi_tphy);
++
++ return 0;
++}
++
++static void mtk_usxgmii_pcs_disable(struct phylink_pcs *pcs)
++{
++ struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
++
++ mpcs->interface = PHY_INTERFACE_MODE_NA;
++
++ phy_power_off(mpcs->xfi_tphy);
++}
++
++static const struct phylink_pcs_ops mtk_usxgmii_pcs_ops = {
++ .pcs_config = mtk_usxgmii_pcs_config,
++ .pcs_get_state = mtk_usxgmii_pcs_get_state,
++ .pcs_an_restart = mtk_usxgmii_pcs_restart_an,
++ .pcs_link_up = mtk_usxgmii_pcs_link_up,
++ .pcs_enable = mtk_usxgmii_pcs_enable,
++ .pcs_disable = mtk_usxgmii_pcs_disable,
++};
++
++static int mtk_usxgmii_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct mtk_usxgmii_pcs *mpcs;
++
++ mpcs = devm_kzalloc(dev, sizeof(*mpcs), GFP_KERNEL);
++ if (!mpcs)
++ return -ENOMEM;
++
++ mpcs->base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(mpcs->base))
++ return PTR_ERR(mpcs->base);
++
++ mpcs->dev = dev;
++ mpcs->pcs.ops = &mtk_usxgmii_pcs_ops;
++ mpcs->pcs.poll = true;
++ mpcs->interface = PHY_INTERFACE_MODE_NA;
++
++ mpcs->clk = devm_clk_get_enabled(mpcs->dev, NULL);
++ if (IS_ERR(mpcs->clk))
++ return PTR_ERR(mpcs->clk);
++
++ mpcs->xfi_tphy = devm_of_phy_get(mpcs->dev, dev->of_node, NULL);
++ if (IS_ERR(mpcs->xfi_tphy))
++ return PTR_ERR(mpcs->xfi_tphy);
++
++ mpcs->reset = devm_reset_control_get_shared(dev, NULL);
++ if (IS_ERR(mpcs->reset))
++ return PTR_ERR(mpcs->reset);
++
++ reset_control_deassert(mpcs->reset);
++
++ platform_set_drvdata(pdev, mpcs);
++
++ return devm_pcs_register(dev, &mpcs->pcs);
++}
++
++static const struct of_device_id mtk_usxgmii_of_mtable[] = {
++ { .compatible = "mediatek,mt7988-usxgmiisys" },
++ { /* sentinel */ },
++};
++MODULE_DEVICE_TABLE(of, mtk_usxgmii_of_mtable);
++
++static struct platform_driver mtk_usxgmii_driver = {
++ .driver = {
++ .name = "mtk_usxgmii",
++ .suppress_bind_attrs = true,
++ .of_match_table = mtk_usxgmii_of_mtable,
++ },
++ .probe = mtk_usxgmii_probe,
++};
++module_platform_driver(mtk_usxgmii_driver);
++
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("MediaTek USXGMII PCS driver");
++MODULE_AUTHOR("Daniel Golle ");
+--
+2.43.0
+
diff --git a/patches/linux/6.18.21/0044-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch b/patches/linux/6.18.21/0044-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch
new file mode 100644
index 000000000..3627d6a1d
--- /dev/null
+++ b/patches/linux/6.18.21/0044-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch
@@ -0,0 +1,296 @@
+From 8917bfe8fac3e16e2c5958cdbf0b8258a7797539 Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Mon, 6 Apr 2026 14:15:43 +0200
+Subject: [PATCH 44/47] net: ethernet: mediatek: add USXGMII support for MT7988
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Organization: Wires
+
+Add support for the USXGMII path used by gmac1 and gmac2 on the MT7988
+SoC (BananaPi BPI-R4). Changes:
+
+ - mtk_eth_soc.h: add MTK_USXGMII capability bit, path bits for all
+ three GMACs, SYSCFG0_SGMII_GMAC3_V2 mask, usxgmii_pcs pointer in
+ struct mtk_mac, and update MT7988_CAPS to include the USXGMII paths
+ and mux
+
+ - mtk_eth_path.c: add set_mux_gmac123_to_usxgmii() which clears the
+ SGMII SYSCFG0 bits and for GMAC2 sets MUX_G2_USXGMII_SEL in
+ TOP_MISC_NETSYS_PCS_MUX; add mtk_gmac_usxgmii_path_setup()
+
+ - mtk_eth_soc.c: wire up the USXGMII PCS — look up the pcs-handle DT
+ property via devm_of_pcs_get() during MAC probe and store in
+ mac->usxgmii_pcs; return it from mtk_mac_select_pcs() for USXGMII,
+ 10GBase-R and 5GBase-R modes; call mtk_gmac_usxgmii_path_setup() in
+ mtk_mac_config(); allow in-band autoneg for XGMII interfaces
+
+Based on work by Henry Yen and
+Daniel Golle .
+
+Signed-off-by: Joachim Wiberg
+---
+ drivers/net/ethernet/mediatek/mtk_eth_path.c | 64 ++++++++++++++++++++
+ drivers/net/ethernet/mediatek/mtk_eth_soc.c | 46 +++++++++++++-
+ drivers/net/ethernet/mediatek/mtk_eth_soc.h | 32 +++++++++-
+ 3 files changed, 139 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/net/ethernet/mediatek/mtk_eth_path.c b/drivers/net/ethernet/mediatek/mtk_eth_path.c
+index b4c01e2878f6..57380776032a 100644
+--- a/drivers/net/ethernet/mediatek/mtk_eth_path.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_path.c
+@@ -37,6 +37,12 @@ static const char *mtk_eth_path_name(u64 path)
+ return "gmac2_gephy";
+ case MTK_ETH_PATH_GDM1_ESW:
+ return "gdm1_esw";
++ case MTK_ETH_PATH_GMAC1_USXGMII:
++ return "gmac1_usxgmii";
++ case MTK_ETH_PATH_GMAC2_USXGMII:
++ return "gmac2_usxgmii";
++ case MTK_ETH_PATH_GMAC3_USXGMII:
++ return "gmac3_usxgmii";
+ default:
+ return "unknown path";
+ }
+@@ -221,6 +227,48 @@ static int set_mux_gmac12_to_gephy_sgmii(struct mtk_eth *eth, u64 path)
+ return 0;
+ }
+
++static int set_mux_gmac123_to_usxgmii(struct mtk_eth *eth, u64 path)
++{
++ unsigned int val = 0;
++ bool updated = true;
++ int mac_id = 0;
++
++ /* Disable SYSCFG1 SGMII */
++ regmap_read(eth->ethsys, ETHSYS_SYSCFG0, &val);
++
++ switch (path) {
++ case MTK_ETH_PATH_GMAC1_USXGMII:
++ val &= ~(u32)SYSCFG0_SGMII_GMAC1_V2;
++ mac_id = MTK_GMAC1_ID;
++ break;
++ case MTK_ETH_PATH_GMAC2_USXGMII:
++ val &= ~(u32)SYSCFG0_SGMII_GMAC2_V2;
++ mac_id = MTK_GMAC2_ID;
++ break;
++ case MTK_ETH_PATH_GMAC3_USXGMII:
++ val &= ~(u32)SYSCFG0_SGMII_GMAC3_V2;
++ mac_id = MTK_GMAC3_ID;
++ break;
++ default:
++ updated = false;
++ break;
++ }
++
++ if (updated) {
++ regmap_update_bits(eth->ethsys, ETHSYS_SYSCFG0,
++ SYSCFG0_SGMII_MASK, val);
++
++ if (mac_id == MTK_GMAC2_ID)
++ regmap_set_bits(eth->infra, TOP_MISC_NETSYS_PCS_MUX,
++ MUX_G2_USXGMII_SEL);
++ }
++
++ dev_dbg(eth->dev, "path %s in %s updated = %d\n",
++ mtk_eth_path_name(path), __func__, updated);
++
++ return 0;
++}
++
+ static const struct mtk_eth_muxc mtk_eth_muxc[] = {
+ {
+ .name = "mux_gdm1_to_gmac1_esw",
+@@ -246,6 +294,10 @@ static const struct mtk_eth_muxc mtk_eth_muxc[] = {
+ .name = "mux_gmac12_to_gephy_sgmii",
+ .cap_bit = MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII,
+ .set_path = set_mux_gmac12_to_gephy_sgmii,
++ }, {
++ .name = "mux_gmac123_to_usxgmii",
++ .cap_bit = MTK_ETH_MUX_GMAC123_TO_USXGMII,
++ .set_path = set_mux_gmac123_to_usxgmii,
+ },
+ };
+
+@@ -328,3 +380,15 @@ int mtk_gmac_rgmii_path_setup(struct mtk_eth *eth, int mac_id)
+ return mtk_eth_mux_setup(eth, path);
+ }
+
++int mtk_gmac_usxgmii_path_setup(struct mtk_eth *eth, int mac_id)
++{
++ u64 path;
++
++ path = (mac_id == MTK_GMAC1_ID) ? MTK_ETH_PATH_GMAC1_USXGMII :
++ (mac_id == MTK_GMAC2_ID) ? MTK_ETH_PATH_GMAC2_USXGMII :
++ MTK_ETH_PATH_GMAC3_USXGMII;
++
++ /* Setup proper MUXes along the path */
++ return mtk_eth_mux_setup(eth, path);
++}
++
+diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+index 8d3e15bc867d..e6347b08648a 100644
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -22,6 +22,7 @@
+ #include
+ #include
+ #include
++#include
+ #include
+ #include
+ #include
+@@ -522,6 +523,17 @@ static struct phylink_pcs *mtk_mac_select_pcs(struct phylink_config *config,
+ struct mtk_eth *eth = mac->hw;
+ unsigned int sid;
+
++ if (mtk_is_netsys_v3_or_greater(eth)) {
++ switch (interface) {
++ case PHY_INTERFACE_MODE_USXGMII:
++ case PHY_INTERFACE_MODE_10GBASER:
++ case PHY_INTERFACE_MODE_5GBASER:
++ return mac->usxgmii_pcs;
++ default:
++ return NULL;
++ }
++ }
++
+ if (interface == PHY_INTERFACE_MODE_SGMII ||
+ phy_interface_mode_is_8023z(interface)) {
+ sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
+@@ -601,6 +613,15 @@ static void mtk_mac_config(struct phylink_config *config, unsigned int mode,
+ goto init_err;
+ }
+ break;
++ case PHY_INTERFACE_MODE_USXGMII:
++ case PHY_INTERFACE_MODE_10GBASER:
++ case PHY_INTERFACE_MODE_5GBASER:
++ if (MTK_HAS_CAPS(eth->soc->caps, MTK_USXGMII)) {
++ err = mtk_gmac_usxgmii_path_setup(eth, mac->id);
++ if (err)
++ goto init_err;
++ }
++ break;
+ default:
+ goto err_phy;
+ }
+@@ -664,7 +685,8 @@ static void mtk_mac_config(struct phylink_config *config, unsigned int mode,
+
+ /* Save the syscfg0 value for mac_finish */
+ mac->syscfg0 = val;
+- } else if (phylink_autoneg_inband(mode)) {
++ } else if (phylink_autoneg_inband(mode) &&
++ !mtk_interface_mode_is_xgmii(eth, state->interface)) {
+ dev_err(eth->dev,
+ "In-band mode not supported in non SGMII mode!\n");
+ return;
+@@ -4912,6 +4934,28 @@ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)
+ mac->phylink_config.supported_interfaces);
+ }
+
++ if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_USXGMII)) {
++ struct phylink_pcs *pcs;
++
++ pcs = devm_of_pcs_get(eth->dev, np, 0);
++ if (IS_ERR(pcs)) {
++ if (PTR_ERR(pcs) == -ENODEV)
++ return dev_err_probe(eth->dev, -EPROBE_DEFER,
++ "waiting for USXGMII PCS\n");
++ /* No pcs-handle property — USXGMII not wired for this MAC */
++ } else if (pcs) {
++ mac->usxgmii_pcs = pcs;
++ mac->phylink_config.mac_capabilities |= MAC_5000FD |
++ MAC_10000FD;
++ __set_bit(PHY_INTERFACE_MODE_USXGMII,
++ mac->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_10GBASER,
++ mac->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_5GBASER,
++ mac->phylink_config.supported_interfaces);
++ }
++ }
++
+ phylink = phylink_create(&mac->phylink_config,
+ of_fwnode_handle(mac->of_node),
+ phy_mode, &mtk_phylink_ops);
+diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+index 0168e2fbc619..2b27ef392b76 100644
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -567,6 +567,7 @@
+ #define SYSCFG0_SGMII_GMAC2 ((3 << 8) & SYSCFG0_SGMII_MASK)
+ #define SYSCFG0_SGMII_GMAC1_V2 BIT(9)
+ #define SYSCFG0_SGMII_GMAC2_V2 BIT(8)
++#define SYSCFG0_SGMII_GMAC3_V2 BIT(7)
+
+
+ /* ethernet subsystem clock register */
+@@ -1012,6 +1013,13 @@ enum mkt_eth_capabilities {
+ MTK_ETH_PATH_GMAC2_2P5GPHY_BIT,
+ MTK_ETH_PATH_GMAC2_GEPHY_BIT,
+ MTK_ETH_PATH_GDM1_ESW_BIT,
++
++ /* USXGMII capability and path bits */
++ MTK_USXGMII_BIT,
++ MTK_ETH_MUX_GMAC123_TO_USXGMII_BIT,
++ MTK_ETH_PATH_GMAC1_USXGMII_BIT,
++ MTK_ETH_PATH_GMAC2_USXGMII_BIT,
++ MTK_ETH_PATH_GMAC3_USXGMII_BIT,
+ };
+
+ /* Supported hardware group on SoCs */
+@@ -1092,6 +1100,23 @@ enum mkt_eth_capabilities {
+ #define MTK_MUX_GMAC12_TO_GEPHY_SGMII \
+ (MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII | MTK_MUX)
+
++/* USXGMII support */
++#define MTK_USXGMII BIT_ULL(MTK_USXGMII_BIT)
++
++#define MTK_ETH_MUX_GMAC123_TO_USXGMII \
++ BIT_ULL(MTK_ETH_MUX_GMAC123_TO_USXGMII_BIT)
++
++#define MTK_ETH_PATH_GMAC1_USXGMII BIT_ULL(MTK_ETH_PATH_GMAC1_USXGMII_BIT)
++#define MTK_ETH_PATH_GMAC2_USXGMII BIT_ULL(MTK_ETH_PATH_GMAC2_USXGMII_BIT)
++#define MTK_ETH_PATH_GMAC3_USXGMII BIT_ULL(MTK_ETH_PATH_GMAC3_USXGMII_BIT)
++
++#define MTK_GMAC1_USXGMII (MTK_ETH_PATH_GMAC1_USXGMII | MTK_USXGMII)
++#define MTK_GMAC2_USXGMII (MTK_ETH_PATH_GMAC2_USXGMII | MTK_USXGMII)
++#define MTK_GMAC3_USXGMII (MTK_ETH_PATH_GMAC3_USXGMII | MTK_USXGMII)
++
++#define MTK_MUX_GMAC123_TO_USXGMII \
++ (MTK_ETH_MUX_GMAC123_TO_USXGMII | MTK_MUX | MTK_INFRA)
++
+ #define MTK_HAS_CAPS(caps, _x) (((caps) & (_x)) == (_x))
+
+ #define MT7621_CAPS (MTK_GMAC1_RGMII | MTK_GMAC1_TRGMII | \
+@@ -1124,8 +1149,9 @@ enum mkt_eth_capabilities {
+ MTK_RSTCTRL_PPE1 | MTK_SRAM)
+
+ #define MT7988_CAPS (MTK_36BIT_DMA | MTK_GDM1_ESW | MTK_GMAC2_2P5GPHY | \
+- MTK_MUX_GMAC2_TO_2P5GPHY | MTK_QDMA | MTK_RSTCTRL_PPE1 | \
+- MTK_RSTCTRL_PPE2 | MTK_SRAM)
++ MTK_GMAC2_USXGMII | MTK_GMAC3_USXGMII | \
++ MTK_MUX_GMAC2_TO_2P5GPHY | MTK_MUX_GMAC123_TO_USXGMII | \
++ MTK_QDMA | MTK_RSTCTRL_PPE1 | MTK_RSTCTRL_PPE2 | MTK_SRAM)
+
+ struct mtk_tx_dma_desc_info {
+ dma_addr_t addr;
+@@ -1372,6 +1398,7 @@ struct mtk_mac {
+ struct device_node *of_node;
+ struct phylink *phylink;
+ struct phylink_config phylink_config;
++ struct phylink_pcs *usxgmii_pcs;
+ struct mtk_eth *hw;
+ struct mtk_hw_stats *hw_stats;
+ __be32 hwlro_ip[MTK_MAX_LRO_IP_CNT];
+@@ -1506,6 +1533,7 @@ int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id);
+ int mtk_gmac_2p5gphy_path_setup(struct mtk_eth *eth, int mac_id);
+ int mtk_gmac_gephy_path_setup(struct mtk_eth *eth, int mac_id);
+ int mtk_gmac_rgmii_path_setup(struct mtk_eth *eth, int mac_id);
++int mtk_gmac_usxgmii_path_setup(struct mtk_eth *eth, int mac_id);
+
+ int mtk_eth_offload_init(struct mtk_eth *eth, u8 id);
+ int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
+--
+2.43.0
+
diff --git a/patches/linux/6.18.21/0045-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch b/patches/linux/6.18.21/0045-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch
new file mode 100644
index 000000000..febdc5cd6
--- /dev/null
+++ b/patches/linux/6.18.21/0045-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch
@@ -0,0 +1,65 @@
+From c62809d597945068569a95b7ccc0398d99db098f Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Mon, 6 Apr 2026 14:15:56 +0200
+Subject: [PATCH 45/47] arm64: dts: mediatek: mt7988a: add USXGMII PCS nodes
+Organization: Wires
+
+Add device nodes for the two USXGMII subsystem blocks (usxgmiisys0 at
+0x10080000 and usxgmiisys1 at 0x10081000), each referencing its clock,
+reset, and xfi_tphy SerDes PHY.
+
+Wire gmac1 to usxgmiisys1 and gmac2 to usxgmiisys0 via the pcs-handle
+property, so the Ethernet driver can discover the PCS at probe time.
+
+Signed-off-by: Joachim Wiberg
+---
+ arch/arm64/boot/dts/mediatek/mt7988a.dtsi | 20 ++++++++++++++++++++
+ 1 file changed, 20 insertions(+)
+
+diff --git a/arch/arm64/boot/dts/mediatek/mt7988a.dtsi b/arch/arm64/boot/dts/mediatek/mt7988a.dtsi
+index 366203a72d6d..5ae43390f6c2 100644
+--- a/arch/arm64/boot/dts/mediatek/mt7988a.dtsi
++++ b/arch/arm64/boot/dts/mediatek/mt7988a.dtsi
+@@ -708,6 +708,24 @@ xfi_pll: clock-controller@11f40000 {
+ #clock-cells = <1>;
+ };
+
++ usxgmiisys0: pcs@10080000 {
++ compatible = "mediatek,mt7988-usxgmiisys";
++ reg = <0 0x10080000 0 0x1000>;
++ resets = <&watchdog 12>;
++ clocks = <&topckgen CLK_TOP_USXGMII_SBUS_0_SEL>;
++ phys = <&xfi_tphy0>;
++ #pcs-cells = <0>;
++ };
++
++ usxgmiisys1: pcs@10081000 {
++ compatible = "mediatek,mt7988-usxgmiisys";
++ reg = <0 0x10081000 0 0x1000>;
++ resets = <&watchdog 13>;
++ clocks = <&topckgen CLK_TOP_USXGMII_SBUS_1_SEL>;
++ phys = <&xfi_tphy1>;
++ #pcs-cells = <0>;
++ };
++
+ efuse@11f50000 {
+ compatible = "mediatek,mt7988-efuse", "mediatek,efuse";
+ reg = <0 0x11f50000 0 0x1000>;
+@@ -979,12 +997,14 @@ gmac1: mac@1 {
+ compatible = "mediatek,eth-mac";
+ reg = <1>;
+ status = "disabled";
++ pcs-handle = <&usxgmiisys1>;
+ };
+
+ gmac2: mac@2 {
+ compatible = "mediatek,eth-mac";
+ reg = <2>;
+ status = "disabled";
++ pcs-handle = <&usxgmiisys0>;
+ };
+
+ mdio_bus: mdio-bus {
+--
+2.43.0
+
diff --git a/patches/linux/6.18.21/0046-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports.patch b/patches/linux/6.18.21/0046-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports.patch
new file mode 100644
index 000000000..be8eae29b
--- /dev/null
+++ b/patches/linux/6.18.21/0046-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports.patch
@@ -0,0 +1,82 @@
+From 12d076e64f65326cf9e453899b6ac5adb31374c3 Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Mon, 6 Apr 2026 14:16:11 +0200
+Subject: [PATCH] arm64: dts: mediatek: bananapi-bpi-r4: enable SFP+ ports and
+ WPS button
+Organization: Wires
+
+Enable the SFP+ cages wired to gmac1 and gmac2. The USXGMII PCS nodes
+and xfi_tphy SerDes are wired up in mt7988a.dtsi; only status = "okay"
+was missing.
+
+gmac2 (sfp1, WAN cage) is common to all R4 variants and is enabled in
+mt7988a-bananapi-bpi-r4.dtsi. gmac1 (sfp2, LAN cage) is present only
+on the standard R4 (2x SFP+) and is enabled here in the variant .dts.
+The 2g5 variant uses gmac1 for the internal 2.5G PHY instead.
+
+Also add a gpio-keys node for the WPS/factory button on GPIO14 (active-low)
+reporting KEY_WPS_BUTTON. This is the physical button labeled WPS/Factory
+on the board silkscreen; GPIO14 is connected to it in the final hardware
+revision of the BPI-R4.
+
+Signed-off-by: Joachim Wiberg
+---
+ .../boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dts | 1 +
+ .../boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi | 13 ++++++++++++-
+ 2 files changed, 13 insertions(+), 1 deletion(-)
+
+diff --git a/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dts b/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dts
+index 4b3796ba82e3..499f0c91c213 100644
+--- a/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dts
++++ b/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dts
+@@ -27,6 +27,7 @@ &gmac1 {
+ managed = "in-band-status";
+ phy-mode = "usxgmii";
+ sfp = <&sfp2>;
++ status = "okay";
+ };
+
+ &pca9545 {
+diff --git a/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi b/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi
+index 0ff69dae45d3..2a904a76f3fe 100644
+--- a/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi
++++ b/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi
+@@ -3,9 +3,9 @@
+ /dts-v1/;
+
+ #include
++#include
+ #include
+ #include
+-#include
+
+ #include "mt7988a.dtsi"
+
+@@ -29,6 +29,16 @@ fan: pwm-fan {
+ status = "okay";
+ };
+
++ gpio-keys {
++ compatible = "gpio-keys";
++
++ wps {
++ label = "wps";
++ gpios = <&pio 14 GPIO_ACTIVE_LOW>;
++ linux,code = ;
++ };
++ };
++
+ gpio-leds {
+ compatible = "gpio-leds";
+
+@@ -152,6 +162,7 @@ &gmac2 {
+ managed = "in-band-status";
+ phy-mode = "usxgmii";
+ sfp = <&sfp1>;
++ status = "okay";
+ };
+
+ &gsw_phy0 {
+--
+2.43.0
+
diff --git a/patches/linux/6.18.21/0047-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch b/patches/linux/6.18.21/0047-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch
new file mode 100644
index 000000000..d1a209d7d
--- /dev/null
+++ b/patches/linux/6.18.21/0047-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch
@@ -0,0 +1,60 @@
+From 4b267c1be4141e0213b49d6d511051b8e6673f7c Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Mon, 6 Apr 2026 14:16:16 +0200
+Subject: [PATCH 47/47] net: phy: sfp: add OEM SFP-10G-T-I quirk
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Organization: Wires
+
+The industrial-temperature variant of the OEM SFP-10G-T copper module
+reports vendor PN "SFP-10G-T-I". Unlike the base "SFP-10G-T" which is a
+genuine RollBall module, the -I variant uses a plain XFI (10GBASER) host
+interface and does not implement the RollBall MDIO protocol.
+
+Both variants zero the extended_cc field in EEPROM, which causes
+sfp_module_parse_support() to hit SFF8024_ECC_UNSPEC and set no interface
+modes, making phylink reject the module with "no common interface modes".
+
+For the -I variant, add sfp_fixup_xfi_cc which only corrects the extended
+compliance code to SFF8024_ECC_10GBASE_T_SFI so the driver picks 10GBASER.
+Skipping the RollBall PHY probe avoids a ~7-minute boot-time delay
+(sfp_poll_timeout * retries ≈ 440 s) when no RollBall PHY is found.
+
+Signed-off-by: Joachim Wiberg
+---
+ drivers/net/phy/sfp.c | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
+index ca0992533572..53aafabeefd4 100644
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -451,6 +451,16 @@ static void sfp_fixup_rollball_cc(struct sfp *sfp)
+ sfp->id.base.extended_cc = SFF8024_ECC_10GBASE_T_SFI;
+ }
+
++static void sfp_fixup_xfi_cc(struct sfp *sfp)
++{
++ /* Module has zeroed extended compliance code but uses a plain XFI
++ * (10GBASER) host interface and does not support the Rollball MDIO
++ * protocol. Only fix the compliance code so the driver picks 10GBASER;
++ * skipping Rollball avoids a ~7 minute PHY probe retry delay on boot.
++ */
++ sfp->id.base.extended_cc = SFF8024_ECC_10GBASE_T_SFI;
++}
++
+ static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
+ struct sfp_module_caps *caps)
+ {
+@@ -559,6 +569,7 @@ static const struct sfp_quirk sfp_quirks[] = {
+ SFP_QUIRK_F("OEM", "SFP-GE-T", sfp_fixup_ignore_tx_fault),
+
+ SFP_QUIRK_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc),
++ SFP_QUIRK_F("OEM", "SFP-10G-T-I", sfp_fixup_xfi_cc),
+ SFP_QUIRK_S("OEM", "SFP-2.5G-T", sfp_quirk_oem_2_5g),
+ SFP_QUIRK_S("OEM", "SFP-2.5G-BX10-D", sfp_quirk_2500basex),
+ SFP_QUIRK_S("OEM", "SFP-2.5G-BX10-U", sfp_quirk_2500basex),
+--
+2.43.0
+
diff --git a/patches/uboot/2026.01/0001-hush-Remove-Ctrl-C-detection-in-loops.patch b/patches/uboot/2026.01/0001-hush-Remove-Ctrl-C-detection-in-loops.patch
new file mode 100644
index 000000000..4371a4c57
--- /dev/null
+++ b/patches/uboot/2026.01/0001-hush-Remove-Ctrl-C-detection-in-loops.patch
@@ -0,0 +1,42 @@
+From cde9fed496833b88094ed6e9fea11969a1be8e25 Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz
+Date: Mon, 10 Jun 2024 13:25:31 +0200
+Subject: [PATCH 01/10] hush: Remove Ctrl-C detection in loops
+Organization: Wires
+
+Assume that the original intent was to emulate SIGINT to a shell. This
+only works as expected if the loop in question is the ouermost, and
+last, statement. In all other cases, it completely breaks the expected
+execution flow. It more or less resurrects Visual Basic's "On Error
+Resume Next".
+
+Disable this behavior and delegate the problem of loop termination to
+the writer of the script instead.
+
+Signed-off-by: Tobias Waldekranz
+Signed-off-by: Joachim Wiberg
+---
+ common/cli_hush.c | 7 -------
+ 1 file changed, 7 deletions(-)
+
+diff --git a/common/cli_hush.c b/common/cli_hush.c
+index 7bd6943d3ed..a58a2d24572 100644
+--- a/common/cli_hush.c
++++ b/common/cli_hush.c
+@@ -1794,13 +1794,6 @@ static int run_list_real(struct pipe *pi)
+ for (; pi; pi = (flag_restore != 0) ? rpipe : pi->next) {
+ if (pi->r_mode == RES_WHILE || pi->r_mode == RES_UNTIL ||
+ pi->r_mode == RES_FOR) {
+-#ifdef __U_BOOT__
+- /* check Ctrl-C */
+- ctrlc();
+- if ((had_ctrlc())) {
+- return 1;
+- }
+-#endif
+ flag_restore = 0;
+ if (!rpipe) {
+ flag_rep = 0;
+--
+2.43.0
+
diff --git a/patches/uboot/2026.01/0002-cmd-new-command-rpidisplay.patch b/patches/uboot/2026.01/0002-cmd-new-command-rpidisplay.patch
new file mode 100644
index 000000000..28af02cb3
--- /dev/null
+++ b/patches/uboot/2026.01/0002-cmd-new-command-rpidisplay.patch
@@ -0,0 +1,153 @@
+From 020c9a036f2916afdce9d18aacc9e8c09a5dfca7 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?=
+Date: Sat, 6 Sep 2025 22:18:27 +0200
+Subject: [PATCH 02/10] cmd: new command rpidisplay
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Organization: Wires
+
+Signed-off-by: Mattias Walström
+Signed-off-by: Joachim Wiberg
+---
+ cmd/Kconfig | 14 ++++++++
+ cmd/Makefile | 2 +-
+ cmd/rpi_display.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 96 insertions(+), 1 deletion(-)
+ create mode 100644 cmd/rpi_display.c
+
+diff --git a/cmd/Kconfig b/cmd/Kconfig
+index 5c611fb3016..5b69cd3a439 100644
+--- a/cmd/Kconfig
++++ b/cmd/Kconfig
+@@ -183,6 +183,20 @@ config CMD_UFETCH
+ Fetch utility for U-Boot (akin to neofetch). Prints information
+ about U-Boot and the board it is running on in a pleasing format.
+
++config CMD_RPI_DISPLAY
++ bool "rpidisplay"
++ depends on ARCH_BCM283X
++ help
++ Enable commands to detect Raspberry Pi 7" DSI display connection.
++
++ This provides two commands:
++ - rpidisplay: Shows detailed detection information
++ - testrpidisplay: Silent test for use in boot scripts
++
++ Detection is performed by querying the VideoCore GPU for the
++ active display resolution. The official Raspberry Pi 7" DSI
++ display uses 800x480 resolution.
++
+ config CMD_FWU_METADATA
+ bool "fwu metadata read"
+ depends on FWU_MULTI_BANK_UPDATE
+diff --git a/cmd/Makefile b/cmd/Makefile
+index 25479907797..94b6890df0e 100644
+--- a/cmd/Makefile
++++ b/cmd/Makefile
+@@ -50,6 +50,7 @@ obj-$(CONFIG_CMD_CLK) += clk.o
+ obj-$(CONFIG_CMD_CLS) += cls.o
+ obj-$(CONFIG_CMD_CONFIG) += config.o
+ obj-$(CONFIG_CMD_CONITRACE) += conitrace.o
++obj-$(CONFIG_CMD_RPI_DISPLAY) += rpi_display.o
+ obj-$(CONFIG_CMD_CONSOLE) += console.o
+ obj-$(CONFIG_CMD_CPU) += cpu.o
+ obj-$(CONFIG_CMD_DATE) += date.o
+@@ -204,7 +205,6 @@ obj-$(CONFIG_CMD_LZMADEC) += lzmadec.o
+ obj-$(CONFIG_CMD_UFS) += ufs.o
+ obj-$(CONFIG_CMD_USB) += usb.o disk.o
+ obj-$(CONFIG_CMD_VIDCONSOLE) += video.o
+-
+ obj-$(CONFIG_CMD_FASTBOOT) += fastboot.o
+ obj-$(CONFIG_CMD_FS_UUID) += fs_uuid.o
+
+diff --git a/cmd/rpi_display.c b/cmd/rpi_display.c
+new file mode 100644
+index 00000000000..07abdc08552
+--- /dev/null
++++ b/cmd/rpi_display.c
+@@ -0,0 +1,81 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Raspberry Pi 7" DSI Display Detection Command
++ * Detects if the official Raspberry Pi 7" DSI display is connected
++ */
++
++#include
++#include
++
++static int do_rpi_display(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
++{
++ int width, height;
++ int ret;
++
++ printf("=== Raspberry Pi 7\" DSI Display Detection ===\n");
++
++ /* Use official VideoCore mailbox interface to get display resolution */
++ ret = bcm2835_get_video_size(&width, &height);
++ if (ret) {
++ printf("Failed to query VideoCore display size (ret=%d)\n", ret);
++ printf("Display Status: NOT DETECTED\n");
++ return 2;
++ }
++
++ printf("VideoCore reports display: %dx%d", width, height);
++
++ /* Official Raspberry Pi 7" DSI display is 800x480 */
++ if (width == 800 && height == 480) {
++ printf(" - RASPBERRY PI 7\" DSI DISPLAY DETECTED!\n");
++ printf("Display Status: CONNECTED\n");
++ return 0;
++ }
++ /* No resolution could indicate no display */
++ else if (width == 0 || height == 0) {
++ printf(" - No active display\n");
++ printf("Display Status: NOT DETECTED\n");
++ return 1;
++ }
++ else {
++ printf(" - Not Raspberry Pi DSI display (resolution %dx%d)\n", width, height);
++ printf("Display Status: NOT DETECTED\n");
++ return 1;
++ }
++}
++
++static int do_test_rpi_display(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
++{
++ int width, height;
++ int ret;
++
++ /* Silent test using VideoCore mailbox interface */
++ ret = bcm2835_get_video_size(&width, &height);
++ if (ret) {
++ return 2; /* Failed to query VideoCore */
++ }
++
++ /* Check for Raspberry Pi DSI display resolutions */
++ if ((width == 800 && height == 480)) {
++ return 0; /* DSI display found */
++ }
++
++ return 1; /* DSI display not found */
++}
++
++U_BOOT_CMD(
++ rpidisplay, 1, 1, do_rpi_display,
++ "detect Raspberry Pi DSI display",
++ "\n"
++ "Detects Raspberry Pi 7\" DSI display by checking resolution:\n"
++ " - 800x480 = Official Raspberry Pi 7\" DSI display\n"
++ "Returns: 0=connected, 1=not detected, 2=no video"
++);
++
++U_BOOT_CMD(
++ testrpidisplay, 1, 1, do_test_rpi_display,
++ "silent test for Raspberry Pi DSI display",
++ "\n"
++ "Silent test for Raspberry Pi DSI display (for use in scripts):\n"
++ "Returns: 0=connected, 1=not detected, 2=no video\n"
++ "Usage: if testrpidisplay; then echo DSI found; fi"
++);
+--
+2.43.0
+
diff --git a/patches/uboot/2026.01/0003-arm-dts-at91-sama7g5ek-supports-high-speed-on-mmc0-e.patch b/patches/uboot/2026.01/0003-arm-dts-at91-sama7g5ek-supports-high-speed-on-mmc0-e.patch
new file mode 100644
index 000000000..8c679922f
--- /dev/null
+++ b/patches/uboot/2026.01/0003-arm-dts-at91-sama7g5ek-supports-high-speed-on-mmc0-e.patch
@@ -0,0 +1,35 @@
+From 1a861c65011f636e464ca557804dfb6853a3871c Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Thu, 12 Feb 2026 10:00:02 +0100
+Subject: [PATCH 03/10] arm: dts: at91: sama7g5ek supports high-speed on mmc0
+ (eMMC)
+Organization: Wires
+
+- eMMC high-speed timing is supported
+- eMMC hardware reset is supported
+
+Tested on sama7g5ek rev 5.
+
+Signed-off-by: Joachim Wiberg
+---
+ arch/arm/dts/at91-sama7g5ek.dts | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/arch/arm/dts/at91-sama7g5ek.dts b/arch/arm/dts/at91-sama7g5ek.dts
+index 9b247fcaf66..45fe6a7f6cd 100644
+--- a/arch/arm/dts/at91-sama7g5ek.dts
++++ b/arch/arm/dts/at91-sama7g5ek.dts
+@@ -776,8 +776,8 @@
+ &sdmmc0 {
+ bus-width = <8>;
+ non-removable;
+- no-1-8-v;
+- sdhci-caps-mask = <0x0 0x00200000>;
++ cap-mmc-highspeed;
++ cap-mmc-hw-reset;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_sdmmc0_default>;
+ status = "okay";
+--
+2.43.0
+
diff --git a/patches/uboot/2026.01/0004-arm-dts-at91-sama7g5ek-increase-clock-for-sdmmc-from.patch b/patches/uboot/2026.01/0004-arm-dts-at91-sama7g5ek-increase-clock-for-sdmmc-from.patch
new file mode 100644
index 000000000..b564eb7dd
--- /dev/null
+++ b/patches/uboot/2026.01/0004-arm-dts-at91-sama7g5ek-increase-clock-for-sdmmc-from.patch
@@ -0,0 +1,36 @@
+From 3fadc90e8c1017aa3d8c165e54a13d8f75536b99 Mon Sep 17 00:00:00 2001
+From: Mihai Sain
+Date: Fri, 5 May 2023 13:28:31 +0300
+Subject: [PATCH 04/10] arm: dts: at91: sama7g5ek: increase clock for sdmmc
+ from 25 MHz to 50 MHz
+Organization: Wires
+
+Current clock for sdmmc0 and sdmmc1 is 25 MHz because of the caps forced
+by the mainline kernel.
+
+Remove the caps in order to increase the clock to 50 MHz. This will
+improve the boot time when reading the kernel binary. Tested on
+sama7g5ek rev 5 using mmcinfo command.
+
+Signed-off-by: Mihai Sain
+Signed-off-by: Joachim Wiberg
+---
+ arch/arm/dts/at91-sama7g5ek.dts | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/arch/arm/dts/at91-sama7g5ek.dts b/arch/arm/dts/at91-sama7g5ek.dts
+index 45fe6a7f6cd..d2d16bd5f1f 100644
+--- a/arch/arm/dts/at91-sama7g5ek.dts
++++ b/arch/arm/dts/at91-sama7g5ek.dts
+@@ -785,8 +785,6 @@
+
+ &sdmmc1 {
+ bus-width = <4>;
+- no-1-8-v;
+- sdhci-caps-mask = <0x0 0x00200000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_sdmmc1_default>;
+ status = "okay";
+--
+2.43.0
+
diff --git a/patches/uboot/2026.01/0005-net-phy-Add-the-Airoha-EN8811H-PHY-driver.patch b/patches/uboot/2026.01/0005-net-phy-Add-the-Airoha-EN8811H-PHY-driver.patch
new file mode 100644
index 000000000..874dfdcf9
--- /dev/null
+++ b/patches/uboot/2026.01/0005-net-phy-Add-the-Airoha-EN8811H-PHY-driver.patch
@@ -0,0 +1,977 @@
+From b8b781fd58eb8f0934edde8db45744d0a417cda8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?=
+Date: Wed, 18 Feb 2026 17:00:09 +0100
+Subject: [PATCH 05/10] net: phy: Add the Airoha EN8811H PHY driver
+Organization: Wires
+
+Add the driver for the Airoha EN8811H 2.5 Gigabit PHY. The PHY supports
+100/1000/2500 Mbps with auto negotiation only.
+
+The driver uses two firmware files, for which updated versions are added to
+linux-firmware already.
+
+Locating the AIROHA FW within the filesystem at the designated partition
+and path will trigger its automatic loading and writing to the PHY via MDIO.
+If need board specific loading override,
+please override the en8811h_read_fw function on board or architecture level.
+
+Linux upstream AIROHA EN8811H driver commit:
+71e79430117d56c409c5ea485a263bc0d8083390
+
+Based on the Linux upstream AIROHA EN8811H driver code(air_en8811h.c),
+I have modified the relevant process to align with the U-Boot boot sequence.
+and have validated this on Banana Pi BPI-R3 Mini.
+
+Signed-off-by: Lucien.Jheng
+Signed-off-by: Joachim Wiberg
+---
+ drivers/net/phy/Kconfig | 25 +
+ drivers/net/phy/Makefile | 1 +
+ drivers/net/phy/air_en8811h.c | 887 ++++++++++++++++++++++++++++++++++
+ 3 files changed, 913 insertions(+)
+ create mode 100644 drivers/net/phy/air_en8811h.c
+
+diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
+index 018be98705a..bc5b9d8a8c4 100644
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -83,6 +83,31 @@ config PHY_ADIN
+ help
+ Add support for configuring RGMII on Analog Devices ADIN PHYs.
+
++menuconfig PHY_AIROHA
++ bool "Airoha Ethernet PHYs support"
++
++config PHY_AIROHA_EN8811H
++ bool "Airoha Ethernet EN8811H support"
++ depends on PHY_AIROHA
++ help
++ AIROHA EN8811H supported.
++
++choice
++ prompt "Location of the Airoha PHY firmware"
++ default PHY_AIROHA_FW_IN_MMC
++ depends on PHY_AIROHA_EN8811H
++
++config PHY_AIROHA_FW_IN_MMC
++ bool "Airoha firmware in MMC partition"
++ help
++ Airoha PHYs use firmware which can be loaded automatically
++ from storage directly attached to the PHY, and then loaded
++ via MDIO commands by the boot loader. The firmware is loaded
++ from a file specified by the U-Boot environment variables
++ en8811h_fw_part, en8811h_fw_dm_dir, and en8811h_fw_dsp_dir.
++
++endchoice
++
+ menuconfig PHY_AQUANTIA
+ bool "Aquantia Ethernet PHYs support"
+ select PHY_GIGE
+diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
+index a339b8ac29d..431d1eef083 100644
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -11,6 +11,7 @@ obj-$(CONFIG_MV88E6352_SWITCH) += mv88e6352.o
+ obj-$(CONFIG_PHYLIB) += phy.o
+ obj-$(CONFIG_PHYLIB_10G) += generic_10g.o
+ obj-$(CONFIG_PHY_ADIN) += adin.o
++obj-$(CONFIG_PHY_AIROHA_EN8811H) += air_en8811h.o
+ obj-$(CONFIG_PHY_AQUANTIA) += aquantia.o
+ obj-$(CONFIG_PHY_ATHEROS) += atheros.o
+ obj-$(CONFIG_PHY_BROADCOM) += broadcom.o
+diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c
+new file mode 100644
+index 00000000000..d856d48b4a5
+--- /dev/null
++++ b/drivers/net/phy/air_en8811h.c
+@@ -0,0 +1,887 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Driver for the Airoha EN8811H 2.5 Gigabit PHY.
++ *
++ * Limitations of the EN8811H:
++ * - Only full duplex supported
++ * - Forced speed (AN off) is not supported by hardware (100Mbps)
++ *
++ * Source originated from linux air_en8811h.c
++ *
++ * Copyright (C) 2025 Airoha Technology Corp.
++ */
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++#define EN8811H_PHY_ID 0x03a2a411
++
++#define AIR_FW_ADDR_DM 0x00000000
++#define AIR_FW_ADDR_DSP 0x00100000
++
++#define EN8811H_MD32_DM_SIZE 0x4000
++#define EN8811H_MD32_DSP_SIZE 0x20000
++
++ #define EN8811H_FW_CTRL_1 0x0f0018
++ #define EN8811H_FW_CTRL_1_START 0x0
++ #define EN8811H_FW_CTRL_1_FINISH 0x1
++ #define EN8811H_FW_CTRL_2 0x800000
++ #define EN8811H_FW_CTRL_2_LOADING BIT(11)
++
++ /* MII Registers */
++ #define AIR_AUX_CTRL_STATUS 0x1d
++ #define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2)
++ #define AIR_AUX_CTRL_STATUS_SPEED_100 0x4
++ #define AIR_AUX_CTRL_STATUS_SPEED_1000 0x8
++ #define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc
++
++#define AIR_EXT_PAGE_ACCESS 0x1f
++#define AIR_PHY_PAGE_STANDARD 0x0000
++#define AIR_PHY_PAGE_EXTENDED_4 0x0004
++
++/* MII Registers Page 4*/
++#define AIR_BPBUS_MODE 0x10
++#define AIR_BPBUS_MODE_ADDR_FIXED 0x0000
++#define AIR_BPBUS_MODE_ADDR_INCR BIT(15)
++#define AIR_BPBUS_WR_ADDR_HIGH 0x11
++#define AIR_BPBUS_WR_ADDR_LOW 0x12
++#define AIR_BPBUS_WR_DATA_HIGH 0x13
++#define AIR_BPBUS_WR_DATA_LOW 0x14
++#define AIR_BPBUS_RD_ADDR_HIGH 0x15
++#define AIR_BPBUS_RD_ADDR_LOW 0x16
++#define AIR_BPBUS_RD_DATA_HIGH 0x17
++#define AIR_BPBUS_RD_DATA_LOW 0x18
++
++/* Registers on MDIO_MMD_VEND1 */
++#define EN8811H_PHY_FW_STATUS 0x8009
++#define EN8811H_PHY_READY 0x02
++
++/* Registers on MDIO_MMD_VEND2 */
++#define AIR_PHY_LED_BCR 0x021
++#define AIR_PHY_LED_BCR_MODE_MASK GENMASK(1, 0)
++#define AIR_PHY_LED_BCR_TIME_TEST BIT(2)
++#define AIR_PHY_LED_BCR_CLK_EN BIT(3)
++#define AIR_PHY_LED_BCR_EXT_CTRL BIT(15)
++
++#define AIR_PHY_LED_DUR_ON 0x022
++
++#define AIR_PHY_LED_DUR_BLINK 0x023
++
++#define AIR_PHY_LED_ON(i) (0x024 + ((i) * 2))
++#define AIR_PHY_LED_ON_MASK (GENMASK(6, 0) | BIT(8))
++#define AIR_PHY_LED_ON_LINK1000 BIT(0)
++#define AIR_PHY_LED_ON_LINK100 BIT(1)
++#define AIR_PHY_LED_ON_LINK10 BIT(2)
++#define AIR_PHY_LED_ON_LINKDOWN BIT(3)
++#define AIR_PHY_LED_ON_FDX BIT(4) /* Full duplex */
++#define AIR_PHY_LED_ON_HDX BIT(5) /* Half duplex */
++#define AIR_PHY_LED_ON_FORCE_ON BIT(6)
++#define AIR_PHY_LED_ON_LINK2500 BIT(8)
++#define AIR_PHY_LED_ON_POLARITY BIT(14)
++#define AIR_PHY_LED_ON_ENABLE BIT(15)
++
++#define AIR_PHY_LED_BLINK(i) (0x025 + ((i) * 2))
++#define AIR_PHY_LED_BLINK_1000TX BIT(0)
++#define AIR_PHY_LED_BLINK_1000RX BIT(1)
++#define AIR_PHY_LED_BLINK_100TX BIT(2)
++#define AIR_PHY_LED_BLINK_100RX BIT(3)
++#define AIR_PHY_LED_BLINK_10TX BIT(4)
++#define AIR_PHY_LED_BLINK_10RX BIT(5)
++#define AIR_PHY_LED_BLINK_COLLISION BIT(6)
++#define AIR_PHY_LED_BLINK_RX_CRC_ERR BIT(7)
++#define AIR_PHY_LED_BLINK_RX_IDLE_ERR BIT(8)
++#define AIR_PHY_LED_BLINK_FORCE_BLINK BIT(9)
++#define AIR_PHY_LED_BLINK_2500TX BIT(10)
++#define AIR_PHY_LED_BLINK_2500RX BIT(11)
++
++#define EN8811H_FW_VERSION 0x3b3c
++
++#define EN8811H_POLARITY 0xca0f8
++#define EN8811H_POLARITY_TX_NORMAL BIT(0)
++#define EN8811H_POLARITY_RX_REVERSE BIT(1)
++
++#define EN8811H_CLK_CGM 0xcf958
++#define EN8811H_CLK_CGM_CKO BIT(26)
++#define EN8811H_HWTRAP1 0xcf914
++#define EN8811H_HWTRAP1_CKO BIT(12)
++
++#define air_upper_16_bits(n) ((u16)((n) >> 16))
++#define air_lower_16_bits(n) ((u16)((n) & 0xffff))
++#define clear_bit(bit, bitmap) __clear_bit(bit, bitmap)
++
++/* Led definitions */
++#define EN8811H_LED_COUNT 3
++
++struct led {
++ unsigned long rules;
++ unsigned long state;
++};
++
++enum {
++ AIR_PHY_LED_STATE_FORCE_ON,
++ AIR_PHY_LED_STATE_FORCE_BLINK,
++};
++
++enum {
++ AIR_PHY_LED_DUR_BLINK_32MS,
++ AIR_PHY_LED_DUR_BLINK_64MS,
++ AIR_PHY_LED_DUR_BLINK_128MS,
++ AIR_PHY_LED_DUR_BLINK_256MS,
++ AIR_PHY_LED_DUR_BLINK_512MS,
++ AIR_PHY_LED_DUR_BLINK_1024MS,
++};
++
++enum {
++ AIR_LED_DISABLE,
++ AIR_LED_ENABLE,
++};
++
++enum {
++ AIR_ACTIVE_LOW,
++ AIR_ACTIVE_HIGH,
++};
++
++enum {
++ AIR_LED_MODE_DISABLE,
++ AIR_LED_MODE_USER_DEFINE,
++};
++
++/* Trigger specific enum */
++enum air_led_trigger_netdev_modes {
++ AIR_TRIGGER_NETDEV_LINK = 0,
++ AIR_TRIGGER_NETDEV_LINK_10,
++ AIR_TRIGGER_NETDEV_LINK_100,
++ AIR_TRIGGER_NETDEV_LINK_1000,
++ AIR_TRIGGER_NETDEV_LINK_2500,
++ AIR_TRIGGER_NETDEV_LINK_5000,
++ AIR_TRIGGER_NETDEV_LINK_10000,
++ AIR_TRIGGER_NETDEV_HALF_DUPLEX,
++ AIR_TRIGGER_NETDEV_FULL_DUPLEX,
++ AIR_TRIGGER_NETDEV_TX,
++ AIR_TRIGGER_NETDEV_RX,
++ AIR_TRIGGER_NETDEV_TX_ERR,
++ AIR_TRIGGER_NETDEV_RX_ERR,
++
++ /* Keep last */
++ __AIR_TRIGGER_NETDEV_MAX,
++};
++
++/* Default LED setup:
++ * GPIO5 <-> LED0 On: Link detected, blink Rx/Tx
++ * GPIO4 <-> LED1 On: Link detected at 2500 and 1000 Mbps
++ * GPIO3 <-> LED2 On: Link detected at 2500 and 100 Mbps
++ */
++#define AIR_DEFAULT_TRIGGER_LED0 (BIT(AIR_TRIGGER_NETDEV_LINK) | \
++ BIT(AIR_TRIGGER_NETDEV_RX) | \
++ BIT(AIR_TRIGGER_NETDEV_TX))
++#define AIR_DEFAULT_TRIGGER_LED1 (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | \
++ BIT(AIR_TRIGGER_NETDEV_LINK_1000))
++#define AIR_DEFAULT_TRIGGER_LED2 (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | \
++ BIT(AIR_TRIGGER_NETDEV_LINK_100))
++
++#define AIR_PHY_LED_DUR_UNIT 781
++#define AIR_PHY_LED_DUR (AIR_PHY_LED_DUR_UNIT << AIR_PHY_LED_DUR_BLINK_64MS)
++
++struct en8811h_priv {
++ int firmware_version;
++ bool mcu_needs_restart;
++ struct led led[EN8811H_LED_COUNT];
++};
++
++static int air_phy_read_page(struct phy_device *phydev)
++{
++ return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS);
++}
++
++static int air_phy_write_page(struct phy_device *phydev, int page)
++{
++ return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page);
++}
++
++int air_phy_select_page(struct phy_device *phydev, int page)
++{
++ int ret, oldpage;
++
++ oldpage = air_phy_read_page(phydev);
++ if (oldpage < 0)
++ return oldpage;
++
++ if (oldpage != page) {
++ ret = air_phy_write_page(phydev, page);
++ if (ret < 0)
++ return ret;
++ }
++
++ return oldpage;
++}
++
++int air_phy_restore_page(struct phy_device *phydev, int oldpage, int ret)
++{
++ int r;
++
++ if (oldpage < 0)
++ return oldpage;
++
++ r = air_phy_write_page(phydev, oldpage);
++ if (ret >= 0 && r < 0)
++ ret = r;
++
++ return ret;
++}
++
++static int air_buckpbus_reg_write(struct phy_device *phydev,
++ u32 pbus_address, u32 pbus_data)
++{
++ int ret, saved_page;
++
++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
++ if (saved_page < 0)
++ return saved_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
++ AIR_BPBUS_MODE_ADDR_FIXED);
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
++ air_upper_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
++ air_lower_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
++ air_upper_16_bits(pbus_data));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
++ air_lower_16_bits(pbus_data));
++ if (ret < 0)
++ goto restore_page;
++
++restore_page:
++ if (ret < 0)
++ printf("%s 0x%08x failed: %d\n", __func__,
++ pbus_address, ret);
++
++ return air_phy_restore_page(phydev, saved_page, ret);
++}
++
++static int air_buckpbus_reg_read(struct phy_device *phydev,
++ u32 pbus_address, u32 *pbus_data)
++{
++ int pbus_data_low, pbus_data_high;
++ int ret = 0, saved_page;
++
++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
++ if (saved_page < 0)
++ return saved_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
++ AIR_BPBUS_MODE_ADDR_FIXED);
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH,
++ air_upper_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
++ air_lower_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH);
++ if (pbus_data_high < 0) {
++ ret = pbus_data_high;
++ goto restore_page;
++ }
++
++ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW);
++ if (pbus_data_low < 0) {
++ ret = pbus_data_low;
++ goto restore_page;
++ }
++
++ *pbus_data = pbus_data_low | (pbus_data_high << 16);
++
++restore_page:
++ if (ret < 0)
++ printf("%s 0x%08x failed: %d\n", __func__,
++ pbus_address, ret);
++
++ return air_phy_restore_page(phydev, saved_page, ret);
++}
++
++static int air_buckpbus_reg_modify(struct phy_device *phydev,
++ u32 pbus_address, u32 mask, u32 set)
++{
++ int pbus_data_low, pbus_data_high;
++ u32 pbus_data_old, pbus_data_new;
++ int ret = 0, saved_page;
++
++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
++ if (saved_page < 0)
++ return saved_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
++ AIR_BPBUS_MODE_ADDR_FIXED);
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH,
++ air_upper_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
++ air_lower_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH);
++ if (pbus_data_high < 0)
++ return pbus_data_high;
++
++ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW);
++ if (pbus_data_low < 0)
++ return pbus_data_low;
++
++ pbus_data_old = pbus_data_low | (pbus_data_high << 16);
++ pbus_data_new = (pbus_data_old & ~mask) | set;
++ if (pbus_data_new == pbus_data_old)
++ return 0;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
++ air_upper_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
++ air_lower_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
++ air_upper_16_bits(pbus_data_new));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
++ air_lower_16_bits(pbus_data_new));
++ if (ret < 0)
++ goto restore_page;
++
++restore_page:
++ if (ret < 0)
++ printf("%s 0x%08x failed: %d\n", __func__,
++ pbus_address, ret);
++
++ return air_phy_restore_page(phydev, saved_page, ret);
++}
++
++static int air_write_buf(struct phy_device *phydev, unsigned long address,
++ unsigned long array_size, const unsigned char *buffer)
++{
++ unsigned int offset;
++ int ret, saved_page;
++ u16 val;
++
++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
++ if (saved_page < 0)
++ return saved_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
++ AIR_BPBUS_MODE_ADDR_INCR);
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
++ air_upper_16_bits(address));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
++ air_lower_16_bits(address));
++ if (ret < 0)
++ goto restore_page;
++
++ for (offset = 0; offset < array_size; offset += 4) {
++ val = get_unaligned_le16(&buffer[offset + 2]);
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, val);
++ if (ret < 0)
++ goto restore_page;
++
++ val = get_unaligned_le16(&buffer[offset]);
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, val);
++ if (ret < 0)
++ goto restore_page;
++ }
++
++restore_page:
++ if (ret < 0)
++ printf("%s 0x%08lx failed: %d\n", __func__,
++ address, ret);
++
++ return air_phy_restore_page(phydev, saved_page, ret);
++}
++
++static int en8811h_wait_mcu_ready(struct phy_device *phydev)
++{
++ int ret, reg_value;
++
++ /* Because of mdio-lock, may have to wait for multiple loads */
++ ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
++ EN8811H_PHY_FW_STATUS, reg_value,
++ reg_value == EN8811H_PHY_READY,
++ 20000, 7500000, true);
++ if (ret) {
++ printf("MCU not ready: 0x%x\n", reg_value);
++ return -ENODEV;
++ }
++
++ return 0;
++}
++
++__weak int en8811h_read_fw(void **addr)
++{
++ const char *fw_dm, *fw_dsp, *fw_part;
++ u32 ca_crc32;
++ void *buffer;
++ loff_t read;
++ int ret;
++
++ if (!IS_ENABLED(CONFIG_PHY_AIROHA_FW_IN_MMC))
++ return -EOPNOTSUPP;
++
++ /* Allocate memory to store both DM and DSP firmware */
++ buffer = malloc(EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE);
++ if (!buffer) {
++ printf("Failed to allocate memory for firmware\n");
++ return -ENOMEM;
++ }
++
++ /* Get the partition name where the firmware is stored */
++ fw_part = env_get("en8811h_fw_part");
++ if (!fw_part) {
++ printf("Error: env var en8811h_fw_part not set.\n");
++ ret = -EINVAL;
++ goto err_free;
++ }
++
++ /* Get the DM firmware file path */
++ fw_dm = env_get("en8811h_fw_dm_dir");
++ if (!fw_dm) {
++ printf("Error: env var en8811h_fw_dm_dir not set.\n");
++ ret = -EINVAL;
++ goto err_free;
++ }
++
++ /* Get the DSP firmware file path */
++ fw_dsp = env_get("en8811h_fw_dsp_dir");
++ if (!fw_dsp) {
++ printf("Error: env var en8811h_fw_dsp_dir not set.\n");
++ ret = -EINVAL;
++ goto err_free;
++ }
++
++ /* Load DM firmware */
++ ret = fs_set_blk_dev("mmc", fw_part, FS_TYPE_ANY);
++ if (ret < 0)
++ goto err_free;
++
++ /* Read DM firmware file into the start of buffer */
++ ret = fs_read(fw_dm, (ulong)buffer, 0, EN8811H_MD32_DM_SIZE, &read);
++ if (ret < 0) {
++ printf("Failed to read DM firmware: %s\n", fw_dm);
++ goto err_free;
++ }
++
++ /* Calculate CRC32 of DM firmware for verification */
++ ca_crc32 = crc32(0, (unsigned char *)buffer, EN8811H_MD32_DM_SIZE);
++ debug("DM crc32: 0x%x\n", ca_crc32);
++
++ /* Load DSP firmware */
++ ret = fs_set_blk_dev("mmc", fw_part, FS_TYPE_ANY);
++ if (ret < 0)
++ goto err_free;
++
++ /* Read DSP firmware file into buffer after DM section */
++ ret = fs_read(fw_dsp, (ulong)buffer + EN8811H_MD32_DM_SIZE, 0,
++ EN8811H_MD32_DSP_SIZE, &read);
++ if (ret < 0) {
++ printf("Failed to read DSP firmware: %s\n", fw_dsp);
++ goto err_free;
++ }
++
++ /* Calculate CRC32 of DSP firmware for verification */
++ ca_crc32 = crc32(0, (unsigned char *)buffer + EN8811H_MD32_DM_SIZE,
++ EN8811H_MD32_DSP_SIZE);
++ debug("DSP crc32: 0x%x\n", ca_crc32);
++
++ *addr = buffer;
++ debug("Found Airoha Firmware.\n");
++
++ return 0;
++
++err_free:
++ free(buffer);
++ return ret;
++}
++
++static int en8811h_load_firmware(struct phy_device *phydev)
++{
++ struct en8811h_priv *priv = phydev->priv;
++ void *buffer = NULL;
++ int ret;
++
++ if (!IS_ENABLED(CONFIG_PHY_AIROHA_FW_IN_MMC)) {
++ printf("Airoha EN8811H firmware loading not implemented\n");
++ return -EOPNOTSUPP;
++ }
++
++ ret = en8811h_read_fw(&buffer);
++ if (ret < 0)
++ goto en8811h_load_firmware_out;
++
++ ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
++ EN8811H_FW_CTRL_1_START);
++ if (ret < 0)
++ goto en8811h_load_firmware_out;
++
++ ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
++ EN8811H_FW_CTRL_2_LOADING,
++ EN8811H_FW_CTRL_2_LOADING);
++ if (ret < 0)
++ goto en8811h_load_firmware_out;
++
++ ret = air_write_buf(phydev, AIR_FW_ADDR_DM, EN8811H_MD32_DM_SIZE,
++ (unsigned char *)buffer);
++ if (ret < 0)
++ goto en8811h_load_firmware_out;
++
++ ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, EN8811H_MD32_DSP_SIZE,
++ (unsigned char *)buffer + EN8811H_MD32_DM_SIZE);
++ if (ret < 0)
++ goto en8811h_load_firmware_out;
++
++ ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
++ EN8811H_FW_CTRL_2_LOADING, 0);
++ if (ret < 0)
++ goto en8811h_load_firmware_out;
++
++ ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
++ EN8811H_FW_CTRL_1_FINISH);
++ if (ret < 0)
++ goto en8811h_load_firmware_out;
++
++ ret = en8811h_wait_mcu_ready(phydev);
++
++ air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION,
++ &priv->firmware_version);
++ printf("MD32 firmware version: %08x\n",
++ priv->firmware_version);
++
++en8811h_load_firmware_out:
++ free(buffer);
++ if (ret < 0)
++ printf("Firmware loading failed: %d\n", ret);
++
++ return ret;
++}
++
++static int en8811h_restart_mcu(struct phy_device *phydev)
++{
++ int ret;
++
++ ret = phy_write_mmd(phydev, 0x1e, 0x8009, 0x0);
++ if (ret < 0)
++ return ret;
++
++ ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
++ EN8811H_FW_CTRL_1_START);
++ if (ret < 0)
++ return ret;
++
++ return air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
++ EN8811H_FW_CTRL_1_FINISH);
++}
++
++static int air_led_hw_control_set(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ struct en8811h_priv *priv = phydev->priv;
++ u16 on = 0, blink = 0;
++ int ret;
++
++ if (index >= EN8811H_LED_COUNT)
++ return -EINVAL;
++
++ priv->led[index].rules = rules;
++
++ if (rules & BIT(AIR_TRIGGER_NETDEV_FULL_DUPLEX))
++ on |= AIR_PHY_LED_ON_FDX;
++
++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_10) | BIT(AIR_TRIGGER_NETDEV_LINK)))
++ on |= AIR_PHY_LED_ON_LINK10;
++
++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_100) | BIT(AIR_TRIGGER_NETDEV_LINK)))
++ on |= AIR_PHY_LED_ON_LINK100;
++
++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_1000) | BIT(AIR_TRIGGER_NETDEV_LINK)))
++ on |= AIR_PHY_LED_ON_LINK1000;
++
++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | BIT(AIR_TRIGGER_NETDEV_LINK)))
++ on |= AIR_PHY_LED_ON_LINK2500;
++
++ if (rules & BIT(AIR_TRIGGER_NETDEV_RX)) {
++ blink |= AIR_PHY_LED_BLINK_10RX |
++ AIR_PHY_LED_BLINK_100RX |
++ AIR_PHY_LED_BLINK_1000RX |
++ AIR_PHY_LED_BLINK_2500RX;
++ }
++
++ if (rules & BIT(AIR_TRIGGER_NETDEV_TX)) {
++ blink |= AIR_PHY_LED_BLINK_10TX |
++ AIR_PHY_LED_BLINK_100TX |
++ AIR_PHY_LED_BLINK_1000TX |
++ AIR_PHY_LED_BLINK_2500TX;
++ }
++
++ if (blink || on) {
++ /* switch hw-control on, so led-on and led-blink are off */
++ clear_bit(AIR_PHY_LED_STATE_FORCE_ON,
++ &priv->led[index].state);
++ clear_bit(AIR_PHY_LED_STATE_FORCE_BLINK,
++ &priv->led[index].state);
++ } else {
++ priv->led[index].rules = 0;
++ }
++
++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index),
++ AIR_PHY_LED_ON_MASK, on);
++
++ if (ret < 0)
++ return ret;
++
++ return phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BLINK(index),
++ blink);
++};
++
++static int air_led_init(struct phy_device *phydev, u8 index, u8 state, u8 pol)
++{
++ int val = 0;
++ int err;
++
++ if (index >= EN8811H_LED_COUNT)
++ return -EINVAL;
++
++ if (state == AIR_LED_ENABLE)
++ val |= AIR_PHY_LED_ON_ENABLE;
++ else
++ val &= ~AIR_PHY_LED_ON_ENABLE;
++
++ if (pol == AIR_ACTIVE_HIGH)
++ val |= AIR_PHY_LED_ON_POLARITY;
++ else
++ val &= ~AIR_PHY_LED_ON_POLARITY;
++
++ err = phy_write_mmd(phydev, 0x1f, AIR_PHY_LED_ON(index), val);
++ if (err < 0)
++ return err;
++
++ return 0;
++}
++
++static int air_leds_init(struct phy_device *phydev, int num, u16 dur, int mode)
++{
++ struct en8811h_priv *priv = phydev->priv;
++ int ret, i;
++
++ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_BLINK,
++ dur);
++ if (ret < 0)
++ return ret;
++
++ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_ON,
++ dur >> 1);
++ if (ret < 0)
++ return ret;
++
++ switch (mode) {
++ case AIR_LED_MODE_DISABLE:
++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR,
++ AIR_PHY_LED_BCR_EXT_CTRL |
++ AIR_PHY_LED_BCR_MODE_MASK, 0);
++ break;
++ case AIR_LED_MODE_USER_DEFINE:
++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR,
++ AIR_PHY_LED_BCR_EXT_CTRL |
++ AIR_PHY_LED_BCR_CLK_EN,
++ AIR_PHY_LED_BCR_EXT_CTRL |
++ AIR_PHY_LED_BCR_CLK_EN);
++ if (ret < 0)
++ return ret;
++ break;
++ default:
++ printf("LED mode %d is not supported\n", mode);
++ return -EINVAL;
++ }
++
++ for (i = 0; i < num; ++i) {
++ ret = air_led_init(phydev, i, AIR_LED_ENABLE, AIR_ACTIVE_HIGH);
++ if (ret < 0) {
++ printf("LED%d init failed: %d\n", i, ret);
++ return ret;
++ }
++ air_led_hw_control_set(phydev, i, priv->led[i].rules);
++ }
++
++ return 0;
++}
++
++static int en8811h_config(struct phy_device *phydev)
++{
++ struct en8811h_priv *priv = phydev->priv;
++ ofnode node = phy_get_ofnode(phydev);
++ u32 pbus_value = 0;
++ int ret = 0;
++
++ /* If restart happened in .probe(), no need to restart now */
++ if (priv->mcu_needs_restart) {
++ ret = en8811h_restart_mcu(phydev);
++ if (ret < 0)
++ return ret;
++ } else {
++ ret = en8811h_load_firmware(phydev);
++ if (ret) {
++ printf("Load firmware fail.\n");
++ return ret;
++ }
++ /* Next calls to .config() mcu needs to restart */
++ priv->mcu_needs_restart = true;
++ }
++
++ ret = phy_write_mmd(phydev, 0x1e, 0x800c, 0x0);
++ if (ret < 0)
++ return ret;
++ ret = phy_write_mmd(phydev, 0x1e, 0x800d, 0x0);
++ if (ret < 0)
++ return ret;
++ ret = phy_write_mmd(phydev, 0x1e, 0x800e, 0x1101);
++ if (ret < 0)
++ return ret;
++ ret = phy_write_mmd(phydev, 0x1e, 0x800f, 0x0002);
++ if (ret < 0)
++ return ret;
++
++ /* Serdes polarity */
++ pbus_value = 0;
++ if (ofnode_read_bool(node, "airoha,pnswap-rx"))
++ pbus_value |= EN8811H_POLARITY_RX_REVERSE;
++ else
++ pbus_value &= ~EN8811H_POLARITY_RX_REVERSE;
++ if (ofnode_read_bool(node, "airoha,pnswap-tx"))
++ pbus_value &= ~EN8811H_POLARITY_TX_NORMAL;
++ else
++ pbus_value |= EN8811H_POLARITY_TX_NORMAL;
++ ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
++ EN8811H_POLARITY_RX_REVERSE |
++ EN8811H_POLARITY_TX_NORMAL, pbus_value);
++ if (ret < 0)
++ return ret;
++
++ ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR,
++ AIR_LED_MODE_USER_DEFINE);
++ if (ret < 0) {
++ printf("Failed to disable leds: %d\n", ret);
++ return ret;
++ }
++
++ return 0;
++}
++
++static int en8811h_parse_status(struct phy_device *phydev)
++{
++ int ret = 0, reg_value;
++
++ phydev->duplex = DUPLEX_FULL;
++
++ reg_value = phy_read(phydev, MDIO_DEVAD_NONE, AIR_AUX_CTRL_STATUS);
++ if (reg_value < 0)
++ return reg_value;
++
++ switch (reg_value & AIR_AUX_CTRL_STATUS_SPEED_MASK) {
++ case AIR_AUX_CTRL_STATUS_SPEED_2500:
++ phydev->speed = SPEED_2500;
++ break;
++ case AIR_AUX_CTRL_STATUS_SPEED_1000:
++ phydev->speed = SPEED_1000;
++ break;
++ case AIR_AUX_CTRL_STATUS_SPEED_100:
++ phydev->speed = SPEED_100;
++ break;
++ default:
++ printf("Auto-neg error, defaulting to 100M/FD\n");
++ phydev->speed = SPEED_100;
++ break;
++ }
++
++ return ret;
++}
++
++static int en8811h_startup(struct phy_device *phydev)
++{
++ int ret = 0;
++
++ ret = genphy_update_link(phydev);
++ if (ret)
++ return ret;
++
++ return en8811h_parse_status(phydev);
++}
++
++static int en8811h_probe(struct phy_device *phydev)
++{
++ struct en8811h_priv *priv;
++
++ priv = malloc(sizeof(*priv));
++ if (!priv)
++ return -ENOMEM;
++ memset(priv, 0, sizeof(*priv));
++
++ priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0;
++ priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1;
++ priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2;
++
++ /* mcu has just restarted after firmware load */
++ priv->mcu_needs_restart = false;
++
++ phydev->priv = priv;
++
++ return 0;
++}
++
++U_BOOT_PHY_DRIVER(en8811h) = {
++ .name = "Airoha EN8811H",
++ .uid = EN8811H_PHY_ID,
++ .mask = 0x0ffffff0,
++ .config = &en8811h_config,
++ .probe = &en8811h_probe,
++ .startup = &en8811h_startup,
++ .shutdown = &genphy_shutdown,
++};
+--
+2.43.0
+
diff --git a/patches/uboot/2026.01/0006-Add-bpi-r3-mini-device-tree.patch b/patches/uboot/2026.01/0006-Add-bpi-r3-mini-device-tree.patch
new file mode 100644
index 000000000..4dfeedbad
--- /dev/null
+++ b/patches/uboot/2026.01/0006-Add-bpi-r3-mini-device-tree.patch
@@ -0,0 +1,272 @@
+From 3ba908cf4b16d729f611f64c20568baa47cf0a31 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?=
+Date: Wed, 18 Feb 2026 17:00:42 +0100
+Subject: [PATCH 06/10] Add bpi-r3-mini device tree
+Organization: Wires
+
+Signed-off-by: Joachim Wiberg
+---
+ arch/arm/dts/Makefile | 1 +
+ arch/arm/dts/mt7986a-bpi-r3-mini.dts | 238 +++++++++++++++++++++++++++
+ 2 files changed, 239 insertions(+)
+ create mode 100644 arch/arm/dts/mt7986a-bpi-r3-mini.dts
+
+diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile
+index fcad6fb2fc7..d4255f95702 100644
+--- a/arch/arm/dts/Makefile
++++ b/arch/arm/dts/Makefile
+@@ -1125,6 +1125,7 @@ dtb-$(CONFIG_ARCH_MEDIATEK) += \
+ mt7981-sd-rfb.dtb \
+ mt7986a-bpi-r3-sd.dtb \
+ mt7986a-bpi-r3-emmc.dtb \
++ mt7986a-bpi-r3-mini.dtb \
+ mt7986a-rfb.dtb \
+ mt7986b-rfb.dtb \
+ mt7986a-sd-rfb.dtb \
+diff --git a/arch/arm/dts/mt7986a-bpi-r3-mini.dts b/arch/arm/dts/mt7986a-bpi-r3-mini.dts
+new file mode 100644
+index 00000000000..469b087fb0c
+--- /dev/null
++++ b/arch/arm/dts/mt7986a-bpi-r3-mini.dts
+@@ -0,0 +1,238 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++
++/dts-v1/;
++#include "mt7986.dtsi"
++#include
++#include
++
++/ {
++ #address-cells = <1>;
++ #size-cells = <1>;
++ model = "Bananapi BPi-R3 Mini";
++ compatible = "mediatek,mt7986", "mediatek,mt7986-rfb";
++
++ chosen {
++ stdout-path = &uart0;
++ tick-timer = &timer0;
++ };
++
++ memory@40000000 {
++ device_type = "memory";
++ reg = <0x40000000 0x80000000>;
++ };
++
++ gpio-keys {
++ compatible = "gpio-keys";
++
++ button-reset {
++ label = "reset";
++ linux,code = ;
++ gpios = <&pio 7 GPIO_ACTIVE_LOW>;
++ };
++ };
++
++ leds {
++ compatible = "gpio-leds";
++
++ status_led: led-0 {
++ label = "green:status";
++ gpios = <&pio 19 GPIO_ACTIVE_HIGH>;
++ };
++
++ led-1 {
++ label = "blue:wlan2g";
++ gpios = <&pio 1 GPIO_ACTIVE_HIGH>;
++ };
++
++ led-2 {
++ label = "blue:wlan5g";
++ gpios = <&pio 2 GPIO_ACTIVE_HIGH>;
++ };
++ };
++
++ reg_1p8v: regulator-1p8v {
++ compatible = "regulator-fixed";
++ regulator-name = "fixed-1.8V";
++ regulator-min-microvolt = <1800000>;
++ regulator-max-microvolt = <1800000>;
++ regulator-boot-on;
++ regulator-always-on;
++ };
++
++ reg_3p3v: regulator-3p3v {
++ compatible = "regulator-fixed";
++ regulator-name = "fixed-3.3V";
++ regulator-min-microvolt = <3300000>;
++ regulator-max-microvolt = <3300000>;
++ regulator-boot-on;
++ regulator-always-on;
++ };
++};
++
++ð {
++ status = "okay";
++ pinctrl-names = "default";
++ pinctrl-0 = <&mdio_pins>;
++
++ mediatek,gmac-id = <0>;
++ phy-mode = "2500base-x";
++ phy-handle = <&phy14>;
++
++ phy14: eth-phy@e {
++ compatible = "ethernet-phy-id03a2.a411";
++ reg = <14>;
++
++ airoha,pnswap-rx;
++
++ reset-gpios = <&pio 49 GPIO_ACTIVE_LOW>;
++ reset-assert-us = <10000>;
++ reset-deassert-us = <20000>;
++ };
++};
++
++&mmc0 {
++ pinctrl-names = "default";
++ pinctrl-0 = <&mmc0_pins_default>;
++ bus-width = <8>;
++ max-frequency = <200000000>;
++ cap-mmc-highspeed;
++ cap-mmc-hw-reset;
++ vmmc-supply = <®_3p3v>;
++ vqmmc-supply = <®_1p8v>;
++ non-removable;
++ status = "okay";
++};
++
++&pio {
++ mdio_pins: mdio-pins {
++ mux {
++ function = "eth";
++ groups = "mdc_mdio";
++ };
++
++ conf-en8811-pwr-a {
++ pins = "GPIO_11";
++ drive-strength = ;
++ bias-pull-down = ;
++ output-low;
++ };
++
++ conf-en8811-pwr-b {
++ pins = "GPIO_12";
++ drive-strength = ;
++ bias-pull-down = ;
++ output-low;
++ };
++ };
++
++ mmc0_pins_default: mmc0default {
++ mux {
++ function = "flash";
++ groups = "emmc_51";
++ };
++
++ conf-cmd-dat {
++ pins = "EMMC_DATA_0", "EMMC_DATA_1", "EMMC_DATA_2",
++ "EMMC_DATA_3", "EMMC_DATA_4", "EMMC_DATA_5",
++ "EMMC_DATA_6", "EMMC_DATA_7", "EMMC_CMD";
++ input-enable;
++ drive-strength = ;
++ bias-pull-up = ;
++ };
++
++ conf-clk {
++ pins = "EMMC_CK";
++ drive-strength = ;
++ bias-pull-down = ;
++ };
++
++ conf-dsl {
++ pins = "EMMC_DSL";
++ bias-pull-down = ;
++ };
++
++ conf-rst {
++ pins = "EMMC_RSTB";
++ drive-strength = ;
++ bias-pull-up = ;
++ };
++ };
++
++ spi_flash_pins: spi0-pins-func-1 {
++ mux {
++ function = "flash";
++ groups = "spi0", "spi0_wp_hold";
++ };
++
++ conf-pu {
++ pins = "SPI2_CS", "SPI2_HOLD", "SPI2_WP";
++ drive-strength = ;
++ bias-pull-up = ;
++ };
++
++ conf-pd {
++ pins = "SPI2_CLK", "SPI2_MOSI", "SPI2_MISO";
++ drive-strength = ;
++ bias-pull-down = ;
++ };
++ };
++
++ pwm_pins: pwm0-pins-func-1 {
++ mux {
++ function = "pwm";
++ groups = "pwm0";
++ };
++ };
++};
++
++&pwm {
++ pinctrl-names = "default";
++ pinctrl-0 = <&pwm_pins>;
++ status = "okay";
++};
++
++&spi0 {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ pinctrl-names = "default";
++ pinctrl-0 = <&spi_flash_pins>;
++ status = "okay";
++ must_tx;
++ enhance_timing;
++ dma_ext;
++ ipm_design;
++ support_quad;
++ tick_dly = <1>;
++ sample_sel = <0>;
++
++ spi_nand@0 {
++ compatible = "spi-nand";
++ reg = <0>;
++ spi-max-frequency = <20000000>;
++
++ partitions {
++ compatible = "fixed-partitions";
++ #address-cells = <1>;
++ #size-cells = <1>;
++
++ partition@0 {
++ label = "bl2";
++ reg = <0x0 0x200000>;
++ };
++
++ partition@200000 {
++ label = "ubi";
++ reg = <0x200000 0x7e00000>;
++ };
++ };
++ };
++
++};
++
++&uart0 {
++ status = "okay";
++};
++
++&watchdog {
++ status = "disabled";
++};
+--
+2.43.0
+
diff --git a/patches/uboot/2026.01/0007-bpi-r3-r4-Add-probe-for-specific-model.patch b/patches/uboot/2026.01/0007-bpi-r3-r4-Add-probe-for-specific-model.patch
new file mode 100644
index 000000000..fe1eb8306
--- /dev/null
+++ b/patches/uboot/2026.01/0007-bpi-r3-r4-Add-probe-for-specific-model.patch
@@ -0,0 +1,559 @@
+From a1ac4c9c4e35782afcf66ab031b4d426d81120a2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?=
+Date: Wed, 18 Feb 2026 17:02:23 +0100
+Subject: [PATCH 07/10] bpi-r3/r4: Add probe for specific model
+Organization: Wires
+
+Probe for bpi-r3 and bpi-r3-mini and select correct device tree,
+prepare for bpi-r4 (not supported yet, but soon), for bpi-r3,
+look if there are phys connected, if so it is a mini, else it is
+regular bpi-r3.
+
+Signed-off-by: Joachim Wiberg
+---
+ arch/arm/mach-mediatek/Kconfig | 3 +
+ board/mediatek/mt7986/Kconfig | 10 ++
+ board/mediatek/mt7986/Makefile | 1 +
+ board/mediatek/mt7986/bpir3.c | 229 +++++++++++++++++++++++++++
+ board/mediatek/mt7988/Kconfig | 9 ++
+ board/mediatek/mt7988/Makefile | 1 +
+ board/mediatek/mt7988/bpir4.c | 33 ++++
+ configs/mt7986a_bpir3_emmc_defconfig | 2 +
+ configs/mt7986a_bpir3_mini_defconfig | 66 ++++++++
+ configs/mt7986a_bpir3_sd_defconfig | 2 +
+ configs/mt7988a_bpir4_defconfig | 85 ++++++++++
+ 11 files changed, 441 insertions(+)
+ create mode 100644 board/mediatek/mt7986/Kconfig
+ create mode 100644 board/mediatek/mt7986/bpir3.c
+ create mode 100644 board/mediatek/mt7988/Kconfig
+ create mode 100644 board/mediatek/mt7988/bpir4.c
+ create mode 100644 configs/mt7986a_bpir3_mini_defconfig
+ create mode 100644 configs/mt7988a_bpir4_defconfig
+
+diff --git a/arch/arm/mach-mediatek/Kconfig b/arch/arm/mach-mediatek/Kconfig
+index e54c456aec0..ca593cceb43 100644
+--- a/arch/arm/mach-mediatek/Kconfig
++++ b/arch/arm/mach-mediatek/Kconfig
+@@ -165,4 +165,7 @@ config MTK_TZ_MOVABLE
+ select OF_SYSTEM_SETUP
+ bool
+
++source "board/mediatek/mt7986/Kconfig"
++source "board/mediatek/mt7988/Kconfig"
++
+ endif
+diff --git a/board/mediatek/mt7986/Kconfig b/board/mediatek/mt7986/Kconfig
+new file mode 100644
+index 00000000000..cea150b52d8
+--- /dev/null
++++ b/board/mediatek/mt7986/Kconfig
+@@ -0,0 +1,10 @@
++if TARGET_MT7986
++
++config BOARD_BPI_R3
++ bool "BananaPi BPI-R3 / BPI-R3-mini board support"
++ select BOARD_LATE_INIT
++ help
++ Enable runtime variant detection for BananaPi BPI-R3 and BPI-R3-mini,
++ selecting the correct device tree from a FIT image.
++
++endif
+diff --git a/board/mediatek/mt7986/Makefile b/board/mediatek/mt7986/Makefile
+index 7bb84fa2f4e..b006ee150bd 100644
+--- a/board/mediatek/mt7986/Makefile
++++ b/board/mediatek/mt7986/Makefile
+@@ -1,3 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0
+
+ obj-y += mt7986_rfb.o
++obj-$(CONFIG_BOARD_BPI_R3) += bpir3.o
+diff --git a/board/mediatek/mt7986/bpir3.c b/board/mediatek/mt7986/bpir3.c
+new file mode 100644
+index 00000000000..01eef55bb74
+--- /dev/null
++++ b/board/mediatek/mt7986/bpir3.c
+@@ -0,0 +1,229 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * BPI-R3 / BPI-R3-mini board variant detection
++ */
++
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++DECLARE_GLOBAL_DATA_PTR;
++
++enum bpir3_variant {
++ BPIR3,
++ BPIR3_MINI,
++};
++
++/*
++ * Detect BPI-R3 vs BPI-R3-mini by probing for the Airoha EN8811H PHY.
++ *
++ * BPI-R3-mini: EN8811H is directly on the MDIO bus at addr 14.
++ * GPIO49 (active-low) is its reset; PHYSID1 = 0x03a2.
++ * BPI-R3: MT7531AE switch via SGMII; nothing at MDIO addr 14 → 0xffff.
++ *
++ * NOTE: I2C 0x50 is NOT usable — BPI-R3 SFP cage EEPROMs occupy 0x50–0x5b.
++ */
++
++/*
++ * MT7986 GPIO — gpio_base = 0x1001f000
++ *
++ * DIR register: base + 0x000 + (pin/32)*0x10, 1 bit/pin (1=output)
++ * DOUT register: base + 0x100 + (pin/32)*0x10, 1 bit/pin
++ * MODE register: base + 0x300 + (pin*4/32)*0x10, 4 bits/pin (0=GPIO, 1=eth…)
++ */
++#define MT7986_GPIO_BASE 0x1001f000UL
++#define GPIO_DIR_REG(p) (MT7986_GPIO_BASE + 0x000 + ((p) / 32) * 0x10)
++#define GPIO_DOUT_REG(p) (MT7986_GPIO_BASE + 0x100 + ((p) / 32) * 0x10)
++#define GPIO_MODE_REG(p) (MT7986_GPIO_BASE + 0x300 + (((p) * 4) / 32) * 0x10)
++#define GPIO_MODE_SHIFT(p) (((p) * 4) % 32)
++
++/* EN8811H PHY reset: GPIO49, active-low */
++#define EN8811H_RST_GPIO 49
++/* EN8811H power control lines: active-low enables on some BPI-R3-mini dts */
++#define EN8811H_PWR_A_GPIO 11
++#define EN8811H_PWR_B_GPIO 12
++/* Alternative EN8811H power control lines used by other mini trees */
++#define EN8811H_PWR2_A_GPIO 16
++#define EN8811H_PWR2_B_GPIO 17
++
++/* MDC/MDIO pins: GPIO67 = MDC, GPIO68 = MDIO, both on function 1 ("eth") */
++#define MDC_GPIO 67
++#define MDIO_GPIO 68
++
++/*
++ * MT7986 Frame Engine: ethernet@15100000, GMAC at +0x10000
++ *
++ * PPSC (+0x0000): MDC clock config — bits[29:24] = divider, bit[4] = turbo
++ * PIAC (+0x0004): PHY indirect access — write then poll PHY_ACS_ST clear
++ */
++#define GMAC_BASE 0x15110000UL
++#define GMAC_PPSC (GMAC_BASE + 0x0000)
++#define GMAC_PIAC (GMAC_BASE + 0x0004)
++
++#define PPSC_MDC_CFG_MASK GENMASK(29, 24)
++#define PPSC_MDC_CFG(d) (((d) & 0x3f) << 24)
++#define PPSC_MDC_TURBO BIT(4)
++/* divider = 10 → MDC ≈ 2.5 MHz from 25 MHz reference */
++#define MDC_DIVIDER 10
++
++#define PIAC_ACS_ST BIT(31)
++#define PIAC_REG_S 25
++#define PIAC_PHY_S 20
++#define PIAC_CMD_S 18
++#define PIAC_ST_S 16
++#define PIAC_DATA_MASK 0xffffU
++#define PIAC_CMD_READ 2
++#define PIAC_ST_C22 1
++
++/* EN8811H PHY ID register values */
++#define EN8811H_PHYSID1 0x03a2
++#define EN8811H_PHYSID2_MASK 0xfff0
++#define EN8811H_PHYSID2 0xa410
++#define MII_PHYSID1 0x02 /* IEEE MII register 2 */
++#define MII_PHYSID2 0x03 /* IEEE MII register 3 */
++
++/* Set a GPIO pin's mode field (0 = GPIO, 1 = eth function, …) */
++static void gpio_set_mode(unsigned int pin, unsigned int func)
++{
++ clrsetbits_le32(GPIO_MODE_REG(pin),
++ 0xf << GPIO_MODE_SHIFT(pin),
++ (func & 0xf) << GPIO_MODE_SHIFT(pin));
++}
++
++/* Configure a GPIO pin as output and drive it high or low */
++static void gpio_out(unsigned int pin, int val)
++{
++ setbits_le32(GPIO_DIR_REG(pin), BIT(pin % 32));
++ if (val)
++ setbits_le32(GPIO_DOUT_REG(pin), BIT(pin % 32));
++ else
++ clrbits_le32(GPIO_DOUT_REG(pin), BIT(pin % 32));
++}
++
++/*
++ * Clause-22 MDIO read via the MT7986 GMAC PHY indirect access register.
++ * Returns the 16-bit register value, or -1 on timeout.
++ */
++static int mdio_read_c22(unsigned int phy, unsigned int reg)
++{
++ u32 val;
++ int i;
++
++ val = (PIAC_ST_C22 << PIAC_ST_S) |
++ (PIAC_CMD_READ << PIAC_CMD_S) |
++ (phy << PIAC_PHY_S) |
++ (reg << PIAC_REG_S);
++ writel(val | PIAC_ACS_ST, GMAC_PIAC);
++
++ for (i = 0; i < 5000; i++) {
++ val = readl(GMAC_PIAC);
++ if (!(val & PIAC_ACS_ST))
++ return (int)(val & PIAC_DATA_MASK);
++ udelay(1);
++ }
++
++ return -1; /* timeout */
++}
++
++static void enable_mini_power_lines(unsigned int gpio_a, unsigned int gpio_b)
++{
++ gpio_set_mode(gpio_a, 0); /* mode 0 = GPIO */
++ gpio_set_mode(gpio_b, 0); /* mode 0 = GPIO */
++ gpio_out(gpio_a, 0); /* active-low enable */
++ gpio_out(gpio_b, 0); /* active-low enable */
++}
++
++static void pulse_mini_phy_reset(void)
++{
++ gpio_set_mode(EN8811H_RST_GPIO, 0); /* mode 0 = GPIO */
++ gpio_out(EN8811H_RST_GPIO, 0); /* assert reset */
++ mdelay(10);
++ gpio_out(EN8811H_RST_GPIO, 1); /* deassert reset */
++ mdelay(20);
++}
++
++static bool is_en8811_id(int id1, int id2)
++{
++ return id1 == EN8811H_PHYSID1 &&
++ (id2 & EN8811H_PHYSID2_MASK) == EN8811H_PHYSID2;
++}
++
++static int probe_en8811_addr(unsigned int phy_addr)
++{
++ int id1, id2;
++
++ id1 = mdio_read_c22(phy_addr, MII_PHYSID1);
++ id2 = mdio_read_c22(phy_addr, MII_PHYSID2);
++
++ if (id1 < 0 || id2 < 0)
++ return 0;
++
++ return is_en8811_id(id1, id2);
++}
++
++static enum bpir3_variant detect_bpir3_variant(void)
++{
++ unsigned int pwr_a[] = { EN8811H_PWR_A_GPIO, EN8811H_PWR2_A_GPIO };
++ unsigned int pwr_b[] = { EN8811H_PWR_B_GPIO, EN8811H_PWR2_B_GPIO };
++ unsigned int i;
++
++ /* Switch GPIO67 (MDC) and GPIO68 (MDIO) to eth function */
++ gpio_set_mode(MDC_GPIO, 1);
++ gpio_set_mode(MDIO_GPIO, 1);
++
++ /* Configure MDC clock: enable turbo + set safe divider (~2.5 MHz) */
++ setbits_le32(GMAC_PPSC, PPSC_MDC_TURBO);
++ clrsetbits_le32(GMAC_PPSC, PPSC_MDC_CFG_MASK, PPSC_MDC_CFG(MDC_DIVIDER));
++
++ /*
++ * Enable EN8811H power rails and probe; some mini boards use
++ * GPIO11/12, others GPIO16/17.
++ */
++ for (i = 0; i < ARRAY_SIZE(pwr_a); i++) {
++ enable_mini_power_lines(pwr_a[i], pwr_b[i]);
++ pulse_mini_phy_reset();
++
++ if (probe_en8811_addr(14) || probe_en8811_addr(15)) {
++ /* Assert reset; DM eth-phy will deassert with proper delays */
++ gpio_out(EN8811H_RST_GPIO, 0);
++ return BPIR3_MINI;
++ }
++ }
++
++ return BPIR3;
++}
++
++int board_fit_config_name_match(const char *name)
++{
++ static int variant = -1;
++
++ if (variant < 0)
++ variant = detect_bpir3_variant();
++
++ switch (variant) {
++ case BPIR3_MINI:
++ return strcmp(name, "mt7986a-bpi-r3-mini") ? -1 : 0;
++ case BPIR3:
++ default:
++ /*
++ * Accept both sd and emmc variants of BPI-R3;
++ * storage type is determined by spl_boot_device(), not here.
++ */
++ return (strcmp(name, "mt7986a-bpi-r3-sd") == 0 ||
++ strcmp(name, "mt7986a-bpi-r3-emmc") == 0) ? 0 : -1;
++ }
++}
++
++int board_late_init(void)
++{
++ const char *model = fdt_getprop(gd->fdt_blob, 0, "model", NULL);
++
++ if (model && strstr(model, "Mini"))
++ env_set("fdtfile", "mediatek/mt7986a-bananapi-bpi-r3-mini.dtb");
++
++ return 0;
++}
+diff --git a/board/mediatek/mt7988/Kconfig b/board/mediatek/mt7988/Kconfig
+new file mode 100644
+index 00000000000..24c70417ec5
+--- /dev/null
++++ b/board/mediatek/mt7988/Kconfig
+@@ -0,0 +1,9 @@
++if TARGET_MT7988
++
++config BOARD_BPI_R4
++ bool "BananaPi BPI-R4 / BPI-R4-2g5 board support"
++ help
++ Enable runtime variant detection for BananaPi BPI-R4 and BPI-R4-2g5,
++ selecting the correct device tree from a FIT image.
++
++endif
+diff --git a/board/mediatek/mt7988/Makefile b/board/mediatek/mt7988/Makefile
+index f1249ab3715..157ee371eae 100644
+--- a/board/mediatek/mt7988/Makefile
++++ b/board/mediatek/mt7988/Makefile
+@@ -1,3 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0
+
+ obj-y += mt7988_rfb.o
++obj-$(CONFIG_BOARD_BPI_R4) += bpir4.o
+diff --git a/board/mediatek/mt7988/bpir4.c b/board/mediatek/mt7988/bpir4.c
+new file mode 100644
+index 00000000000..46e22d38135
+--- /dev/null
++++ b/board/mediatek/mt7988/bpir4.c
+@@ -0,0 +1,33 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * BPI-R4 / BPI-R4-2g5 board variant detection
++ */
++
++#include
++#include
++
++enum bpir4_variant {
++ BPIR4,
++ BPIR4_2G5,
++};
++
++/*
++ * Detect which BPI-R4 variant this board is.
++ * TODO: implement real hardware detection
++ */
++static enum bpir4_variant detect_bpir4_variant(void)
++{
++ /* Stub: always report BPI-R4 */
++ return BPIR4;
++}
++
++int board_fit_config_name_match(const char *name)
++{
++ switch (detect_bpir4_variant()) {
++ case BPIR4_2G5:
++ return strcmp(name, "mt7988a-bananapi-bpi-r4-2g5") ? -1 : 0;
++ case BPIR4:
++ default:
++ return strcmp(name, "mt7988a-bananapi-bpi-r4") ? -1 : 0;
++ }
++}
+diff --git a/configs/mt7986a_bpir3_emmc_defconfig b/configs/mt7986a_bpir3_emmc_defconfig
+index 153c1934bd0..2f9baa723ff 100644
+--- a/configs/mt7986a_bpir3_emmc_defconfig
++++ b/configs/mt7986a_bpir3_emmc_defconfig
+@@ -9,6 +9,8 @@ CONFIG_ENV_SIZE=0x80000
+ CONFIG_ENV_OFFSET=0x300000
+ CONFIG_DEFAULT_DEVICE_TREE="mt7986a-bpi-r3-emmc"
+ CONFIG_TARGET_MT7986=y
++CONFIG_BOARD_BPI_R3=y
++CONFIG_SPL_LOAD_FIT=y
+ CONFIG_SYS_LOAD_ADDR=0x46000000
+ CONFIG_DEBUG_UART_BASE=0x11002000
+ CONFIG_DEBUG_UART_CLOCK=40000000
+diff --git a/configs/mt7986a_bpir3_mini_defconfig b/configs/mt7986a_bpir3_mini_defconfig
+new file mode 100644
+index 00000000000..39e44555c55
+--- /dev/null
++++ b/configs/mt7986a_bpir3_mini_defconfig
+@@ -0,0 +1,66 @@
++CONFIG_ARM=y
++CONFIG_SYS_HAS_NONCACHED_MEMORY=y
++CONFIG_POSITION_INDEPENDENT=y
++CONFIG_ARCH_MEDIATEK=y
++CONFIG_TEXT_BASE=0x41e00000
++CONFIG_SYS_MALLOC_F_LEN=0x4000
++CONFIG_NR_DRAM_BANKS=1
++CONFIG_ENV_SIZE=0x80000
++CONFIG_ENV_OFFSET=0x300000
++CONFIG_DEFAULT_DEVICE_TREE="mt7986a-bpi-r3-mini"
++CONFIG_TARGET_MT7986=y
++CONFIG_BOARD_BPI_R3=y
++CONFIG_SPL_LOAD_FIT=y
++CONFIG_SYS_LOAD_ADDR=0x46000000
++CONFIG_DEBUG_UART_BASE=0x11002000
++CONFIG_DEBUG_UART_CLOCK=40000000
++CONFIG_DEBUG_UART=y
++# CONFIG_EFI_LOADER is not set
++# CONFIG_AUTOBOOT is not set
++CONFIG_DEFAULT_FDT_FILE="mt7986a-bpi-r3-mini"
++CONFIG_SYS_CBSIZE=512
++CONFIG_SYS_PBSIZE=1049
++CONFIG_LOGLEVEL=7
++CONFIG_LOG=y
++CONFIG_SYS_PROMPT="BPI-R3> "
++# CONFIG_BOOTM_NETBSD is not set
++# CONFIG_BOOTM_PLAN9 is not set
++# CONFIG_BOOTM_RTEMS is not set
++# CONFIG_BOOTM_VXWORKS is not set
++# CONFIG_CMD_ELF is not set
++# CONFIG_CMD_UNLZ4 is not set
++# CONFIG_CMD_UNZIP is not set
++CONFIG_CMD_GPIO=y
++CONFIG_CMD_GPT=y
++CONFIG_CMD_GPT_RENAME=y
++CONFIG_CMD_LSBLK=y
++CONFIG_CMD_MMC=y
++CONFIG_CMD_PART=y
++CONFIG_CMD_READ=y
++CONFIG_CMD_PING=y
++CONFIG_CMD_SMC=y
++CONFIG_CMD_FAT=y
++CONFIG_CMD_FS_GENERIC=y
++CONFIG_PARTITION_TYPE_GUID=y
++CONFIG_ENV_OVERWRITE=y
++CONFIG_ENV_IS_IN_MMC=y
++CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
++CONFIG_NET_RANDOM_ETHADDR=y
++CONFIG_REGMAP=y
++CONFIG_SYSCON=y
++CONFIG_CLK=y
++CONFIG_MMC_HS200_SUPPORT=y
++CONFIG_MMC_MTK=y
++CONFIG_PHY_FIXED=y
++CONFIG_MEDIATEK_ETH=y
++CONFIG_PINCTRL=y
++CONFIG_PINCONF=y
++CONFIG_PINCTRL_MT7986=y
++CONFIG_POWER_DOMAIN=y
++CONFIG_MTK_POWER_DOMAIN=y
++CONFIG_DM_REGULATOR=y
++CONFIG_DM_REGULATOR_FIXED=y
++CONFIG_DM_SERIAL=y
++CONFIG_MTK_SERIAL=y
++CONFIG_FAT_WRITE=y
++CONFIG_HEXDUMP=y
+diff --git a/configs/mt7986a_bpir3_sd_defconfig b/configs/mt7986a_bpir3_sd_defconfig
+index ad9711da614..fc4d5d91fbc 100644
+--- a/configs/mt7986a_bpir3_sd_defconfig
++++ b/configs/mt7986a_bpir3_sd_defconfig
+@@ -9,6 +9,8 @@ CONFIG_ENV_SIZE=0x80000
+ CONFIG_ENV_OFFSET=0x300000
+ CONFIG_DEFAULT_DEVICE_TREE="mt7986a-bpi-r3-sd"
+ CONFIG_TARGET_MT7986=y
++CONFIG_BOARD_BPI_R3=y
++CONFIG_SPL_LOAD_FIT=y
+ CONFIG_SYS_LOAD_ADDR=0x46000000
+ CONFIG_DEBUG_UART_BASE=0x11002000
+ CONFIG_DEBUG_UART_CLOCK=40000000
+diff --git a/configs/mt7988a_bpir4_defconfig b/configs/mt7988a_bpir4_defconfig
+new file mode 100644
+index 00000000000..e2e128ec29d
+--- /dev/null
++++ b/configs/mt7988a_bpir4_defconfig
+@@ -0,0 +1,85 @@
++CONFIG_ARM=y
++CONFIG_SYS_HAS_NONCACHED_MEMORY=y
++CONFIG_POSITION_INDEPENDENT=y
++CONFIG_ARCH_MEDIATEK=y
++CONFIG_TEXT_BASE=0x41e00000
++CONFIG_SYS_MALLOC_F_LEN=0x4000
++CONFIG_NR_DRAM_BANKS=1
++CONFIG_DEFAULT_DEVICE_TREE="mt7988a-bananapi-bpi-r4"
++CONFIG_TARGET_MT7988=y
++CONFIG_BOARD_BPI_R4=y
++CONFIG_SPL_LOAD_FIT=y
++CONFIG_SYS_LOAD_ADDR=0x46000000
++CONFIG_DEBUG_UART_BASE=0x11000000
++CONFIG_DEBUG_UART_CLOCK=40000000
++CONFIG_DEBUG_UART=y
++# CONFIG_EFI_LOADER is not set
++# CONFIG_AUTOBOOT is not set
++CONFIG_DEFAULT_FDT_FILE="mt7988a-bananapi-bpi-r4"
++CONFIG_SYS_CBSIZE=512
++CONFIG_SYS_PBSIZE=1049
++CONFIG_LOGLEVEL=7
++CONFIG_LOG=y
++CONFIG_SYS_PROMPT="BPI-R4> "
++# CONFIG_BOOTM_NETBSD is not set
++# CONFIG_BOOTM_PLAN9 is not set
++# CONFIG_BOOTM_RTEMS is not set
++# CONFIG_BOOTM_VXWORKS is not set
++# CONFIG_CMD_ELF is not set
++CONFIG_CMD_CLK=y
++CONFIG_CMD_DM=y
++CONFIG_CMD_GPIO=y
++CONFIG_CMD_PWM=y
++CONFIG_CMD_MMC=y
++CONFIG_CMD_MTD=y
++CONFIG_CMD_PING=y
++CONFIG_CMD_SMC=y
++CONFIG_DOS_PARTITION=y
++CONFIG_EFI_PARTITION=y
++CONFIG_PARTITION_TYPE_GUID=y
++CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
++CONFIG_USE_IPADDR=y
++CONFIG_IPADDR="192.168.1.1"
++CONFIG_USE_NETMASK=y
++CONFIG_NETMASK="255.255.255.0"
++CONFIG_USE_SERVERIP=y
++CONFIG_SERVERIP="192.168.1.2"
++CONFIG_PROT_TCP=y
++CONFIG_NET_RANDOM_ETHADDR=y
++CONFIG_REGMAP=y
++CONFIG_SYSCON=y
++CONFIG_CLK=y
++CONFIG_MMC_HS200_SUPPORT=y
++CONFIG_MMC_MTK=y
++CONFIG_MTD=y
++CONFIG_DM_MTD=y
++CONFIG_MTD_SPI_NAND=y
++CONFIG_DM_SPI_FLASH=y
++CONFIG_SPI_FLASH_SFDP_SUPPORT=y
++CONFIG_SPI_FLASH_EON=y
++CONFIG_SPI_FLASH_GIGADEVICE=y
++CONFIG_SPI_FLASH_ISSI=y
++CONFIG_SPI_FLASH_MACRONIX=y
++CONFIG_SPI_FLASH_SPANSION=y
++CONFIG_SPI_FLASH_STMICRO=y
++CONFIG_SPI_FLASH_WINBOND=y
++CONFIG_SPI_FLASH_XMC=y
++CONFIG_SPI_FLASH_XTX=y
++CONFIG_SPI_FLASH_MTD=y
++CONFIG_PHY_FIXED=y
++CONFIG_MEDIATEK_ETH=y
++CONFIG_PINCTRL=y
++CONFIG_PINCONF=y
++CONFIG_PINCTRL_MT7988=y
++CONFIG_POWER_DOMAIN=y
++CONFIG_MTK_POWER_DOMAIN=y
++CONFIG_DM_PWM=y
++CONFIG_PWM_MTK=y
++CONFIG_RAM=y
++CONFIG_DM_SERIAL=y
++CONFIG_MTK_SERIAL=y
++CONFIG_SPI=y
++CONFIG_DM_SPI=y
++CONFIG_MTK_SPIM=y
++CONFIG_LZO=y
++CONFIG_HEXDUMP=y
+--
+2.43.0
+
diff --git a/patches/uboot/2026.01/0008-arm-mediatek-fix-MT7622-BROM-image-header-type.patch b/patches/uboot/2026.01/0008-arm-mediatek-fix-MT7622-BROM-image-header-type.patch
new file mode 100644
index 000000000..7847b04b1
--- /dev/null
+++ b/patches/uboot/2026.01/0008-arm-mediatek-fix-MT7622-BROM-image-header-type.patch
@@ -0,0 +1,34 @@
+From e09f64e56b1f10dd4ad6f066b6c05a604daf80e5 Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Thu, 26 Feb 2026 11:22:28 +0100
+Subject: [PATCH 08/10] arm: mediatek: fix MT7622 BROM image header type
+Organization: Wires
+
+The LK image header for MT7622 was inadvertently changed to "media=nor"
+when board Kconfigs were merged into mach-mediatek/Kconfig. MT7622 uses
+the same ATF/LK-based boot flow as MT7623 and requires "lk=1" so that
+mkimage generates the correct header for the BROM to accept.
+
+Reported-by: Shiji Yang
+Signed-off-by: Joachim Wiberg
+---
+ arch/arm/mach-mediatek/Kconfig | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/arch/arm/mach-mediatek/Kconfig b/arch/arm/mach-mediatek/Kconfig
+index ca593cceb43..59d678b7a08 100644
+--- a/arch/arm/mach-mediatek/Kconfig
++++ b/arch/arm/mach-mediatek/Kconfig
+@@ -158,7 +158,8 @@ config MTK_BROM_HEADER_INFO
+ string
+ default "media=nor" if TARGET_MT8518 || TARGET_MT8512 || TARGET_MT7629
+ default "media=emmc" if TARGET_MT8516 || TARGET_MT8365 || TARGET_MT8183
+- default "lk=1" if TARGET_MT7623
++ default "media=snand;nandinfo=2k+64" if TARGET_MT7981 || TARGET_MT7986 || TARGET_MT7988
++ default "lk=1" if TARGET_MT7622 || TARGET_MT7623
+
+ config MTK_TZ_MOVABLE
+ select ARCH_MISC_INIT
+--
+2.43.0
+
diff --git a/patches/uboot/2026.01/0009-arm-mediatek-add-BananaPi-BPI-R64-MT7622-defconfigs.patch b/patches/uboot/2026.01/0009-arm-mediatek-add-BananaPi-BPI-R64-MT7622-defconfigs.patch
new file mode 100644
index 000000000..d7158a2f7
--- /dev/null
+++ b/patches/uboot/2026.01/0009-arm-mediatek-add-BananaPi-BPI-R64-MT7622-defconfigs.patch
@@ -0,0 +1,710 @@
+From 92ed8c8fbbacab3c2e98c65008b3c314f609fd00 Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Thu, 26 Feb 2026 11:23:28 +0100
+Subject: [PATCH 09/10] arm: mediatek: add BananaPi BPI-R64 (MT7622) defconfigs
+Organization: Wires
+
+Add U-Boot support for the BananaPi BPI-R64 router board, based on the
+MediaTek MT7622 SoC (dual-core ARM Cortex-A53, 64-bit). Three defconfigs
+are provided, following the BPI-R3 pattern:
+
+ mt7622_bpir64_emmc_defconfig - eMMC (mmc0), env at 4 MiB
+ mt7622_bpir64_sd_defconfig - SD card (mmc1), env at 4 MiB
+ mt7622_bpir64_snand_defconfig - SPI NAND via SNFI, env in UBI
+
+All variants include PCIe, USB3 (xHCI + T-PHY), Ethernet (MT7531 switch
+via SGMII/2500base-x), GPIO-LEDs, and push-button support.
+
+The SNAND variant uses the upstream CONFIG_MTK_SNFI_SPI + MTD_SPI_NAND
+path with a spi-nand child under &snfi, adapting OpenWRT patch 403 to
+the upstream driver model (which has no separate &snand node or
+CONFIG_MTK_SPI_NAND). NAND layout: bl2 at 0x0 (512 KiB), ubi for the
+remainder of the flash.
+
+Also, add hardware description missing from the original device tree:
+ - ethernet0 alias for the MT7531 switch
+ - gpio-keys nodes for the reset (GPIO0) and WPS (GPIO102) buttons
+ - gpio-leds nodes for the green (GPIO89) and blue (GPIO85) LEDs
+ - Fix cap-sd-highspeed -> cap-mmc-highspeed on mmc0 (eMMC requires
+ the MMC-specific capability flag, not the SD one)
+ - Reduce mmc1 (SD card) max-frequency to 25 MHz for stability
+
+Sources:
+ OpenWRT patches 402-404/408 for package/boot/uboot-mediatek
+ https://git.infobricfleet.com/gtu/openwrt/-/tree/master/package/boot/uboot-mediatek/patches
+ Frank Wunderlich's initial MT7622/BPI-R64 U-Boot port (2019)
+ https://github.com/frank-w/u-boot/commit/c451ad3950e63d54b074f71e930459281dd3f594
+
+Signed-off-by: Joachim Wiberg
+---
+ arch/arm/dts/mt7622-bananapi-bpi-r64-nand.dts | 290 ++++++++++++++++++
+ arch/arm/dts/mt7622-bananapi-bpi-r64.dts | 38 ++-
+ board/mediatek/mt7622/MAINTAINERS | 5 +
+ configs/mt7622_bpir64_emmc_defconfig | 82 +++++
+ configs/mt7622_bpir64_sd_defconfig | 82 +++++
+ configs/mt7622_bpir64_snand_defconfig | 88 ++++++
+ 6 files changed, 583 insertions(+), 2 deletions(-)
+ create mode 100644 arch/arm/dts/mt7622-bananapi-bpi-r64-nand.dts
+ create mode 100644 configs/mt7622_bpir64_emmc_defconfig
+ create mode 100644 configs/mt7622_bpir64_sd_defconfig
+ create mode 100644 configs/mt7622_bpir64_snand_defconfig
+
+diff --git a/arch/arm/dts/mt7622-bananapi-bpi-r64-nand.dts b/arch/arm/dts/mt7622-bananapi-bpi-r64-nand.dts
+new file mode 100644
+index 00000000000..afdcb646f69
+--- /dev/null
++++ b/arch/arm/dts/mt7622-bananapi-bpi-r64-nand.dts
+@@ -0,0 +1,290 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2019 MediaTek Inc.
++ * Author: Sam Shih
++ *
++ * BananaPi BPI-R64 - SPI NAND flash variant
++ */
++
++/dts-v1/;
++#include
++#include "mt7622.dtsi"
++#include "mt7622-u-boot.dtsi"
++
++/ {
++ #address-cells = <1>;
++ #size-cells = <1>;
++ model = "mt7622-bpi-r64";
++ compatible = "mediatek,mt7622", "mediatek,mt7622-rfb";
++ chosen {
++ stdout-path = &uart0;
++ tick-timer = &timer0;
++ };
++
++ aliases {
++ spi0 = &snfi;
++ ethernet0 = ð
++ };
++
++ memory@40000000 {
++ device_type = "memory";
++ reg = <0x40000000 0x40000000>;
++ };
++
++ gpio-keys {
++ compatible = "gpio-keys";
++
++ factory-reset {
++ label = "factory-reset";
++ gpios = <&gpio 0 GPIO_ACTIVE_LOW>;
++ linux,code = ;
++ };
++
++ wps {
++ label = "wps";
++ gpios = <&gpio 102 GPIO_ACTIVE_LOW>;
++ linux,code = ;
++ };
++ };
++
++ leds {
++ compatible = "gpio-leds";
++
++ green {
++ label = "bpi-r64:pio:green";
++ gpios = <&gpio 89 GPIO_ACTIVE_HIGH>;
++ default-state = "off";
++ };
++
++ blue {
++ label = "bpi-r64:pio:blue";
++ gpios = <&gpio 85 GPIO_ACTIVE_LOW>;
++ default-state = "off";
++ };
++ };
++
++ reg_1p8v: regulator-1p8v {
++ compatible = "regulator-fixed";
++ regulator-name = "fixed-1.8V";
++ regulator-min-microvolt = <1800000>;
++ regulator-max-microvolt = <1800000>;
++ regulator-boot-on;
++ regulator-always-on;
++ };
++
++ reg_3p3v: regulator-3p3v {
++ compatible = "regulator-fixed";
++ regulator-name = "fixed-3.3V";
++ regulator-min-microvolt = <3300000>;
++ regulator-max-microvolt = <3300000>;
++ regulator-boot-on;
++ regulator-always-on;
++ };
++};
++
++&pcie {
++ pinctrl-names = "default";
++ pinctrl-0 = <&pcie0_pins>, <&pcie1_pins>;
++ status = "okay";
++
++ pcie@0,0 {
++ status = "okay";
++ };
++
++ pcie@1,0 {
++ status = "okay";
++ };
++};
++
++&pinctrl {
++ pcie0_pins: pcie0-pins {
++ mux {
++ function = "pcie";
++ groups = "pcie0_pad_perst",
++ "pcie0_1_waken",
++ "pcie0_1_clkreq";
++ };
++ };
++
++ pcie1_pins: pcie1-pins {
++ mux {
++ function = "pcie";
++ groups = "pcie1_pad_perst",
++ "pcie1_0_waken",
++ "pcie1_0_clkreq";
++ };
++ };
++
++ snfi_pins: snfi-pins {
++ mux {
++ function = "flash";
++ groups = "snfi";
++ };
++ };
++
++ uart0_pins: uart0 {
++ mux {
++ function = "uart";
++ groups = "uart0_0_tx_rx" ;
++ };
++ };
++
++ pwm_pins: pwm1 {
++ mux {
++ function = "pwm";
++ groups = "pwm_ch1_0" ;
++ };
++ };
++
++ watchdog_pins: watchdog-default {
++ mux {
++ function = "watchdog";
++ groups = "watchdog";
++ };
++ };
++
++ mmc0_pins_default: mmc0default {
++ mux {
++ function = "emmc";
++ groups = "emmc";
++ };
++
++ conf-cmd-dat {
++ pins = "NDL0", "NDL1", "NDL2",
++ "NDL3", "NDL4", "NDL5",
++ "NDL6", "NDL7", "NRB";
++ input-enable;
++ bias-pull-up;
++ };
++
++ conf-clk {
++ pins = "NCLE";
++ bias-pull-down;
++ };
++ };
++
++ mmc1_pins_default: mmc1default {
++ mux {
++ function = "sd";
++ groups = "sd_0";
++ };
++
++ conf-cmd-data {
++ pins = "I2S2_OUT", "I2S4_IN", "I2S3_IN",
++ "I2S2_IN","I2S4_OUT";
++ input-enable;
++ drive-strength = <8>;
++ bias-pull-up;
++ };
++
++ conf-clk {
++ pins = "I2S3_OUT";
++ drive-strength = <12>;
++ bias-pull-down;
++ };
++
++ conf-cd {
++ pins = "TXD3";
++ bias-pull-up;
++ };
++ };
++};
++
++&snfi {
++ pinctrl-names = "default";
++ pinctrl-0 = <&snfi_pins>;
++ status = "okay";
++
++ spi-nand@0 {
++ compatible = "spi-nand";
++ reg = <0>;
++ spi-tx-bus-width = <4>;
++ spi-rx-bus-width = <4>;
++
++ partitions {
++ compatible = "fixed-partitions";
++ #address-cells = <1>;
++ #size-cells = <1>;
++
++ partition@0 {
++ label = "bl2";
++ reg = <0x0 0x80000>;
++ };
++
++ partition@80000 {
++ label = "ubi";
++ reg = <0x80000 0x7f80000>;
++ };
++ };
++ };
++};
++
++&uart0 {
++ status = "okay";
++};
++
++&pwm {
++ pinctrl-names = "default";
++ pinctrl-0 = <&pwm_pins>;
++ status = "okay";
++};
++
++&mmc0 {
++ pinctrl-names = "default";
++ pinctrl-0 = <&mmc0_pins_default>;
++ status = "okay";
++ bus-width = <8>;
++ max-frequency = <50000000>;
++ cap-mmc-highspeed;
++ vmmc-supply = <®_3p3v>;
++ vqmmc-supply = <®_3p3v>;
++ non-removable;
++};
++
++&mmc1 {
++ pinctrl-names = "default";
++ pinctrl-0 = <&mmc1_pins_default>;
++ status = "okay";
++ bus-width = <4>;
++ max-frequency = <25000000>;
++ cap-sd-highspeed;
++ r_smpl = <1>;
++ vmmc-supply = <®_3p3v>;
++ vqmmc-supply = <®_3p3v>;
++};
++
++&watchdog {
++ pinctrl-names = "default";
++ pinctrl-0 = <&watchdog_pins>;
++ status = "okay";
++};
++
++ð {
++ status = "okay";
++ mediatek,gmac-id = <0>;
++ phy-mode = "2500base-x";
++ mediatek,switch = "mt7531";
++ reset-gpios = <&gpio 54 GPIO_ACTIVE_HIGH>;
++
++ fixed-link {
++ speed = <2500>;
++ full-duplex;
++ };
++};
++
++&gpio {
++ /*gpio 90 for setting mode to sata*/
++ asm_sel {
++ gpio-hog;
++ gpios = <90 GPIO_ACTIVE_HIGH>;
++ output-low;
++ };
++};
++
++&ssusb {
++ status = "okay";
++};
++
++&u3phy {
++ status = "okay";
++};
+diff --git a/arch/arm/dts/mt7622-bananapi-bpi-r64.dts b/arch/arm/dts/mt7622-bananapi-bpi-r64.dts
+index 717baf26d3f..f25257be63d 100644
+--- a/arch/arm/dts/mt7622-bananapi-bpi-r64.dts
++++ b/arch/arm/dts/mt7622-bananapi-bpi-r64.dts
+@@ -5,6 +5,7 @@
+ */
+
+ /dts-v1/;
++#include
+ #include "mt7622.dtsi"
+ #include "mt7622-u-boot.dtsi"
+
+@@ -20,6 +21,7 @@
+
+ aliases {
+ spi0 = &snfi;
++ ethernet0 = ð
+ };
+
+ memory@40000000 {
+@@ -27,6 +29,38 @@
+ reg = <0x40000000 0x40000000>;
+ };
+
++ gpio-keys {
++ compatible = "gpio-keys";
++
++ factory-reset {
++ label = "factory-reset";
++ gpios = <&gpio 0 GPIO_ACTIVE_LOW>;
++ linux,code = ;
++ };
++
++ wps {
++ label = "wps";
++ gpios = <&gpio 102 GPIO_ACTIVE_LOW>;
++ linux,code = ;
++ };
++ };
++
++ leds {
++ compatible = "gpio-leds";
++
++ green {
++ label = "bpi-r64:pio:green";
++ gpios = <&gpio 89 GPIO_ACTIVE_HIGH>;
++ default-state = "off";
++ };
++
++ blue {
++ label = "bpi-r64:pio:blue";
++ gpios = <&gpio 85 GPIO_ACTIVE_LOW>;
++ default-state = "off";
++ };
++ };
++
+ reg_1p8v: regulator-1p8v {
+ compatible = "regulator-fixed";
+ regulator-name = "fixed-1.8V";
+@@ -197,7 +231,7 @@
+ status = "okay";
+ bus-width = <8>;
+ max-frequency = <50000000>;
+- cap-sd-highspeed;
++ cap-mmc-highspeed;
+ vmmc-supply = <®_3p3v>;
+ vqmmc-supply = <®_3p3v>;
+ non-removable;
+@@ -208,7 +242,7 @@
+ pinctrl-0 = <&mmc1_pins_default>;
+ status = "okay";
+ bus-width = <4>;
+- max-frequency = <50000000>;
++ max-frequency = <25000000>;
+ cap-sd-highspeed;
+ r_smpl = <1>;
+ vmmc-supply = <®_3p3v>;
+diff --git a/board/mediatek/mt7622/MAINTAINERS b/board/mediatek/mt7622/MAINTAINERS
+index a3e0e75ca07..a7da7948a65 100644
+--- a/board/mediatek/mt7622/MAINTAINERS
++++ b/board/mediatek/mt7622/MAINTAINERS
+@@ -4,3 +4,8 @@ S: Maintained
+ F: board/mediatek/mt7622
+ F: include/configs/mt7622.h
+ F: configs/mt7622_rfb_defconfig
++F: configs/mt7622_bpir64_emmc_defconfig
++F: configs/mt7622_bpir64_sd_defconfig
++F: configs/mt7622_bpir64_snand_defconfig
++F: arch/arm/dts/mt7622-bananapi-bpi-r64.dts
++F: arch/arm/dts/mt7622-bananapi-bpi-r64-nand.dts
+diff --git a/configs/mt7622_bpir64_emmc_defconfig b/configs/mt7622_bpir64_emmc_defconfig
+new file mode 100644
+index 00000000000..9af7b280c93
+--- /dev/null
++++ b/configs/mt7622_bpir64_emmc_defconfig
+@@ -0,0 +1,82 @@
++CONFIG_ARM=y
++CONFIG_SYS_HAS_NONCACHED_MEMORY=y
++CONFIG_POSITION_INDEPENDENT=y
++CONFIG_ARCH_MEDIATEK=y
++CONFIG_TARGET_MT7622=y
++CONFIG_TEXT_BASE=0x41e00000
++CONFIG_SYS_MALLOC_F_LEN=0x4000
++CONFIG_NR_DRAM_BANKS=1
++CONFIG_ENV_SIZE=0x80000
++CONFIG_ENV_OFFSET=0x400000
++CONFIG_DEFAULT_DEVICE_TREE="mt7622-bananapi-bpi-r64"
++CONFIG_SYS_LOAD_ADDR=0x40080000
++CONFIG_DEBUG_UART_BASE=0x11002000
++CONFIG_DEBUG_UART_CLOCK=25000000
++CONFIG_PCI=y
++CONFIG_DEBUG_UART=y
++CONFIG_FIT=y
++# CONFIG_EFI_LOADER is not set
++# CONFIG_AUTOBOOT is not set
++CONFIG_DEFAULT_FDT_FILE="mt7622-bananapi-bpi-r64"
++CONFIG_SYS_CBSIZE=512
++CONFIG_SYS_PBSIZE=1049
++CONFIG_LOGLEVEL=7
++CONFIG_LOG=y
++CONFIG_SYS_PROMPT="BPI-R64> "
++# CONFIG_BOOTM_NETBSD is not set
++# CONFIG_BOOTM_PLAN9 is not set
++# CONFIG_BOOTM_RTEMS is not set
++# CONFIG_BOOTM_VXWORKS is not set
++# CONFIG_CMD_ELF is not set
++# CONFIG_CMD_UNLZ4 is not set
++# CONFIG_CMD_UNZIP is not set
++CONFIG_CMD_GPIO=y
++CONFIG_CMD_GPT=y
++CONFIG_CMD_GPT_RENAME=y
++CONFIG_CMD_LSBLK=y
++CONFIG_CMD_MMC=y
++CONFIG_CMD_PART=y
++CONFIG_CMD_READ=y
++CONFIG_CMD_PCI=y
++CONFIG_CMD_USB=y
++CONFIG_CMD_PING=y
++CONFIG_CMD_SMC=y
++CONFIG_CMD_FAT=y
++CONFIG_CMD_FS_GENERIC=y
++CONFIG_PARTITION_TYPE_GUID=y
++CONFIG_ENV_OVERWRITE=y
++CONFIG_ENV_IS_IN_MMC=y
++CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
++CONFIG_NET_RANDOM_ETHADDR=y
++CONFIG_REGMAP=y
++CONFIG_SYSCON=y
++CONFIG_BUTTON=y
++CONFIG_BUTTON_GPIO=y
++CONFIG_CLK=y
++CONFIG_GPIO_HOG=y
++CONFIG_LED=y
++CONFIG_LED_GPIO=y
++CONFIG_SUPPORT_EMMC_BOOT=y
++CONFIG_MMC_HS200_SUPPORT=y
++CONFIG_MMC_MTK=y
++CONFIG_PHY_FIXED=y
++CONFIG_MEDIATEK_ETH=y
++CONFIG_PCIE_MEDIATEK=y
++CONFIG_PHY=y
++CONFIG_PHY_MTK_TPHY=y
++CONFIG_PINCTRL=y
++CONFIG_PINCONF=y
++CONFIG_PINCTRL_MT7622=y
++CONFIG_POWER_DOMAIN=y
++CONFIG_MTK_POWER_DOMAIN=y
++CONFIG_DM_REGULATOR=y
++CONFIG_DM_REGULATOR_FIXED=y
++CONFIG_RAM=y
++CONFIG_DM_SERIAL=y
++CONFIG_MTK_SERIAL=y
++CONFIG_USB=y
++CONFIG_USB_XHCI_HCD=y
++CONFIG_USB_XHCI_MTK=y
++CONFIG_USB_STORAGE=y
++CONFIG_FAT_WRITE=y
++CONFIG_HEXDUMP=y
+diff --git a/configs/mt7622_bpir64_sd_defconfig b/configs/mt7622_bpir64_sd_defconfig
+new file mode 100644
+index 00000000000..5e9340a09a6
+--- /dev/null
++++ b/configs/mt7622_bpir64_sd_defconfig
+@@ -0,0 +1,82 @@
++CONFIG_ARM=y
++CONFIG_SYS_HAS_NONCACHED_MEMORY=y
++CONFIG_POSITION_INDEPENDENT=y
++CONFIG_ARCH_MEDIATEK=y
++CONFIG_TARGET_MT7622=y
++CONFIG_TEXT_BASE=0x41e00000
++CONFIG_SYS_MALLOC_F_LEN=0x4000
++CONFIG_NR_DRAM_BANKS=1
++CONFIG_ENV_SIZE=0x80000
++CONFIG_ENV_OFFSET=0x400000
++CONFIG_DEFAULT_DEVICE_TREE="mt7622-bananapi-bpi-r64"
++CONFIG_SYS_LOAD_ADDR=0x40080000
++CONFIG_DEBUG_UART_BASE=0x11002000
++CONFIG_DEBUG_UART_CLOCK=25000000
++CONFIG_PCI=y
++CONFIG_DEBUG_UART=y
++CONFIG_FIT=y
++# CONFIG_EFI_LOADER is not set
++# CONFIG_AUTOBOOT is not set
++CONFIG_DEFAULT_FDT_FILE="mt7622-bananapi-bpi-r64"
++CONFIG_SYS_CBSIZE=512
++CONFIG_SYS_PBSIZE=1049
++CONFIG_LOGLEVEL=7
++CONFIG_LOG=y
++CONFIG_SYS_PROMPT="BPI-R64> "
++# CONFIG_BOOTM_NETBSD is not set
++# CONFIG_BOOTM_PLAN9 is not set
++# CONFIG_BOOTM_RTEMS is not set
++# CONFIG_BOOTM_VXWORKS is not set
++# CONFIG_CMD_ELF is not set
++# CONFIG_CMD_UNLZ4 is not set
++# CONFIG_CMD_UNZIP is not set
++CONFIG_CMD_GPIO=y
++CONFIG_CMD_GPT=y
++CONFIG_CMD_GPT_RENAME=y
++CONFIG_CMD_LSBLK=y
++CONFIG_CMD_MMC=y
++CONFIG_CMD_PART=y
++CONFIG_CMD_READ=y
++CONFIG_CMD_PCI=y
++CONFIG_CMD_USB=y
++CONFIG_CMD_PING=y
++CONFIG_CMD_SMC=y
++CONFIG_CMD_FAT=y
++CONFIG_CMD_FS_GENERIC=y
++CONFIG_PARTITION_TYPE_GUID=y
++CONFIG_ENV_OVERWRITE=y
++CONFIG_ENV_IS_IN_MMC=y
++CONFIG_SYS_MMC_ENV_DEV=1
++CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
++CONFIG_NET_RANDOM_ETHADDR=y
++CONFIG_REGMAP=y
++CONFIG_SYSCON=y
++CONFIG_BUTTON=y
++CONFIG_BUTTON_GPIO=y
++CONFIG_CLK=y
++CONFIG_GPIO_HOG=y
++CONFIG_LED=y
++CONFIG_LED_GPIO=y
++CONFIG_MMC_HS200_SUPPORT=y
++CONFIG_MMC_MTK=y
++CONFIG_PHY_FIXED=y
++CONFIG_MEDIATEK_ETH=y
++CONFIG_PCIE_MEDIATEK=y
++CONFIG_PHY=y
++CONFIG_PHY_MTK_TPHY=y
++CONFIG_PINCTRL=y
++CONFIG_PINCONF=y
++CONFIG_PINCTRL_MT7622=y
++CONFIG_POWER_DOMAIN=y
++CONFIG_MTK_POWER_DOMAIN=y
++CONFIG_DM_REGULATOR=y
++CONFIG_DM_REGULATOR_FIXED=y
++CONFIG_RAM=y
++CONFIG_DM_SERIAL=y
++CONFIG_MTK_SERIAL=y
++CONFIG_USB=y
++CONFIG_USB_XHCI_HCD=y
++CONFIG_USB_XHCI_MTK=y
++CONFIG_USB_STORAGE=y
++CONFIG_FAT_WRITE=y
++CONFIG_HEXDUMP=y
+diff --git a/configs/mt7622_bpir64_snand_defconfig b/configs/mt7622_bpir64_snand_defconfig
+new file mode 100644
+index 00000000000..9be13d26fd9
+--- /dev/null
++++ b/configs/mt7622_bpir64_snand_defconfig
+@@ -0,0 +1,88 @@
++CONFIG_ARM=y
++CONFIG_SYS_HAS_NONCACHED_MEMORY=y
++CONFIG_POSITION_INDEPENDENT=y
++CONFIG_ARCH_MEDIATEK=y
++CONFIG_TARGET_MT7622=y
++CONFIG_TEXT_BASE=0x41e00000
++CONFIG_SYS_MALLOC_F_LEN=0x4000
++CONFIG_NR_DRAM_BANKS=1
++CONFIG_DEFAULT_DEVICE_TREE="mt7622-bananapi-bpi-r64-nand"
++CONFIG_SYS_LOAD_ADDR=0x40080000
++CONFIG_DEBUG_UART_BASE=0x11002000
++CONFIG_DEBUG_UART_CLOCK=25000000
++CONFIG_PCI=y
++CONFIG_DEBUG_UART=y
++CONFIG_FIT=y
++# CONFIG_EFI_LOADER is not set
++# CONFIG_AUTOBOOT is not set
++CONFIG_DEFAULT_FDT_FILE="mt7622-bananapi-bpi-r64-nand"
++CONFIG_SYS_CBSIZE=512
++CONFIG_SYS_PBSIZE=1049
++CONFIG_LOGLEVEL=7
++CONFIG_LOG=y
++CONFIG_SYS_PROMPT="BPI-R64> "
++# CONFIG_BOOTM_NETBSD is not set
++# CONFIG_BOOTM_PLAN9 is not set
++# CONFIG_BOOTM_RTEMS is not set
++# CONFIG_BOOTM_VXWORKS is not set
++# CONFIG_CMD_ELF is not set
++# CONFIG_CMD_UNLZ4 is not set
++# CONFIG_CMD_UNZIP is not set
++CONFIG_CMD_GPIO=y
++CONFIG_CMD_MMC=y
++CONFIG_CMD_MTD=y
++CONFIG_CMD_PART=y
++CONFIG_CMD_READ=y
++CONFIG_CMD_PCI=y
++CONFIG_CMD_USB=y
++CONFIG_CMD_PING=y
++CONFIG_CMD_SMC=y
++CONFIG_CMD_FAT=y
++CONFIG_CMD_FS_GENERIC=y
++CONFIG_CMD_UBI=y
++CONFIG_PARTITION_TYPE_GUID=y
++CONFIG_ENV_OVERWRITE=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_VARS_UBOOT_RUNTIME_CONFIG=y
++CONFIG_NET_RANDOM_ETHADDR=y
++CONFIG_REGMAP=y
++CONFIG_SYSCON=y
++CONFIG_BUTTON=y
++CONFIG_BUTTON_GPIO=y
++CONFIG_CLK=y
++CONFIG_GPIO_HOG=y
++CONFIG_LED=y
++CONFIG_LED_GPIO=y
++CONFIG_MMC_HS200_SUPPORT=y
++CONFIG_MMC_MTK=y
++CONFIG_MTD=y
++CONFIG_DM_MTD=y
++CONFIG_MTD_SPI_NAND=y
++CONFIG_PHY_FIXED=y
++CONFIG_MEDIATEK_ETH=y
++CONFIG_PCIE_MEDIATEK=y
++CONFIG_PHY=y
++CONFIG_PHY_MTK_TPHY=y
++CONFIG_PINCTRL=y
++CONFIG_PINCONF=y
++CONFIG_PINCTRL_MT7622=y
++CONFIG_POWER_DOMAIN=y
++CONFIG_MTK_POWER_DOMAIN=y
++CONFIG_DM_REGULATOR=y
++CONFIG_DM_REGULATOR_FIXED=y
++CONFIG_RAM=y
++CONFIG_DM_SERIAL=y
++CONFIG_MTK_SERIAL=y
++CONFIG_SPI=y
++CONFIG_DM_SPI=y
++CONFIG_MTK_SNFI_SPI=y
++CONFIG_USB=y
++CONFIG_USB_XHCI_HCD=y
++CONFIG_USB_XHCI_MTK=y
++CONFIG_USB_STORAGE=y
++CONFIG_FAT_WRITE=y
++CONFIG_HEXDUMP=y
+--
+2.43.0
+
diff --git a/patches/uboot/2026.01/0010-arm-mediatek-add-BananaPi-BPI-R4-MT7988A-board-suppo.patch b/patches/uboot/2026.01/0010-arm-mediatek-add-BananaPi-BPI-R4-MT7988A-board-suppo.patch
new file mode 100644
index 000000000..8386f5889
--- /dev/null
+++ b/patches/uboot/2026.01/0010-arm-mediatek-add-BananaPi-BPI-R4-MT7988A-board-suppo.patch
@@ -0,0 +1,784 @@
+From 8b3d206ff8c398de5290a1c4237c841389655505 Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Sun, 15 Mar 2026 12:51:39 +0100
+Subject: [PATCH] arm: mediatek: add BananaPi BPI-R4 (MT7988A) board support
+Organization: Wires
+
+Add complete U-Boot support for the BananaPi BPI-R4 router board,
+based on MediaTek MT7988A (Filogic 880, quad-core Cortex-A73).
+
+Device tree (mt7988a-bananapi-bpi-r4.dts):
+- UART0 (console)
+- eMMC (8-bit, non-removable, 52 MHz, high-speed)
+- SPI NAND via SPI0
+- Gigabit/10G Ethernet via MT7988 internal switch (usxgmii)
+- PCIe x4 (pcie0-3)
+- USB 3.0 (xhci1)
+- PWM (fan control)
+- I2C0: RT5190A PMIC
+- I2C2: PCA9545 mux with PCF8563 RTC and 24C02 EEPROM on channel 0
+- gpio-keys: factory-reset / WPS button on GPIO14 (active-low);
+ U-Boot uses it for factory reset detection via the 'button' command,
+ Linux uses the same GPIO as KEY_WPS_BUTTON at runtime
+
+SD card variant (mt7988a-bananapi-bpi-r4-sd.dts + mt7988a_bpir4_sd_defconfig):
+- MT7988A eMMC (pins 38-49) and SD card (pins 32-37) use separate
+ physical pads so both are electrically independent; only one can be
+ active per boot session via the single MMC controller (mmc@11230000)
+- SD variant enables cap-sd-highspeed (50 MHz) vs legacy 25 MHz;
+ GPIO12 used as card-detect
+- pinctrl-mt7988.c: add 'sdcard' pin group (pins 32-37, function 5)
+ to match the Linux kernel driver
+
+Board support (mt7988_rfb.c, Kconfig, defconfig):
+- detect_boot_media(): reads BOOT_DEVICE register (0x1001f6f0 bits
+ 10-11) to set $bootmedia env var (nor/spim-nand/emmc/snand)
+- detect_ram_size(): sets $ram_gb (4 or 8) based on detected DRAM
+- board_late_init(): calls both detectors at late init
+- ft_system_setup(): patches /chosen/rootdisk phandle in kernel DT
+ to point at the detected boot device node
+- BOARD_BPI_R4 now selects BOARD_LATE_INIT and OF_SYSTEM_SETUP
+
+Defconfig gaps vs upstream filled in (FIT, eMMC, USB, PCIe/NVMe, I2C).
+CONFIG_CMD_BUTTON=y added to both defconfigs for factory-reset button.
+
+The bpir4.c board file (from commit 57b3cd86) provides
+board_fit_config_name_match() for BPI-R4 / BPI-R4-2g5 variant
+selection via FIT image.
+
+Based on Frank Wunderlich's porting work:
+ https://github.com/frank-w/u-boot/tree/2026-01-bpi
+
+Signed-off-by: Joachim Wiberg
+---
+ arch/arm/dts/Makefile | 3 +
+ arch/arm/dts/mt7988a-bananapi-bpi-r4-2g5.dts | 16 ++
+ arch/arm/dts/mt7988a-bananapi-bpi-r4-sd.dts | 33 +++
+ arch/arm/dts/mt7988a-bananapi-bpi-r4.dts | 227 +++++++++++++++++++
+ board/mediatek/mt7988/Kconfig | 2 +
+ board/mediatek/mt7988/bpir4.c | 23 +-
+ board/mediatek/mt7988/mt7988_rfb.c | 96 ++++++++
+ configs/mt7988a_bpir4_defconfig | 33 ++-
+ configs/mt7988a_bpir4_sd_defconfig | 114 ++++++++++
+ drivers/pinctrl/mediatek/pinctrl-mt7988.c | 6 +-
+ 10 files changed, 547 insertions(+), 6 deletions(-)
+ create mode 100644 arch/arm/dts/mt7988a-bananapi-bpi-r4-2g5.dts
+ create mode 100644 arch/arm/dts/mt7988a-bananapi-bpi-r4-sd.dts
+ create mode 100644 arch/arm/dts/mt7988a-bananapi-bpi-r4.dts
+ create mode 100644 configs/mt7988a_bpir4_sd_defconfig
+
+diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile
+index d4255f95702..697d3383b01 100644
+--- a/arch/arm/dts/Makefile
++++ b/arch/arm/dts/Makefile
+@@ -1134,6 +1134,9 @@ dtb-$(CONFIG_ARCH_MEDIATEK) += \
+ mt7986b-emmc-rfb.dtb \
+ mt7988-rfb.dtb \
+ mt7988-sd-rfb.dtb \
++ mt7988a-bananapi-bpi-r4.dtb \
++ mt7988a-bananapi-bpi-r4-2g5.dtb \
++ mt7988a-bananapi-bpi-r4-sd.dtb \
+ mt8183-pumpkin.dtb \
+ mt8512-bm1-emmc.dtb \
+ mt8516-pumpkin.dtb \
+diff --git a/arch/arm/dts/mt7988a-bananapi-bpi-r4-2g5.dts b/arch/arm/dts/mt7988a-bananapi-bpi-r4-2g5.dts
+new file mode 100644
+index 00000000000..0dd82cdc9fd
+--- /dev/null
++++ b/arch/arm/dts/mt7988a-bananapi-bpi-r4-2g5.dts
+@@ -0,0 +1,16 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2024 BananaPi
++ * Copyright (c) 2026 KernelKit
++ *
++ * U-Boot device tree for BananaPi BPI-R4-2g5 (MediaTek MT7988A)
++ * Variant with internal 2.5G PHY on GMAC1 instead of SFP+/USXGMII
++ */
++
++#include "mt7988a-bananapi-bpi-r4.dts"
++
++/ {
++ model = "BananaPi BPI-R4-2g5";
++ compatible = "bananapi,bpi-r4-2g5", "bananapi,bpi-r4",
++ "mediatek,mt7988a", "mediatek,mt7988";
++};
+diff --git a/arch/arm/dts/mt7988a-bananapi-bpi-r4-sd.dts b/arch/arm/dts/mt7988a-bananapi-bpi-r4-sd.dts
+new file mode 100644
+index 00000000000..4fb411bad4d
+--- /dev/null
++++ b/arch/arm/dts/mt7988a-bananapi-bpi-r4-sd.dts
+@@ -0,0 +1,33 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * SD card boot variant of the BananaPi BPI-R4 U-Boot device tree.
++ * eMMC and SD use separate physical pins on MT7988, so both are
++ * accessible simultaneously — boot from SD, flash eMMC.
++ */
++
++/dts-v1/;
++#include "mt7988a-bananapi-bpi-r4.dts"
++
++/* Override mmc0 for SD card: swap pinctrl, enable high-speed, add CD */
++&mmc0 {
++ pinctrl-names = "default";
++ pinctrl-0 = <&mmc0_pins_sdcard>;
++ bus-width = <4>;
++ max-frequency = <52000000>;
++ cap-sd-highspeed;
++ cd-gpios = <&pio 12 GPIO_ACTIVE_LOW>;
++ vmmc-supply = <®_3p3v>;
++ vqmmc-supply = <®_3p3v>;
++ /delete-property/ cap-mmc-highspeed;
++ /delete-property/ cap-mmc-hw-reset;
++ /delete-property/ non-removable;
++};
++
++&pio {
++ mmc0_pins_sdcard: mmc0-sdcard-pins {
++ mux {
++ function = "flash";
++ groups = "sdcard";
++ };
++ };
++};
+diff --git a/arch/arm/dts/mt7988a-bananapi-bpi-r4.dts b/arch/arm/dts/mt7988a-bananapi-bpi-r4.dts
+new file mode 100644
+index 00000000000..d959d778716
+--- /dev/null
++++ b/arch/arm/dts/mt7988a-bananapi-bpi-r4.dts
+@@ -0,0 +1,227 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2024 BananaPi
++ * Copyright (c) 2026 KernelKit
++ *
++ * U-Boot device tree for BananaPi BPI-R4 (MediaTek MT7988A)
++ * Based on Frank Wunderlich's work: https://github.com/frank-w/u-boot
++ */
++
++/dts-v1/;
++#include "mt7988.dtsi"
++#include
++#include
++
++/ {
++ model = "BananaPi BPI-R4";
++ compatible = "bananapi,bpi-r4", "mediatek,mt7988a", "mediatek,mt7988";
++
++ chosen {
++ stdout-path = &uart0;
++ };
++
++ memory@40000000 {
++ device_type = "memory";
++ reg = <0 0x40000000 1 0x00000000>; /* 4 GiB */
++ };
++
++ gpio-keys {
++ compatible = "gpio-keys";
++
++ factory-reset {
++ label = "factory-reset";
++ gpios = <&pio 14 GPIO_ACTIVE_LOW>;
++ linux,code = ;
++ };
++ };
++
++ reg_1p8v: regulator-1p8v {
++ compatible = "regulator-fixed";
++ regulator-name = "fixed-1.8V";
++ regulator-min-microvolt = <1800000>;
++ regulator-max-microvolt = <1800000>;
++ regulator-boot-on;
++ regulator-always-on;
++ };
++
++ reg_3p3v: regulator-3p3v {
++ compatible = "regulator-fixed";
++ regulator-name = "fixed-3.3V";
++ regulator-min-microvolt = <3300000>;
++ regulator-max-microvolt = <3300000>;
++ regulator-boot-on;
++ regulator-always-on;
++ };
++};
++
++&uart0 {
++ status = "okay";
++};
++
++/* RT5190A PMIC */
++&i2c0 {
++ pinctrl-names = "default";
++ pinctrl-0 = <&i2c0_pins>;
++ status = "okay";
++
++ rt5190a: pmic@64 {
++ compatible = "richtek,rt5190a";
++ reg = <0x64>;
++ };
++};
++
++/* I2C mux: PCA9545 at 0x70, with RTC and board EEPROM on channel 0 */
++&i2c2 {
++ pinctrl-names = "default";
++ pinctrl-0 = <&i2c2_1_pins>;
++ status = "okay";
++
++ i2c_mux: i2c-mux@70 {
++ compatible = "nxp,pca9545";
++ reg = <0x70>;
++ reset-gpios = <&pio 5 GPIO_ACTIVE_LOW>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ i2c_ch0: i2c@0 {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ reg = <0>;
++
++ rtc@51 {
++ compatible = "nxp,pcf8563";
++ reg = <0x51>;
++ };
++
++ eeprom@57 {
++ compatible = "atmel,24c02";
++ reg = <0x57>;
++ };
++ };
++ };
++};
++
++ð0 {
++ status = "okay";
++ phy-mode = "usxgmii";
++ mediatek,switch = "mt7988";
++
++ fixed-link {
++ speed = <10000>;
++ full-duplex;
++ pause;
++ };
++};
++
++&pcie0 {
++ status = "okay";
++};
++
++&pcie1 {
++ status = "okay";
++};
++
++&pcie2 {
++ status = "okay";
++};
++
++&pcie3 {
++ status = "okay";
++};
++
++&pio {
++ i2c0_pins: i2c0-g0-pins {
++ mux {
++ function = "i2c";
++ groups = "i2c0_1";
++ };
++ };
++
++ i2c2_1_pins: i2c2-g1-pins {
++ mux {
++ function = "i2c";
++ groups = "i2c2_1";
++ };
++ };
++
++ mmc0_pins_emmc: mmc0-emmc-pins {
++ mux {
++ function = "flash";
++ groups = "emmc_51";
++ };
++
++ conf-cmd-dat {
++ pins = "EMMC_DATA_0", "EMMC_DATA_1", "EMMC_DATA_2",
++ "EMMC_DATA_3", "EMMC_DATA_4", "EMMC_DATA_5",
++ "EMMC_DATA_6", "EMMC_DATA_7", "EMMC_CMD";
++ input-enable;
++ drive-strength = ;
++ mediatek,pull-up-adv = <1>;
++ };
++
++ conf-clk {
++ pins = "EMMC_CK";
++ drive-strength = ;
++ mediatek,pull-down-adv = <2>;
++ };
++
++ conf-dsl {
++ pins = "EMMC_DSL";
++ mediatek,pull-down-adv = <2>;
++ };
++
++ conf-rst {
++ pins = "EMMC_RSTB";
++ drive-strength = ;
++ mediatek,pull-up-adv = <1>;
++ };
++ };
++
++ spi0_flash_pins: spi0-flash-pins {
++ mux {
++ function = "spi";
++ groups = "spi0", "spi0_wp_hold";
++ };
++ };
++};
++
++&pwm {
++ status = "okay";
++};
++
++/* SPI NAND */
++&spi0 {
++ pinctrl-names = "default";
++ pinctrl-0 = <&spi0_flash_pins>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ status = "okay";
++ enhance_timing;
++ dma_ext;
++ ipm_design;
++ support_quad;
++ tick_dly = <2>;
++ sample_sel = <0>;
++
++ spi_nand: flash@0 {
++ compatible = "spi-nand";
++ reg = <0>;
++ spi-max-frequency = <52000000>;
++ spi-tx-bus-width = <4>;
++ spi-rx-bus-width = <4>;
++ };
++};
++
++/* eMMC */
++&mmc0 {
++ pinctrl-names = "default";
++ pinctrl-0 = <&mmc0_pins_emmc>;
++ max-frequency = <52000000>;
++ bus-width = <8>;
++ cap-mmc-highspeed;
++ cap-mmc-hw-reset;
++ vmmc-supply = <®_3p3v>;
++ vqmmc-supply = <®_1p8v>;
++ non-removable;
++ status = "okay";
++};
+diff --git a/board/mediatek/mt7988/Kconfig b/board/mediatek/mt7988/Kconfig
+index 24c70417ec5..9bcad9ecefd 100644
+--- a/board/mediatek/mt7988/Kconfig
++++ b/board/mediatek/mt7988/Kconfig
+@@ -2,6 +2,8 @@ if TARGET_MT7988
+
+ config BOARD_BPI_R4
+ bool "BananaPi BPI-R4 / BPI-R4-2g5 board support"
++ select BOARD_LATE_INIT
++ select OF_SYSTEM_SETUP
+ help
+ Enable runtime variant detection for BananaPi BPI-R4 and BPI-R4-2g5,
+ selecting the correct device tree from a FIT image.
+diff --git a/board/mediatek/mt7988/bpir4.c b/board/mediatek/mt7988/bpir4.c
+index 46e22d38135..cfc05c607fc 100644
+--- a/board/mediatek/mt7988/bpir4.c
++++ b/board/mediatek/mt7988/bpir4.c
+@@ -13,21 +13,36 @@ enum bpir4_variant {
+
+ /*
+ * Detect which BPI-R4 variant this board is.
+- * TODO: implement real hardware detection
++ *
++ * Unlike BPI-R3 vs R3-Mini, the R4-2g5 uses the MT7988A's internal 2.5G
++ * PHY (always present in SoC silicon at MDIO addr 15) rather than an
++ * external EN8811H, so MDIO probing cannot distinguish the variants.
++ *
++ * TODO: read variant ID from the 24C02 EEPROM on I2C2/PCA9545 ch0 (addr
++ * 0x57) once BananaPi's board-ID byte offset/format is known.
+ */
+ static enum bpir4_variant detect_bpir4_variant(void)
+ {
+- /* Stub: always report BPI-R4 */
++ /* Stub: always report standard BPI-R4 */
+ return BPIR4;
+ }
+
+ int board_fit_config_name_match(const char *name)
+ {
+- switch (detect_bpir4_variant()) {
++ static int variant = -1;
++
++ if (variant < 0)
++ variant = detect_bpir4_variant();
++
++ switch (variant) {
+ case BPIR4_2G5:
+ return strcmp(name, "mt7988a-bananapi-bpi-r4-2g5") ? -1 : 0;
+ case BPIR4:
+ default:
+- return strcmp(name, "mt7988a-bananapi-bpi-r4") ? -1 : 0;
++ if (!strcmp(name, "mt7988a-bananapi-bpi-r4") ||
++ !strcmp(name, "mt7988a-bananapi-bpi-r4-sd"))
++ return 0;
++ return -1;
+ }
+ }
++
+diff --git a/board/mediatek/mt7988/mt7988_rfb.c b/board/mediatek/mt7988/mt7988_rfb.c
+index 0ca87a88aed..ed184574b50 100644
+--- a/board/mediatek/mt7988/mt7988_rfb.c
++++ b/board/mediatek/mt7988/mt7988_rfb.c
+@@ -4,3 +4,99 @@
+ * Author: Sam Shih
+ */
+
++#include
++#include
++#include
++#include
++#include
++#include
++
++#define MT7988_BOOT_NOR 0
++#define MT7988_BOOT_SPIM_NAND 1
++#define MT7988_BOOT_EMMC 2
++#define MT7988_BOOT_SNFI_NAND 3
++
++DECLARE_GLOBAL_DATA_PTR;
++
++int board_init(void)
++{
++ return 0;
++}
++
++static void detect_boot_media(void)
++{
++ const char *media;
++
++ switch ((readl(0x1001f6f0) & 0xc00) >> 10) {
++ case MT7988_BOOT_NOR:
++ media = "nor";
++ break;
++ case MT7988_BOOT_SPIM_NAND:
++ media = "spim-nand";
++ break;
++ case MT7988_BOOT_EMMC:
++ media = "emmc";
++ break;
++ case MT7988_BOOT_SNFI_NAND:
++ media = "snand";
++ break;
++ default:
++ media = "unknown";
++ break;
++ }
++
++ env_set("bootmedia", media);
++}
++
++static void detect_ram_size(void)
++{
++ env_set("ram_gb", gd->ram_size > (6ULL << 30) ? "8" : "4");
++}
++
++int board_late_init(void)
++{
++ const char *model;
++
++ detect_boot_media();
++ detect_ram_size();
++
++ model = fdt_getprop(gd->fdt_blob, 0, "model", NULL);
++ if (model && strstr(model, "2g5"))
++ env_set("fdtfile", "mediatek/mt7988a-bananapi-bpi-r4-2g5.dtb");
++
++ return 0;
++}
++
++int ft_system_setup(void *blob, struct bd_info *bd)
++{
++ const u32 *media_handle_p;
++ int chosen, len, ret;
++ char media[32];
++ const char *bootdev;
++ u32 media_handle;
++
++ if (!env_get("bootmedia"))
++ detect_boot_media();
++
++ bootdev = env_get("bootmedia");
++ snprintf(media, sizeof(media), "rootdisk-%s", bootdev);
++
++ chosen = fdt_path_offset(blob, "/chosen");
++ if (chosen <= 0)
++ return 0;
++
++ media_handle_p = fdt_getprop(blob, chosen, media, &len);
++ if (!media_handle_p || len != 4)
++ return 0;
++
++ media_handle = *media_handle_p;
++ ret = fdt_setprop(blob, chosen, "rootdisk", &media_handle, sizeof(media_handle));
++ if (ret) {
++ printf("cannot set media phandle %s as rootdisk in /chosen\n", media);
++ return ret;
++ }
++
++ printf("set /chosen/rootdisk to boot media: %s (phandle 0x%08x)\n",
++ media, fdt32_to_cpu(media_handle));
++ return 0;
++}
+diff --git a/configs/mt7988a_bpir4_defconfig b/configs/mt7988a_bpir4_defconfig
+index e2e128ec29d..9d4a2f6e75e 100644
+--- a/configs/mt7988a_bpir4_defconfig
++++ b/configs/mt7988a_bpir4_defconfig
+@@ -9,6 +9,7 @@ CONFIG_DEFAULT_DEVICE_TREE="mt7988a-bananapi-bpi-r4"
+ CONFIG_TARGET_MT7988=y
+ CONFIG_BOARD_BPI_R4=y
+ CONFIG_SPL_LOAD_FIT=y
++CONFIG_FIT=y
+ CONFIG_SYS_LOAD_ADDR=0x46000000
+ CONFIG_DEBUG_UART_BASE=0x11000000
+ CONFIG_DEBUG_UART_CLOCK=40000000
+@@ -16,6 +17,8 @@ CONFIG_DEBUG_UART=y
+ # CONFIG_EFI_LOADER is not set
+ # CONFIG_AUTOBOOT is not set
+ CONFIG_DEFAULT_FDT_FILE="mt7988a-bananapi-bpi-r4"
++CONFIG_MULTI_DTB_FIT=y
++CONFIG_OF_LIST="mt7988a-bananapi-bpi-r4 mt7988a-bananapi-bpi-r4-2g5"
+ CONFIG_SYS_CBSIZE=512
+ CONFIG_SYS_PBSIZE=1049
+ CONFIG_LOGLEVEL=7
+@@ -26,14 +29,19 @@ CONFIG_SYS_PROMPT="BPI-R4> "
+ # CONFIG_BOOTM_RTEMS is not set
+ # CONFIG_BOOTM_VXWORKS is not set
+ # CONFIG_CMD_ELF is not set
++CONFIG_CMD_BUTTON=y
+ CONFIG_CMD_CLK=y
+ CONFIG_CMD_DM=y
+ CONFIG_CMD_GPIO=y
+-CONFIG_CMD_PWM=y
++CONFIG_CMD_I2C=y
+ CONFIG_CMD_MMC=y
+ CONFIG_CMD_MTD=y
++CONFIG_CMD_MDIO=y
++CONFIG_CMD_PCI=y
+ CONFIG_CMD_PING=y
++CONFIG_CMD_PWM=y
+ CONFIG_CMD_SMC=y
++CONFIG_CMD_USB=y
+ CONFIG_DOS_PARTITION=y
+ CONFIG_EFI_PARTITION=y
+ CONFIG_PARTITION_TYPE_GUID=y
+@@ -49,8 +57,15 @@ CONFIG_NET_RANDOM_ETHADDR=y
+ CONFIG_REGMAP=y
+ CONFIG_SYSCON=y
+ CONFIG_CLK=y
++CONFIG_I2C=y
++CONFIG_DM_I2C=y
++CONFIG_SYS_I2C_MTK=y
++CONFIG_I2C_MUX=y
++CONFIG_I2C_MUX_PCA954x=y
+ CONFIG_MMC_HS200_SUPPORT=y
++CONFIG_MMC_WRITE=y
+ CONFIG_MMC_MTK=y
++CONFIG_SUPPORT_EMMC_BOOT=y
+ CONFIG_MTD=y
+ CONFIG_DM_MTD=y
+ CONFIG_MTD_SPI_NAND=y
+@@ -67,7 +82,14 @@ CONFIG_SPI_FLASH_XMC=y
+ CONFIG_SPI_FLASH_XTX=y
+ CONFIG_SPI_FLASH_MTD=y
+ CONFIG_PHY_FIXED=y
++CONFIG_DM_MDIO=y
++CONFIG_DM_ETH_PHY=y
++CONFIG_PHY_ETHERNET_ID=y
+ CONFIG_MEDIATEK_ETH=y
++CONFIG_PCI=y
++CONFIG_PCIE_MEDIATEK_GEN3=y
++CONFIG_NVME_PCI=y
++CONFIG_NVME=y
+ CONFIG_PINCTRL=y
+ CONFIG_PINCONF=y
+ CONFIG_PINCTRL_MT7988=y
+@@ -78,8 +100,17 @@ CONFIG_PWM_MTK=y
+ CONFIG_RAM=y
+ CONFIG_DM_SERIAL=y
+ CONFIG_MTK_SERIAL=y
++CONFIG_PHY=y
++CONFIG_PHY_MTK_TPHY=y
++CONFIG_USB=y
++CONFIG_USB_XHCI_HCD=y
++CONFIG_USB_XHCI_MTK=y
++CONFIG_USB_STORAGE=y
+ CONFIG_SPI=y
+ CONFIG_DM_SPI=y
+ CONFIG_MTK_SPIM=y
++CONFIG_OF_LIBFDT_OVERLAY=y
++CONFIG_OF_SYSTEM_SETUP=y
++CONFIG_BOARD_LATE_INIT=y
+ CONFIG_LZO=y
+ CONFIG_HEXDUMP=y
+diff --git a/configs/mt7988a_bpir4_sd_defconfig b/configs/mt7988a_bpir4_sd_defconfig
+new file mode 100644
+index 00000000000..7e78dc17e54
+--- /dev/null
++++ b/configs/mt7988a_bpir4_sd_defconfig
+@@ -0,0 +1,114 @@
++CONFIG_ARM=y
++CONFIG_SYS_HAS_NONCACHED_MEMORY=y
++CONFIG_POSITION_INDEPENDENT=y
++CONFIG_ARCH_MEDIATEK=y
++CONFIG_TEXT_BASE=0x41e00000
++CONFIG_SYS_MALLOC_F_LEN=0x4000
++CONFIG_NR_DRAM_BANKS=1
++CONFIG_DEFAULT_DEVICE_TREE="mt7988a-bananapi-bpi-r4-sd"
++CONFIG_TARGET_MT7988=y
++CONFIG_BOARD_BPI_R4=y
++CONFIG_SPL_LOAD_FIT=y
++CONFIG_FIT=y
++CONFIG_SYS_LOAD_ADDR=0x46000000
++CONFIG_DEBUG_UART_BASE=0x11000000
++CONFIG_DEBUG_UART_CLOCK=40000000
++CONFIG_DEBUG_UART=y
++# CONFIG_EFI_LOADER is not set
++# CONFIG_AUTOBOOT is not set
++CONFIG_DEFAULT_FDT_FILE="mt7988a-bananapi-bpi-r4-sd"
++CONFIG_MULTI_DTB_FIT=y
++CONFIG_OF_LIST="mt7988a-bananapi-bpi-r4-sd"
++CONFIG_SYS_CBSIZE=512
++CONFIG_SYS_PBSIZE=1049
++CONFIG_LOGLEVEL=7
++CONFIG_LOG=y
++CONFIG_SYS_PROMPT="BPI-R4> "
++# CONFIG_BOOTM_NETBSD is not set
++# CONFIG_BOOTM_PLAN9 is not set
++# CONFIG_BOOTM_RTEMS is not set
++# CONFIG_BOOTM_VXWORKS is not set
++# CONFIG_CMD_ELF is not set
++CONFIG_CMD_BUTTON=y
++CONFIG_CMD_CLK=y
++CONFIG_CMD_DM=y
++CONFIG_CMD_GPIO=y
++CONFIG_CMD_I2C=y
++CONFIG_CMD_MMC=y
++CONFIG_CMD_MTD=y
++CONFIG_CMD_MDIO=y
++CONFIG_CMD_PCI=y
++CONFIG_CMD_PING=y
++CONFIG_CMD_PWM=y
++CONFIG_CMD_SMC=y
++CONFIG_CMD_USB=y
++CONFIG_DOS_PARTITION=y
++CONFIG_EFI_PARTITION=y
++CONFIG_PARTITION_TYPE_GUID=y
++CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
++CONFIG_USE_IPADDR=y
++CONFIG_IPADDR="192.168.1.1"
++CONFIG_USE_NETMASK=y
++CONFIG_NETMASK="255.255.255.0"
++CONFIG_USE_SERVERIP=y
++CONFIG_SERVERIP="192.168.1.2"
++CONFIG_PROT_TCP=y
++CONFIG_NET_RANDOM_ETHADDR=y
++CONFIG_REGMAP=y
++CONFIG_SYSCON=y
++CONFIG_CLK=y
++CONFIG_I2C=y
++CONFIG_DM_I2C=y
++CONFIG_SYS_I2C_MTK=y
++CONFIG_I2C_MUX=y
++CONFIG_I2C_MUX_PCA954x=y
++CONFIG_MMC_WRITE=y
++CONFIG_MMC_MTK=y
++CONFIG_MTD=y
++CONFIG_DM_MTD=y
++CONFIG_MTD_SPI_NAND=y
++CONFIG_DM_SPI_FLASH=y
++CONFIG_SPI_FLASH_SFDP_SUPPORT=y
++CONFIG_SPI_FLASH_EON=y
++CONFIG_SPI_FLASH_GIGADEVICE=y
++CONFIG_SPI_FLASH_ISSI=y
++CONFIG_SPI_FLASH_MACRONIX=y
++CONFIG_SPI_FLASH_SPANSION=y
++CONFIG_SPI_FLASH_STMICRO=y
++CONFIG_SPI_FLASH_WINBOND=y
++CONFIG_SPI_FLASH_XMC=y
++CONFIG_SPI_FLASH_XTX=y
++CONFIG_SPI_FLASH_MTD=y
++CONFIG_PHY_FIXED=y
++CONFIG_DM_MDIO=y
++CONFIG_DM_ETH_PHY=y
++CONFIG_PHY_ETHERNET_ID=y
++CONFIG_MEDIATEK_ETH=y
++CONFIG_PCI=y
++CONFIG_PCIE_MEDIATEK_GEN3=y
++CONFIG_NVME_PCI=y
++CONFIG_NVME=y
++CONFIG_PINCTRL=y
++CONFIG_PINCONF=y
++CONFIG_PINCTRL_MT7988=y
++CONFIG_POWER_DOMAIN=y
++CONFIG_MTK_POWER_DOMAIN=y
++CONFIG_DM_PWM=y
++CONFIG_PWM_MTK=y
++CONFIG_RAM=y
++CONFIG_DM_SERIAL=y
++CONFIG_MTK_SERIAL=y
++CONFIG_PHY=y
++CONFIG_PHY_MTK_TPHY=y
++CONFIG_USB=y
++CONFIG_USB_XHCI_HCD=y
++CONFIG_USB_XHCI_MTK=y
++CONFIG_USB_STORAGE=y
++CONFIG_SPI=y
++CONFIG_DM_SPI=y
++CONFIG_MTK_SPIM=y
++CONFIG_OF_LIBFDT_OVERLAY=y
++CONFIG_OF_SYSTEM_SETUP=y
++CONFIG_BOARD_LATE_INIT=y
++CONFIG_LZO=y
++CONFIG_HEXDUMP=y
+diff --git a/drivers/pinctrl/mediatek/pinctrl-mt7988.c b/drivers/pinctrl/mediatek/pinctrl-mt7988.c
+index 639e2415c63..317a325d8c6 100644
+--- a/drivers/pinctrl/mediatek/pinctrl-mt7988.c
++++ b/drivers/pinctrl/mediatek/pinctrl-mt7988.c
+@@ -942,6 +942,9 @@ static const int mt7988_emmc_45_pins[] = {
+ 21, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37 };
+ static const int mt7988_emmc_45_funcs[] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 };
+
++static const int mt7988_sdcard_pins[] = { 32, 33, 34, 35, 36, 37 };
++static const int mt7988_sdcard_funcs[] = { 5, 5, 5, 5, 5, 5 };
++
+ static const int mt7988_emmc_51_pins[] = {
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49 };
+ static const int mt7988_emmc_51_funcs[] = {
+@@ -1101,6 +1104,7 @@ static const struct mtk_group_desc mt7988_groups[] = {
+ PINCTRL_PIN_GROUP("udi", mt7988_udi),
+ PINCTRL_PIN_GROUP("emmc_45", mt7988_emmc_45),
+ PINCTRL_PIN_GROUP("emmc_51", mt7988_emmc_51),
++ PINCTRL_PIN_GROUP("sdcard", mt7988_sdcard),
+ PINCTRL_PIN_GROUP("2p5g_ext_mdio", mt7988_2p5g_ext_mdio),
+ PINCTRL_PIN_GROUP("gbe_ext_mdio", mt7988_gbe_ext_mdio),
+ PINCTRL_PIN_GROUP("pcm", mt7988_pcm),
+@@ -1197,7 +1201,7 @@ static const char *const mt7988_pmic_groups[] = { "pmic", };
+ static const char *const mt7988_wdt_groups[] = { "watchdog", };
+ static const char *const mt7988_spi_groups[] = { "spi0", "spi0_wp_hold",
+ "spi1", "spi2", "spi2_wp_hold", };
+-static const char *const mt7988_flash_groups[] = { "emmc_45", "snfi",
++static const char *const mt7988_flash_groups[] = { "emmc_45", "sdcard", "snfi",
+ "emmc_51" };
+ static const char *const mt7988_uart_groups[] = { "uart2", "tops_uart0_0",
+ "uart2_0", "uart1_0", "uart2_1",
+--
+2.43.0
+
diff --git a/utils/mkimage.sh b/utils/mkimage.sh
index 882d34bab..1b01e19e3 100755
--- a/utils/mkimage.sh
+++ b/utils/mkimage.sh
@@ -186,6 +186,13 @@ get_bootloader_name()
echo "bpi_r3_sd_boot"
fi
;;
+ bananapi-bpi-r4)
+ if [ "$target" = "emmc" ]; then
+ echo "bpi_r4_emmc_boot"
+ else
+ echo "bpi_r4_sd_boot"
+ fi
+ ;;
bananapi-bpi-r64)
if [ "$target" = "emmc" ]; then
echo "bpi_r64_emmc_boot"