Orange Pi 5 Plus GPIO, I²C and SPI on Ubuntu 26.04 with EDK2
A tested method for activating the 40-pin header on a mainline-style Ubuntu installation without replacing the working firmware-supplied base device tree.
i2c-2 as an active rk3x-i2c bus and exposed spi0 in
/sys/class/spi_master/. The live device tree reported both
/i2c@feaa0000/status and /spi@feb00000/status as okay.
1. Why this procedure is necessary
The Orange Pi 5 Plus uses the Rockchip RK3588, whose pins are multiplexed between GPIO and peripheral functions. On a clean Ubuntu 26.04 installation booted through RK3588 EDK2, the Linux drivers for GPIO, I²C and SPI may all be present while the desired 40-pin-header functions remain disabled in the active device tree.
On the tested system, all five RK3588 GPIO banks were active, but I²C2 and SPI0 were disabled. Importantly, the firmware-provided device tree already contained the correct pin-control definitions. This allowed a small overlay to enable only the desired interfaces rather than replacing the complete DTB.
2. Establish the starting condition
GPIO
sudo apt install -y gpiod sudo gpiodetect sudo gpioinfo
The tested board reported five controllers, each with 32 lines:
gpiochip0 [gpio0] (32 lines) gpiochip1 [gpio1] (32 lines) gpiochip2 [gpio2] (32 lines) gpiochip3 [gpio3] (32 lines) gpiochip4 [gpio4] (32 lines)
gpioinfo and the board pin mapping before driving a line.
I²C
sudo apt install -y i2c-tools i2cdetect -l
Before the overlay, the tested system exposed I²C0, I²C6 and I²C7 plus HDMI DDC buses, but not I²C2.
SPI
ls -l /dev/spidev* 2>/dev/null || echo "No spidev devices currently present"
for f in /proc/device-tree/aliases/spi*; do
[ -e "$f" ] || continue
alias=$(basename "$f")
path=$(tr -d '\0' < "$f")
node="/proc/device-tree${path}"
printf "%-10s -> %-30s " "$alias" "$path"
if [ -f "$node/status" ]; then
tr -d '\0' < "$node/status"
else
printf "no-status"
fi
echo
done
The tested system initially reported:
spi0 -> /spi@feb00000 disabled spi1 -> /spi@feb10000 disabled spi2 -> /spi@feb20000 okay spi3 -> /spi@feb30000 disabled spi4 -> /spi@fecb0000 disabled
SPI2 should be left alone. It is used internally by the RK3588 platform/PMIC configuration on this system. SPI0 is the controller intentionally selected for the 40-pin header.
3. Confirm the live device tree
Install the device-tree compiler and decompile the DTB Linux actually received:
sudo apt install -y device-tree-compiler
sudo dtc -I fs -O dts \
/sys/firmware/devicetree/base \
-o /tmp/op5-live.dts 2>/dev/null
For I²C2, the live tree on the tested system contained:
i2c@feaa0000 {
pinctrl-names = "default";
pinctrl-0 = <0xb1>;
...
status = "disabled";
};
i2c2m0-xfer {
rockchip,pins = <...>;
};
This demonstrated that I²C2 was already pointed at the I2C2_M0 pin group and only needed to be enabled.
For SPI0, however, the controller was disabled and pointed at its M0 group:
spi@feb00000 {
pinctrl-names = "default";
num-cs = <2>;
pinctrl-0 = <0xb5 0xb6 0xb7>;
...
status = "disabled";
};
The live tree also contained symbolic definitions for spi0m2_pins, spi0m2_cs0 and spi0m2_cs1, which are the desired header mapping.
4. 40-pin header assignment used
| Physical pin | I²C2_M0 |
|---|---|
| 3 | SDA |
| 5 | SCL |
| Physical pin | SPI0_M2 |
|---|---|
| 19 | MOSI |
| 21 | MISO |
| 23 | SCLK |
| 24 | CS0 |
| 26 | CS1 |
| Physical pin | Power / ground |
|---|---|
| 1 | 3.3 V |
| 2, 4 | 5 V |
| 6, 9, 14, 20, 25, 30, 34, 39 | Ground |
5. Create one combined EDK2 device-tree overlay
This overlay enables I²C2 using its existing I2C2_M0 definition, enables SPI0, and changes SPI0 from the default M0 pin group to SPI0_M2 for the 40-pin header. Other unclaimed pins remain available for normal GPIO use through libgpiod.
cat > /tmp/op5-header.dts <<'EOF'
/dts-v1/;
/plugin/;
/ {
/*
* Orange Pi 5 Plus 40-pin header
*
* I2C2_M0:
* pin 3 = SDA
* pin 5 = SCL
*
* SPI0_M2:
* pin 19 = MOSI
* pin 21 = MISO
* pin 23 = SCLK
* pin 24 = CS0
* pin 26 = CS1
*/
fragment@0 {
target-path = "/i2c@feaa0000";
__overlay__ {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c2m0_xfer>;
};
};
fragment@1 {
target-path = "/spi@feb00000";
__overlay__ {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi0m2_pins
&spi0m2_cs0
&spi0m2_cs1>;
num-cs = <2>;
};
};
};
EOF
Compile the overlay:
dtc -@ -I dts -O dtb \
-o /tmp/op5-header.dtbo \
/tmp/op5-header.dts
6. Install the overlay where EDK2 expects it
The RK3588 EDK2 firmware supports platform-specific overlays under
\dtb\overlays\<PLATFORM-DT-NAME>. The Orange Pi 5 Plus platform name is
rk3588-orangepi-5-plus.
sudo mkdir -p \
/boot/efi/dtb/overlays/rk3588-orangepi-5-plus
sudo cp /tmp/op5-header.dtbo \
/boot/efi/dtb/overlays/rk3588-orangepi-5-plus/
find /boot/efi/dtb/overlays -type f -print
If earlier test overlays were installed, remove them so that the combined overlay is the only header configuration being applied:
sudo rm -f \ /boot/efi/dtb/overlays/rk3588-orangepi-5-plus/enable-i2c2-m0.dtbo sudo rm -f \ /boot/efi/dtb/overlays/rk3588-orangepi-5-plus/enable-spi0-m2.dtbo
7. Enable overlay support in EDK2
EDK2 Device Manager → Rockchip Platform Configuration → ACPI / Device Tree
Set Support DTB override & overlays to Enabled, save the firmware settings, and boot Ubuntu.
From Linux, the next reboot can usually be sent directly to firmware setup with:
sudo systemctl reboot --firmware-setup
Do not change unrelated PCIe, SATA, USB or display firmware settings merely to enable this overlay.
8. Verify after reboot
I²C2
i2cdetect -l
The tested result included:
i2c-2 unknown rk3x-i2c N/A
Verify the live DT status:
printf "I2C2: " tr -d '\0' < /proc/device-tree/i2c@feaa0000/status echo
Expected:
I2C2: okay
With a device connected, or simply to verify that the bus opens:
sudo i2cdetect -y 2
An empty scan showing -- is normal when no I²C devices are attached.
SPI0
ls /sys/class/spi_master/
The tested system reported spi0, confirming that SPI0 was instantiated. Verify the device-tree state:
printf "SPI0: " tr -d '\0' < /proc/device-tree/spi@feb00000/status echo
Expected:
SPI0: okay
spi0 confirms that the controller and pinmux are active. A raw userspace node such as /dev/spidev0.0 is a separate device-binding decision and should be created only when needed for the attached SPI peripheral.
GPIO
sudo gpiodetect sudo gpioinfo
No blanket GPIO overlay is required. The five RK3588 GPIO banks are already registered by Linux.
Unused header pins can be requested dynamically through the GPIO character-device API and libgpiod.
9. Optional: allow a non-root user to access GPIO
On the tested installation, /dev/gpiochip* initially appeared as root:root with mode 0600. A dedicated group and udev rule can provide controlled user access:
sudo groupadd -f gpio sudo usermod -aG gpio "$USER" sudo tee /etc/udev/rules.d/60-op5-gpio.rules >/dev/null <<'EOF' SUBSYSTEM=="gpio", KERNEL=="gpiochip*", GROUP="gpio", MODE="0660" EOF sudo udevadm control --reload-rules sudo udevadm trigger --subsystem-match=gpio
Log out and back in, then verify:
groups ls -l /dev/gpiochip* gpiodetect
10. Why North Rivet uses this approach
This configuration intentionally makes the minimum device-tree changes required. I²C2 and SPI0_M2 are reserved for their bus functions, while unrelated pins remain free for GPIO or future peripheral assignments. This avoids a large board-specific replacement DTB and reduces the chance of unintentionally changing NVMe, HDMI, Ethernet, USB or power-control behavior.
For development and industrial prototyping, the resulting software stack is straightforward:
i2c-dev for I²C, the Linux SPI subsystem for SPI, and libgpiod for GPIO.
11. Tested final state
| Subsystem | Result | Evidence |
|---|---|---|
| GPIO controllers | Working | gpiochip0 through gpiochip4, 32 lines each |
| I²C2_M0 | Working | i2c-2 present; DT status okay |
| SPI0_M2 | Working | spi0 present; DT status okay |
| NVMe / Ubuntu boot | Unaffected | System continued to boot normally through EDK2/UEFI |
References
Orange Pi 5 Plus documentation:
Orange Pi 5 Plus Wiki
RK3588 EDK2 firmware documentation:
edk2-rk3588 README