Embed M7 firmware in TorizonCore and load it automatically on Verdin iMX8M-Mini

Hi @vix,

Thanks for sharing. I will try to change the UART on my side and update you if I have any news.

Best Regards,

I’ve just found the possible reason for the issue.
It’s not the MPU, but it’s the RDC (Resource Domain Controller).
The register RDC_PDAP105 which is the one for UART2 on the Plus (see i.MX 8M Plus Applications Processor Reference Manual - 3.2.5 RDC Memory Map/Register Definition) has a reset value of 0x000000FF, but when I connect my JTAG (after having halted the U-Boot) I see it has a value of 0x00000003.
On the other side, the register RDC_PDAP70 which is the one for UART4 on the Plus has its default reset of 0x000000FF.
This means that the domain 1 (Cortex-M) has no access rights to UART4, and this explains the HardFault.
Looking to all the RDC_PDAPxx registers I see that only some of them have values different from the reset:

  • RDC_PDAP08 which is WDG1 has a value of 0x00000003
  • RDC_PDAP29 which is RDC has a value of 0x0000000B
  • RDC_PDAP105 which is UART2 has a value of 0x00000003

Tomorrow I’m going to investigate this, but @hfranco.tx do you have idea why U-Boot changes these registers?

Hi @vix,

thanks for the info. I finally made it work, but I had to patch u-boot to enable UART on the Cortex-M side:

From 4471ae9dd3d5963fc59567017ce68db6ef72b61d Mon Sep 17 00:00:00 2001
From: Hiago De Franco <hiago.franco@toradex.com>
Date: Thu, 25 May 2023 17:39:25 +0000
Subject: [PATCH] imx8mp-verdin-u-boot: Enable UART 2

Signed-off-by: Hiago De Franco <hiago.franco@toradex.com>
 arch/arm/dts/imx8mp-verdin-u-boot.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm/dts/imx8mp-verdin-u-boot.dtsi b/arch/arm/dts/imx8mp-verdin-u-boot.dtsi
index 9cd0343b0ed..0e8163078a5 100644
--- a/arch/arm/dts/imx8mp-verdin-u-boot.dtsi
+++ b/arch/arm/dts/imx8mp-verdin-u-boot.dtsi
@@ -44,6 +44,7 @@
                            RDC_MDA  RDC_MDA_SDMA3_SPBA2 DID1 0x0 0x0
                            RDC_PDAP RDC_PDAP_SAI3  PDAP_D1_ACCESS 0x0 0x0
                            RDC_PDAP RDC_PDAP_UART4 PDAP_D1_ACCESS 0x0 0x0
+                           RDC_PDAP RDC_PDAP_UART2 PDAP_D1_ACCESS 0x0 0x0
                            RDC_PDAP RDC_PDAP_GPT1  PDAP_D1_ACCESS 0x0 0x0
                            RDC_PDAP RDC_PDAP_SDMA3 PDAP_D1_ACCESS 0x0 0x0
                            RDC_PDAP RDC_PDAP_I2C3  PDAP_D1_ACCESS 0x0 0x0

After applying this patch, I’ve also patched the MCUXpresso code:

diff --git a/boards/evkmimx8mp/demo_apps/hello_world_change_uart/board.h b/boards/evkmimx8mp/demo_apps/hello_world_change_uart/board.h
index 4f02852..b25ea8b 100644
--- a/boards/evkmimx8mp/demo_apps/hello_world_change_uart/board.h
+++ b/boards/evkmimx8mp/demo_apps/hello_world_change_uart/board.h
@@ -20,13 +20,13 @@
 /* The UART to use for debug messages. */
 #define BOARD_DEBUG_UART_TYPE     kSerialPort_Uart
 #define BOARD_DEBUG_UART_CLK_FREQ                                                           \
-    CLOCK_GetPllFreq(kCLOCK_SystemPll1Ctrl) / (CLOCK_GetRootPreDivider(kCLOCK_RootUart4)) / \
-        (CLOCK_GetRootPostDivider(kCLOCK_RootUart4)) / 10
-#define BOARD_UART_IRQ         UART4_IRQn
+    CLOCK_GetPllFreq(kCLOCK_SystemPll1Ctrl) / (CLOCK_GetRootPreDivider(kCLOCK_RootUart2)) / \
+        (CLOCK_GetRootPostDivider(kCLOCK_RootUart2)) / 10
+#define BOARD_UART_IRQ         UART2_IRQn
 #define BOARD_MU_IRQ       MU1_M7_IRQn
