A step-by-step guide to bringing up your NXP i.MX8

Date: June 14, 2021

Author: Jitheesh T

Human Machine Interface (HMI) has become one of the most important technologies of Industrial Automation. Thus. It's not surprising to see several companies coming up with innovative platforms aimed at easing the interaction between humans and machines. An HMI, which can either be software or a device, enables a user to communicate efficiently with a machine. Thus, its popularity in the industrial automation landscape seems pretty reasonable.

HMI in Industrial Automation

Resources like I/O, SoftPlc CoDeSys or Ethercat and Embedded Operating Systems allow a user to communicate with any production system, enabling visualization and control of applications. An HMI device is usually built to suit the requirement of the industrial plant. Thus, an HMI may widely vary in processors, connectivity, memory, display technologies etc. HMIs are available in various forms, shapes and sizes. They can either be seen as a computer monitor or as built-in screens on machines or even as a tablet.

In an industrial environment, HMI in Industrial Automation is mostly used for – Overseeing KPIs; Monitoring I/Os; Visualization of data; tracking production time, trends and tags etc.

Role of i.MX8 in HMI for Industrial Automation

Reference - https://www.nxp.com/docs/en/fact-sheet/IMX8FAMFS.pdf

What makes i.MX8 ideal for Industrial HMI?

  1. One single processor can accommodate multiple platforms and multiple operating systems
  2. Provides exceptional security with SECO HSM
  3. Improves reliability of your system with FDSOI
  4. Capable of creating advanced vision-based HMIs

iMX8 processor is the latest in the i.MX series of processors from NXP.  Major components in iMX8 are System Control Unit (SCU), Cortex-A based multi-core application processor, Cortex-M based application processor and other dedicated processors for imaging, video and graphics processing.

System Control Unit (SCU) and two application platforms based on Cortex-A and Cortex-M4 are the major components. The SCU itself contains multiple components such as a Cortex-M4 processor, a dedicated security controller (SECO) that performs security-related functionalities. SCU is responsible for booting the system and managing power/clock and reset of internal subsystems. It's the presence of a multi-core Cortex-A processor, that makes i.MX8 suitable for high-performance applications. Further, the presence of Multimedia processors makes it suitable 

for advanced graphics, imaging, audio, voice and video applications. Hence processors from the i.MX8 family are used by a variety of industries around the world. It may be noted here that a lot of products based on iMX6 are getting ported to the iMX8 platform.

At PathPartner, we help you build robust applications with i.MX8. As the first in the series of articles dedicated to i.MX8 architecture, we will provide information on custom board bringup.This article will help you in understanding i.MX8 boot architecture and software components involved in each boot phase.

Board Bringup for Custom Boards on i.MX8

i.MX8 has multiple boot stages and hence board bring-up needs to be performed at various levels. However, delving into the entire boot process is beyond the scope of this article, for which you can refer to this document from the NXP community. This article primarily focuses on bring-up of interfaces such as DDR, eMMC, SCFW, uBoot and Kernel.

The following block diagram shows the minimum interfaces (or peripherals) required during the custom board bring-up. These modules should be brought up as part of the bringup process.

Understanding i.MX8 Boot Flow Sequence

The below diagram shows the basic boot flow for the iMX8 platform.  On Power-on-Reset, iMX8 ROM bootloader is the first piece of software that gets executed. The ROM bootloader has two separate images called SCU ROM and SECO ROM.

i.MX8 Software Components

Now, with a basic understanding of the boot flow sequency, let us look at the various software components involved in the boot process. After SCFW and SECO comes the Cortex-A/M4 images. Based on the intended application one can use either Cortex-A or Cortex-M4 or both together. This article focuses only on booting the Cortex-A and thus, Cortex-M4 will be left unused.

Cortex-A image is nothing but a primary bootloader, called uboot. Another software component called Arm Trusted Firmware (bl31.bin provided by NXP) comes into the picture when we use uboot. 

