Merge pull request #1470 from kernelkit/bpi-r4-support

Add support for Banana Pi BPI-R4
This commit is contained in:
Joachim Wiberg
2026-04-10 14:05:07 +02:00
committed by GitHub
80 changed files with 6425 additions and 31 deletions
+2
View File
@@ -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
+7
View File
@@ -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
+1
View File
@@ -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"
+7 -6
View File
@@ -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/)
@@ -0,0 +1,2 @@
# Locally calculated
sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
@@ -0,0 +1,2 @@
# Locally calculated
sha256 dbe4eae8debbba8135297e15f24aeefef0b4c03781f3f9328db4398d58a728b3 LICENSE
+11
View File
@@ -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
+13
View File
@@ -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.
+360
View File
@@ -0,0 +1,360 @@
# Banana Pi BPI-R4 / BPI-R4-2g5 / BPI-R4P
<img src="banana_pi_bpi-r4_poe.jpg" alt="The board" width=800 padding=10>
## 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, lan1lan3 | lan0lan3 | lan0lan3 |
| 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
<img align="right" src="console-header.png" alt="Console header pins" width=200 padding=10>
### 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
<img align="right" src="bootstrap-sw3.png" alt="DIP switches" width=140 padding=10>
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
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

@@ -0,0 +1,2 @@
# Locally calculated
sha256 d48246c717b505cc11df95171f2fd548b389e1a463f1af4c68d0b69fe0d1009b LICENSE
@@ -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))
Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