diff --git a/boards/evkmimx8mp/demo_apps/hello_world_change_uart/clock_config.c b/boards/evkmimx8mp/demo_apps/hello_world_change_uart/clock_config.c
index 82a7869..d2d611c 100644
--- a/boards/evkmimx8mp/demo_apps/hello_world_change_uart/clock_config.c
+++ b/boards/evkmimx8mp/demo_apps/hello_world_change_uart/clock_config.c
@@ -102,8 +102,8 @@ void BOARD_BootClockRUN(void)
     CLOCK_SetRootDivider(kCLOCK_RootAudioAhb, 1U, 2U);                    /* Set root clock freq to 800MHZ/ 2= 400MHZ*/
     CLOCK_SetRootMux(kCLOCK_RootAudioAhb, kCLOCK_AudioAhbRootmuxSysPll1); /* switch AUDIO AHB to SYSTEM PLL1 */
-    CLOCK_SetRootMux(kCLOCK_RootUart4, kCLOCK_UartRootmuxSysPll1Div10); /* Set UART source to SysPLL1 Div10 80MHZ */
-    CLOCK_SetRootDivider(kCLOCK_RootUart4, 1U, 1U);                     /* Set root clock to 80MHZ/ 1= 80MHZ */
+    CLOCK_SetRootMux(kCLOCK_RootUart2, kCLOCK_UartRootmuxSysPll1Div10); /* Set UART source to SysPLL1 Div10 80MHZ */
+    CLOCK_SetRootDivider(kCLOCK_RootUart2, 1U, 1U);                     /* Set root clock to 80MHZ/ 1= 80MHZ */
     CLOCK_EnableClock(kCLOCK_Rdc);   /* Enable RDC clock */
     CLOCK_EnableClock(kCLOCK_Ocram); /* Enable Ocram clock */
diff --git a/boards/evkmimx8mp/demo_apps/hello_world_change_uart/hello_world.c b/boards/evkmimx8mp/demo_apps/hello_world_change_uart/hello_world.c
index 10c0a4b..a3f06e9 100644
--- a/boards/evkmimx8mp/demo_apps/hello_world_change_uart/hello_world.c
+++ b/boards/evkmimx8mp/demo_apps/hello_world_change_uart/hello_world.c
@@ -29,8 +29,6 @@
 int main(void)