Summarizing the software components,

  • SCFW - System Controller Firmware binary
  • SECO FW - Security Controller Firmware
  • bl31.bin - ARM Trusted Firmware (ATF)
  • u-boot.bin - U-boot binary for Cortex-A

The above-mentioned software components are appended together to have a single bootable image called flash.bin. NXP provides a tool (mkimage tool) to merge all the images mentioned above to create a bootable image. Along with the images discussed above, flash.bin also contains several information required to load/authenticate all images to respective core/memory (data such as image offset, size, load address, entry point etc., for each image), flags to control the behavior of SCFW execution. However, this article doesn’t cover a detailed explanation of what is inside the bootable image (also called boot container). This bootable image can be loaded to either eMMC/SD card.

The next question is, where do we load the flash.bin in the eMMC/SD card (please note that our discussion in this article is limited to eMMC/SD card boot)?  That would be the specific locations mentioned by NXP.

The below table shows the location to load the flash.bin

Boot Device Image Offset
eMMC 0, if located in the Boot Partition 32KB, if located in the User Area

But, how do we write into eMMC since we are not even ready to boot the board yet? During board bring-up, it is advisable to use an SD card as the bootable device. With the instructions given above, flash.bin can be copied to SD card at the 32KB offset.

Now that we are ready with a bootable image on SD Card, this bootable card can be inserted into the SD card slot in the hardware. ROM bootloader is responsible for reading the flash.bin image from SD card and loading SCFW and SECO FW to SCU.

Considerations Required for Customization

The previous sections familiarized us with boot stages, software components and how to flash fresh new board, now let’s see how to use these to boot your custom board? What components need to be customized? There are few basic things to keep in mind while customizing the software.

  • SECO FW and Arm Trusted Firmware provided by NXP can be used directly
  • SCFW customization – Did you change any core critical signals from the reference EVK of NXP? Do you want to implement something intelligent logic for determining the HW configuration?
  • DCD file customization - Did you change the DDR from reference EVK? You want to update/initialize some arguments for your u-Boot to consider? 
  • Proper configuration of the serial port used as debug console. 
  • Configuring required interfaces (eMMC/SD interface) for the next level of firmware loading (kernel in our case)

i.MX8 Power checkup

The first stage of booting happens from ROM and hence required no customization. But for the ROM bootloader to boot correctly, proper power needs to be provided to i.MX8. This usually is the responsibility of a hardware engineer to ensure that the custom board power circuitry is properly designed and verified.

To manage the distribution of power to various parts of the system, PMIC is one of the major components in the i.MX8 power circuitry. PMIC will provide a power supply of varying voltages to different peripherals/subsystems, including the Cortex-A/M4 cores, GPU, DDR and other peripherals. Thus, ensuring that required voltage levels are available for each peripheral/subsystem for the proper system boot.

About DDR Configuration

Once we have a proper power supply to i.MX8, the ROM bootloader will boot and load SCFW to TCM and Cortex-A/M4 image to DDR memory. Before loading Cortex-A/M4 images to DDR memory, DDR has to be configured properly, which is done by the SCU ROM. But, how is DDR configured? DDR configuration is available in a file called DCD file and is part of the SCFW. SCU ROM will read this file and configure DDR. Hence any customization needed for DDR can be accommodated in the DCD file.

DCD file which defines the DDR configuration might need modification if the DDR memory used is different. For DDR Configuration, NXP provides a DDR Register Programming Aid (RPA) in the form of an excel sheet. Users need to update the Device information table in the RPA register configuration worksheet tab. After filling in the device information, the configuration value will be calculated automatically in the same worksheet tab. This can be used as a basic DDR configuration. One such document on RPA can be found here.

Further, DDR timings can be fine-tuned using the DDR stress test tool. Please refer to section 3.2.1 of the BSP porting guide for more details on DDR configuration.

Understanding SPL Boot Flow

