Bringup V3s in 3 hours #
I tried to bringup my BingPi-M1 V3s board with almost zero experience in embedded Linux, and it turns out that I can configure and run a fully mainling U-Boot 2024.10 and Linux 6.9 within 3 hours. Thanks to Icenowy’s work in V3s mainline, bringing up a new V3s board can be extremely easy.
V3s, despite its well mainline support, is a really simple SoC that has embedded Ethernet PHY, USB Controller, and USB PHY, so I do not need to deal with things like CPU voltage, regulators, HMAC pins, etc. This makes V3s an excellent introductory chip for embedded Linux development and mainling porting. It allows me to port my own mainline vanilla Linux kernel and U-boot with just less than 50 lines of device tree code, also without much effort reading the schematics.
My board is BingPi-M1, which is very similar to the LicheePi Zero. By reading the schematics, it has:
- USB: USB 2.0 A female socket + RTL8188EUS USB WiFi + USB 2.0 D+ D- Headers connected to a FE1.1S hub, which is then connected to the SoC via USB-DP and USB-DM ports. The hub can also be replaced by a USB-C female socket by switching the resistors. There is only USB 2.0 support. There is no USB mode detection (host / device) pin.
- Ethernet: Eth 100Mbps socket connected directly to the EPHY-TXP, EPHY-TXN, ETHY-RXP, ETHY-RXN, SPD-LED, and LINK-LED pins on the SoC.
- SPI: W25N01GVZEIG 128MiB flash connected to the SPI-MISO, SPI-CLK, SPI-CS, and SPI-MOSI pins on the SoC.
- DRAM: No external DRAM connected.
- CSI: CSI interface exported to headers.
- SD Card: SD Card socket connected to the SDC0-* pins of the SoC.
- Audio: Microphone and 3.5" jack connected to the relevant pins of the SoC.
- LCD: LCD interface exported to headers.
- GPIO: GPIO G (six in total) exported to headers, with PG5 connected to user button (active when low) and PG1 connected to user LED (active when low).
- UART: UART0 connected to CH340E via a dedicated USB-C port; UART2 exported to headers.
Due to extensive existing mainling effort on V3s, it is quite easy to just use mainline device trees. The vendor docs on this board also suggest using the Lichee Pi Zero defconfig and device trees.
This blog only demonstrates a basic bringup of the board with as minimal changes as possible. The following peripherals work:
- SD card
- Ethernet
- USB
- User LED (as
/sys/class/led/
) - User button (as input event)
The following are untested or not working:
- Audio jack and microphone
- CSI camera
- LCD display
- SPI flash
- GPIO
- Linux kernel modules
- SPI / I2C / ADC pins
You should have some very basic understanding to device tree (DT) though (My personal understanding only, may not be correct):
- The dt is organized in a tree-like structure that defines the system and peripherals (e.g., what drivers are required and what properties they have, like registers, pins, etc.).
- Both U-Boot and Linux use dts, but we don’t usually share them. U-Boot obviously don’t need lots of hardware in the dts because it just needs to boot the kernel.
- The dt source is in files called
.dts
, and they compile into binaries in.dtb
. For U-Boot, the dtb is compiled in the final binary. For Linux, the dtb is loaded by the bootloader. - DT are board-specific as they describe board-specific pins and configurations. When you get a new development board or after building your own board, you should write a new dts.
- We usually don’t start working on blank new dts. We copy existing dts with similar configuration (e.g., Lichee Pi Zero is similar to BingPi M1). Then we make corresponding changes.
- The
compatible =
configuration in dts specifies which kernel module is required for this node to work. We use this configuration to determine the required kernel configuration. - We almost always extend (include) from existing DTS (like, we include the DTS for V3s and
do our own modifications for the board). Nodes are by-default disabled. We need to put our
own
status = "okay";
to enable them.
U-Boot 2024.10 #
First, make a copy of LicheePi_zero_defconfig
and sun8i-v3s-licheepi-zero.dts
and name
them bingpi_defconfig
and sun8i-v3s-bingpi.dts
. Make changes to the defconfig to point
to the new dts. Then make defconfig.
# bingpi_defconfig (copied from LicheePi_zero_defconfig)
CONFIG_ARM=y
CONFIG_ARCH_SUNXI=y
CONFIG_DEFAULT_DEVICE_TREE="sun8i-v3s-bingpi" # Only changed this line
CONFIG_SPL=y
CONFIG_MACH_SUN8I_V3S=y
CONFIG_DRAM_CLK=360
Then make defconfig. The above defconfig is enough to bring up the device, or you can add more features by using nconfig.
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bingpi_defconfig
Then, edit the dts. I highlighted everything I changed from sun8i-v3s-licheepi-zero.dts
:
// Copied from sun8i-v3s-licheepi-zero.dts. Only changes are highlighted.
/dts-v1/;
#include "sun8i-v3s.dtsi"
#include "sunxi-common-regulators.dtsi"
/ {
model = "BingPi M1"; // Changed model name
compatible = "bingpi,bingpi-m1", "allwinner,sun8i-v3s"; // Changed compatible to bingpi,bingpi-m1, but this shouldn't matter.
aliases {
serial0 = &uart0;
};
chosen {
stdout-path = "serial0:115200n8";
};
leds {
compatible = "gpio-leds";
blue_led {
label = "bingpi:blue:usr"; // Changed label
gpios = <&pio 6 1 GPIO_ACTIVE_LOW>; /* PG1 */
};
// Removed the two other LEDs because they don't exist on the board.
};
};
&mmc0 {
broken-cd;
bus-width = <4>;
vmmc-supply = <®_vcc3v3>;
status = "okay";
};
&uart0 {
pinctrl-0 = <&uart0_pb_pins>;
pinctrl-names = "default";
status = "okay";
};
&usb_otg {
dr_mode = "host"; // Changed from otg to host because there isn't mode detection pin.
status = "okay";
};
&usbphy {
// Removed usb0_id_det-gpios because there isn't a mode detection pin.
status = "okay";
};
We can build the image:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j
The output file is at u-boot-sunxi-with-spl.bin
. Write it to the SD card at 8192B (LBA16).
Boot the device, you should see the U-Boot shell.
Note that this offset conflicts with GPT header. If you want to use GPT, change number of entries from 128 to 16, so the first usable LBA is 16 (8192Bytes). You can also add a partition from LBA16 to LBA2047 to “protect” anyone from overwritting the SPL by accident. Because U-Boot auto boots the first partition by default, I tweaked so that the rootfs is the first partition, although it is below the first reserved partition.
Linux v6.9 #
First, make a copy of sun8i-v3s-licheepi-zero.dts
and name it sun8i-v3s-bingpi.dts
.
We can also add that to arch/arm/boot/dts/allwinner/Makefile
so it will be automatically
built:
diff --git a/arch/arm/boot/dts/allwinner/Makefile b/arch/arm/boot/dts/allwinner/Makefile
index 2d26c3397f14..82d93494b57e 100644
--- a/arch/arm/boot/dts/allwinner/Makefile
+++ b/arch/arm/boot/dts/allwinner/Makefile
@@ -259,7 +259,8 @@ dtb-$(CONFIG_MACH_SUN8I) += \
sun8i-v3s-anbernic-rg-nano.dtb \
sun8i-v3s-licheepi-zero.dtb \
sun8i-v3s-licheepi-zero-dock.dtb \
- sun8i-v40-bananapi-m2-berry.dtb
+ sun8i-v40-bananapi-m2-berry.dtb \
+ sun8i-v3s-bingpi.dtb
dtb-$(CONFIG_MACH_SUN8I) += \
sun8i-a23-evb.dtb \
sun8i-a23-gt90h-v4.dtb \
@@ -321,7 +322,8 @@ dtb-$(CONFIG_MACH_SUN8I) += \
sun8i-v3-sl631-imx179.dtb \
sun8i-v3s-licheepi-zero.dtb \
sun8i-v3s-licheepi-zero-dock.dtb \
- sun8i-v40-bananapi-m2-berry.dtb
+ sun8i-v40-bananapi-m2-berry.dtb \
+ sun8i-v3s-bingpi.dtb
dtb-$(CONFIG_MACH_SUN9I) += \
sun9i-a80-optimus.dtb \
sun9i-a80-cubieboard4.dtb
Then, use sunxi_defconfig
. It enables all drivers we need and compiles them in without
using modules:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- sunxi_defconfig
Next, edit the dts. I highlighted everywhere I modified.
// Copied from sun8i-v3s-licheepi-zero.dts. Only changes are highlighted.
/dts-v1/;
#include "sun8i-v3s.dtsi"
#include "sunxi-common-regulators.dtsi"
/ {
model = "BingPi M1"; // Changed model name
compatible = "bingpi,bingpi-m1", "allwinner,sun8i-v3s"; // Changed compatible to bingpi,bingpi-m1, but this shouldn't matter.
aliases {
serial0 = &uart0;
ethernet0 = &emac; // Enable ethernet, copied from licheepi-zero-dock.dts
};
chosen {
stdout-path = "serial0:115200n8";
};
leds {
compatible = "gpio-leds";
blue_led {
label = "bingpi:blue:usr"; // Changed label, this will appear as /sys/class/leds/bingpi:blue:usr.
gpios = <&pio 6 1 GPIO_ACTIVE_LOW>; /* PG1 */
};
// Removed other two LEDs.
};
// Treat the user button as a keyboard input event. You need to manually enable relevant kernel
// modules for gpio-keys to work.
gpio-keys {
compatible = "gpio-keys";
autorepeat;
usr_btn {
label = "bingpi:usr";
gpios = <&pio 6 5 GPIO_ACTIVE_LOW>; /* PG5 */
linux,code = <103>;
autorepeat;
};
};
};
&mmc0 {
broken-cd;
bus-width = <4>;
vmmc-supply = <®_vcc3v3>;
status = "okay";
};
&uart0 {
pinctrl-0 = <&uart0_pb_pins>;
pinctrl-names = "default";
status = "okay";
};
&usb_otg {
dr_mode = "host"; // Changed from otg to host because there isn't mode detection pin.
status = "okay";
};
&usbphy {
// Removed usb0_id_det-gpios because there isn't a mode detection pin.
status = "okay";
};
// Enable ethernet, copied from licheepi-zero-dock.dts
&emac {
allwinner,leds-active-low;
status = "okay";
};
// Enable USB 2.0
&ehci {
status = "okay";
};
To find which kernel modules are required to drive something, lookup the source
code by compatible =
strings. These tags are used by the kernel to find relevant
compatible kernel modules to load for each node in the DTS. The top-level
compatible = "bingpi,bingpi-m1", "allwinner,sun8i-v3s";
doesn’t really matter, though.
Then, lookup which Kconfig option enables building the relevant source files from the Makefile’s.
Lastly, enable the options and rebuild the kernel.
For example, to enable gpio-keys
, we can grep(1) gpio-keys
from the kernel
source tree, which gives us drivers/input/keyboard/gpio_keys.c
. Then, search gpio_keys.o
gives us drivers/input/keyboard/Makefile
, which has an option CONFIG_KEYBOARD_GPIO
that builds gpio_keys.c
. Enable that in nconfig.
Finally, build the kernel:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
Whenever you change the dts, you can rebuild with the dtbs
target without building
the kernel again.
Lastly, put the kernel (arch/arm/boot/zImage
) and dtb (arch/arm/boot/dts/allwinner/sun8i-v3s-bingpi.dtb
)
to a partition on the SD card. Obviously, the file system has to be supported by the U-Boot,
and the default U-Boot configuration enables ext4. I put them into /boot/
. As usual,
supply a minimal rootfs for your embedded system.
Boot the device and run:
setenv bootargs console=ttyS0,115200 root=/dev/mmcblk0p1 init=/bin/sh rootwait
load mmc 0:1 0x43000000 boot/sun8i-v3s-bingpi.dtb
load mmc 0:1 0x42000000 boot/zImage
bootz 0x42000000 - 0x43000000
to start the kernel. If everything works, you should see the shell.
References #
- My friend: Lots of help!
- Icenowy: It’s impossible to bring up mainling kernel on V3s without her extensive work on the device trees (she made most dts on V3s).
- Board vendor’s quick start guide on how to build U-Boot and Linux.
- Board schematics.
- https://linux-sunxi.org/U-Boot
- https://linux-sunxi.org/Mainline_Kernel_Howto