diff --git a/board/aarch64/bananapi-bpi-r3/Config.in b/board/aarch64/bananapi-bpi-r3/Config.in index c7cccf3fc..4801e6eff 100644 --- a/board/aarch64/bananapi-bpi-r3/Config.in +++ b/board/aarch64/bananapi-bpi-r3/Config.in @@ -5,6 +5,7 @@ config BR2_PACKAGE_BANANAPI_BPI_R3 select BR2_PACKAGE_LINUX_FIRMWARE select BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK select BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7986 + select BR2_PACKAGE_LINUX_FIRMWARE_AIROHA_EN8811H select SDCARD_AUX help Build Banana PI R3 support diff --git a/board/aarch64/bananapi-bpi-r3/README.md b/board/aarch64/bananapi-bpi-r3/README.md index 6d503b36e..fa1a95379 100644 --- a/board/aarch64/bananapi-bpi-r3/README.md +++ b/board/aarch64/bananapi-bpi-r3/README.md @@ -1,20 +1,29 @@ -# Banana Pi BPI-R3 +# Banana Pi BPI-R3 / BPI-R3 Mini The board ## Overview -The Banana Pi R3 is a high-performance networking board with full Infix -support for all enabled features including switched Ethernet ports, WiFi, -and SFP interfaces. +The Banana Pi BPI-R3 and BPI-R3 Mini are high-performance networking +boards with full Infix support for all enabled features including +Ethernet, WiFi, and SFP interfaces. + +Both boards share the same SoC and most peripherals. The key +differences are: + +| Feature | BPI-R3 | BPI-R3 Mini | +|-----------------|--------------------|-------------------------| +| Ethernet switch | Yes (4x LAN + WAN) | No (WAN/LAN ports only) | +| SD card slot | Yes | No | ### Hardware Features - MediaTek MT7986 ARM Cortex-A53 quad-core processor @ 2.0 GHz - 2 GB DDR4 RAM -- 8 GB eMMC storage + microSD card slot -- 5x Gigabit Ethernet ports (4x LAN, 1x WAN) -- 2x SFP cages for fiber connectivity (1G/2.5G) +- 8 GB eMMC storage +- microSD card slot (BPI-R3 only) +- 5x Gigabit Ethernet ports with switch core (BPI-R3 only) +- 2x SFP cages for fiber connectivity (1G/2.5G) (BPI-R3 only) - Dual-band WiFi (2.4 GHz + 5 GHz) - USB 3.0 port - Mini PCIe slot @@ -23,16 +32,16 @@ and SFP interfaces. Infix comes preconfigured with: -- **LAN ports** (lan1-lan4): Bridged for internal networking +- **LAN ports** (lan1-lan4): Bridged for internal networking (BPI-R3 only) - **WAN port**: DHCP client enabled for internet connectivity -- **SFP ports** (sfp1, sfp2): Available for configuration +- **SFP ports** (sfp1, sfp2): Available for configuration (BPI-R3 only) - **WiFi interfaces** (wifi0, wifi1): Available for configuration ## Getting Started -### Quick Start with SD Card +### BPI-R3: Quick Start with SD Card -The easiest way to get started is using an SD card: +The easiest way to get started with the BPI-R3 is using an SD card: > [!NOTE] > SD card boot works but we have observed stability issues. For production @@ -49,6 +58,12 @@ The easiest way to get started is using an SD card: - Connect to LAN port or console (115200 8N1) - Default login: `admin` / `admin` +### BPI-R3 Mini: Getting Started + +The BPI-R3 Mini does not have an SD card slot, so you must install +directly to eMMC. See [Installing to eMMC — BPI-R3 +Mini](#bpi-r3-mini) below. + ### Boot Switch Reference The BPI-R3 has a 4-position DIP switch that controls boot media: @@ -62,60 +77,44 @@ The BPI-R3 has a 4-position DIP switch that controls boot media: | 1010 | SPI NAND | Boot from SPI NAND (advanced users) | > [!NOTE] -> Switch position is read from left to right: "0" = OFF, "1" = ON. +> Switch position is read from left to right: "0" = OFF, "1" = ON. > When the DIP switch is in the "UP" position it is OFF(0). -## Advanced: Installing to eMMC +## Installing to eMMC For production deployments or better performance, you can install Infix -to the internal eMMC storage. This is more complex but provides faster -boot times and eliminates the external SD card. +to the internal eMMC storage. > [!IMPORTANT] -> While Infix boots on both SD card and eMMC, we have observed stability -> issues with SD cards on this platform. **eMMC is recommended** for -> reliable operation. - -### Why Use eMMC? - -**Advantages:** - -- More reliable than SD card (stability issues observed with SD cards) -- Faster boot and better performance -- No external SD card to manage -- More robust for industrial/embedded deployments - -**Disadvantages:** - -- More complex installation process -- Requires intermediate NAND boot step -- Harder to recover from errors +> While Infix boots on both SD card and eMMC on the BPI-R3, we have +> observed stability issues with SD cards on this platform. **eMMC is +> recommended** for reliable operation. ### Prerequisites - FTDI USB-to-serial cable (3.3V) for console access -- microSD card with Infix (for initial boot) - USB flash drive (FAT32 formatted) +- microSD card with Infix, for initial boot (BPI-R3 only) - Downloaded files (see below) ### Required Files Download and place these files on a FAT32-formatted USB drive: -1. **Intermediate NAND bootloader** (from Frank-W's U-Boot): - - [bpi-r3_spim-nand_bl2.img][5] (BL2 loader) - - [bpi-r3_spim-nand_fip.bin][6] (FIP image) -2. **Infix eMMC image:** +1. **Infix eMMC image:** - [infix-bpi-r3-emmc.img][3] (Complete system image) -3. **eMMC bootloader** (extracted from): +2. **eMMC bootloader** (extracted from): - [bpi-r3-emmc-boot-2025.01-latest.tar.gz][4] - Extract `bl2.img` from the tarball to your USB drive +3. **Intermediate NAND bootloader** (BPI-R3 only, from Frank-W's U-Boot): + - [bpi-r3_spim-nand_bl2.img][5] (BL2 loader) + - [bpi-r3_spim-nand_fip.bin][6] (FIP image) > [!WARNING] > The following process involves multiple boot mode changes. Take your -> time verify each step carefully. +> time and verify each step carefully. -### Installation Steps +### BPI-R3 #### Step 1: Boot from SD card @@ -177,13 +176,48 @@ mmc partconf 0 1 1 0 Your BPI-R3 should now boot Infix from internal eMMC storage! +### BPI-R3 Mini + +The BPI-R3 Mini does not have an SD card slot, so you have to go +through the factory-installed Linux (OpenWRT) to flash the Infix +bootloader and OS to eMMC. + +#### Step 1: Boot from NAND (factory default) + +BPI-R3 Mini NAND boot switch position + +1. Ensure boot switches are set to NAND mode (factory default) +2. Place `infix-bpi-r3-emmc.img` and `bl2.img` on a USB drive +3. Power on and boot into Linux +4. Mount the USB drive to `/mnt` + +#### Step 2: Flash Infix to eMMC + +From the Linux shell: + +``` +echo 0 > /proc/sys/kernel/printk +dd if=/mnt/infix-bpi-r3-emmc.img of=/dev/mmcblk0 +echo 0 > /sys/block/mmcblk0boot0/force_ro +dd if=/mnt/bl2.img of=/dev/mmcblk0boot0 +sync +``` + +#### Step 3: Boot from eMMC + +BPI-R3 Mini eMMC boot switch position + +1. Power off the board +2. Set boot switches to eMMC mode (see image above) +3. Power on + ## Troubleshooting ### Board won't boot - Verify boot switch positions (check twice!) - Ensure power supply provides adequate current (12V/2A recommended) -- Try booting from SD card with switches at **0000** +- Try booting from SD card with switches at **0000** (BPI-R3 only) ### Can't break into U-Boot @@ -195,9 +229,9 @@ Your BPI-R3 should now boot Infix from internal eMMC storage! - Boot from NAND (**1010**) and verify eMMC image was written - Check USB drive contents - ensure all files are present -- Re-run Step 5 (eMMC boot configuration) +- Re-run the eMMC boot configuration step -### Reverting to SD card +### Reverting to SD card (BPI-R3 only) Simply set boot switches back to **0000** and boot from SD card. The eMMC installation does not affect SD card functionality. diff --git a/board/aarch64/bananapi-bpi-r3/bananapi-bpi-r3.mk b/board/aarch64/bananapi-bpi-r3/bananapi-bpi-r3.mk index 93b20786c..14caf570c 100644 --- a/board/aarch64/bananapi-bpi-r3/bananapi-bpi-r3.mk +++ b/board/aarch64/bananapi-bpi-r3/bananapi-bpi-r3.mk @@ -9,6 +9,7 @@ define BANANAPI_BPI_R3_LINUX_CONFIG_FIXUPS $(call KCONFIG_ENABLE_OPT,CONFIG_MEDIATEK_WATCHDOG) $(call KCONFIG_ENABLE_OPT,CONFIG_MEDIATEK_GE_PHY) $(call KCONFIG_ENABLE_OPT,CONFIG_REALTEK_PHY) + $(call KCONFIG_SET_OPT,CONFIG_AIR_EN8811H_PHY,m) $(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) diff --git a/board/aarch64/bananapi-bpi-r3/dts/Makefile b/board/aarch64/bananapi-bpi-r3/dts/Makefile index 974394efb..aa34a9795 100644 --- a/board/aarch64/bananapi-bpi-r3/dts/Makefile +++ b/board/aarch64/bananapi-bpi-r3/dts/Makefile @@ -1 +1 @@ -dtb-y += mediatek/mt7986a-bananapi-bpi-r3-sd.dtb mediatek/mt7986a-bananapi-bpi-r3-emmc.dtb +dtb-y += mediatek/mt7986a-bananapi-bpi-r3-sd.dtb mediatek/mt7986a-bananapi-bpi-r3-emmc.dtb mediatek/mt7986a-bananapi-bpi-r3-mini.dtb diff --git a/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-emmc.dts b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-emmc.dts index dafbe2451..573285c8c 100644 --- a/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-emmc.dts +++ b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-emmc.dts @@ -1,2 +1,4 @@ +#include + #include "mt7986a-bananapi-bpi-r3.dtsi" #include "mt7986a-bananapi-bpi-r3-emmc.dtsi" diff --git a/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-mini.dts b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-mini.dts new file mode 100644 index 000000000..bcb323e6b --- /dev/null +++ b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-mini.dts @@ -0,0 +1,12 @@ +#include + +#include "mt7986a-bananapi-bpi-r3.dtsi" +#include "mt7986a-bananapi-bpi-r3-emmc.dtsi" + +&phy0 { + compatible = "ethernet-phy-id03a2.a411"; +}; +&phy1 { + compatible = "ethernet-phy-id03a2.a411"; +}; + diff --git a/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-mini.dtsi b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-mini.dtsi new file mode 100644 index 000000000..dafbe2451 --- /dev/null +++ b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-mini.dtsi @@ -0,0 +1,2 @@ +#include "mt7986a-bananapi-bpi-r3.dtsi" +#include "mt7986a-bananapi-bpi-r3-emmc.dtsi" diff --git a/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-sd.dts b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-sd.dts index 725f7b4e2..640586fc7 100644 --- a/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-sd.dts +++ b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3-sd.dts @@ -1,2 +1,4 @@ +#include + #include "mt7986a-bananapi-bpi-r3.dtsi" #include "mt7986a-bananapi-bpi-r3-sd.dtsi" diff --git a/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3.dtsi b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3.dtsi index f4637d603..355afd163 100644 --- a/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3.dtsi +++ b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3.dtsi @@ -1,5 +1,3 @@ -#include - / { chosen { infix { diff --git a/board/aarch64/bananapi-bpi-r3/genimage.cfg.in b/board/aarch64/bananapi-bpi-r3/genimage.cfg.in index cbb345f69..f9fc6fccf 100644 --- a/board/aarch64/bananapi-bpi-r3/genimage.cfg.in +++ b/board/aarch64/bananapi-bpi-r3/genimage.cfg.in @@ -51,6 +51,11 @@ image #INFIX_ID##VERSION#-bpi-r3-#TARGET#.img { size = 4096s } + partition en8811h_fw { + size = 10M + image = "en8811h-fw.bin" + } + partition aux { partition-uuid = D4EF35A0-0652-45A1-B3DE-D63339C82035 image = "aux.ext4" diff --git a/board/aarch64/bananapi-bpi-r3/r3mini_emmcboot.png b/board/aarch64/bananapi-bpi-r3/r3mini_emmcboot.png new file mode 100644 index 000000000..05a7a2c3f Binary files /dev/null and b/board/aarch64/bananapi-bpi-r3/r3mini_emmcboot.png differ diff --git a/board/aarch64/bananapi-bpi-r3/r3mini_nandboot2.png b/board/aarch64/bananapi-bpi-r3/r3mini_nandboot2.png new file mode 100644 index 000000000..cbc5796a4 Binary files /dev/null and b/board/aarch64/bananapi-bpi-r3/r3mini_nandboot2.png differ diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg new file mode 100644 index 000000000..19068393f --- /dev/null +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg @@ -0,0 +1,445 @@ +{ + "ieee802-dot1ab-lldp:lldp": { + "infix-lldp:enabled": true + }, + "ietf-hardware:hardware": { + "component": [ + { + "name": "USB", + "class": "infix-hardware:usb", + "state": { + "admin-state": "unlocked" + } + }, + { + "class": "infix-hardware:usb", + "name": "USB2", + "state": { + "admin-state": "unlocked" + } + }, + { + "name": "radio0", + "class": "infix-hardware:wifi", + "infix-hardware:wifi-radio": { + "country-code": "DE", + "band": "2.4GHz", + "channel": "auto" + } + }, + { + "name": "radio1", + "class": "infix-hardware:wifi", + "infix-hardware:wifi-radio": { + "country-code": "DE", + "band": "5GHz", + "channel": "auto" + } + } + ] + }, + "ietf-interfaces:interfaces": { + "interface": [ + { + "name": "br0", + "type": "infix-if-type:bridge", + "ietf-ip:ipv4": { + "address": [ + { + "ip": "192.168.0.1", + "prefix-length": 24 + } + ] + } + }, + { + "name": "lan", + "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" + }, + { + "id": "dns-server" + }, + { + "id": "router" + }, + { + "id": "netmask" + }, + { + "id": "vendor-class", + "value": "Banana Pi BPI-R3" + } + ] + } + }, + "ietf-ip:ipv6": { + "infix-dhcpv6-client:dhcp": { + "option": [ + { + "id": "ntp-server" + }, + { + "id": "client-fqdn" + }, + { + "id": "domain-search" + }, + { + "id": "dns-server" + } + ] + } + } + }, + { + "name": "wifi0-ap", + "type": "infix-if-type:wifi", + "infix-interfaces:wifi": { + "radio": "radio0", + "access-point": { + "ssid": "Infix", + "security": { + "secret": "wifi" + } + } + }, + "infix-interfaces:bridge-port": { + "bridge": "br0" + } + }, + { + "name": "wifi1-ap", + "type": "infix-if-type:wifi", + "infix-interfaces:wifi": { + "radio": "radio1", + "access-point": { + "ssid": "Infix5Ghz", + "security": { + "secret": "wifi" + } + } + }, + "infix-interfaces:bridge-port": { + "bridge": "br0" + } + } + ] + }, + "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": {} + } + ] + }, + "symmetric-keys": { + "symmetric-key": [ + { + "name": "wifi", + "cleartext-symmetric-key": "aW5maXhpbmZpeA==", + "key-format": "infix-crypto-types:passphrase-key-format" + } + ] + } + }, + "ietf-netconf-acm:nacm": { + "enable-nacm": true, + "read-default": "permit", + "write-default": "permit", + "exec-default": "permit", + "groups": { + "group": [ + { + "name": "admin", + "user-name": [ + "admin" + ] + }, + { + "name": "operator", + "user-name": [] + }, + { + "name": "guest", + "user-name": [] + } + ] + }, + "rule-list": [ + { + "name": "admin-acl", + "group": [ + "admin" + ], + "rule": [ + { + "name": "permit-all", + "module-name": "*", + "access-operations": "*", + "action": "permit", + "comment": "Allow 'admin' group complete access to all operations and data." + } + ] + }, + { + "name": "operator-acl", + "group": [ + "operator" + ], + "rule": [ + { + "name": "permit-system-rpcs", + "module-name": "ietf-system", + "rpc-name": "*", + "access-operations": "exec", + "action": "permit", + "comment": "Operators can reboot, shutdown, and set system time." + } + ] + }, + { + "name": "guest-acl", + "group": [ + "guest" + ], + "rule": [ + { + "name": "deny-all-write+exec", + "module-name": "*", + "access-operations": "create update delete exec", + "action": "deny", + "comment": "Guests cannot change anything or exec rpcs." + } + ] + }, + { + "name": "default-deny-all", + "group": [ + "*" + ], + "rule": [ + { + "name": "deny-password-access", + "path": "/ietf-system:system/authentication/user/password", + "access-operations": "*", + "action": "deny", + "comment": "No user except admins can access password hashes." + }, + { + "name": "deny-keystore-access", + "module-name": "ietf-keystore", + "access-operations": "*", + "action": "deny", + "comment": "No user except admins can access cryptographic keys." + }, + { + "name": "deny-truststore-access", + "module-name": "ietf-truststore", + "access-operations": "*", + "action": "deny", + "comment": "No user except admins can access trust store." + } + ] + } + ] + }, + "ietf-netconf-server:netconf-server": { + "listen": { + "endpoints": { + "endpoint": [ + { + "name": "default-ssh", + "ssh": { + "tcp-server-parameters": { + "local-bind": [ + { + "local-address": "::" + } + ] + }, + "ssh-server-parameters": { + "server-identity": { + "host-key": [ + { + "name": "default-key", + "public-key": { + "central-keystore-reference": "genkey" + } + } + ] + } + } + } + } + ] + } + } + }, + "ietf-system:system": { + "hostname": "bpi-%m", + "ntp": { + "server": [ + { + "name": "default", + "udp": { + "address": "pool.ntp.org" + } + } + ] + }, + "authentication": { + "user": [ + { + "name": "admin", + "password": "$factory$", + "infix-system:shell": "bash" + } + ] + }, + "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo=" + }, + "infix-dhcp-server:dhcp-server": { + "option": [ + { + "id": "ntp-server", + "address": "auto" + }, + { + "id": "dns-server", + "address": "auto" + }, + { + "id": "router", + "address": "auto" + } + ], + "subnet": [ + { + "subnet": "192.168.0.0/24", + "pool": { + "start-address": "192.168.0.100", + "end-address": "192.168.0.250" + } + } + ] + }, + "infix-firewall:firewall": { + "default": "wan", + "zone": [ + { + "name": "lan", + "action": "accept", + "interface": [ + "br0" + ] + }, + { + "name": "wan", + "action": "drop", + "interface": [ + "wan" + ], + "service": [ + "dhcpv6-client" + ] + } + ], + "policy": [ + { + "name": "lan-to-wan", + "action": "accept", + "ingress": [ + "lan" + ], + "egress": [ + "wan" + ], + "masquerade": true + } + ] + }, + "infix-meta:meta": { + "version": "1.7" + }, + "infix-services:mdns": { + "enabled": true + }, + "infix-services:ssh": { + "enabled": true, + "hostkey": [ + "genkey" + ], + "listen": [ + { + "name": "ipv4", + "address": "0.0.0.0", + "port": 22 + }, + { + "name": "ipv6", + "address": "::", + "port": 22 + } + ] + }, + "infix-services:web": { + "enabled": true, + "console": { + "enabled": true + }, + "netbrowse": { + "enabled": true + }, + "restconf": { + "enabled": true + } + } +} diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/udev/rules.d/90-bpi-r3-mini-rename-ethernet.rules b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/udev/rules.d/90-bpi-r3-mini-rename-ethernet.rules new file mode 100644 index 000000000..23366a8e4 --- /dev/null +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/udev/rules.d/90-bpi-r3-mini-rename-ethernet.rules @@ -0,0 +1,2 @@ +ACTION=="add", SUBSYSTEM=="net", DEVPATH=="/devices/platform/soc/15100000.ethernet/net/eth0", NAME="lan" +ACTION=="add", SUBSYSTEM=="net", DEVPATH=="/devices/platform/soc/15100000.ethernet/net/eth1", NAME="wan" diff --git a/board/aarch64/bananapi-bpi-r3/uboot/extras.config b/board/aarch64/bananapi-bpi-r3/uboot/extras.config index 70fa5ab64..364c6c92b 100644 --- a/board/aarch64/bananapi-bpi-r3/uboot/extras.config +++ b/board/aarch64/bananapi-bpi-r3/uboot/extras.config @@ -3,6 +3,9 @@ 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="mt7986a-bpi-r3-sd mt7986a-bpi-r3-emmc mt7986a-bpi-r3-mini" + CONFIG_USB=y CONFIG_USB_XHCI_HCD=y CONFIG_USB_XHCI_MTK=y @@ -26,8 +29,17 @@ CONFIG_MTD_SPI_NAND=y CONFIG_MTK_SPIM=y CONFIG_MTK_SNOR=y +CONFIG_DM_MDIO=y +CONFIG_DM_ETH_PHY=y +CONFIG_PHY_ETHERNET_ID=y + +CONFIG_PHY_AIROHA=y +CONFIG_PHY_AIROHA_EN8811H=y +CONFIG_PHY_AIROHA_FW_IN_MMC=y + CONFIG_CMD_SF=y CONFIG_CMD_USB=y CONFIG_CMD_MTD=y +CONFIG_CMD_MDIO=y CONFIG_CMD_MTDPARTS=y CONFIG_CMD_DM=y diff --git a/board/aarch64/bananapi-bpi-r3/uboot/mt7986-env.dtsi b/board/aarch64/bananapi-bpi-r3/uboot/mt7986-env.dtsi index 20a532f22..6964340f8 100644 --- a/board/aarch64/bananapi-bpi-r3/uboot/mt7986-env.dtsi +++ b/board/aarch64/bananapi-bpi-r3/uboot/mt7986-env.dtsi @@ -15,6 +15,10 @@ scriptaddr = "0x48000000"; ramdisk_addr_r = "0x4A000000"; + en8811h_fw_part = "0#en8811h_fw"; + en8811h_fw_dm_dir = "EthMD32.dm.bin"; + en8811h_fw_dsp_dir = "EthMD32.DSP.bin"; + /* This is a development platform, keep * developer mode statically enabled. */ diff --git a/configs/bpi_r3_emmc_boot_defconfig b/configs/bpi_r3_emmc_boot_defconfig index 184febd2c..387ed369e 100644 --- a/configs/bpi_r3_emmc_boot_defconfig +++ b/configs/bpi_r3_emmc_boot_defconfig @@ -11,6 +11,8 @@ BR2_SSP_NONE=y BR2_INIT_NONE=y BR2_SYSTEM_BIN_SH_NONE=y # BR2_PACKAGE_BUSYBOX is not set +BR2_PACKAGE_LINUX_FIRMWARE=y +BR2_PACKAGE_LINUX_FIRMWARE_AIROHA_EN8811H=y # BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set # BR2_TARGET_ROOTFS_TAR is not set BR2_TARGET_ARM_TRUSTED_FIRMWARE=y diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index e32f763fc..2d55e23a8 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -12,11 +12,16 @@ All notable changes to the project are documented in this file. - Upgrade Buildroot to 2025.02.11 (LTS) - Add support for Microchip SAMA7G54-EK Evaluation Kit, Arm Cortex-A7 - Add GPS/GNSS receiver support with NTP reference clock integration +- Add support for [Banana Pi R3 Mini][BPI-R3-MINI], a 2 port router with 2 WiFi chip, + uses the same bootloader as BPI-R3 (eMMC-version) ### Fixes N/A +[BPI-R3-MINI]: https://wiki.banana-pi.org/Banana_Pi_BPI-R3_Mini + + [v26.01.0][] - 2026-02-03 ------------------------- diff --git a/patches/uboot/2025.01/0001-hush-Remove-Ctrl-C-detection-in-loops.patch b/patches/uboot/2025.01/0001-hush-Remove-Ctrl-C-detection-in-loops.patch index 0e1c08801..ce9970206 100644 --- a/patches/uboot/2025.01/0001-hush-Remove-Ctrl-C-detection-in-loops.patch +++ b/patches/uboot/2025.01/0001-hush-Remove-Ctrl-C-detection-in-loops.patch @@ -1,8 +1,11 @@ -From 989ef8436df2ee48d308981098c44b1b8257c23b Mon Sep 17 00:00:00 2001 +From 9a9885324c70d607c9c79814441dbc829e212e41 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Mon, 10 Jun 2024 13:25:31 +0200 -Subject: [PATCH 8/8] hush: Remove Ctrl-C detection in loops -Organization: Addiva Elektronik +Subject: [PATCH 1/7] hush: Remove Ctrl-C detection in loops +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +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 @@ -14,15 +17,16 @@ Disable this behavior and delegate the problem of loop termination to the writer of the script instead. Signed-off-by: Tobias Waldekranz +Signed-off-by: Mattias Walström --- common/cli_hush.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/common/cli_hush.c b/common/cli_hush.c -index 171069f5f4..d6d487893f 100644 +index a6a8edce1f4..0711e05a779 100644 --- a/common/cli_hush.c +++ b/common/cli_hush.c -@@ -1796,13 +1796,6 @@ static int run_list_real(struct pipe *pi) +@@ -1792,13 +1792,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) { @@ -37,5 +41,5 @@ index 171069f5f4..d6d487893f 100644 if (!rpipe) { flag_rep = 0; -- -2.34.1 +2.43.0 diff --git a/patches/uboot/2025.01/0002-add-rpidisplay-check.patch b/patches/uboot/2025.01/0002-cmd-new-command-rpidisplay.patch similarity index 86% rename from patches/uboot/2025.01/0002-add-rpidisplay-check.patch rename to patches/uboot/2025.01/0002-cmd-new-command-rpidisplay.patch index 5629d150f..9594f4433 100644 --- a/patches/uboot/2025.01/0002-add-rpidisplay-check.patch +++ b/patches/uboot/2025.01/0002-cmd-new-command-rpidisplay.patch @@ -1,3 +1,20 @@ +From 7efc3aa61fbc84b2c0c959e737257d2de2e8020c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Sat, 6 Sep 2025 22:18:27 +0200 +Subject: [PATCH 2/7] 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 +--- + 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 1d7ddb4ed36..8572dc4353e 100644 --- a/cmd/Kconfig @@ -130,3 +147,6 @@ index 00000000000..07abdc08552 + "Returns: 0=connected, 1=not detected, 2=no video\n" + "Usage: if testrpidisplay; then echo DSI found; fi" +); +-- +2.43.0 + diff --git a/patches/uboot/2025.01/0003-arm-dts-at91-sama7g5ek-increase-clock-for-sdmmc-from.patch b/patches/uboot/2025.01/0003-arm-dts-at91-sama7g5ek-increase-clock-for-sdmmc-from.patch new file mode 100644 index 000000000..0e96b9214 --- /dev/null +++ b/patches/uboot/2025.01/0003-arm-dts-at91-sama7g5ek-increase-clock-for-sdmmc-from.patch @@ -0,0 +1,48 @@ +From c646eef3ee304ae49ad7e3f998ed08bfa0efb7b9 Mon Sep 17 00:00:00 2001 +From: Mihai Sain +Date: Fri, 5 May 2023 13:28:31 +0300 +Subject: [PATCH 3/7] arm: dts: at91: sama7g5ek: increase clock for sdmmc from + 25 MHz to 50 MHz +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Current clock for sdmmc0 and sdmmc1 is 25 MHz because of the caps forced +by the mainline kernel. + +Remove the caps in order to increase the clock to 50 MHz. This will +improve the boot time when reading the kernel binary. Tested on +sama7g5ek rev 5 using mmcinfo command. + +Signed-off-by: Mihai Sain +Signed-off-by: Mattias Walström +--- + arch/arm/dts/at91-sama7g5ek.dts | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/arch/arm/dts/at91-sama7g5ek.dts b/arch/arm/dts/at91-sama7g5ek.dts +index 9b247fcaf66..5bbbdd8ed76 100644 +--- a/arch/arm/dts/at91-sama7g5ek.dts ++++ b/arch/arm/dts/at91-sama7g5ek.dts +@@ -776,8 +776,6 @@ + &sdmmc0 { + bus-width = <8>; + non-removable; +- no-1-8-v; +- sdhci-caps-mask = <0x0 0x00200000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_sdmmc0_default>; + status = "okay"; +@@ -785,8 +783,6 @@ + + &sdmmc1 { + bus-width = <4>; +- no-1-8-v; +- sdhci-caps-mask = <0x0 0x00200000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_sdmmc1_default>; + status = "okay"; +-- +2.43.0 + diff --git a/patches/uboot/2025.01/0004-arm-dts-at91-sama7g5ek-supports-high-speed-on-mmc0-e.patch b/patches/uboot/2025.01/0004-arm-dts-at91-sama7g5ek-supports-high-speed-on-mmc0-e.patch new file mode 100644 index 000000000..6bbef0563 --- /dev/null +++ b/patches/uboot/2025.01/0004-arm-dts-at91-sama7g5ek-supports-high-speed-on-mmc0-e.patch @@ -0,0 +1,37 @@ +From 5a530e4eebf64887fd6b449dd45df04eccd07cad Mon Sep 17 00:00:00 2001 +From: Joachim Wiberg +Date: Thu, 12 Feb 2026 10:00:02 +0100 +Subject: [PATCH 4/7] arm: dts: at91: sama7g5ek supports high-speed on mmc0 + (eMMC) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +- eMMC high-speed timing is supported +- eMMC hardware reset is supported + +Tested on sama7g5ek rev 5. + +Signed-off-by: Joachim Wiberg +Signed-off-by: Mattias Walström +--- + arch/arm/dts/at91-sama7g5ek.dts | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/arch/arm/dts/at91-sama7g5ek.dts b/arch/arm/dts/at91-sama7g5ek.dts +index 5bbbdd8ed76..d2d16bd5f1f 100644 +--- a/arch/arm/dts/at91-sama7g5ek.dts ++++ b/arch/arm/dts/at91-sama7g5ek.dts +@@ -776,6 +776,8 @@ + &sdmmc0 { + bus-width = <8>; + non-removable; ++ cap-mmc-highspeed; ++ cap-mmc-hw-reset; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_sdmmc0_default>; + status = "okay"; +-- +2.43.0 + diff --git a/patches/uboot/2025.01/0005-net-phy-Add-the-Airoha-EN8811H-PHY-driver.patch b/patches/uboot/2025.01/0005-net-phy-Add-the-Airoha-EN8811H-PHY-driver.patch new file mode 100644 index 000000000..81e8eccc5 --- /dev/null +++ b/patches/uboot/2025.01/0005-net-phy-Add-the-Airoha-EN8811H-PHY-driver.patch @@ -0,0 +1,980 @@ +From be250151366460ee318ca47bb15ec23e5e322a84 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Wed, 18 Feb 2026 17:00:09 +0100 +Subject: [PATCH 5/7] net: phy: Add the Airoha EN8811H PHY driver +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Add the driver for the Airoha EN8811H 2.5 Gigabit PHY. The PHY supports +100/1000/2500 Mbps with auto negotiation only. + +The driver uses two firmware files, for which updated versions are added to +linux-firmware already. + +Locating the AIROHA FW within the filesystem at the designated partition +and path will trigger its automatic loading and writing to the PHY via MDIO. +If need board specific loading override, +please override the en8811h_read_fw function on board or architecture level. + +Linux upstream AIROHA EN8811H driver commit: +71e79430117d56c409c5ea485a263bc0d8083390 + +Based on the Linux upstream AIROHA EN8811H driver code(air_en8811h.c), +I have modified the relevant process to align with the U-Boot boot sequence. +and have validated this on Banana Pi BPI-R3 Mini. + +Signed-off-by: Lucien.Jheng +Signed-off-by: Mattias Walström +--- + 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 13e73810ad6..4f7451bf0c1 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 2487f366e1c..87dee3c15b9 100644 +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -11,6 +11,7 @@ obj-$(CONFIG_MV88E6352_SWITCH) += mv88e6352.o + obj-$(CONFIG_PHYLIB) += phy.o + obj-$(CONFIG_PHYLIB_10G) += generic_10g.o + obj-$(CONFIG_PHY_ADIN) += adin.o ++obj-$(CONFIG_PHY_AIROHA_EN8811H) += air_en8811h.o + obj-$(CONFIG_PHY_AQUANTIA) += aquantia.o + obj-$(CONFIG_PHY_ATHEROS) += atheros.o + obj-$(CONFIG_PHY_BROADCOM) += broadcom.o +diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c +new file mode 100644 +index 00000000000..d856d48b4a5 +--- /dev/null ++++ b/drivers/net/phy/air_en8811h.c +@@ -0,0 +1,887 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Driver for the Airoha EN8811H 2.5 Gigabit PHY. ++ * ++ * Limitations of the EN8811H: ++ * - Only full duplex supported ++ * - Forced speed (AN off) is not supported by hardware (100Mbps) ++ * ++ * Source originated from linux air_en8811h.c ++ * ++ * Copyright (C) 2025 Airoha Technology Corp. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define EN8811H_PHY_ID 0x03a2a411 ++ ++#define AIR_FW_ADDR_DM 0x00000000 ++#define AIR_FW_ADDR_DSP 0x00100000 ++ ++#define EN8811H_MD32_DM_SIZE 0x4000 ++#define EN8811H_MD32_DSP_SIZE 0x20000 ++ ++ #define EN8811H_FW_CTRL_1 0x0f0018 ++ #define EN8811H_FW_CTRL_1_START 0x0 ++ #define EN8811H_FW_CTRL_1_FINISH 0x1 ++ #define EN8811H_FW_CTRL_2 0x800000 ++ #define EN8811H_FW_CTRL_2_LOADING BIT(11) ++ ++ /* MII Registers */ ++ #define AIR_AUX_CTRL_STATUS 0x1d ++ #define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2) ++ #define AIR_AUX_CTRL_STATUS_SPEED_100 0x4 ++ #define AIR_AUX_CTRL_STATUS_SPEED_1000 0x8 ++ #define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc ++ ++#define AIR_EXT_PAGE_ACCESS 0x1f ++#define AIR_PHY_PAGE_STANDARD 0x0000 ++#define AIR_PHY_PAGE_EXTENDED_4 0x0004 ++ ++/* MII Registers Page 4*/ ++#define AIR_BPBUS_MODE 0x10 ++#define AIR_BPBUS_MODE_ADDR_FIXED 0x0000 ++#define AIR_BPBUS_MODE_ADDR_INCR BIT(15) ++#define AIR_BPBUS_WR_ADDR_HIGH 0x11 ++#define AIR_BPBUS_WR_ADDR_LOW 0x12 ++#define AIR_BPBUS_WR_DATA_HIGH 0x13 ++#define AIR_BPBUS_WR_DATA_LOW 0x14 ++#define AIR_BPBUS_RD_ADDR_HIGH 0x15 ++#define AIR_BPBUS_RD_ADDR_LOW 0x16 ++#define AIR_BPBUS_RD_DATA_HIGH 0x17 ++#define AIR_BPBUS_RD_DATA_LOW 0x18 ++ ++/* Registers on MDIO_MMD_VEND1 */ ++#define EN8811H_PHY_FW_STATUS 0x8009 ++#define EN8811H_PHY_READY 0x02 ++ ++/* Registers on MDIO_MMD_VEND2 */ ++#define AIR_PHY_LED_BCR 0x021 ++#define AIR_PHY_LED_BCR_MODE_MASK GENMASK(1, 0) ++#define AIR_PHY_LED_BCR_TIME_TEST BIT(2) ++#define AIR_PHY_LED_BCR_CLK_EN BIT(3) ++#define AIR_PHY_LED_BCR_EXT_CTRL BIT(15) ++ ++#define AIR_PHY_LED_DUR_ON 0x022 ++ ++#define AIR_PHY_LED_DUR_BLINK 0x023 ++ ++#define AIR_PHY_LED_ON(i) (0x024 + ((i) * 2)) ++#define AIR_PHY_LED_ON_MASK (GENMASK(6, 0) | BIT(8)) ++#define AIR_PHY_LED_ON_LINK1000 BIT(0) ++#define AIR_PHY_LED_ON_LINK100 BIT(1) ++#define AIR_PHY_LED_ON_LINK10 BIT(2) ++#define AIR_PHY_LED_ON_LINKDOWN BIT(3) ++#define AIR_PHY_LED_ON_FDX BIT(4) /* Full duplex */ ++#define AIR_PHY_LED_ON_HDX BIT(5) /* Half duplex */ ++#define AIR_PHY_LED_ON_FORCE_ON BIT(6) ++#define AIR_PHY_LED_ON_LINK2500 BIT(8) ++#define AIR_PHY_LED_ON_POLARITY BIT(14) ++#define AIR_PHY_LED_ON_ENABLE BIT(15) ++ ++#define AIR_PHY_LED_BLINK(i) (0x025 + ((i) * 2)) ++#define AIR_PHY_LED_BLINK_1000TX BIT(0) ++#define AIR_PHY_LED_BLINK_1000RX BIT(1) ++#define AIR_PHY_LED_BLINK_100TX BIT(2) ++#define AIR_PHY_LED_BLINK_100RX BIT(3) ++#define AIR_PHY_LED_BLINK_10TX BIT(4) ++#define AIR_PHY_LED_BLINK_10RX BIT(5) ++#define AIR_PHY_LED_BLINK_COLLISION BIT(6) ++#define AIR_PHY_LED_BLINK_RX_CRC_ERR BIT(7) ++#define AIR_PHY_LED_BLINK_RX_IDLE_ERR BIT(8) ++#define AIR_PHY_LED_BLINK_FORCE_BLINK BIT(9) ++#define AIR_PHY_LED_BLINK_2500TX BIT(10) ++#define AIR_PHY_LED_BLINK_2500RX BIT(11) ++ ++#define EN8811H_FW_VERSION 0x3b3c ++ ++#define EN8811H_POLARITY 0xca0f8 ++#define EN8811H_POLARITY_TX_NORMAL BIT(0) ++#define EN8811H_POLARITY_RX_REVERSE BIT(1) ++ ++#define EN8811H_CLK_CGM 0xcf958 ++#define EN8811H_CLK_CGM_CKO BIT(26) ++#define EN8811H_HWTRAP1 0xcf914 ++#define EN8811H_HWTRAP1_CKO BIT(12) ++ ++#define air_upper_16_bits(n) ((u16)((n) >> 16)) ++#define air_lower_16_bits(n) ((u16)((n) & 0xffff)) ++#define clear_bit(bit, bitmap) __clear_bit(bit, bitmap) ++ ++/* Led definitions */ ++#define EN8811H_LED_COUNT 3 ++ ++struct led { ++ unsigned long rules; ++ unsigned long state; ++}; ++ ++enum { ++ AIR_PHY_LED_STATE_FORCE_ON, ++ AIR_PHY_LED_STATE_FORCE_BLINK, ++}; ++ ++enum { ++ AIR_PHY_LED_DUR_BLINK_32MS, ++ AIR_PHY_LED_DUR_BLINK_64MS, ++ AIR_PHY_LED_DUR_BLINK_128MS, ++ AIR_PHY_LED_DUR_BLINK_256MS, ++ AIR_PHY_LED_DUR_BLINK_512MS, ++ AIR_PHY_LED_DUR_BLINK_1024MS, ++}; ++ ++enum { ++ AIR_LED_DISABLE, ++ AIR_LED_ENABLE, ++}; ++ ++enum { ++ AIR_ACTIVE_LOW, ++ AIR_ACTIVE_HIGH, ++}; ++ ++enum { ++ AIR_LED_MODE_DISABLE, ++ AIR_LED_MODE_USER_DEFINE, ++}; ++ ++/* Trigger specific enum */ ++enum air_led_trigger_netdev_modes { ++ AIR_TRIGGER_NETDEV_LINK = 0, ++ AIR_TRIGGER_NETDEV_LINK_10, ++ AIR_TRIGGER_NETDEV_LINK_100, ++ AIR_TRIGGER_NETDEV_LINK_1000, ++ AIR_TRIGGER_NETDEV_LINK_2500, ++ AIR_TRIGGER_NETDEV_LINK_5000, ++ AIR_TRIGGER_NETDEV_LINK_10000, ++ AIR_TRIGGER_NETDEV_HALF_DUPLEX, ++ AIR_TRIGGER_NETDEV_FULL_DUPLEX, ++ AIR_TRIGGER_NETDEV_TX, ++ AIR_TRIGGER_NETDEV_RX, ++ AIR_TRIGGER_NETDEV_TX_ERR, ++ AIR_TRIGGER_NETDEV_RX_ERR, ++ ++ /* Keep last */ ++ __AIR_TRIGGER_NETDEV_MAX, ++}; ++ ++/* Default LED setup: ++ * GPIO5 <-> LED0 On: Link detected, blink Rx/Tx ++ * GPIO4 <-> LED1 On: Link detected at 2500 and 1000 Mbps ++ * GPIO3 <-> LED2 On: Link detected at 2500 and 100 Mbps ++ */ ++#define AIR_DEFAULT_TRIGGER_LED0 (BIT(AIR_TRIGGER_NETDEV_LINK) | \ ++ BIT(AIR_TRIGGER_NETDEV_RX) | \ ++ BIT(AIR_TRIGGER_NETDEV_TX)) ++#define AIR_DEFAULT_TRIGGER_LED1 (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | \ ++ BIT(AIR_TRIGGER_NETDEV_LINK_1000)) ++#define AIR_DEFAULT_TRIGGER_LED2 (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | \ ++ BIT(AIR_TRIGGER_NETDEV_LINK_100)) ++ ++#define AIR_PHY_LED_DUR_UNIT 781 ++#define AIR_PHY_LED_DUR (AIR_PHY_LED_DUR_UNIT << AIR_PHY_LED_DUR_BLINK_64MS) ++ ++struct en8811h_priv { ++ int firmware_version; ++ bool mcu_needs_restart; ++ struct led led[EN8811H_LED_COUNT]; ++}; ++ ++static int air_phy_read_page(struct phy_device *phydev) ++{ ++ return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS); ++} ++ ++static int air_phy_write_page(struct phy_device *phydev, int page) ++{ ++ return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page); ++} ++ ++int air_phy_select_page(struct phy_device *phydev, int page) ++{ ++ int ret, oldpage; ++ ++ oldpage = air_phy_read_page(phydev); ++ if (oldpage < 0) ++ return oldpage; ++ ++ if (oldpage != page) { ++ ret = air_phy_write_page(phydev, page); ++ if (ret < 0) ++ return ret; ++ } ++ ++ return oldpage; ++} ++ ++int air_phy_restore_page(struct phy_device *phydev, int oldpage, int ret) ++{ ++ int r; ++ ++ if (oldpage < 0) ++ return oldpage; ++ ++ r = air_phy_write_page(phydev, oldpage); ++ if (ret >= 0 && r < 0) ++ ret = r; ++ ++ return ret; ++} ++ ++static int air_buckpbus_reg_write(struct phy_device *phydev, ++ u32 pbus_address, u32 pbus_data) ++{ ++ int ret, saved_page; ++ ++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); ++ if (saved_page < 0) ++ return saved_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, ++ AIR_BPBUS_MODE_ADDR_FIXED); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH, ++ air_upper_16_bits(pbus_address)); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW, ++ air_lower_16_bits(pbus_address)); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, ++ air_upper_16_bits(pbus_data)); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, ++ air_lower_16_bits(pbus_data)); ++ if (ret < 0) ++ goto restore_page; ++ ++restore_page: ++ if (ret < 0) ++ printf("%s 0x%08x failed: %d\n", __func__, ++ pbus_address, ret); ++ ++ return air_phy_restore_page(phydev, saved_page, ret); ++} ++ ++static int air_buckpbus_reg_read(struct phy_device *phydev, ++ u32 pbus_address, u32 *pbus_data) ++{ ++ int pbus_data_low, pbus_data_high; ++ int ret = 0, saved_page; ++ ++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); ++ if (saved_page < 0) ++ return saved_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, ++ AIR_BPBUS_MODE_ADDR_FIXED); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH, ++ air_upper_16_bits(pbus_address)); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW, ++ air_lower_16_bits(pbus_address)); ++ if (ret < 0) ++ goto restore_page; ++ ++ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH); ++ if (pbus_data_high < 0) { ++ ret = pbus_data_high; ++ goto restore_page; ++ } ++ ++ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW); ++ if (pbus_data_low < 0) { ++ ret = pbus_data_low; ++ goto restore_page; ++ } ++ ++ *pbus_data = pbus_data_low | (pbus_data_high << 16); ++ ++restore_page: ++ if (ret < 0) ++ printf("%s 0x%08x failed: %d\n", __func__, ++ pbus_address, ret); ++ ++ return air_phy_restore_page(phydev, saved_page, ret); ++} ++ ++static int air_buckpbus_reg_modify(struct phy_device *phydev, ++ u32 pbus_address, u32 mask, u32 set) ++{ ++ int pbus_data_low, pbus_data_high; ++ u32 pbus_data_old, pbus_data_new; ++ int ret = 0, saved_page; ++ ++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); ++ if (saved_page < 0) ++ return saved_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, ++ AIR_BPBUS_MODE_ADDR_FIXED); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH, ++ air_upper_16_bits(pbus_address)); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW, ++ air_lower_16_bits(pbus_address)); ++ if (ret < 0) ++ goto restore_page; ++ ++ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH); ++ if (pbus_data_high < 0) ++ return pbus_data_high; ++ ++ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW); ++ if (pbus_data_low < 0) ++ return pbus_data_low; ++ ++ pbus_data_old = pbus_data_low | (pbus_data_high << 16); ++ pbus_data_new = (pbus_data_old & ~mask) | set; ++ if (pbus_data_new == pbus_data_old) ++ return 0; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH, ++ air_upper_16_bits(pbus_address)); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW, ++ air_lower_16_bits(pbus_address)); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, ++ air_upper_16_bits(pbus_data_new)); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, ++ air_lower_16_bits(pbus_data_new)); ++ if (ret < 0) ++ goto restore_page; ++ ++restore_page: ++ if (ret < 0) ++ printf("%s 0x%08x failed: %d\n", __func__, ++ pbus_address, ret); ++ ++ return air_phy_restore_page(phydev, saved_page, ret); ++} ++ ++static int air_write_buf(struct phy_device *phydev, unsigned long address, ++ unsigned long array_size, const unsigned char *buffer) ++{ ++ unsigned int offset; ++ int ret, saved_page; ++ u16 val; ++ ++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); ++ if (saved_page < 0) ++ return saved_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, ++ AIR_BPBUS_MODE_ADDR_INCR); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH, ++ air_upper_16_bits(address)); ++ if (ret < 0) ++ goto restore_page; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW, ++ air_lower_16_bits(address)); ++ if (ret < 0) ++ goto restore_page; ++ ++ for (offset = 0; offset < array_size; offset += 4) { ++ val = get_unaligned_le16(&buffer[offset + 2]); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, val); ++ if (ret < 0) ++ goto restore_page; ++ ++ val = get_unaligned_le16(&buffer[offset]); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, val); ++ if (ret < 0) ++ goto restore_page; ++ } ++ ++restore_page: ++ if (ret < 0) ++ printf("%s 0x%08lx failed: %d\n", __func__, ++ address, ret); ++ ++ return air_phy_restore_page(phydev, saved_page, ret); ++} ++ ++static int en8811h_wait_mcu_ready(struct phy_device *phydev) ++{ ++ int ret, reg_value; ++ ++ /* Because of mdio-lock, may have to wait for multiple loads */ ++ ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, ++ EN8811H_PHY_FW_STATUS, reg_value, ++ reg_value == EN8811H_PHY_READY, ++ 20000, 7500000, true); ++ if (ret) { ++ printf("MCU not ready: 0x%x\n", reg_value); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++__weak int en8811h_read_fw(void **addr) ++{ ++ const char *fw_dm, *fw_dsp, *fw_part; ++ u32 ca_crc32; ++ void *buffer; ++ loff_t read; ++ int ret; ++ ++ if (!IS_ENABLED(CONFIG_PHY_AIROHA_FW_IN_MMC)) ++ return -EOPNOTSUPP; ++ ++ /* Allocate memory to store both DM and DSP firmware */ ++ buffer = malloc(EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE); ++ if (!buffer) { ++ printf("Failed to allocate memory for firmware\n"); ++ return -ENOMEM; ++ } ++ ++ /* Get the partition name where the firmware is stored */ ++ fw_part = env_get("en8811h_fw_part"); ++ if (!fw_part) { ++ printf("Error: env var en8811h_fw_part not set.\n"); ++ ret = -EINVAL; ++ goto err_free; ++ } ++ ++ /* Get the DM firmware file path */ ++ fw_dm = env_get("en8811h_fw_dm_dir"); ++ if (!fw_dm) { ++ printf("Error: env var en8811h_fw_dm_dir not set.\n"); ++ ret = -EINVAL; ++ goto err_free; ++ } ++ ++ /* Get the DSP firmware file path */ ++ fw_dsp = env_get("en8811h_fw_dsp_dir"); ++ if (!fw_dsp) { ++ printf("Error: env var en8811h_fw_dsp_dir not set.\n"); ++ ret = -EINVAL; ++ goto err_free; ++ } ++ ++ /* Load DM firmware */ ++ ret = fs_set_blk_dev("mmc", fw_part, FS_TYPE_ANY); ++ if (ret < 0) ++ goto err_free; ++ ++ /* Read DM firmware file into the start of buffer */ ++ ret = fs_read(fw_dm, (ulong)buffer, 0, EN8811H_MD32_DM_SIZE, &read); ++ if (ret < 0) { ++ printf("Failed to read DM firmware: %s\n", fw_dm); ++ goto err_free; ++ } ++ ++ /* Calculate CRC32 of DM firmware for verification */ ++ ca_crc32 = crc32(0, (unsigned char *)buffer, EN8811H_MD32_DM_SIZE); ++ debug("DM crc32: 0x%x\n", ca_crc32); ++ ++ /* Load DSP firmware */ ++ ret = fs_set_blk_dev("mmc", fw_part, FS_TYPE_ANY); ++ if (ret < 0) ++ goto err_free; ++ ++ /* Read DSP firmware file into buffer after DM section */ ++ ret = fs_read(fw_dsp, (ulong)buffer + EN8811H_MD32_DM_SIZE, 0, ++ EN8811H_MD32_DSP_SIZE, &read); ++ if (ret < 0) { ++ printf("Failed to read DSP firmware: %s\n", fw_dsp); ++ goto err_free; ++ } ++ ++ /* Calculate CRC32 of DSP firmware for verification */ ++ ca_crc32 = crc32(0, (unsigned char *)buffer + EN8811H_MD32_DM_SIZE, ++ EN8811H_MD32_DSP_SIZE); ++ debug("DSP crc32: 0x%x\n", ca_crc32); ++ ++ *addr = buffer; ++ debug("Found Airoha Firmware.\n"); ++ ++ return 0; ++ ++err_free: ++ free(buffer); ++ return ret; ++} ++ ++static int en8811h_load_firmware(struct phy_device *phydev) ++{ ++ struct en8811h_priv *priv = phydev->priv; ++ void *buffer = NULL; ++ int ret; ++ ++ if (!IS_ENABLED(CONFIG_PHY_AIROHA_FW_IN_MMC)) { ++ printf("Airoha EN8811H firmware loading not implemented\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ ret = en8811h_read_fw(&buffer); ++ if (ret < 0) ++ goto en8811h_load_firmware_out; ++ ++ ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, ++ EN8811H_FW_CTRL_1_START); ++ if (ret < 0) ++ goto en8811h_load_firmware_out; ++ ++ ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, ++ EN8811H_FW_CTRL_2_LOADING, ++ EN8811H_FW_CTRL_2_LOADING); ++ if (ret < 0) ++ goto en8811h_load_firmware_out; ++ ++ ret = air_write_buf(phydev, AIR_FW_ADDR_DM, EN8811H_MD32_DM_SIZE, ++ (unsigned char *)buffer); ++ if (ret < 0) ++ goto en8811h_load_firmware_out; ++ ++ ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, EN8811H_MD32_DSP_SIZE, ++ (unsigned char *)buffer + EN8811H_MD32_DM_SIZE); ++ if (ret < 0) ++ goto en8811h_load_firmware_out; ++ ++ ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, ++ EN8811H_FW_CTRL_2_LOADING, 0); ++ if (ret < 0) ++ goto en8811h_load_firmware_out; ++ ++ ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, ++ EN8811H_FW_CTRL_1_FINISH); ++ if (ret < 0) ++ goto en8811h_load_firmware_out; ++ ++ ret = en8811h_wait_mcu_ready(phydev); ++ ++ air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION, ++ &priv->firmware_version); ++ printf("MD32 firmware version: %08x\n", ++ priv->firmware_version); ++ ++en8811h_load_firmware_out: ++ free(buffer); ++ if (ret < 0) ++ printf("Firmware loading failed: %d\n", ret); ++ ++ return ret; ++} ++ ++static int en8811h_restart_mcu(struct phy_device *phydev) ++{ ++ int ret; ++ ++ ret = phy_write_mmd(phydev, 0x1e, 0x8009, 0x0); ++ if (ret < 0) ++ return ret; ++ ++ ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, ++ EN8811H_FW_CTRL_1_START); ++ if (ret < 0) ++ return ret; ++ ++ return air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, ++ EN8811H_FW_CTRL_1_FINISH); ++} ++ ++static int air_led_hw_control_set(struct phy_device *phydev, u8 index, ++ unsigned long rules) ++{ ++ struct en8811h_priv *priv = phydev->priv; ++ u16 on = 0, blink = 0; ++ int ret; ++ ++ if (index >= EN8811H_LED_COUNT) ++ return -EINVAL; ++ ++ priv->led[index].rules = rules; ++ ++ if (rules & BIT(AIR_TRIGGER_NETDEV_FULL_DUPLEX)) ++ on |= AIR_PHY_LED_ON_FDX; ++ ++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_10) | BIT(AIR_TRIGGER_NETDEV_LINK))) ++ on |= AIR_PHY_LED_ON_LINK10; ++ ++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_100) | BIT(AIR_TRIGGER_NETDEV_LINK))) ++ on |= AIR_PHY_LED_ON_LINK100; ++ ++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_1000) | BIT(AIR_TRIGGER_NETDEV_LINK))) ++ on |= AIR_PHY_LED_ON_LINK1000; ++ ++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | BIT(AIR_TRIGGER_NETDEV_LINK))) ++ on |= AIR_PHY_LED_ON_LINK2500; ++ ++ if (rules & BIT(AIR_TRIGGER_NETDEV_RX)) { ++ blink |= AIR_PHY_LED_BLINK_10RX | ++ AIR_PHY_LED_BLINK_100RX | ++ AIR_PHY_LED_BLINK_1000RX | ++ AIR_PHY_LED_BLINK_2500RX; ++ } ++ ++ if (rules & BIT(AIR_TRIGGER_NETDEV_TX)) { ++ blink |= AIR_PHY_LED_BLINK_10TX | ++ AIR_PHY_LED_BLINK_100TX | ++ AIR_PHY_LED_BLINK_1000TX | ++ AIR_PHY_LED_BLINK_2500TX; ++ } ++ ++ if (blink || on) { ++ /* switch hw-control on, so led-on and led-blink are off */ ++ clear_bit(AIR_PHY_LED_STATE_FORCE_ON, ++ &priv->led[index].state); ++ clear_bit(AIR_PHY_LED_STATE_FORCE_BLINK, ++ &priv->led[index].state); ++ } else { ++ priv->led[index].rules = 0; ++ } ++ ++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index), ++ AIR_PHY_LED_ON_MASK, on); ++ ++ if (ret < 0) ++ return ret; ++ ++ return phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BLINK(index), ++ blink); ++}; ++ ++static int air_led_init(struct phy_device *phydev, u8 index, u8 state, u8 pol) ++{ ++ int val = 0; ++ int err; ++ ++ if (index >= EN8811H_LED_COUNT) ++ return -EINVAL; ++ ++ if (state == AIR_LED_ENABLE) ++ val |= AIR_PHY_LED_ON_ENABLE; ++ else ++ val &= ~AIR_PHY_LED_ON_ENABLE; ++ ++ if (pol == AIR_ACTIVE_HIGH) ++ val |= AIR_PHY_LED_ON_POLARITY; ++ else ++ val &= ~AIR_PHY_LED_ON_POLARITY; ++ ++ err = phy_write_mmd(phydev, 0x1f, AIR_PHY_LED_ON(index), val); ++ if (err < 0) ++ return err; ++ ++ return 0; ++} ++ ++static int air_leds_init(struct phy_device *phydev, int num, u16 dur, int mode) ++{ ++ struct en8811h_priv *priv = phydev->priv; ++ int ret, i; ++ ++ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_BLINK, ++ dur); ++ if (ret < 0) ++ return ret; ++ ++ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_ON, ++ dur >> 1); ++ if (ret < 0) ++ return ret; ++ ++ switch (mode) { ++ case AIR_LED_MODE_DISABLE: ++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR, ++ AIR_PHY_LED_BCR_EXT_CTRL | ++ AIR_PHY_LED_BCR_MODE_MASK, 0); ++ break; ++ case AIR_LED_MODE_USER_DEFINE: ++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR, ++ AIR_PHY_LED_BCR_EXT_CTRL | ++ AIR_PHY_LED_BCR_CLK_EN, ++ AIR_PHY_LED_BCR_EXT_CTRL | ++ AIR_PHY_LED_BCR_CLK_EN); ++ if (ret < 0) ++ return ret; ++ break; ++ default: ++ printf("LED mode %d is not supported\n", mode); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < num; ++i) { ++ ret = air_led_init(phydev, i, AIR_LED_ENABLE, AIR_ACTIVE_HIGH); ++ if (ret < 0) { ++ printf("LED%d init failed: %d\n", i, ret); ++ return ret; ++ } ++ air_led_hw_control_set(phydev, i, priv->led[i].rules); ++ } ++ ++ return 0; ++} ++ ++static int en8811h_config(struct phy_device *phydev) ++{ ++ struct en8811h_priv *priv = phydev->priv; ++ ofnode node = phy_get_ofnode(phydev); ++ u32 pbus_value = 0; ++ int ret = 0; ++ ++ /* If restart happened in .probe(), no need to restart now */ ++ if (priv->mcu_needs_restart) { ++ ret = en8811h_restart_mcu(phydev); ++ if (ret < 0) ++ return ret; ++ } else { ++ ret = en8811h_load_firmware(phydev); ++ if (ret) { ++ printf("Load firmware fail.\n"); ++ return ret; ++ } ++ /* Next calls to .config() mcu needs to restart */ ++ priv->mcu_needs_restart = true; ++ } ++ ++ ret = phy_write_mmd(phydev, 0x1e, 0x800c, 0x0); ++ if (ret < 0) ++ return ret; ++ ret = phy_write_mmd(phydev, 0x1e, 0x800d, 0x0); ++ if (ret < 0) ++ return ret; ++ ret = phy_write_mmd(phydev, 0x1e, 0x800e, 0x1101); ++ if (ret < 0) ++ return ret; ++ ret = phy_write_mmd(phydev, 0x1e, 0x800f, 0x0002); ++ if (ret < 0) ++ return ret; ++ ++ /* Serdes polarity */ ++ pbus_value = 0; ++ if (ofnode_read_bool(node, "airoha,pnswap-rx")) ++ pbus_value |= EN8811H_POLARITY_RX_REVERSE; ++ else ++ pbus_value &= ~EN8811H_POLARITY_RX_REVERSE; ++ if (ofnode_read_bool(node, "airoha,pnswap-tx")) ++ pbus_value &= ~EN8811H_POLARITY_TX_NORMAL; ++ else ++ pbus_value |= EN8811H_POLARITY_TX_NORMAL; ++ ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY, ++ EN8811H_POLARITY_RX_REVERSE | ++ EN8811H_POLARITY_TX_NORMAL, pbus_value); ++ if (ret < 0) ++ return ret; ++ ++ ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR, ++ AIR_LED_MODE_USER_DEFINE); ++ if (ret < 0) { ++ printf("Failed to disable leds: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int en8811h_parse_status(struct phy_device *phydev) ++{ ++ int ret = 0, reg_value; ++ ++ phydev->duplex = DUPLEX_FULL; ++ ++ reg_value = phy_read(phydev, MDIO_DEVAD_NONE, AIR_AUX_CTRL_STATUS); ++ if (reg_value < 0) ++ return reg_value; ++ ++ switch (reg_value & AIR_AUX_CTRL_STATUS_SPEED_MASK) { ++ case AIR_AUX_CTRL_STATUS_SPEED_2500: ++ phydev->speed = SPEED_2500; ++ break; ++ case AIR_AUX_CTRL_STATUS_SPEED_1000: ++ phydev->speed = SPEED_1000; ++ break; ++ case AIR_AUX_CTRL_STATUS_SPEED_100: ++ phydev->speed = SPEED_100; ++ break; ++ default: ++ printf("Auto-neg error, defaulting to 100M/FD\n"); ++ phydev->speed = SPEED_100; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int en8811h_startup(struct phy_device *phydev) ++{ ++ int ret = 0; ++ ++ ret = genphy_update_link(phydev); ++ if (ret) ++ return ret; ++ ++ return en8811h_parse_status(phydev); ++} ++ ++static int en8811h_probe(struct phy_device *phydev) ++{ ++ struct en8811h_priv *priv; ++ ++ priv = malloc(sizeof(*priv)); ++ if (!priv) ++ return -ENOMEM; ++ memset(priv, 0, sizeof(*priv)); ++ ++ priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0; ++ priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1; ++ priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2; ++ ++ /* mcu has just restarted after firmware load */ ++ priv->mcu_needs_restart = false; ++ ++ phydev->priv = priv; ++ ++ return 0; ++} ++ ++U_BOOT_PHY_DRIVER(en8811h) = { ++ .name = "Airoha EN8811H", ++ .uid = EN8811H_PHY_ID, ++ .mask = 0x0ffffff0, ++ .config = &en8811h_config, ++ .probe = &en8811h_probe, ++ .startup = &en8811h_startup, ++ .shutdown = &genphy_shutdown, ++}; +-- +2.43.0 + diff --git a/patches/uboot/2025.01/0006-Add-bpi-r3-mini-device-tree.patch b/patches/uboot/2025.01/0006-Add-bpi-r3-mini-device-tree.patch new file mode 100644 index 000000000..1811ce3c6 --- /dev/null +++ b/patches/uboot/2025.01/0006-Add-bpi-r3-mini-device-tree.patch @@ -0,0 +1,275 @@ +From a987f92a768291669eec1a8c5d413190bd9241ec Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Wed, 18 Feb 2026 17:00:42 +0100 +Subject: [PATCH 6/7] Add bpi-r3-mini device tree +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Signed-off-by: Mattias Walström +--- + 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 6ad59aeed5f..3561e4c3651 100644 +--- a/arch/arm/dts/Makefile ++++ b/arch/arm/dts/Makefile +@@ -1199,6 +1199,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..b8d0a01c9ed +--- /dev/null ++++ b/arch/arm/dts/mt7986a-bpi-r3-mini.dts +@@ -0,0 +1,238 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/dts-v1/; ++#include "mt7986.dtsi" ++#include ++#include ++ ++/ { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ model = "Bananapi BPi-R3 Mini"; ++ compatible = "mediatek,mt7986", "mediatek,mt7986-rfb"; ++ ++ chosen { ++ stdout-path = &uart0; ++ tick-timer = &timer0; ++ }; ++ ++ memory@40000000 { ++ device_type = "memory"; ++ reg = <0x40000000 0x80000000>; ++ }; ++ ++ gpio-keys { ++ compatible = "gpio-keys"; ++ ++ button-reset { ++ label = "reset"; ++ linux,code = ; ++ gpios = <&gpio 7 GPIO_ACTIVE_LOW>; ++ }; ++ }; ++ ++ leds { ++ compatible = "gpio-leds"; ++ ++ status_led: led-0 { ++ label = "green:status"; ++ gpios = <&gpio 19 GPIO_ACTIVE_HIGH>; ++ }; ++ ++ led-1 { ++ label = "blue:wlan2g"; ++ gpios = <&gpio 1 GPIO_ACTIVE_HIGH>; ++ }; ++ ++ led-2 { ++ label = "blue:wlan5g"; ++ gpios = <&gpio 2 GPIO_ACTIVE_HIGH>; ++ }; ++ }; ++ ++ reg_1p8v: regulator-1p8v { ++ compatible = "regulator-fixed"; ++ regulator-name = "fixed-1.8V"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <1800000>; ++ regulator-boot-on; ++ regulator-always-on; ++ }; ++ ++ reg_3p3v: regulator-3p3v { ++ compatible = "regulator-fixed"; ++ regulator-name = "fixed-3.3V"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ regulator-boot-on; ++ regulator-always-on; ++ }; ++}; ++ ++ð { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&mdio_pins>; ++ ++ mediatek,gmac-id = <0>; ++ phy-mode = "2500base-x"; ++ phy-handle = <&phy14>; ++ ++ phy14: eth-phy@e { ++ compatible = "ethernet-phy-id03a2.a411"; ++ reg = <14>; ++ ++ airoha,pnswap-rx; ++ ++ reset-gpios = <&gpio 49 GPIO_ACTIVE_LOW>; ++ reset-assert-us = <10000>; ++ reset-deassert-us = <20000>; ++ }; ++}; ++ ++&mmc0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&mmc0_pins_default>; ++ bus-width = <8>; ++ max-frequency = <200000000>; ++ cap-mmc-highspeed; ++ cap-mmc-hw-reset; ++ vmmc-supply = <®_3p3v>; ++ vqmmc-supply = <®_1p8v>; ++ non-removable; ++ status = "okay"; ++}; ++ ++&pinctrl { ++ mdio_pins: mdio-pins { ++ mux { ++ function = "eth"; ++ groups = "mdc_mdio"; ++ }; ++ ++ conf-en8811-pwr-a { ++ pins = "GPIO_11"; ++ drive-strength = ; ++ bias-pull-down = ; ++ output-low; ++ }; ++ ++ conf-en8811-pwr-b { ++ pins = "GPIO_12"; ++ drive-strength = ; ++ bias-pull-down = ; ++ output-low; ++ }; ++ }; ++ ++ mmc0_pins_default: mmc0default { ++ mux { ++ function = "flash"; ++ groups = "emmc_51"; ++ }; ++ ++ conf-cmd-dat { ++ pins = "EMMC_DATA_0", "EMMC_DATA_1", "EMMC_DATA_2", ++ "EMMC_DATA_3", "EMMC_DATA_4", "EMMC_DATA_5", ++ "EMMC_DATA_6", "EMMC_DATA_7", "EMMC_CMD"; ++ input-enable; ++ drive-strength = ; ++ bias-pull-up = ; ++ }; ++ ++ conf-clk { ++ pins = "EMMC_CK"; ++ drive-strength = ; ++ bias-pull-down = ; ++ }; ++ ++ conf-dsl { ++ pins = "EMMC_DSL"; ++ bias-pull-down = ; ++ }; ++ ++ conf-rst { ++ pins = "EMMC_RSTB"; ++ drive-strength = ; ++ bias-pull-up = ; ++ }; ++ }; ++ ++ spi_flash_pins: spi0-pins-func-1 { ++ mux { ++ function = "flash"; ++ groups = "spi0", "spi0_wp_hold"; ++ }; ++ ++ conf-pu { ++ pins = "SPI2_CS", "SPI2_HOLD", "SPI2_WP"; ++ drive-strength = ; ++ bias-pull-up = ; ++ }; ++ ++ conf-pd { ++ pins = "SPI2_CLK", "SPI2_MOSI", "SPI2_MISO"; ++ drive-strength = ; ++ bias-pull-down = ; ++ }; ++ }; ++ ++ pwm_pins: pwm0-pins-func-1 { ++ mux { ++ function = "pwm"; ++ groups = "pwm0"; ++ }; ++ }; ++}; ++ ++&pwm { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm_pins>; ++ status = "okay"; ++}; ++ ++&spi0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi_flash_pins>; ++ status = "okay"; ++ must_tx; ++ enhance_timing; ++ dma_ext; ++ ipm_design; ++ support_quad; ++ tick_dly = <1>; ++ sample_sel = <0>; ++ ++ spi_nand@0 { ++ compatible = "spi-nand"; ++ reg = <0>; ++ spi-max-frequency = <20000000>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ partition@0 { ++ label = "bl2"; ++ reg = <0x0 0x200000>; ++ }; ++ ++ partition@200000 { ++ label = "ubi"; ++ reg = <0x200000 0x7e00000>; ++ }; ++ }; ++ }; ++ ++}; ++ ++&uart0 { ++ status = "okay"; ++}; ++ ++&watchdog { ++ status = "disabled"; ++}; +-- +2.43.0 + diff --git a/patches/uboot/2025.01/0007-bpi-r3-r4-Add-probe-for-specific-model.patch b/patches/uboot/2025.01/0007-bpi-r3-r4-Add-probe-for-specific-model.patch new file mode 100644 index 000000000..a3e1bbe7a --- /dev/null +++ b/patches/uboot/2025.01/0007-bpi-r3-r4-Add-probe-for-specific-model.patch @@ -0,0 +1,562 @@ +From 57b3cd8612a8da852f0796d97e6a7526cc4742cc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Wed, 18 Feb 2026 17:02:23 +0100 +Subject: [PATCH 7/7] bpi-r3/r4: Add probe for specific model +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +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: Mattias Walström +--- + 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 ff1fdee5c8d..def8a564d90 100644 +--- a/arch/arm/mach-mediatek/Kconfig ++++ b/arch/arm/mach-mediatek/Kconfig +@@ -153,4 +153,7 @@ config MTK_BROM_HEADER_INFO + default "media=snand;nandinfo=2k+64" if TARGET_MT7981 || TARGET_MT7986 || TARGET_MT7988 + default "lk=1" if TARGET_MT7623 + ++source "board/mediatek/mt7986/Kconfig" ++source "board/mediatek/mt7988/Kconfig" ++ + endif +diff --git a/board/mediatek/mt7986/Kconfig b/board/mediatek/mt7986/Kconfig +new file mode 100644 +index 00000000000..cea150b52d8 +--- /dev/null ++++ b/board/mediatek/mt7986/Kconfig +@@ -0,0 +1,10 @@ ++if TARGET_MT7986 ++ ++config BOARD_BPI_R3 ++ bool "BananaPi BPI-R3 / BPI-R3-mini board support" ++ select BOARD_LATE_INIT ++ help ++ Enable runtime variant detection for BananaPi BPI-R3 and BPI-R3-mini, ++ selecting the correct device tree from a FIT image. ++ ++endif +diff --git a/board/mediatek/mt7986/Makefile b/board/mediatek/mt7986/Makefile +index 7bb84fa2f4e..b006ee150bd 100644 +--- a/board/mediatek/mt7986/Makefile ++++ b/board/mediatek/mt7986/Makefile +@@ -1,3 +1,4 @@ + # SPDX-License-Identifier: GPL-2.0 + + obj-y += mt7986_rfb.o ++obj-$(CONFIG_BOARD_BPI_R3) += bpir3.o +diff --git a/board/mediatek/mt7986/bpir3.c b/board/mediatek/mt7986/bpir3.c +new file mode 100644 +index 00000000000..01eef55bb74 +--- /dev/null ++++ b/board/mediatek/mt7986/bpir3.c +@@ -0,0 +1,229 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * BPI-R3 / BPI-R3-mini board variant detection ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++DECLARE_GLOBAL_DATA_PTR; ++ ++enum bpir3_variant { ++ BPIR3, ++ BPIR3_MINI, ++}; ++ ++/* ++ * Detect BPI-R3 vs BPI-R3-mini by probing for the Airoha EN8811H PHY. ++ * ++ * BPI-R3-mini: EN8811H is directly on the MDIO bus at addr 14. ++ * GPIO49 (active-low) is its reset; PHYSID1 = 0x03a2. ++ * BPI-R3: MT7531AE switch via SGMII; nothing at MDIO addr 14 → 0xffff. ++ * ++ * NOTE: I2C 0x50 is NOT usable — BPI-R3 SFP cage EEPROMs occupy 0x50–0x5b. ++ */ ++ ++/* ++ * MT7986 GPIO — gpio_base = 0x1001f000 ++ * ++ * DIR register: base + 0x000 + (pin/32)*0x10, 1 bit/pin (1=output) ++ * DOUT register: base + 0x100 + (pin/32)*0x10, 1 bit/pin ++ * MODE register: base + 0x300 + (pin*4/32)*0x10, 4 bits/pin (0=GPIO, 1=eth…) ++ */ ++#define MT7986_GPIO_BASE 0x1001f000UL ++#define GPIO_DIR_REG(p) (MT7986_GPIO_BASE + 0x000 + ((p) / 32) * 0x10) ++#define GPIO_DOUT_REG(p) (MT7986_GPIO_BASE + 0x100 + ((p) / 32) * 0x10) ++#define GPIO_MODE_REG(p) (MT7986_GPIO_BASE + 0x300 + (((p) * 4) / 32) * 0x10) ++#define GPIO_MODE_SHIFT(p) (((p) * 4) % 32) ++ ++/* EN8811H PHY reset: GPIO49, active-low */ ++#define EN8811H_RST_GPIO 49 ++/* EN8811H power control lines: active-low enables on some BPI-R3-mini dts */ ++#define EN8811H_PWR_A_GPIO 11 ++#define EN8811H_PWR_B_GPIO 12 ++/* Alternative EN8811H power control lines used by other mini trees */ ++#define EN8811H_PWR2_A_GPIO 16 ++#define EN8811H_PWR2_B_GPIO 17 ++ ++/* MDC/MDIO pins: GPIO67 = MDC, GPIO68 = MDIO, both on function 1 ("eth") */ ++#define MDC_GPIO 67 ++#define MDIO_GPIO 68 ++ ++/* ++ * MT7986 Frame Engine: ethernet@15100000, GMAC at +0x10000 ++ * ++ * PPSC (+0x0000): MDC clock config — bits[29:24] = divider, bit[4] = turbo ++ * PIAC (+0x0004): PHY indirect access — write then poll PHY_ACS_ST clear ++ */ ++#define GMAC_BASE 0x15110000UL ++#define GMAC_PPSC (GMAC_BASE + 0x0000) ++#define GMAC_PIAC (GMAC_BASE + 0x0004) ++ ++#define PPSC_MDC_CFG_MASK GENMASK(29, 24) ++#define PPSC_MDC_CFG(d) (((d) & 0x3f) << 24) ++#define PPSC_MDC_TURBO BIT(4) ++/* divider = 10 → MDC ≈ 2.5 MHz from 25 MHz reference */ ++#define MDC_DIVIDER 10 ++ ++#define PIAC_ACS_ST BIT(31) ++#define PIAC_REG_S 25 ++#define PIAC_PHY_S 20 ++#define PIAC_CMD_S 18 ++#define PIAC_ST_S 16 ++#define PIAC_DATA_MASK 0xffffU ++#define PIAC_CMD_READ 2 ++#define PIAC_ST_C22 1 ++ ++/* EN8811H PHY ID register values */ ++#define EN8811H_PHYSID1 0x03a2 ++#define EN8811H_PHYSID2_MASK 0xfff0 ++#define EN8811H_PHYSID2 0xa410 ++#define MII_PHYSID1 0x02 /* IEEE MII register 2 */ ++#define MII_PHYSID2 0x03 /* IEEE MII register 3 */ ++ ++/* Set a GPIO pin's mode field (0 = GPIO, 1 = eth function, …) */ ++static void gpio_set_mode(unsigned int pin, unsigned int func) ++{ ++ clrsetbits_le32(GPIO_MODE_REG(pin), ++ 0xf << GPIO_MODE_SHIFT(pin), ++ (func & 0xf) << GPIO_MODE_SHIFT(pin)); ++} ++ ++/* Configure a GPIO pin as output and drive it high or low */ ++static void gpio_out(unsigned int pin, int val) ++{ ++ setbits_le32(GPIO_DIR_REG(pin), BIT(pin % 32)); ++ if (val) ++ setbits_le32(GPIO_DOUT_REG(pin), BIT(pin % 32)); ++ else ++ clrbits_le32(GPIO_DOUT_REG(pin), BIT(pin % 32)); ++} ++ ++/* ++ * Clause-22 MDIO read via the MT7986 GMAC PHY indirect access register. ++ * Returns the 16-bit register value, or -1 on timeout. ++ */ ++static int mdio_read_c22(unsigned int phy, unsigned int reg) ++{ ++ u32 val; ++ int i; ++ ++ val = (PIAC_ST_C22 << PIAC_ST_S) | ++ (PIAC_CMD_READ << PIAC_CMD_S) | ++ (phy << PIAC_PHY_S) | ++ (reg << PIAC_REG_S); ++ writel(val | PIAC_ACS_ST, GMAC_PIAC); ++ ++ for (i = 0; i < 5000; i++) { ++ val = readl(GMAC_PIAC); ++ if (!(val & PIAC_ACS_ST)) ++ return (int)(val & PIAC_DATA_MASK); ++ udelay(1); ++ } ++ ++ return -1; /* timeout */ ++} ++ ++static void enable_mini_power_lines(unsigned int gpio_a, unsigned int gpio_b) ++{ ++ gpio_set_mode(gpio_a, 0); /* mode 0 = GPIO */ ++ gpio_set_mode(gpio_b, 0); /* mode 0 = GPIO */ ++ gpio_out(gpio_a, 0); /* active-low enable */ ++ gpio_out(gpio_b, 0); /* active-low enable */ ++} ++ ++static void pulse_mini_phy_reset(void) ++{ ++ gpio_set_mode(EN8811H_RST_GPIO, 0); /* mode 0 = GPIO */ ++ gpio_out(EN8811H_RST_GPIO, 0); /* assert reset */ ++ mdelay(10); ++ gpio_out(EN8811H_RST_GPIO, 1); /* deassert reset */ ++ mdelay(20); ++} ++ ++static bool is_en8811_id(int id1, int id2) ++{ ++ return id1 == EN8811H_PHYSID1 && ++ (id2 & EN8811H_PHYSID2_MASK) == EN8811H_PHYSID2; ++} ++ ++static int probe_en8811_addr(unsigned int phy_addr) ++{ ++ int id1, id2; ++ ++ id1 = mdio_read_c22(phy_addr, MII_PHYSID1); ++ id2 = mdio_read_c22(phy_addr, MII_PHYSID2); ++ ++ if (id1 < 0 || id2 < 0) ++ return 0; ++ ++ return is_en8811_id(id1, id2); ++} ++ ++static enum bpir3_variant detect_bpir3_variant(void) ++{ ++ unsigned int pwr_a[] = { EN8811H_PWR_A_GPIO, EN8811H_PWR2_A_GPIO }; ++ unsigned int pwr_b[] = { EN8811H_PWR_B_GPIO, EN8811H_PWR2_B_GPIO }; ++ unsigned int i; ++ ++ /* Switch GPIO67 (MDC) and GPIO68 (MDIO) to eth function */ ++ gpio_set_mode(MDC_GPIO, 1); ++ gpio_set_mode(MDIO_GPIO, 1); ++ ++ /* Configure MDC clock: enable turbo + set safe divider (~2.5 MHz) */ ++ setbits_le32(GMAC_PPSC, PPSC_MDC_TURBO); ++ clrsetbits_le32(GMAC_PPSC, PPSC_MDC_CFG_MASK, PPSC_MDC_CFG(MDC_DIVIDER)); ++ ++ /* ++ * Enable EN8811H power rails and probe; some mini boards use ++ * GPIO11/12, others GPIO16/17. ++ */ ++ for (i = 0; i < ARRAY_SIZE(pwr_a); i++) { ++ enable_mini_power_lines(pwr_a[i], pwr_b[i]); ++ pulse_mini_phy_reset(); ++ ++ if (probe_en8811_addr(14) || probe_en8811_addr(15)) { ++ /* Assert reset; DM eth-phy will deassert with proper delays */ ++ gpio_out(EN8811H_RST_GPIO, 0); ++ return BPIR3_MINI; ++ } ++ } ++ ++ return BPIR3; ++} ++ ++int board_fit_config_name_match(const char *name) ++{ ++ static int variant = -1; ++ ++ if (variant < 0) ++ variant = detect_bpir3_variant(); ++ ++ switch (variant) { ++ case BPIR3_MINI: ++ return strcmp(name, "mt7986a-bpi-r3-mini") ? -1 : 0; ++ case BPIR3: ++ default: ++ /* ++ * Accept both sd and emmc variants of BPI-R3; ++ * storage type is determined by spl_boot_device(), not here. ++ */ ++ return (strcmp(name, "mt7986a-bpi-r3-sd") == 0 || ++ strcmp(name, "mt7986a-bpi-r3-emmc") == 0) ? 0 : -1; ++ } ++} ++ ++int board_late_init(void) ++{ ++ const char *model = fdt_getprop(gd->fdt_blob, 0, "model", NULL); ++ ++ if (model && strstr(model, "Mini")) ++ env_set("fdtfile", "mediatek/mt7986a-bananapi-bpi-r3-mini.dtb"); ++ ++ return 0; ++} +diff --git a/board/mediatek/mt7988/Kconfig b/board/mediatek/mt7988/Kconfig +new file mode 100644 +index 00000000000..24c70417ec5 +--- /dev/null ++++ b/board/mediatek/mt7988/Kconfig +@@ -0,0 +1,9 @@ ++if TARGET_MT7988 ++ ++config BOARD_BPI_R4 ++ bool "BananaPi BPI-R4 / BPI-R4-2g5 board support" ++ help ++ Enable runtime variant detection for BananaPi BPI-R4 and BPI-R4-2g5, ++ selecting the correct device tree from a FIT image. ++ ++endif +diff --git a/board/mediatek/mt7988/Makefile b/board/mediatek/mt7988/Makefile +index f1249ab3715..157ee371eae 100644 +--- a/board/mediatek/mt7988/Makefile ++++ b/board/mediatek/mt7988/Makefile +@@ -1,3 +1,4 @@ + # SPDX-License-Identifier: GPL-2.0 + + obj-y += mt7988_rfb.o ++obj-$(CONFIG_BOARD_BPI_R4) += bpir4.o +diff --git a/board/mediatek/mt7988/bpir4.c b/board/mediatek/mt7988/bpir4.c +new file mode 100644 +index 00000000000..46e22d38135 +--- /dev/null ++++ b/board/mediatek/mt7988/bpir4.c +@@ -0,0 +1,33 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * BPI-R4 / BPI-R4-2g5 board variant detection ++ */ ++ ++#include ++#include ++ ++enum bpir4_variant { ++ BPIR4, ++ BPIR4_2G5, ++}; ++ ++/* ++ * Detect which BPI-R4 variant this board is. ++ * TODO: implement real hardware detection ++ */ ++static enum bpir4_variant detect_bpir4_variant(void) ++{ ++ /* Stub: always report BPI-R4 */ ++ return BPIR4; ++} ++ ++int board_fit_config_name_match(const char *name) ++{ ++ switch (detect_bpir4_variant()) { ++ case BPIR4_2G5: ++ return strcmp(name, "mt7988a-bananapi-bpi-r4-2g5") ? -1 : 0; ++ case BPIR4: ++ default: ++ return strcmp(name, "mt7988a-bananapi-bpi-r4") ? -1 : 0; ++ } ++} +diff --git a/configs/mt7986a_bpir3_emmc_defconfig b/configs/mt7986a_bpir3_emmc_defconfig +index ef6a4822c18..193acb4a9b4 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 3d971f5c313..e498b58a4a5 100644 +--- a/configs/mt7986a_bpir3_sd_defconfig ++++ b/configs/mt7986a_bpir3_sd_defconfig +@@ -9,6 +9,8 @@ CONFIG_ENV_SIZE=0x80000 + CONFIG_ENV_OFFSET=0x300000 + CONFIG_DEFAULT_DEVICE_TREE="mt7986a-bpi-r3-sd" + CONFIG_TARGET_MT7986=y ++CONFIG_BOARD_BPI_R3=y ++CONFIG_SPL_LOAD_FIT=y + CONFIG_SYS_LOAD_ADDR=0x46000000 + CONFIG_DEBUG_UART_BASE=0x11002000 + CONFIG_DEBUG_UART_CLOCK=40000000 +diff --git a/configs/mt7988a_bpir4_defconfig b/configs/mt7988a_bpir4_defconfig +new file mode 100644 +index 00000000000..e2e128ec29d +--- /dev/null ++++ b/configs/mt7988a_bpir4_defconfig +@@ -0,0 +1,85 @@ ++CONFIG_ARM=y ++CONFIG_SYS_HAS_NONCACHED_MEMORY=y ++CONFIG_POSITION_INDEPENDENT=y ++CONFIG_ARCH_MEDIATEK=y ++CONFIG_TEXT_BASE=0x41e00000 ++CONFIG_SYS_MALLOC_F_LEN=0x4000 ++CONFIG_NR_DRAM_BANKS=1 ++CONFIG_DEFAULT_DEVICE_TREE="mt7988a-bananapi-bpi-r4" ++CONFIG_TARGET_MT7988=y ++CONFIG_BOARD_BPI_R4=y ++CONFIG_SPL_LOAD_FIT=y ++CONFIG_SYS_LOAD_ADDR=0x46000000 ++CONFIG_DEBUG_UART_BASE=0x11000000 ++CONFIG_DEBUG_UART_CLOCK=40000000 ++CONFIG_DEBUG_UART=y ++# CONFIG_EFI_LOADER is not set ++# CONFIG_AUTOBOOT is not set ++CONFIG_DEFAULT_FDT_FILE="mt7988a-bananapi-bpi-r4" ++CONFIG_SYS_CBSIZE=512 ++CONFIG_SYS_PBSIZE=1049 ++CONFIG_LOGLEVEL=7 ++CONFIG_LOG=y ++CONFIG_SYS_PROMPT="BPI-R4> " ++# CONFIG_BOOTM_NETBSD is not set ++# CONFIG_BOOTM_PLAN9 is not set ++# CONFIG_BOOTM_RTEMS is not set ++# CONFIG_BOOTM_VXWORKS is not set ++# CONFIG_CMD_ELF is not set ++CONFIG_CMD_CLK=y ++CONFIG_CMD_DM=y ++CONFIG_CMD_GPIO=y ++CONFIG_CMD_PWM=y ++CONFIG_CMD_MMC=y ++CONFIG_CMD_MTD=y ++CONFIG_CMD_PING=y ++CONFIG_CMD_SMC=y ++CONFIG_DOS_PARTITION=y ++CONFIG_EFI_PARTITION=y ++CONFIG_PARTITION_TYPE_GUID=y ++CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y ++CONFIG_USE_IPADDR=y ++CONFIG_IPADDR="192.168.1.1" ++CONFIG_USE_NETMASK=y ++CONFIG_NETMASK="255.255.255.0" ++CONFIG_USE_SERVERIP=y ++CONFIG_SERVERIP="192.168.1.2" ++CONFIG_PROT_TCP=y ++CONFIG_NET_RANDOM_ETHADDR=y ++CONFIG_REGMAP=y ++CONFIG_SYSCON=y ++CONFIG_CLK=y ++CONFIG_MMC_HS200_SUPPORT=y ++CONFIG_MMC_MTK=y ++CONFIG_MTD=y ++CONFIG_DM_MTD=y ++CONFIG_MTD_SPI_NAND=y ++CONFIG_DM_SPI_FLASH=y ++CONFIG_SPI_FLASH_SFDP_SUPPORT=y ++CONFIG_SPI_FLASH_EON=y ++CONFIG_SPI_FLASH_GIGADEVICE=y ++CONFIG_SPI_FLASH_ISSI=y ++CONFIG_SPI_FLASH_MACRONIX=y ++CONFIG_SPI_FLASH_SPANSION=y ++CONFIG_SPI_FLASH_STMICRO=y ++CONFIG_SPI_FLASH_WINBOND=y ++CONFIG_SPI_FLASH_XMC=y ++CONFIG_SPI_FLASH_XTX=y ++CONFIG_SPI_FLASH_MTD=y ++CONFIG_PHY_FIXED=y ++CONFIG_MEDIATEK_ETH=y ++CONFIG_PINCTRL=y ++CONFIG_PINCONF=y ++CONFIG_PINCTRL_MT7988=y ++CONFIG_POWER_DOMAIN=y ++CONFIG_MTK_POWER_DOMAIN=y ++CONFIG_DM_PWM=y ++CONFIG_PWM_MTK=y ++CONFIG_RAM=y ++CONFIG_DM_SERIAL=y ++CONFIG_MTK_SERIAL=y ++CONFIG_SPI=y ++CONFIG_DM_SPI=y ++CONFIG_MTK_SPIM=y ++CONFIG_LZO=y ++CONFIG_HEXDUMP=y +-- +2.43.0 + diff --git a/utils/mkimage.sh b/utils/mkimage.sh index 98eace9de..41698182b 100755 --- a/utils/mkimage.sh +++ b/utils/mkimage.sh @@ -259,6 +259,50 @@ download_bootloader() log "Bootloader ready in temporary directory" } +# Build EN8811H firmware partition image as an ext4 filesystem containing +# EthMD32.dm.bin and EthMD32.DSP.bin. Returns 0 when created, 1 when source +# files are missing. +prepare_en8811h_fw_image() +{ + src_dir="$1" + out_img="$2" + dm_fw="${src_dir}/EthMD32.dm.bin" + dsp_fw="${src_dir}/EthMD32.DSP.bin" + fw_staging="" + + [ -f "$dm_fw" ] && [ -f "$dsp_fw" ] || return 1 + + fw_staging=$(mktemp -d) + cp "$dm_fw" "$fw_staging/EthMD32.dm.bin" + cp "$dsp_fw" "$fw_staging/EthMD32.DSP.bin" + + rm -f "$out_img" + truncate -s 2M "$out_img" + + if command -v mke2fs >/dev/null 2>&1; then + mke2fs -q -t ext4 -L en8811h_fw -d "$fw_staging" "$out_img" + elif command -v mkfs.ext4 >/dev/null 2>&1; then + mkfs.ext4 -q -L en8811h_fw -d "$fw_staging" "$out_img" + else + rm -rf "$fw_staging" + die "mke2fs/mkfs.ext4 not found, cannot create en8811h-fw.bin" + fi + + rm -rf "$fw_staging" + return 0 +} + +maybe_prepare_en8811h_fw_image() +{ + out_img="${BINARIES_DIR}/en8811h-fw.bin" + + [ -f "$out_img" ] && return 0 + + prepare_en8811h_fw_image "${ROOT_DIR}/images/airoha" "$out_img" || \ + prepare_en8811h_fw_image "${ROOT_DIR}/airoha" "$out_img" || \ + prepare_en8811h_fw_image "${BINARIES_DIR}/airoha" "$out_img" || return 0 +} + # Discover boot files for Raspberry Pi boot partition # Scans rpi-firmware directory and builds file list for genimage discover_rpi_boot_files() @@ -519,6 +563,11 @@ if [ -n "$DOWNLOAD_BOOT" ]; then fi fi +if [ "$BOARD" = "bananapi-bpi-r3" ]; then + maybe_prepare_en8811h_fw_image + [ -f "${BINARIES_DIR}/en8811h-fw.bin" ] || die "Missing EN8811H firmware image: ${BINARIES_DIR}/en8811h-fw.bin" +fi + # Template expansion log "Generating genimage configuration for $BOARD..."