For most of the custom hardware, NXP provided SCFW can be used. In case if any customization is needed, please refer to the Advanced SCFW article. However, I would like to discuss a Special boot flow (in contrast to what we discussed in the Boot Flow section). As we discussed in the Boot Flow section, DDR initialization is performed by SCU ROM, which means the user has no control over the DDR initialization. In some cases, if we want to do the DDR initialization from SCFW (which users can customize) instead of doing it from SCU ROM, NXP has an option for this too, which is called Special (SPL) boot flow.

If we are using SPL Boot Flow, it is evident that DDR is not initialized in SCU ROM. So how do we load the application (Cortex-A) images to DDR without initializing it? In the SPL Boot Flow, SPL image (which will be part of Cortex-A uboot image) will take care of loading application images to DDR after enabling the Cortex-A core from SCFW. So SCU ROM will only be responsible for loading SCFW and SECO FW to memory.

How do we enable SPL Boot Flow? What are the steps to be followed?

Well, the answer is, this has to be done during the creation of the bootable image. Even though SCU ROM does DDR Initialization in Non-SPL Boot mode, the API to perform the initialization is defined in the SCFW board file. The board file is located at platform/board/mx8*/ directory. The API used is board_init_ddr() and which internally calls board_ddr_config(). Following are the steps to required to enable SPL Boot mode.

  • Define SKIP_DDR inboard.h file. This will skip the DDR initialization during SCU ROM execution. Refer board_init_ddr() API inboard.c to know how this works.
  • Remove #include “dcd/dcd.h” from  board_ddr_config() API
  • Create a copy of board_init_ddr() API and rename it to something like board_init_ddr_custom(). Also remove the section containing SKIP_DDR from board_init_ddr_custom() API.
  • Invoke #include "dcd/dcd.h" and board_init_ddr_custom() from board_system_config() API. board_system_config() is used by SCFW
  • Finally, build the bootable image (flash.bin) using an additional argument to imx-mkimage utility called flash_spl. Example make options would look like, make SOC=iMX8QX flash_spl (for i.MX8QXP)

As shared in the boot sequence, once we move from the BOOTROM code to custom built software components, we should be able to bring-up the high-level software components – uBoot and Kernel more easily. Customization and bring-up of the same will be discussed in <place holder for uBoot and kernel bringup>

About Uboot bring-up

Uboot source code from NXP has support for multiple reference boards based on iMX8, such as imx8qm_mek, imx8qxp_mek, etc... i.MX reference board files located in different directories listing below.

  • Configuration file (defconfig file) is available in configs/ directory
  • Board file inboard/freescale/
  • Board config file in include/configs/

We need to identify the reference board (from NXP), which is most suitable for our custom board. Duplicate the files (of the matching reference board) from the above directories and rename them to match your custom board. Now we have our files which we are going to customize based on our hardware design.

For example, let us make a copy of imx8qxp_mek reference board. Files/Directories need to be duplicated are.

  • configs/imx8qxp_mek_defconfig
  • arch/arm/dts/fsl-imx8qxp-mek.dts
  • board/freescale/imx8qxp_mek/ directory
  • include/configs/imx8qxp_mek.h file

These can be duplicated as below.

  • configs/imx8qxp_cutsom_defconfig
  • arch/arm/dts/fsl-imx8qxp-custom.dts
  • board/freescale/imx8qxp_custom/ directory
  • include/configs/imx8qxp_custom.h file
Board file customization

At the early stages of board bring-up, it is essential to know what is happening in the system. Hence the serial debug console has to be brought up first. So, what customization we need to do to make the serial port work? 

Uboot provides initialization in 3 different stages. These initializations are available in imx8qxp_custom.c file.

board_early_init_f() function is called at the early phase if we define CONFIG_BOARD_EARLY_INIT_F. We will define the UART (used for debug console) pin mux configuration in this function. Most of the NXP reference board files already contain this initialization. We need to make sure that the correct UART interface (check your schematics) is initialized at this stage. With this, we have very minimal changes to make sure that our board boots at least till uboot.