-    char ch;
     /* Init board hardware. */
     /* M7 has its local cache and enabled by default,
      * need to set smart subsystems (0x28000000 ~ 0x3FFFFFFF)
@@ -44,11 +42,16 @@ int main(void)
-    PRINTF("hello world.\r\n");
+    uint8_t counter = 0;
+    int i;
     while (1)
-        ch = GETCHAR();
-        PUTCHAR(ch);
+        if (counter == 0xFF)
+            counter = 0;
+        else 
+            counter++;
+        PRINTF("hello world. %d\r\n", counter);
+        for (i = 0; i < 10000000; i++);
diff --git a/boards/evkmimx8mp/demo_apps/hello_world_change_uart/pin_mux.c b/boards/evkmimx8mp/demo_apps/hello_world_change_uart/pin_mux.c
index 95c21cb..1120c9c 100644
--- a/boards/evkmimx8mp/demo_apps/hello_world_change_uart/pin_mux.c
+++ b/boards/evkmimx8mp/demo_apps/hello_world_change_uart/pin_mux.c
@@ -55,12 +55,12 @@ BOARD_InitPins:
  * END ****************************************************************************************************************/
 void BOARD_InitPins(void) {                                /*!< Function assigned for the core: Cortex-M7F[m7] */
                         IOMUXC_SW_PAD_CTL_PAD_PUE_MASK |
                         IOMUXC_SW_PAD_CTL_PAD_PUE_MASK |

Here is my setup:

After booting the image and loading it with u-boot, I can see all the messages being printed on my UART2. If now I boot the OS, I get a kernel panic because this peripheral is assigned to Cortex-M, so you will have to create an overlay to disable UART2 on the Linux side.

Here is the Yocto layer that I’ve used to patch u-boot:

├── conf
│   └── layer.conf
└── recipes-bsp
    └── u-boot
        ├── u-boot-toradex
        │   └── imx8mp-verdin-u-boot-Enable-UART-2.patch
        └── u-boot-toradex_%.bbappend
cat meta-imx8mp-hmp/recipes-bsp/u-boot/u-boot-toradex_%.bbappend 


SRC_URI += "\ 
    file://imx8mp-verdin-u-boot-Enable-UART-2.patch \

I think there is no other way here, you will need to patch u-boot. This is imported from the NXP u-boot source code, so there is nothing that I can see that can handle this without changing u-boot.

Please let me know if that helps on your side.

Best Regards,

1 Like

Hi @hfranco.tx,
I really appreciate your help and your hard job on thbis topic.
Now I think everything is almost clear.
Great that you’ve been able to patch and rebuild u-boot.
For my level of experience, I see it as a difficult job, and so I prefer not to go in that direction.

I decided to use UART1 as debug console for M7, and this works quite easily (patching board.h, clock_config.c and pin_mux.c in the hello_world example only - no changes to u-boot).
In this scenario, UART2 (which is reserved to Cortex-A) can be safely used as general-purpose UART for Cortex-A application.
Maybe, this can be given as an advice to other Verdin iMX8M-Plus users.

As a bonus track :wink: I add that I’ve been able to overwrite RDC_PDAP105 configuration from U-Boot (on the fly), with the command

Verdin iMX8MP # mw.l 0x303d05a4 0xff

After I didi it, I was able to run the hello_world example using UART2, without patching and rebuilding u-boot.
As expected, at the next boot RDC_PDAP105 has been restored as 0x00000003.

I was satisfied about the result, but I’m curious, and I’d like understanding what happened.
I didn’t see any point in u-boot sources that sets that RDC_PDAPxx registers and I was confused.
So I opened a topic on NXP Community to ask their help, and the reason is inside ATF, in particular these lines inside imx8mp_bl31_setup.c.
I have a vagous idea of what ATF is, but I thinkl is somehow built inside u-boot.

Not it’s clear “where” the registers RDC_PDAPxxx are set, but not “why”.
But the answer in NXP Community helps:

Please make a note that uart2 is used for debug console in default BSP.

One needs to make changes to use another uart as A53 debug console.

I see taht in TorizonCore this debug console is UART3 (on the Plus), which is connected to UART_3 (of the Verdin).
@hfranco.tx do you know if Toradex decided to change this debug console (respect to what is in NXP reference design)?
Wouldn’t be better if on Verdin iMX8M-Plus SoM the UART2 had been connected to UART_3?
In this way, the Verdin debug console would be UART_3 (as the family), but internally the NXP-suggested UART2 is used.

Hi @vix,

I’m glad that you’re able to use it! This is certainly helpful for others.

Nice! Good catch. If you want to make it permanently, you could also make u-boot run this command automatically at every boot, in case UART2 is necessary.

ATF runs before U-boot, that would make sense. This is the Arm Trusted Firmware, that does some special configurations at a very early boot. I need to investigate more to really make sure of what is going on. The only thing is that the code that you shared is inside an ifdef for IMX_ANDROID_BUILD, and I’d say by the name that is not enabled, so this code will not make anything. But again, I need to check.

I can verify that we changed from UART2 to UART3 for the linux debug console. Comparing the iMX8MP EVK from NXP and our device tree, I can see the following codes:

#include "dt-bindings/pwm/pwm.h"
#include "imx8mp.dtsi"

/ {
	chosen {
		stdout-path = &uart3;


#include <dt-bindings/usb/pd.h>
#include "imx8mp.dtsi"

/ {
	model = "NXP i.MX8MPlus EVK board";
	compatible = "fsl,imx8mp-evk", "fsl,imx8mp";

	chosen {
		stdout-path = &uart2;

These are very good insights. I will check internally more about the ATF and if we can do something to release UART2. But anyways, choosing the UART is not arbitrary, there are many hardware decisions, not only software. For example, all the Verdin family must be pin compatible, so that’s why is not easy to just follow NXP defaults.

Thanks for the insights @vix, I’ll get back when I have more information.

Best Regards,

Hello @hfranco.tx
I thought I had solved all the issues, but I was too optmistic, unfortunately :worried:

Everything is ok in U-Boot.
Then I rebuilt a TorizonCore with Evaluation Container 6.3.0 applying this patched verdin-imx8mp_hmp_overlay.dts

// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
 * Copyright 2023 Toradex

/* Enable RPMSG and the RemoteProc M7 driver.
 * Note: This overlay is working only on nonwifi modules. For more information, please
 * check the Verdin iMX8MP Datasheet section 5.4 Wi-Fi and Bluetooth.


#include <dt-bindings/clock/imx8mp-clock.h>

/ {
	compatible = "toradex,verdin-imx8mp";

&{/} {
	imx8mp-cm7 {
		compatible = "fsl,imx8mp-cm7";
		clocks = <&clk IMX8MP_CLK_M7_DIV>;
		mbox-names = "tx", "rx", "rxdb";
		mboxes = <&mu 0 1
			  &mu 1 1
			  &mu 3 1>;
		memory-region = <&vdevbuffer>, <&vdev0vring0>, <&vdev0vring1>, <&rsc_table>, <&m7_reserved>;
		rsc-da = <0x55000000>;
		syscon = <&src>;
		fsl,startup-delay-ms = <500>;

&i2c3 {
	status = "disabled";

&pwm4 {
	status = "disabled";

&resmem {
	#address-cells = <2>;
	#size-cells = <2>;

	m7_reserved: m7@0x80000000 {
		reg = <0 0x80000000 0 0x1000000>;

	vdev0vring0: vdev0vring0@55000000 {
		reg = <0 0x55000000 0 0x8000>;

	vdev0vring1: vdev0vring1@55008000 {
		reg = <0 0x55008000 0 0x8000>;

	vdevbuffer: vdevbuffer@55400000 {
		compatible = "shared-dma-pool";
		reg = <0 0x55400000 0 0x100000>;

	rsc_table: rsc_table@550ff000 {
		reg = <0 0x550ff000 0 0x1000>;

&sai3 {
	status = "disabled";

&sdma3 {
	status = "disabled";

&uart1 {
	status = "disabled";

The only change based on the Toradex dts is from

&uart4 {
	status = "disabled";


&uart1 {
	status = "disabled";

since the firmware I loaded on M7 uses UART1.

I boot the SoM and I see that U-Boot loads and starts the firmware on M7, and so I started to see messaged from UART1

hello world from uart1. 1
hello world from uart1. 2
hello world from uart1. 3
hello world from uart1. 4
hello world from uart1. 5
hello world from uart1. 6
hello world from uart1. 7
hello world from uart1. 8
hello world from uart1. 9
hello world from uart1. 10

but as soon as Linux starts, I got the usual kernel panic

Starting kernel ...

[    0.880600] Internal error: synchronous external abort: 96000210 [#1] PREEMPT SMP
[    0.888100] Modules linked in:
[    0.891162] CPU: 2 PID: 1 Comm: swapper/0 Not tainted 5.15.77-6.3.0-devel+git.95b4e4bb8a10 #1-TorizonCore
[    0.900739] Hardware name: Toradex Verdin iMX8M Plus WB on Verdin Development Board (DT)
[    0.908836] pstate: 60000005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[    0.915808] pc : imx_uart_probe+0x31c/0x7d0
[    0.920007] lr : imx_uart_probe+0x30c/0x7d0
[    0.924197] sp : ffff80000a28bb60
[    0.927512] x29: ffff80000a28bb60 x28: 0000000000000000 x27: ffff800009a204c0
[    0.934661] x26: ffff0000c4d71c80 x25: 00000000fffffffa x24: 00000000fffffffa
[    0.941809] x23: ffff0000c015a010 x22: 000000000000002a x21: ffff0000c015a000
[    0.948959] x20: 0000000000000000 x19: ffff0000c4d0b880 x18: ffffffffffffffff
[    0.956108] x17: 647561625f657361 x16: 62202c3533203d20 x15: ffff0000c4d7180a
[    0.963257] x14: ffffffffffffffff x13: 0000000000000038 x12: 0101010101010101
[    0.970405] x11: 0000000000000000 x10: 0101010101010101 x9 : 0000000000000004
[    0.977555] x8 : 0101010101010101 x7 : 7f7f7f7f7f7f7f7f x6 : fffefefefeff6f6c
[    0.984704] x5 : 8080808080800000 x4 : 0000000000000010 x3 : 0000000000000000
[    0.991854] x2 : 0000000000000000 x1 : ffff80000aac0080 x0 : 0000000000000000
[    0.999005] Call trace:
[    1.001453]  imx_uart_probe+0x31c/0x7d0
[    1.005295]  platform_probe+0x68/0xe0
[    1.008962]  really_probe+0xbc/0x46c
[    1.012550]  __driver_probe_device+0x114/0x190
[    1.017003]  driver_probe_device+0x40/0x100
[    1.021195]  __driver_attach+0xac/0x210
[    1.025044]  bus_for_each_dev+0x70/0xd0
[    1.028885]  driver_attach+0x24/0x30
[    1.032468]  bus_add_driver+0x14c/0x250
[    1.036317]  driver_register+0x78/0x130
[    1.040161]  __platform_driver_register+0x28/0x34
[    1.044874]  imx_uart_init+0x3c/0x64
[    1.048460]  do_one_initcall+0x44/0x290
[    1.052303]  kernel_init_freeable+0x228/0x2b0
[    1.056671]  kernel_init+0x24/0x12c
[    1.060170]  ret_from_fork+0x10/0x20
[    1.063755] Code: 2a0003f4 35001820 f9400a61 91020021 (b9400021)
[    1.069874] ---[ end trace 25959e1e9e069563 ]---
[    1.074497] Kernel panic - not syncing: synchronous external abort: Fatal exception
[    1.082160] SMP: stopping secondary CPUs
[    1.086097] Kernel Offset: disabled
[    1.089586] CPU features: 0x00002001,20000846
[    1.093949] Memory Limit: none
[    1.097005] Rebooting in 5 seconds..

Is there something I missed?

As far as I know, the .dts have a mechanism of inclusion where the tree of the including
file overlays (overwrites) the tree of the included file.
And I suppose that the overlays applied in tcbuild.yaml with

    custom: linux/arch/arm64/boot/dts/freescale/imx8mp-verdin-wifi-dev.dts

are the last ones to be applied.
But since I see the hard fault, I’m dubios about the “order of inclusion” since in the supplied imx8mp-verdin-wifi-dev.dts includes imx8mp-verdin-dev.dtsi where I see
/* Verdin UART_1, connector X50 through RS485 transceiver */

&uart1 {
	status = "okay";

Is it possible that this configuration is not overlayed with the

&uart1 {
	status = "disabled";

I applied addin my custom overlay?

Hi @vix,

Can you please share with me all the overlays you’re applying? Or is it just the modified version of hmp?

Best Regards,

Hi @hfranco.tx
thanks to this great webinar of yesterday I was able to better understand the device trees and what happened.
I learned from the webinar that the device tree overlays are applied at run time and they’re not built into the Linux image.
For this reason, my device tree overlay that disables UART1 doesn’t “overwrite” imx8mp-verdin-wifi-dev.dts (that enables UART1) since this is not an overlay, and so it’s built into Linux.
And so, since U-Boot has already loaded a firmware into M7, and this firmware has already opened UART1, kernel panic happens when Linux wants to load the tty driver for UART1.
As far as I understood, the only solution is build a custom device tree into a custom build of TorizonCore, and I was able to do it through ApolloX extension and a TCB project.
I created my own imx8mp-verdin-wifi-m7-dev.dts (m7 means that it includes some tweaks needed for my m7 firmware) with the following content

// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
 * Copyright 2022 Toradex


#include "imx8mp-verdin.dtsi"
#include "imx8mp-verdin-wifi.dtsi"
#include "imx8mp-verdin-m7-dev.dtsi"

/ {
	model = "Toradex Verdin iMX8M Plus WB on Verdin Development Board";
	compatible = "toradex,verdin-imx8mp-wifi-dev",

The only difference from the supplied imx8mp-verdin-wifi-dev.dts is

#include "imx8mp-verdin-m7-dev.dtsi"

instead of

#include "imx8mp-verdin-dev.dtsi"

Note the m7, with the same meaning as above.
Then I created my own imx8mp-verdin-m7-dev.dtsi starting from imx8mp-verdin-dev.dtsi and replacing

&uart1 {
	status = "okay";


&uart1 {
	status = "disabled";

Then I customized tcbuild.yaml with

  # Customization section items.
      - linux/include
    custom: device-trees-custom/imx8mp-verdin-wifi-m7-dev.dts

In this way I was able to successuflly boot my custom build of TorizonCore while UART1 is used by the m7 firmware.

Maybe some of the above steps could be added to Toradex KB, to help other users.