@@ -0,0 +1,2 @@
dtb-y += infix/bananapi,bpi-r4.dtbo
dtb-y += infix/bananapi,bpi-r4-2g5.dtbo
@@ -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";
};
@@ -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";
};
};
};
@@ -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"
}
}
@@ -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}
@@ -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}
@@ -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}
@@ -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}
@@ -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
}
}
}
@@ -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<compatible>" — 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"
@@ -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"
@@ -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
}
}
}
@@ -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"
@@ -0,0 +1 @@
CONFIG_DEVICE_TREE_INCLUDES="infix-env.dtsi infix-key.dtsi mt7988-emmc-env.dtsi"
@@ -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
@@ -0,0 +1,7 @@
#include <mt7988-env.dtsi>
&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";
};
@@ -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";
};
};
};
@@ -0,0 +1,7 @@
#include <mt7988-env.dtsi>
&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";
};
@@ -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"
+15 -23
View File
@@ -30,20 +30,15 @@ Infix comes preconfigured with:
<img align="right" src="bpi-r64-sw1.png" alt="SW1 Boot Switch" width=90 padding=10>
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
@@ -0,0 +1,2 @@
# Locally calculated
sha256 d48246c717b505cc11df95171f2fd548b389e1a463f1af4c68d0b69fe0d1009b LICENSE
@@ -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
@@ -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
@@ -0,0 +1,2 @@
# Locally calculated
sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
@@ -0,0 +1,2 @@
# Locally calculated
sha256 dbe4eae8debbba8135297e15f24aeefef0b4c03781f3f9328db4398d58a728b3 LICENSE
@@ -0,0 +1,2 @@
# Locally calculated
sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
@@ -0,0 +1,2 @@
# Locally calculated
sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
@@ -0,0 +1,2 @@
# Locally calculated
sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
@@ -0,0 +1,2 @@
# Locally calculated
sha256 dbe4eae8debbba8135297e15f24aeefef0b4c03781f3f9328db4398d58a728b3 LICENSE
@@ -0,0 +1,2 @@
# Locally calculated
sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
@@ -0,0 +1,2 @@
# Locally calculated
sha256 d48246c717b505cc11df95171f2fd548b389e1a463f1af4c68d0b69fe0d1009b LICENSE
@@ -0,0 +1,2 @@
# Locally calculated
sha256 dbe4eae8debbba8135297e15f24aeefef0b4c03781f3f9328db4398d58a728b3 LICENSE
+1
View File
@@ -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");
+2 -1
View File
@@ -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
+4
View File
@@ -6,4 +6,8 @@ $(DESTDIR)/boot/%.dtb: %.dtb
@echo " DTB-INSTALL $<"
@install -D $< $@
$(DESTDIR)/boot/%.dtbo: %.dtbo
@echo " DTBO-INSTALL $<"
@install -D $< $@
.PHONY: install
+1
View File
@@ -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
+1
View File
@@ -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
+42
View File
@@ -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
+42
View File
@@ -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
+1
View File
@@ -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"
+1
View File
@@ -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"
+6
View File
@@ -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
-------------------------
@@ -0,0 +1,2 @@
# Locally calculated
sha256 360336cbf0f228b12b7ca0996b33e442d97f496edcf2ec31b655e63631e7f96f LICENSE
@@ -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
+2
View File
@@ -0,0 +1,2 @@
# Locally calculated
sha256 2f44058572d6e56953b0928a24dc7f816f35d33fb16097e97dfcd5b000bcc140 LICENSE
+2
View File
@@ -0,0 +1,2 @@
# Locally calculated
sha256 ab15fd526bd8dd18a9e77ebc139656bf4d33e97fc7238cd11bf60e2b9b8666c6 COPYING
@@ -0,0 +1,189 @@
From 8d2aec5525d60e643f85d68d62186cae530f17fc Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <daniel@makrotopia.org>.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
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 <daniel@makrotopia.org>
+ */
+
+#include <linux/pcs/pcs-standalone.h>
+#include <linux/phylink.h>
+
+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 <daniel@makrotopia.org>");
+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 <linux/device.h>
+#include <linux/phylink.h>
+#include <linux/phy/phy.h>
+#include <linux/of.h>
+
+#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
@@ -0,0 +1,465 @@
From a11a254420cf8777497b7c027208b382c49964e7 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <henry.yen@mediatek.com> and
Daniel Golle <daniel@makrotopia.org>.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
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 <henry.yen@mediatek.com>
+ * Daniel Golle <daniel@makrotopia.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mdio.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/reset.h>
+#include <linux/pcs/pcs-standalone.h>
+#include <linux/platform_device.h>
+
+/* 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 <daniel@makrotopia.org>");
--
2.43.0
@@ -0,0 +1,296 @@
From 8917bfe8fac3e16e2c5958cdbf0b8258a7797539 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <henry.yen@mediatek.com> and
Daniel Golle <daniel@makrotopia.org>.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
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 <linux/pinctrl/devinfo.h>
#include <linux/phylink.h>
#include <linux/pcs/pcs-mtk-lynxi.h>
+#include <linux/pcs/pcs-standalone.h>
#include <linux/jhash.h>
#include <linux/bitfield.h>
#include <net/dsa.h>
@@ -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
@@ -0,0 +1,65 @@
From c62809d597945068569a95b7ccc0398d99db098f Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <troglobit@gmail.com>
---
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
@@ -0,0 +1,82 @@
From 12d076e64f65326cf9e453899b6ac5adb31374c3 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <troglobit@gmail.com>
---
.../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 <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
#include <dt-bindings/leds/common.h>
#include <dt-bindings/regulator/richtek,rt5190a-regulator.h>
-#include <dt-bindings/leds/common.h>
#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 = <KEY_WPS_BUTTON>;
+ };
+ };
+
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
@@ -0,0 +1,60 @@
From 4b267c1be4141e0213b49d6d511051b8e6673f7c Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <troglobit@gmail.com>
---
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
@@ -0,0 +1,42 @@
From cde9fed496833b88094ed6e9fea11969a1be8e25 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
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 <tobias@waldekranz.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
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
@@ -0,0 +1,153 @@
From 020c9a036f2916afdce9d18aacc9e8c09a5dfca7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
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 <lazzer@gmail.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
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 <command.h>
+#include <asm/arch/msg.h>
+
+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
@@ -0,0 +1,35 @@
From 1a861c65011f636e464ca557804dfb6853a3871c Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <troglobit@gmail.com>
---
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
@@ -0,0 +1,36 @@
From 3fadc90e8c1017aa3d8c165e54a13d8f75536b99 Mon Sep 17 00:00:00 2001
From: Mihai Sain <mihai.sain@microchip.com>
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 <mihai.sain@microchip.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
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
@@ -0,0 +1,977 @@
From b8b781fd58eb8f0934edde8db45744d0a417cda8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
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 <lucienzx159@gmail.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
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 <phy.h>
+#include <errno.h>
+#include <log.h>
+#include <env.h>
+#include <malloc.h>
+#include <fs.h>
+#include <asm/unaligned.h>
+#include <linux/iopoll.h>
+#include <linux/bitops.h>
+#include <linux/compat.h>
+#include <dm/device_compat.h>
+#include <u-boot/crc.h>
+
+#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
@@ -0,0 +1,272 @@
From 3ba908cf4b16d729f611f64c20568baa47cf0a31 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
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 <troglobit@gmail.com>
---
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 <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/linux-event-codes.h>
+
+/ {
+ #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 = <KEY_RESTART>;
+ 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;
+ };
+};
+
+&eth {
+ 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 = <&reg_3p3v>;
+ vqmmc-supply = <&reg_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 = <MTK_DRIVE_8mA>;
+ bias-pull-down = <MTK_PUPD_SET_R1R0_11>;
+ output-low;
+ };
+
+ conf-en8811-pwr-b {
+ pins = "GPIO_12";
+ drive-strength = <MTK_DRIVE_8mA>;
+ bias-pull-down = <MTK_PUPD_SET_R1R0_11>;
+ 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 = <MTK_DRIVE_4mA>;
+ bias-pull-up = <MTK_PUPD_SET_R1R0_01>;
+ };
+
+ conf-clk {
+ pins = "EMMC_CK";
+ drive-strength = <MTK_DRIVE_6mA>;
+ bias-pull-down = <MTK_PUPD_SET_R1R0_10>;
+ };
+
+ conf-dsl {
+ pins = "EMMC_DSL";
+ bias-pull-down = <MTK_PUPD_SET_R1R0_10>;
+ };
+
+ conf-rst {
+ pins = "EMMC_RSTB";
+ drive-strength = <MTK_DRIVE_4mA>;
+ bias-pull-up = <MTK_PUPD_SET_R1R0_01>;
+ };
+ };
+
+ 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 = <MTK_DRIVE_8mA>;
+ bias-pull-up = <MTK_PUPD_SET_R1R0_11>;
+ };
+
+ conf-pd {
+ pins = "SPI2_CLK", "SPI2_MOSI", "SPI2_MISO";
+ drive-strength = <MTK_DRIVE_8mA>;
+ bias-pull-down = <MTK_PUPD_SET_R1R0_11>;
+ };
+ };
+
+ 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
@@ -0,0 +1,559 @@
From a1ac4c9c4e35782afcf66ab031b4d426d81120a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
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 <troglobit@gmail.com>
---
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 <env.h>
+#include <fdt_support.h>
+#include <image.h>
+#include <asm/global_data.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+
+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 0x500x5b.
+ */
+
+/*
+ * 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 <image.h>
+#include <linux/string.h>
+
+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
@@ -0,0 +1,34 @@
From e09f64e56b1f10dd4ad6f066b6c05a604daf80e5 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <yangshiji66@outlook.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
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
@@ -0,0 +1,710 @@
From 92ed8c8fbbacab3c2e98c65008b3c314f609fd00 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <troglobit@gmail.com>
---
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 <sam.shih@mediatek.com>
+ *
+ * BananaPi BPI-R64 - SPI NAND flash variant
+ */
+
+/dts-v1/;
+#include <dt-bindings/input/linux-event-codes.h>
+#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 = &eth;
+ };
+
+ 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 = <KEY_RESTART>;
+ };
+
+ wps {
+ label = "wps";
+ gpios = <&gpio 102 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_WPS_BUTTON>;
+ };
+ };
+
+ 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 = <&reg_3p3v>;
+ vqmmc-supply = <&reg_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 = <&reg_3p3v>;
+ vqmmc-supply = <&reg_3p3v>;
+};
+
+&watchdog {
+ pinctrl-names = "default";
+ pinctrl-0 = <&watchdog_pins>;
+ status = "okay";
+};
+
+&eth {
+ 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 <dt-bindings/input/linux-event-codes.h>
#include "mt7622.dtsi"
#include "mt7622-u-boot.dtsi"
@@ -20,6 +21,7 @@
aliases {
spi0 = &snfi;
+ ethernet0 = &eth;
};
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 = <KEY_RESTART>;
+ };
+
+ wps {
+ label = "wps";
+ gpios = <&gpio 102 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_WPS_BUTTON>;
+ };
+ };
+
+ 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 = <&reg_3p3v>;
vqmmc-supply = <&reg_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 = <&reg_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
@@ -0,0 +1,784 @@
From 8b3d206ff8c398de5290a1c4237c841389655505 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <troglobit@gmail.com>
---
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 = <&reg_3p3v>;
+ vqmmc-supply = <&reg_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 <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/linux-event-codes.h>
+
+/ {
+ 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 = <KEY_RESTART>;
+ };
+ };
+
+ 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>;
+ };
+ };
+ };
+};
+
+&eth0 {
+ 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 = <MTK_DRIVE_4mA>;
+ mediatek,pull-up-adv = <1>;
+ };
+
+ conf-clk {
+ pins = "EMMC_CK";
+ drive-strength = <MTK_DRIVE_6mA>;
+ mediatek,pull-down-adv = <2>;
+ };
+
+ conf-dsl {
+ pins = "EMMC_DSL";
+ mediatek,pull-down-adv = <2>;
+ };
+
+ conf-rst {
+ pins = "EMMC_RSTB";
+ drive-strength = <MTK_DRIVE_4mA>;
+ 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 = <&reg_3p3v>;
+ vqmmc-supply = <&reg_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 <sam.shih@mediatek.com>
*/
+#include <env.h>
+#include <asm/io.h>
+#include <linux/libfdt.h>
+#include <linux/string.h>
+#include <fdt_support.h>
+#include <asm/global_data.h>
+
+#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
+7
View File
@@ -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"