Next two stages of initialization functions are board_init() and board_late_init(). We can put the driver initializations for features needed (example: Ethernet , GPIOs etc …)

Defconfig customization

Defconfig need not be customized at this stage. This can be customized to enable/disable based on the features needed once the basic bring-up is completed. However, make sure that features required for bring-up are enabled.

DTS customization

The device tree is used to enable drivers for required interfaces and also to do the pin mux configuration for the interfaces. The role of the uboot is to prepare the system for loading the kernel image.

So, what things need to be customized for this? Kernel image is going to be placed in one of the partitions in the eMMC/SD card. Hence, the basic interface that needs to be initialized in uboot is the eMMC/SD card interface called the SDHC interface. So the customization required in the device tree file is to make sure that SDHC driver for eMMC/SD card interface is enabled. Also, we are going to put the pin mux configuration for the hardware lines involved. Please do a basic investigation to understand the protocol and how the communication works before making these changes.

Now the uBoot is ready to communicate with eMMC/SD card with the proper drivers.

Board config file customization

Even though the eMMC/SD card interface is up, how do we know from which location to read the kernel image and kernel dtb from the media. This information can be added in the config file. Let us look at a code snippet from board config file,

#define CONFIG_SYS_MMC_IMG_LOAD_PART 1 /* Partition-1 */
#define CONFIG_MMCROOT "/dev/mmcblk1p2" /* USDHC2 */
"image=kernel.bin\0" \
"console=ttyLP0\0" \
"earlycon=lpuart32,0x5a060000\0" \
"fdt_addr=0x83000000\0" \
"fdt_high=0xffffffffffffffff\0" \
"fdt_file=fsl-imx8qxp-mek.dtb\0" \
"mmcdev="__stringify(CONFIG_SYS_MMC_ENV_DEV)"\0" \
"mmcpart=" __stringify(CONFIG_SYS_MMC_IMG_LOAD_PART) "\0" \
"mmcroot=" CONFIG_MMCROOT " rootwait rw\0" \
"mmcargs=setenv bootargs console=${console},${baudrate} earlycon=${earlycon},${baudrate} root=${mmcroot}\0 " \
"loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}\0" \
"loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}\0" \
  • mmcdev - selection between eMMC/SD card. USDHC1 is used for eMMC and USDHC2 for SD Card. Make the right selection based on your requirement. However, during the bring-up phase, let us use the SD card interface.
  • mmcpart - represents the partition from which the kernel image can be loaded. Let us put the kernel image and dtb in partition-1
  • image - Kernel image file name
  • fdt_file - dtb filename
  • loadimage - command to load the kernel image
  • loadfdt - command to load the dtb file

If you have noticed loadimage and loadfdt uses fatload command to load the kernel and dtb. So, make sure that the partition where kernel & dtb are copied should be formatted as FAT. fatload can be replaced with ext2load if you want the partition to be EXT4.

The parameters mentioned above set up the uboot to read kernel image and dtb to memory. Now before we start executing the kernel, we can pass some parameters from uboot to kernel. Basic parameters to be passed are.

  • console - represents the serial debug console. Ensure the proper console parameter is set so that we can see the debug prints during kernel boot-up.
  • mmcroot - defines the root filesystem (RFS) partition. Kernel will use this information to load the RFS.

These parameters are passed through bootargs (see mmcargs in the above code snippet). It is possible that any information can be passed from uboot to kernel with this method. For example, any board-related information already retrieved by uboot from the hardware can be passed to the kernel.

Board config file also include configuration information for DDR location and size. Make sure these configurations match with your custom hardware. DDR configuration information from iMX8QXP MEK board config file is given below.

#define CONFIG_SYS_SDRAM_BASE 0x80000000
#define PHYS_SDRAM_1 0x80000000
#define PHYS_SDRAM_2 0x880000000
#define PHYS_SDRAM_1_SIZE 0x80000000 /* 2 GB */
/* LPDDR4 board total DDR is 3GB */
#define PHYS_SDRAM_2_SIZE 0x40000000 /* 1 GB */

