Custom Board & Architecture Config
About 2277 wordsAbout 8 min
2026-03-23
Overview
Adding a custom board or custom SoC architecture support to U-Boot involves several layers:
- Architecture layer (
arch/) — Only if you have a new CPU/ISA (rare) - SoC layer (
arch/<arch>/mach-<soc>/) — SoC-specific initialization - Board layer (
board/<vendor>/<board>/) — Board-specific peripherals - Device Tree (
arch/<arch>/dts/) — Hardware description - Kconfig/defconfig — Build-time configuration
This guide walks through creating a complete custom board called myboard based on a hypothetical ARM Cortex-A55 SoC called mysoc (similar to what you would do for NXP i.MX8M, Rockchip RK356x, TI AM62x, etc.).
Step 1: Understand the Existing SoC Support
Before creating your board, check if your SoC is already supported:
# Find existing SoC support
ls arch/arm/mach-*/
# Check configs directory for similar platforms
ls configs/ | grep -i "imx8\|rk35\|am62"
# Find existing board code as a reference
find board/ -name "*.c" | xargs grep -l "board_init" | head -5For this guide, assume:
- SoC:
ARM Cortex-A55dual-core @ 1.5 GHz - DRAM: 1 GB LPDDR4 at 0x40000000
- eMMC: HS400, SDHCI controller
- UART: NS16550-compatible at 0x30860000
- Ethernet: Designware GMAC at 0x30BE0000
- SPL: Loaded into 256 KB SRAM at 0x00100000
- U-Boot: Relocated to top 4 MB of DRAM
Step 2: Create the Board Directory
mkdir -p board/myvendor/myboardboard/myvendor/myboard/Makefile
# board/myvendor/myboard/Makefile
obj-y += myboard.o
obj-$(CONFIG_SPL_BUILD) += spl.oboard/myvendor/myboard/Kconfig
# board/myvendor/myboard/Kconfig
if TARGET_MYBOARD
config SYS_BOARD
default "myboard"
config SYS_VENDOR
default "myvendor"
config SYS_SOC
default "mysoc"
config SYS_CONFIG_NAME
default "myboard"
endifStep 3: Create the Board C File
board/myvendor/myboard/myboard.c
// board/myvendor/myboard/myboard.c
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2026 My Company
* Author: Your Name <your.email@company.com>
*
* Board file for MyBoard (MySoC Cortex-A55)
*/
#include <common.h>
#include <env.h>
#include <fdt_support.h>
#include <hang.h>
#include <init.h>
#include <log.h>
#include <mmc.h>
#include <net.h>
#include <phy.h>
#include <asm/arch/clock.h>
#include <asm/arch/ddrphy.h>
#include <asm/arch/gpio.h>
#include <asm/arch/sys_proto.h>
#include <asm/io.h>
#include <dm/uclass.h>
#include <linux/delay.h>
DECLARE_GLOBAL_DATA_PTR;
/*
* board_early_init_f() - called very early, before DRAM init
* Use: Configure mux, clocks, debug UART if needed before SPL console
*/
int board_early_init_f(void)
{
/* Enable UART clock and configure pin mux */
mysoc_uart_init(CONFIG_DEBUG_UART_BASE);
/* Configure power domains needed for early init */
mysoc_power_domain_up(PD_UART0);
mysoc_power_domain_up(PD_TIMER0);
return 0;
}
/*
* board_init() - post-relocation board initialization
* Called after U-Boot has relocated to DRAM and DM is up.
*/
int board_init(void)
{
/* If using an NXP-style architecture, save boot mode */
/* save_boot_params_ret: see arch/arm/cpu/armv8/start.S */
/* Configure LED GPIOs */
gpio_request(GPIO_LED_STATUS, "led-status");
gpio_direction_output(GPIO_LED_STATUS, 1);
/* Example: configure USB PHY power */
gpio_request(GPIO_USB_PWR_EN, "usb-pwr-en");
gpio_direction_output(GPIO_USB_PWR_EN, 1);
udelay(10000); /* 10 ms settle time */
return 0;
}
/*
* dram_init() - mandatory: set gd->ram_size
* Called during board_init_f pre-relocation.
*/
int dram_init(void)
{
/* Use get_ram_size() to probe actual DRAM
* or just trust hardware configuration */
gd->ram_size = SZ_1G; /* 1 GB */
return 0;
}
/*
* dram_init_banksize() - fill in bd->bi_dram[]
*/
int dram_init_banksize(void)
{
gd->bd->bi_dram[0].start = CFG_SYS_SDRAM_BASE; /* 0x40000000 */
gd->bd->bi_dram[0].size = gd->ram_size;
return 0;
}
/*
* board_late_init() - last chance before main_loop()
* Use: Set env variables, detect boot device, configure bootcmd.
*/
int board_late_init(void)
{
uint32_t boot_mode;
/* Detect boot source */
boot_mode = mysoc_get_boot_mode();
switch (boot_mode) {
case BOOT_FROM_EMMC:
env_set("bootdev", "mmc 0");
env_set("devnum", "0");
env_set("devtype", "mmc");
break;
case BOOT_FROM_SD:
env_set("bootdev", "mmc 1");
env_set("devnum", "1");
env_set("devtype", "mmc");
break;
case BOOT_FROM_NET:
env_set("bootdev", "net");
break;
}
/* Set board revision in env */
char rev[8];
snprintf(rev, sizeof(rev), "%d", mysoc_get_board_rev());
env_set("board_rev", rev);
/* Set MAC address from fuses if not already set */
if (!env_get("ethaddr")) {
u8 mac[6];
mysoc_read_mac_from_fuse(mac);
eth_env_set_enetaddr("ethaddr", mac);
}
return 0;
}
/*
* misc_init_r() - optional: miscellaneous post-relocation init
*/
int misc_init_r(void)
{
/* Example: initialize an external PMIC via I2C */
return 0;
}
/*
* ft_board_setup() - called to modify device tree before passing to kernel
* @blob: pointer to FDT blob in DRAM (modifiable copy)
* @bd: board info
*/
int ft_board_setup(void *blob, struct bd_info *bd)
{
/* Fixup memory nodes */
fdt_fixup_memory(blob, CFG_SYS_SDRAM_BASE, gd->ram_size);
/* Modify MAC address in Ethernet node */
u8 mac[6];
eth_env_get_enetaddr("ethaddr", mac);
do_fixup_by_compat(blob, "snps,dwmac-4.10a",
"local-mac-address", mac, 6, 1);
/* Example: disable a node based on hardware strap */
if (!mysoc_has_wifi()) {
int off = fdt_path_offset(blob, "/wifi");
if (off > 0)
fdt_set_node_status(blob, off, FDT_STATUS_DISABLED);
}
return 0;
}
/*
* checkcpu() - optional: print CPU info
*/
int checkcpu(void)
{
printf("SoC: MySoC Dual Cortex-A55 @ 1.5 GHz\n");
return 0;
}
/*
* show_board_info() - optional: print board info
*/
int show_board_info(void)
{
printf("Board: MyBoard v%d\n", mysoc_get_board_rev());
return 0;
}
/*
* print_cpuinfo() - optional: called during init_sequence_f
*/
int print_cpuinfo(void)
{
printf("CPU: Cortex-A55 dual-core, %d MHz\n",
mysoc_get_cpu_freq() / 1000000);
return 0;
}Step 4: Create the SPL Board File
board/myvendor/myboard/spl.c
// board/myvendor/myboard/spl.c
// SPDX-License-Identifier: GPL-2.0+
#include <common.h>
#include <hang.h>
#include <init.h>
#include <log.h>
#include <spl.h>
#include <asm/arch/clock.h>
#include <asm/arch/ddr.h>
#include <asm/arch/sys_proto.h>
#include <asm/io.h>
/*
* board_early_init_f() in SPL context
* Init only what is needed to get SPL running:
* - watchdog (disable or feed)
* - clocks
* - debug UART
*/
void board_init_f(ulong dummy)
{
int ret;
/* Initialize global data early */
ret = spl_early_init();
if (ret) {
debug("spl_early_init() failed: %d\n", ret);
hang();
}
/* Must come very first: disable watchdog before it fires */
mysoc_wdt_disable();
/* Set up PLLs/clocks for DRAM init */
mysoc_clock_init();
/* Optional: early UART for SPL debug */
preloader_console_init();
/* DRAM initialization — must complete before board_init_r */
ret = mysoc_ddr_init();
if (ret) {
puts("DDR init failed!\n");
hang();
}
puts("DDR: ");
print_size(gd->ram_size, "\n");
}
/*
* spl_board_init() - board-level init called from SPL common code
* after board_init_r() has set up DM, serial, etc.
*/
void spl_board_init(void)
{
/* Initialize eMMC PHY before SPL tries to read from it */
mysoc_emmc_phy_init();
}
/*
* spl_boot_device() - tell SPL where to load next stage from
*/
u32 spl_boot_device(void)
{
/* Read boot mode straps */
uint32_t bmode = mysoc_get_boot_mode();
switch (bmode) {
case BOOT_FROM_EMMC:
return BOOT_DEVICE_MMC1; /* eMMC on MMC1 */
case BOOT_FROM_SD:
return BOOT_DEVICE_MMC2; /* SD on MMC2 */
case BOOT_FROM_SPI_NOR:
return BOOT_DEVICE_SPI;
case BOOT_FROM_UART:
return BOOT_DEVICE_UART;
default:
puts("Unknown boot device, defaulting to MMC1\n");
return BOOT_DEVICE_MMC1;
}
}
/*
* spl_mmc_emmc_boot_partition() - select eMMC boot partition
*/
int spl_mmc_emmc_boot_partition(struct mmc *mmc)
{
/* Boot from eMMC boot partition 1 (index 1) */
return 1; /* 0 = user, 1 = boot1, 2 = boot2 */
}
/*
* board_fit_config_name_match() - select FIT config from SPL
* Called when loading a FIT image to match board-specific config node.
*/
int board_fit_config_name_match(const char *name)
{
/* Accept any FIT config starting with "myboard" */
if (!strncmp(name, "myboard", 7))
return 0;
return -1;
}
/*
* spl_perform_fixups() - modify FDT passed to U-Boot Proper
* Only called if CONFIG_SPL_OF_CONTROL=y and CONFIG_SPL_STANDALONE_LOAD_ADDR
*/
void spl_perform_fixups(struct spl_image_info *spl_image)
{
/* Can patch FDT here if needed before U-Boot Proper gets it */
}Step 5: Create the SoC Architecture Layer
If your SoC does not have existing U-Boot support, you also need to add it under arch/arm/mach-mysoc/:
mkdir -p arch/arm/mach-mysocarch/arm/mach-mysoc/Makefile
obj-y += clock.o
obj-y += ddr.o
obj-y += sys_proto.o
obj-$(CONFIG_SPL_BUILD) += spl.oarch/arm/mach-mysoc/Kconfig
if ARCH_MYSOC
choice
prompt "MySoC variant"
optional
config TARGET_MYSOC_EVK
bool "MySoC EVK Reference Board"
select BOARD_LATE_INIT
select SUPPORT_SPL
imply CMD_FUSE
help
MySoC evaluation kit reference board.
config TARGET_MYBOARD
bool "MyBoard custom board"
select BOARD_LATE_INIT
select SUPPORT_SPL
help
Custom board based on MySoC.
endchoice
config SYS_SOC
default "mysoc"
source "board/myvendor/myboard/Kconfig"
endif
config ARCH_MYSOC
bool "MySoC SoC family"
select ARM64
select DM
select DM_GPIO
select DM_I2C
select DM_MMC
select DM_SERIAL
select DM_SPI
select OF_CONTROL
select OF_LIVE
select PINCTRL
select SUPPORT_SPL
select SYS_MALLOC_F
select SYS_MALLOC_F_LEN=0x10000
help
Enable support for MySoC series SoC family.arch/arm/Kconfig — Hook your SoC in
# In arch/arm/Kconfig, add:
source "arch/arm/mach-mysoc/Kconfig"Step 6: Create the Device Tree
arch/arm/dts/mysoc-myboard.dts
// arch/arm/dts/mysoc-myboard.dts
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/dts-v1/;
#include "mysoc.dtsi" /* SoC-level DTS include */
#include "mysoc-mysoc-pins.dtsi" /* pin mux include */
/ {
model = "MyVendor MyBoard";
compatible = "myvendor,myboard", "myvendor,mysoc";
chosen {
stdout-path = &uart0;
/* U-Boot specific: tell U-Boot where console is */
};
memory@40000000 {
device_type = "memory";
reg = <0x0 0x40000000 0x0 0x40000000>; /* 1 GB at 0x40000000 */
};
/* Board-specific fixed regulators */
reg_3v3: regulator-3v3 {
compatible = "regulator-fixed";
regulator-name = "VCC_3V3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
reg_1v8: regulator-1v8 {
compatible = "regulator-fixed";
regulator-name = "VCC_1V8";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
regulator-always-on;
};
/* LED */
leds {
compatible = "gpio-leds";
status-led {
label = "status";
gpios = <&gpio3 14 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
};
};
/* Override SoC defaults for board-specific config */
&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
status = "okay";
};
&usdhc1 {
/* eMMC on USDHC1 */
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&usdhc1_pins>;
pinctrl-1 = <&usdhc1_pins_100mhz>;
pinctrl-2 = <&usdhc1_pins_200mhz>;
vmmc-supply = <®_3v3>;
vqmmc-supply = <®_1v8>;
bus-width = <8>;
non-removable;
status = "okay";
};
&usdhc2 {
/* SD card on USDHC2 */
pinctrl-names = "default";
pinctrl-0 = <&usdhc2_pins>;
vmmc-supply = <®_3v3>;
bus-width = <4>;
cd-gpios = <&gpio2 12 GPIO_ACTIVE_LOW>;
status = "okay";
};
&fec1 {
/* Ethernet (Designware GMAC) */
pinctrl-names = "default";
pinctrl-0 = <&fec1_pins>;
phy-mode = "rgmii-id";
phy-handle = <ðphy0>;
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <1>;
reset-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
reset-assert-us = <10000>;
reset-deassert-us = <300000>;
};
};
};
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <400000>;
status = "okay";
/* PMIC example */
pmic: pmic@4b {
compatible = "rohm,bd71847";
reg = <0x4b>;
pinctrl-names = "default";
pinctrl-0 = <&pmic_pins>;
interrupt-parent = <&gpio1>;
interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
rohm,reset-snvs-powered;
// ...
};
};arch/arm/dts/Makefile — Register the DTB
# In arch/arm/dts/Makefile, add:
dtb-$(CONFIG_TARGET_MYBOARD) += mysoc-myboard.dtbStep 7: Create the defconfig
configs/myboard_defconfig
# configs/myboard_defconfig
# MySoC MyBoard defconfig for U-Boot 2026.01
CONFIG_ARM=y
CONFIG_ARCH_MYSOC=y
CONFIG_TARGET_MYBOARD=y
CONFIG_ARM64=y
CONFIG_DEFAULT_DEVICE_TREE="mysoc-myboard"
# Text base: U-Boot will relocate here initially, then move to top of DRAM
CONFIG_SYS_TEXT_BASE=0x40200000
# SPL
CONFIG_SPL=y
CONFIG_SPL_TEXT_BASE=0x00100000
CONFIG_SPL_MAX_SIZE=0x40000
CONFIG_SPL_STACK=0x00140000
CONFIG_SPL_BSS_START_ADDR=0x00140000
CONFIG_SPL_BSS_MAX_SIZE=0x2000
CONFIG_SPL_DM=y
CONFIG_SPL_OF_CONTROL=y
CONFIG_SPL_SERIAL=y
CONFIG_SPL_WATCHDOG=y
CONFIG_SPL_MMC=y
CONFIG_SPL_LOAD_FIT=y
CONFIG_SPL_LOAD_FIT_ADDRESS=0x40400000
# DRAM
CONFIG_NR_DRAM_BANKS=1
# Memory
CONFIG_SYS_MALLOC_LEN=0x2000000
# Environment - stored in eMMC
CONFIG_ENV_IS_IN_MMC=y
CONFIG_ENV_SIZE=0x10000
CONFIG_ENV_OFFSET=0x400000
CONFIG_SYS_MMC_ENV_DEV=0
CONFIG_SYS_MMC_ENV_PART=1
# Boot
CONFIG_BOOTDELAY=3
CONFIG_USE_BOOTCOMMAND=y
CONFIG_BOOTCOMMAND="run distro_bootcmd"
CONFIG_DISTRO_DEFAULTS=y
# Console
CONFIG_DEBUG_UART=y
CONFIG_DEBUG_UART_NS16550=y
CONFIG_DEBUG_UART_BASE=0x30860000
CONFIG_DEBUG_UART_CLOCK=24000000
CONFIG_DEBUG_UART_SHIFT=2
# Drivers
CONFIG_DM=y
CONFIG_DM_GPIO=y
CONFIG_DM_I2C=y
CONFIG_DM_MMC=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_SDMA=y
CONFIG_MMC_SDHCI_ADMA=y
CONFIG_DM_PCI=y
CONFIG_DM_SERIAL=y
CONFIG_SERIAL_NS16550=y
CONFIG_DM_SPI=y
CONFIG_DM_ETH=y
CONFIG_DM_ETH_PHY=y
CONFIG_ETH_DESIGNWARE=y
CONFIG_PINCTRL=y
CONFIG_PINMUX=y
CONFIG_CLK=y
CONFIG_DM_REGULATOR=y
CONFIG_DM_REGULATOR_FIXED=y
CONFIG_DM_REGULATOR_GPIO=y
# Filesystems
CONFIG_FS_FAT=y
CONFIG_FAT_WRITE=y
CONFIG_FS_EXT4=y
CONFIG_EXT4_WRITE=y
CONFIG_CMD_FAT=y
CONFIG_CMD_EXT4=y
CONFIG_CMD_FS_GENERIC=y
# Network (for development / TFTP boot)
CONFIG_NET=y
CONFIG_CMD_NET=y
CONFIG_CMD_DHCP=y
CONFIG_CMD_PING=y
CONFIG_CMD_TFTPBOOT=y
# Verified Boot (FIT)
CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=y
CONFIG_RSA=y
CONFIG_RSA_VERIFY=y
CONFIG_SHA256=y
CONFIG_SHA512=y
# USB
CONFIG_USB=y
CONFIG_DM_USB=y
CONFIG_USB_XHCI_HCD=y
CONFIG_USB_STORAGE=y
CONFIG_CMD_USB=y
# EFI
CONFIG_EFI_LOADER=y
CONFIG_EFI_SECURE_BOOT=y
# Logging / Debug (reduce for production)
CONFIG_LOG=y
CONFIG_LOGLEVEL=6Step 8: Register the Board in Top-Level Kconfig
arch/arm/Kconfig (add to appropriate selection)
config TARGET_MYBOARD
bool "MyBoard (MySoC)"
depends on ARCH_MYSOC
select BOARD_LATE_INIT
select SUPPORT_SPL
help
Custom board based on MySoC ARM Cortex-A55 SoC.Step 9: Build and Verify the Custom Board
export CROSS_COMPILE=aarch64-none-linux-gnu-
# Load defconfig
make myboard_defconfig
# Optionally verify via menuconfig
make menuconfig
# Build
make -j$(nproc)
# Verify outputs
ls -lh u-boot.bin spl/u-boot-spl.bin
file u-boot
aarch64-none-linux-gnu-readelf -h u-boot | grep -E "Type|Entry"
# Verify entry point matches CONFIG_SYS_TEXT_BASE
# Entry point address: 0x40200000 for U-Boot properStep 10: Flash the Images
Flash SPL to eMMC boot partition 1
# Access eMMC on Linux host via USB OTG + ums or directly
# Enable eMMC boot partition 1 for writing:
sudo mmc bootpart enable 1 1 /dev/mmcblkX
# Write SPL to eMMC boot partition 1 (offset 0):
sudo dd if=spl/u-boot-spl.bin of=/dev/mmcblkXboot0 bs=1024 seek=0 conv=fsync
# Write U-Boot FIT image to eMMC user partition (offset 4 MB):
sudo dd if=u-boot.itb of=/dev/mmcblkX bs=1024 seek=4096 conv=fsync
# Alternatively, write to SD card for bring-up:
sudo dd if=spl/u-boot-spl.bin of=/dev/sdX bs=1024 seek=4 conv=fsync
sudo dd if=u-boot.itb of=/dev/sdX bs=1024 seek=1024 conv=fsyncCustom Architecture: Adding a New CPU Architecture
If you need to support a completely new ISA (Instruction Set Architecture) that U-Boot does not currently support:
Required Files
arch/mynewarch/
├── Kconfig # Architecture Kconfig
├── Makefile # Arch Makefile
├── config.mk # Compiler flags
├── cpu/
│ ├── Makefile
│ ├── start.S # Reset vector / entry point
│ ├── cpu.c # CPU info, reset
│ ├── cache.c # Cache management
│ └── interrupts.c # Interrupt handling
├── include/
│ └── asm/
│ ├── global_data.h # DECLARE_GLOBAL_DATA_PTR register binding
│ ├── io.h # MMIO access (readl/writel)
│ ├── processor.h # CPU-specific primitives
│ ├── sections.h # Linker section definitions
│ └── u-boot-mynewarch.lds # Linker script
└── lib/
├── Makefile
├── bootm.c # bootm kernel handoff
└── relocate.S # Relocation codeLinker Script Template (arch/mynewarch/cpu/u-boot.lds)
OUTPUT_FORMAT("elf64-mynewarch", "elf64-mynewarch", "elf64-mynewarch")
OUTPUT_ARCH(mynewarch)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
.text : {
*(.__image_copy_start)
arch/mynewarch/cpu/start.o (.text*)
*(.text*)
}
. = ALIGN(8);
.rodata : { *(.rodata*) }
. = ALIGN(8);
.data : { *(.data*) }
. = ALIGN(8);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(8);
__image_copy_end = .;
.bss (NOLOAD) : {
__bss_start = .;
*(.bss*)
. = ALIGN(8);
__bss_end = .;
}
}arch/mynewarch/include/asm/global_data.h
#ifndef __ASM_MYNEWARCH_GLOBAL_DATA_H
#define __ASM_MYNEWARCH_GLOBAL_DATA_H
/* Define which register stores the gd pointer.
* Choose a callee-saved register per the ABI. */
#define DECLARE_GLOBAL_DATA_PTR \
register volatile gd_t *gd asm("a9")
#include <asm-generic/global_data.h>
#endifTop-level arch/Kconfig — Register the arch
# In arch/Kconfig:
config MYNEWARCH
bool "MyNewArch"
select SYS_SUPPORTS_LITTLE_ENDIAN
select DM
select OF_CONTROL
help
Support for MyNewArch instruction set architecture.