Kernel bringup

Kernel bring-up mainly involves customizing the device tree file. We can create a duplicate of one of the reference board device tree as we did in uboot. So let us make a copy of arch/arm64/boot/dts/freescale/fsl-imx8qxp-mek.dtsi and rename it to arch/arm64/boot/dts/freescale/fsl-imx8qxp-custom.dtsi

Minimum required driver/pinmux configurations in the device tree are for eMMC/SD, UART (debug) interfaces. This will ensure that the user can get the debug prints on the console. Also, the kernel can access the eMMC/SD memory and load the RFS from the partition as specified in the previous section.

The next stage in kernel bring-up is to make sure that all the devices connected to the i.MX8 processor in your custom board should be functional. This can be achieved by modifying the device tree by enabling the proper device tree nodes and defining the correct pin mux configuration for all the hardware lines involved. Since this part is generic for any platform and hence, I am not getting into the details. However, I would like to share one of the fundamental problems we faced in our custom board bring-up.

Most of the problems we faced are either a wrong/missing interface configuration or a bug in the hardware.

Unable to get serial console login access

After the kernel loads the RFS, login services will run to get the serial console login access. Without getting access to the system, it is challenging to debug during bring-up.

The missing piece was the UART configuration. The reference code we used had a different UART port (UART0) enabled for the serial console. But our custom board was using UART1; hence UART1 device tree node should be enabled and configured correctly. Also, make sure the pin mux configuration for all the UART lines are done properly. Always check your hardware schematics while doing the configuration. UART lines may include RTS/CTS lines also apart from RX/TX lines based on your hardware design.

There are many other issues we faced, but most of them related to hardware bugs. I am not going to explain all those issues in this article. But always keep in mind that the critical thing in debugging any hardware interface is understanding the protocol first. Also, make sure that all the hardware lines behave properly during idle and run-time. Happy debugging :)

About eMMC Booting

Till now, we have discussed booting from SD Card. Once the bring-up is completed we can move on and boot from eMMC.

ROM bootloader will take care of booting the Uboot image from eMMC with proper boot mode selection. With eMMC we have options for booting either from boot partitions (mmcblk0boot0/mmcblk0boot1) called as fast boot or from user partitions (mmcblk0). For more information on fast/regular boot selection, please refer eMMC Fast Boot in i.MX 8X article.

Now to boot kernel and load RFS from eMMC, the configuration has to be done at uboot level. For this purpose, uboot board config file needs to be customized. Please refer uboot bring-up section for detailed information on loading kernel, dtb and RFS from eMMC partitions.


Generally, we are familiar with uBoot and kernel debug console. But with low level software components that are interlinked with BOOTROM, you might be puzzled for debugging. NXP i.MX8

platform has a dedicated UART that can be configured and used for debugging SCFW. If the need arises to use this UART only for SCFW, ensure that the corresponding SoC pad is configured as immovable in SCFW. Also, make sure that the same pad is not used for any other purpose by the application cores in the hardware design.

Multiple UARTs available for application cores. Any one of these can be configured as a serial console in the hardware design and can be used for serial debugging purposes. 

To debug most of the issues in the kernel, always try to enable the debug option in the bootargs. This will enable additional debugging information on the serial console, which might be helpful.


i.MX8 family has a different booting procedure when compared to previous i.MX SOC versions. This article provided hardware and software details required for a custom board. It also covered various debugging techniques, customizations and technical challenges faced during our development. We hope you find this article helpful in building an application using i.MX8 architecture.

For any questions, please write to us at marcom@pathpartnertech.com

By submitting this form, you authorize PathPartner to contact you with further information about our relevant content, products and services. You may unsubscribe any time. We are committed to your privacy. For more details, refer our Privacy Policy

Notify of
Inline Feedbacks
View all comments
Back to Top