* [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-04-30 8:33 [PATCH v4 0/4] imx9{4,5,52}: Add Quickboot support Simona Toaca (OSS)
@ 2026-04-30 8:33 ` Simona Toaca (OSS)
2026-05-25 8:59 ` Emanuele Ghidoli
` (2 more replies)
2026-04-30 8:33 ` [PATCH v4 2/4] arm: mach-imx: Add command to expose QB functionality Simona Toaca (OSS)
` (3 subsequent siblings)
4 siblings, 3 replies; 20+ messages in thread
From: Simona Toaca (OSS) @ 2026-04-30 8:33 UTC (permalink / raw)
To: uboot-imx, u-boot
Cc: Stefano Babic, festevam, peng.fan, alice.guo, ye.li, simona.toaca,
viorel.suman, fedor.ross, marex, joao.goncalves, ravi, ping.bai,
ji.luo, qijian.guo
From: Simona Toaca <simona.toaca@nxp.com>
DDR training data can be saved to NVM and be available
to OEI at boot time, which will trigger QuickBoot flow.
U-Boot only checks for data integrity (CRC32), while
OEI is in charge of authentication when it tries to
load the data from NVM.
On iMX95 A0/A1, 'authentication' is done via another
CRC32. On the other SoCs, authentication is done by
using ELE to check the MAC stored in the ddrphy_qb_state
structure.
Supported platforms: iMX94, iMX95, iMX952 (using OEI)
Supported storage types: eMMC, SD, SPI flash.
Signed-off-by: Viorel Suman <viorel.suman@nxp.com>
Signed-off-by: Ye Li <ye.li@nxp.com>
Signed-off-by: Simona Toaca <simona.toaca@nxp.com>
---
arch/arm/include/asm/arch-imx9/ddr.h | 48 +++-
arch/arm/include/asm/mach-imx/qb.h | 15 +
arch/arm/mach-imx/Kconfig | 9 +
arch/arm/mach-imx/imx9/Makefile | 6 +-
arch/arm/mach-imx/imx9/qb.c | 403 +++++++++++++++++++++++++++
arch/arm/mach-imx/imx9/scmi/soc.c | 7 +
drivers/ddr/imx/imx9/Kconfig | 7 +
7 files changed, 492 insertions(+), 3 deletions(-)
create mode 100644 arch/arm/include/asm/mach-imx/qb.h
create mode 100644 arch/arm/mach-imx/imx9/qb.c
diff --git a/arch/arm/include/asm/arch-imx9/ddr.h b/arch/arm/include/asm/arch-imx9/ddr.h
index a8e3f7354c7..bba12369f06 100644
--- a/arch/arm/include/asm/arch-imx9/ddr.h
+++ b/arch/arm/include/asm/arch-imx9/ddr.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
- * Copyright 2022 NXP
+ * Copyright 2022-2026 NXP
*/
#ifndef __ASM_ARCH_IMX8M_DDR_H
@@ -100,6 +100,52 @@ struct dram_timing_info {
extern struct dram_timing_info dram_timing;
+/* Quick Boot related */
+#define DDRPHY_QB_CSR_SIZE 5168
+#define DDRPHY_QB_ACSM_SIZE (4 * 1024)
+#define DDRPHY_QB_MSB_SIZE 0x200
+#define DDRPHY_QB_PSTATES 0
+#define DDRPHY_QB_PST_SIZE (DDRPHY_QB_PSTATES * 4 * 1024)
+
+/**
+ * This structure needs to be aligned with the one in OEI.
+ */
+struct ddrphy_qb_state {
+ u32 crc; /* Used for ensuring integrity in DRAM */
+#define MAC_LENGTH 8 /* 256 bits, 32-bit aligned */
+ u32 mac[MAC_LENGTH]; /* For 95A0/1 use mac[0] to keep CRC32 value */
+ u8 trained_vrefca_a0;
+ u8 trained_vrefca_a1;
+ u8 trained_vrefca_b0;
+ u8 trained_vrefca_b1;
+ u8 trained_vrefdq_a0;
+ u8 trained_vrefdq_a1;
+ u8 trained_vrefdq_b0;
+ u8 trained_vrefdq_b1;
+ u8 trained_vrefdqu_a0;
+ u8 trained_vrefdqu_a1;
+ u8 trained_vrefdqu_b0;
+ u8 trained_vrefdqu_b1;
+ u8 trained_dramdfe_a0;
+ u8 trained_dramdfe_a1;
+ u8 trained_dramdfe_b0;
+ u8 trained_dramdfe_b1;
+ u8 trained_dramdca_a0;
+ u8 trained_dramdca_a1;
+ u8 trained_dramdca_b0;
+ u8 trained_dramdca_b1;
+ u16 qb_pll_upll_prog0;
+ u16 qb_pll_upll_prog1;
+ u16 qb_pll_upll_prog2;
+ u16 qb_pll_upll_prog3;
+ u16 qb_pll_ctrl1;
+ u16 qb_pll_ctrl4;
+ u16 qb_pll_ctrl5;
+ u16 csr[DDRPHY_QB_CSR_SIZE];
+ u16 acsm[DDRPHY_QB_ACSM_SIZE];
+ u16 pst[DDRPHY_QB_PST_SIZE];
+};
+
void ddr_load_train_firmware(enum fw_type type);
int ddr_init(struct dram_timing_info *timing_info);
int ddr_cfg_phy(struct dram_timing_info *timing_info);
diff --git a/arch/arm/include/asm/mach-imx/qb.h b/arch/arm/include/asm/mach-imx/qb.h
new file mode 100644
index 00000000000..a874c9c5e36
--- /dev/null
+++ b/arch/arm/include/asm/mach-imx/qb.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2026 NXP
+ */
+
+#ifndef __IMX_QB_H__
+#define __IMX_QB_H__
+
+#include <stdbool.h>
+
+bool imx_qb_check(void);
+int imx_qb(const char *ifname, const char *dev, bool save);
+void spl_imx_qb_save(void);
+
+#endif
diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig
index 259f4a4ce99..bb62a0cf2f6 100644
--- a/arch/arm/mach-imx/Kconfig
+++ b/arch/arm/mach-imx/Kconfig
@@ -71,6 +71,15 @@ config CSF_SIZE
Define the maximum size for Command Sequence File (CSF) binary
this information is used to define the image boot data.
+config IMX_QB
+ bool "Support Quickboot flow for Synopsis DDR PHY on iMX platforms"
+ default y
+ depends on IMX94 || IMX95 || IMX952
+ help
+ Enable the logic for saving DDR training data from volatile
+ memory to non-volatile storage. OEI uses the saved data to
+ run Quickboot flow and skip re-training the DDR PHY.
+
config CMD_BMODE
bool "Support the 'bmode' command"
default y
diff --git a/arch/arm/mach-imx/imx9/Makefile b/arch/arm/mach-imx/imx9/Makefile
index 53cc97c6b47..80b697396ea 100644
--- a/arch/arm/mach-imx/imx9/Makefile
+++ b/arch/arm/mach-imx/imx9/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0+
#
-# Copyright 2022 NXP
+# Copyright 2022,2026 NXP
obj-y += lowlevel_init.o
@@ -12,4 +12,6 @@ endif
ifneq ($(CONFIG_SPL_BUILD),y)
obj-y += imx_bootaux.o
-endif
\ No newline at end of file
+endif
+
+obj-$(CONFIG_$(PHASE_)IMX_QB) += qb.o
diff --git a/arch/arm/mach-imx/imx9/qb.c b/arch/arm/mach-imx/imx9/qb.c
new file mode 100644
index 00000000000..1a0a12de3d4
--- /dev/null
+++ b/arch/arm/mach-imx/imx9/qb.c
@@ -0,0 +1,403 @@
+// SPDX-License-Identifier: GPL-2.0+
+/**
+ * Copyright 2024-2026 NXP
+ */
+#include <dm/device-internal.h>
+#include <dm/uclass.h>
+#include <errno.h>
+#include <imx_container.h>
+#include <linux/bitfield.h>
+#include <mmc.h>
+#include <spi_flash.h>
+#include <spl.h>
+#include <stdlib.h>
+#include <u-boot/crc.h>
+
+#include <asm/arch/ddr.h>
+#include <asm/mach-imx/boot_mode.h>
+#include <asm/mach-imx/sys_proto.h>
+
+#define QB_STATE_LOAD_SIZE SZ_64K
+
+#define BLK_DEV 0
+#define SPI_DEV 1
+
+#define IMG_FLAGS_IMG_TYPE_MASK 0xF
+#define IMG_FLAGS_IMG_TYPE(x) FIELD_GET(IMG_FLAGS_IMG_TYPE_MASK, (x))
+
+#define IMG_TYPE_DDR_TDATA_DUMMY 0xD /* dummy DDR training data image */
+
+static const struct {
+ const char *ifname;
+ const char *dev;
+} imx_boot_devs[] = {
+ [BOOT_DEVICE_MMC1] = { "mmc", "0" },
+ [BOOT_DEVICE_MMC2] = { "mmc", "1" },
+ [BOOT_DEVICE_SPI] = { "spi", "" },
+};
+
+static int imx_qb_get_board_boot_device(void)
+{
+ switch (get_boot_device()) {
+ case SD1_BOOT:
+ case MMC1_BOOT:
+ return BOOT_DEVICE_MMC1;
+ case SD2_BOOT:
+ case MMC2_BOOT:
+ return BOOT_DEVICE_MMC2;
+ case USB_BOOT:
+ return BOOT_DEVICE_BOARD;
+ case QSPI_BOOT:
+ return BOOT_DEVICE_SPI;
+ default:
+ return BOOT_DEVICE_NONE;
+ }
+}
+
+static int imx_qb_get_boot_dev_str(const char **ifname, const char **dev)
+{
+ int boot_dev;
+
+ if (IS_ENABLED(CONFIG_XPL_BUILD))
+ boot_dev = spl_boot_device();
+ else
+ boot_dev = imx_qb_get_board_boot_device();
+
+ if (boot_dev == BOOT_DEVICE_NONE || boot_dev == BOOT_DEVICE_BOARD)
+ return -EINVAL;
+
+ *ifname = imx_boot_devs[boot_dev].ifname;
+ *dev = imx_boot_devs[boot_dev].dev;
+
+ return 0;
+}
+
+bool imx_qb_check(void)
+{
+ struct ddrphy_qb_state *qb_state;
+ u32 size, crc;
+
+ /**
+ * Ensure CRC is not empty, the reason is that
+ * the data is invalidated after first save run
+ * or after it is overwritten.
+ */
+ qb_state = (struct ddrphy_qb_state *)CONFIG_QB_SAVED_STATE_BASE;
+ size = sizeof(struct ddrphy_qb_state) - sizeof(qb_state->crc);
+ crc = crc32(0, (u8 *)qb_state->mac, size);
+
+ if (!qb_state->crc || crc != qb_state->crc)
+ return false;
+
+ return true;
+}
+
+static int imx_qb_get_blk_boot_part(const char * const ifname,
+ const char * const dev,
+ struct blk_desc **bdesc)
+{
+ struct udevice *udev;
+ struct disk_partition info;
+ struct mmc *mmc;
+ int part;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_XPL_BUILD))
+ return blk_get_device_part_str(ifname, dev, bdesc, &info, 1);
+
+ /**
+ * SPL does not have access to part_get_info,
+ * so get the partition manually. Currently only
+ * supporting MMC devices.
+ */
+ ret = blk_get_device_by_str(ifname, dev, bdesc);
+
+ if (ret < 0)
+ return -ENODEV;
+
+ if ((*bdesc)->uclass_id != UCLASS_MMC)
+ return -EOPNOTSUPP;
+
+ udev = dev_get_parent((*bdesc)->bdev);
+ mmc = mmc_get_mmc_dev(udev);
+
+ if (IS_SD(mmc) || mmc->part_config == MMCPART_NOAVAILABLE)
+ return 0;
+
+ part = EXT_CSD_EXTRACT_BOOT_PART(mmc->part_config);
+
+ if (part == EMMC_BOOT_PART_BOOT1 || part == EMMC_BOOT_PART_BOOT2)
+ return part;
+
+ return 0;
+}
+
+static ulong imx_qb_get_boot_device_offset(void *dev, int dev_type)
+{
+ struct blk_desc *bdesc;
+
+ switch (dev_type) {
+ case BLK_DEV:
+ bdesc = dev;
+
+ /* eMMC boot partition */
+ if (bdesc->hwpart)
+ return CONTAINER_HDR_EMMC_OFFSET;
+
+ return CONTAINER_HDR_MMCSD_OFFSET;
+ case SPI_DEV:
+ return CONTAINER_HDR_QSPI_OFFSET;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int imx_qb_parse_container(void *addr, u64 *qb_data_off)
+{
+ struct container_hdr *phdr;
+ struct boot_img_t *img_entry;
+ u32 img_type, img_end;
+ int i;
+
+ phdr = addr;
+ if (phdr->tag != 0x87 || (phdr->version != 0x0 && phdr->version != 0x2))
+ return -EINVAL;
+
+ img_entry = addr + sizeof(struct container_hdr);
+ for (i = 0; i < phdr->num_images; i++) {
+ img_type = IMG_FLAGS_IMG_TYPE(img_entry->hab_flags);
+ if (img_type == IMG_TYPE_DDR_TDATA_DUMMY && img_entry->size == 0) {
+ /* Image entry pointing to DDR Training Data */
+ *qb_data_off = img_entry->offset;
+ return 0;
+ }
+
+ img_end = img_entry->offset + img_entry->size;
+ if (i + 1 < phdr->num_images) {
+ img_entry++;
+ if (img_end + QB_STATE_LOAD_SIZE == img_entry->offset) {
+ /* hole detected */
+ *qb_data_off = img_end;
+ return 0;
+ }
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int imx_qb_get_dev_qbdata_offset(void *dev, int dev_type, ulong offset,
+ u64 *qbdata_offset)
+{
+ struct blk_desc *bdesc;
+ u8 *buf;
+ ulong count;
+ int ret;
+
+ buf = malloc(CONTAINER_HDR_ALIGNMENT);
+ if (!buf)
+ return -ENOMEM;
+
+ switch (dev_type) {
+ case BLK_DEV:
+ bdesc = dev;
+
+ count = blk_dread(bdesc,
+ offset / bdesc->blksz,
+ CONTAINER_HDR_ALIGNMENT / bdesc->blksz,
+ buf);
+ if (count == 0) {
+ printf("Read container image from MMC/SD failed\n");
+ ret = -EIO;
+ goto imx_qb_get_dev_qbdata_offset_exit;
+ }
+ break;
+ case SPI_DEV:
+ if (!CONFIG_IS_ENABLED(SPI)) {
+ ret = -EOPNOTSUPP;
+ goto imx_qb_get_dev_qbdata_offset_exit;
+ }
+
+ ret = spi_flash_read_dm(dev, offset,
+ CONTAINER_HDR_ALIGNMENT, buf);
+ if (ret) {
+ printf("Read container header from SPI failed\n");
+ ret = -EIO;
+ goto imx_qb_get_dev_qbdata_offset_exit;
+ }
+ break;
+ default:
+ printf("Support for device %d not enabled\n", dev_type);
+ ret = -EOPNOTSUPP;
+ goto imx_qb_get_dev_qbdata_offset_exit;
+ }
+
+ ret = imx_qb_parse_container(buf, qbdata_offset);
+
+imx_qb_get_dev_qbdata_offset_exit:
+ free(buf);
+
+ return ret;
+}
+
+static int imx_qb_get_qbdata_offset(void *dev, int dev_type,
+ u64 *qbdata_offset)
+{
+ u64 cont_offset;
+ int ret, i;
+
+ cont_offset = imx_qb_get_boot_device_offset(dev, dev_type);
+
+ for (i = 0; i < 3; i++) {
+ ret = imx_qb_get_dev_qbdata_offset(dev, dev_type, cont_offset,
+ qbdata_offset);
+ if (ret == 0) {
+ (*qbdata_offset) += cont_offset;
+ break;
+ }
+
+ cont_offset += CONTAINER_HDR_ALIGNMENT;
+ }
+
+ return ret;
+}
+
+static int imx_qb_blk(const char * const ifname,
+ const char * const dev, bool save)
+{
+ struct blk_desc *bdesc;
+ u64 offset;
+ u64 load_size;
+ int part, orig_part;
+ int ret;
+
+ part = imx_qb_get_blk_boot_part(ifname, dev, &bdesc);
+
+ if (part < 0) {
+ printf("Failed to find %s %s\n", ifname, dev);
+ return -ENODEV;
+ }
+
+ orig_part = bdesc->hwpart;
+
+ ret = blk_dselect_hwpart(bdesc, part);
+ if (ret && ret != -EMEDIUMTYPE) {
+ printf("Failed to select hwpart, ret %d\n", ret);
+ return ret;
+ }
+
+ ret = imx_qb_get_qbdata_offset(bdesc, BLK_DEV, &offset);
+ if (ret) {
+ printf("get_qbdata_offset failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ offset /= bdesc->blksz;
+ load_size = QB_STATE_LOAD_SIZE / bdesc->blksz;
+
+ if (save) {
+ /* QB data is stored in DDR -> can use it as buf */
+ ret = blk_dwrite(bdesc, offset, load_size,
+ (const void *)CONFIG_QB_SAVED_STATE_BASE);
+ } else {
+ /* erase */
+ ret = blk_derase(bdesc, offset, load_size);
+ }
+
+ if (!ret) {
+ printf("Failed to write to block device\n");
+ return -EIO;
+ }
+
+ /* Return to original partition */
+ ret = blk_dselect_hwpart(bdesc, orig_part);
+ if (ret && ret != -EMEDIUMTYPE) {
+ printf("Failed to select hwpart, ret %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_qb_spi(bool save)
+{
+ struct udevice *flash;
+ u64 offset;
+ int ret;
+
+ if (!CONFIG_IS_ENABLED(SPI)) {
+ printf("SPI not enabled\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = uclass_first_device_err(UCLASS_SPI_FLASH, &flash);
+ if (ret) {
+ printf("SPI flash not found.\n");
+ return -ENODEV;
+ }
+
+ ret = imx_qb_get_qbdata_offset(flash, SPI_DEV, &offset);
+ if (ret) {
+ printf("get_qbdata_offset failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ ret = spi_flash_erase_dm(flash, offset, QB_STATE_LOAD_SIZE);
+
+ if (ret)
+ return ret;
+
+ if (!save)
+ return 0;
+
+ /* QB data is stored in DDR -> can use it as buf */
+ ret = spi_flash_write_dm(flash, offset,
+ QB_STATE_LOAD_SIZE,
+ (const void *)CONFIG_QB_SAVED_STATE_BASE);
+
+ return ret;
+}
+
+int imx_qb(const char *ifname, const char *dev, bool save)
+{
+ int ret;
+
+ ret = 0;
+
+ /* Try to use boot device */
+ if (!strcmp(ifname, "auto"))
+ ret = imx_qb_get_boot_dev_str(&ifname, &dev);
+
+ if (ret)
+ return ret;
+
+ if (save && !imx_qb_check())
+ return -EINVAL;
+
+ if (!strcmp(ifname, "spi"))
+ ret = imx_qb_spi(save);
+ else
+ ret = imx_qb_blk(ifname, dev, save);
+
+ if (ret)
+ return ret;
+
+ if (!save)
+ return 0;
+
+ /**
+ * invalidate qb_state mem so that at next boot
+ * the check function will fail and save won't happen
+ */
+ memset((void *)CONFIG_QB_SAVED_STATE_BASE, 0,
+ sizeof(struct ddrphy_qb_state));
+
+ return 0;
+}
+
+void spl_imx_qb_save(void)
+{
+ /* Save QB data on current boot device */
+ if (imx_qb("auto", "", true))
+ printf("QB save failed\n");
+}
diff --git a/arch/arm/mach-imx/imx9/scmi/soc.c b/arch/arm/mach-imx/imx9/scmi/soc.c
index 47e8fc247df..ab0f7394c9e 100644
--- a/arch/arm/mach-imx/imx9/scmi/soc.c
+++ b/arch/arm/mach-imx/imx9/scmi/soc.c
@@ -310,6 +310,13 @@ static struct mm_region imx9_mem_map[] = {
.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
PTE_BLOCK_NON_SHARE |
PTE_BLOCK_PXN | PTE_BLOCK_UXN
+ }, {
+ /* QB data */
+ .virt = CONFIG_QB_SAVED_STATE_BASE,
+ .phys = CONFIG_QB_SAVED_STATE_BASE,
+ .size = 0x200000UL, /* 2M */
+ .attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) |
+ PTE_BLOCK_OUTER_SHARE
}, {
/* empty entry to split table entry 5 if needed when TEEs are used */
0,
diff --git a/drivers/ddr/imx/imx9/Kconfig b/drivers/ddr/imx/imx9/Kconfig
index b953bca4f06..7b3dbf53dff 100644
--- a/drivers/ddr/imx/imx9/Kconfig
+++ b/drivers/ddr/imx/imx9/Kconfig
@@ -29,4 +29,11 @@ config SAVED_DRAM_TIMING_BASE
after DRAM is trained, need to save the dram related timming
info into memory for low power use.
+config QB_SAVED_STATE_BASE
+ hex "Define the base address for saved QuickBoot state"
+ default 0x8fe00000
+ help
+ Once DRAM is trained, the resulted training info is
+ saved into memory in order to be reachable from U-Boot.
+
endmenu
--
2.43.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-04-30 8:33 ` [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM Simona Toaca (OSS)
@ 2026-05-25 8:59 ` Emanuele Ghidoli
2026-05-27 14:33 ` Simona Toaca
2026-05-28 5:25 ` Kumar, Udit
2026-06-02 11:20 ` Marek Vasut
2 siblings, 1 reply; 20+ messages in thread
From: Emanuele Ghidoli @ 2026-05-25 8:59 UTC (permalink / raw)
To: Simona Toaca (OSS), uboot-imx, u-boot
Cc: Stefano Babic, festevam, peng.fan, alice.guo, ye.li, simona.toaca,
viorel.suman, fedor.ross, marex, joao.goncalves, ravi, ping.bai,
ji.luo, qijian.guo
On 4/30/26 10:33, Simona Toaca (OSS) wrote:
> From: Simona Toaca <simona.toaca@nxp.com>
>
> +/**
> + * This structure needs to be aligned with the one in OEI.
> + */
> +struct ddrphy_qb_state {
> + u32 crc; /* Used for ensuring integrity in DRAM */
> +#define MAC_LENGTH 8 /* 256 bits, 32-bit aligned */
> + u32 mac[MAC_LENGTH]; /* For 95A0/1 use mac[0] to keep CRC32 value */
> + u8 trained_vrefca_a0;
> + u8 trained_vrefca_a1;
> + u8 trained_vrefca_b0;
> + u8 trained_vrefca_b1;
> + u8 trained_vrefdq_a0;
> + u8 trained_vrefdq_a1;
> + u8 trained_vrefdq_b0;
> + u8 trained_vrefdq_b1;
> + u8 trained_vrefdqu_a0;
> + u8 trained_vrefdqu_a1;
> + u8 trained_vrefdqu_b0;
> + u8 trained_vrefdqu_b1;
> + u8 trained_dramdfe_a0;
> + u8 trained_dramdfe_a1;
> + u8 trained_dramdfe_b0;
> + u8 trained_dramdfe_b1;
> + u8 trained_dramdca_a0;
> + u8 trained_dramdca_a1;
> + u8 trained_dramdca_b0;
> + u8 trained_dramdca_b1;
> + u16 qb_pll_upll_prog0;
> + u16 qb_pll_upll_prog1;
> + u16 qb_pll_upll_prog2;
> + u16 qb_pll_upll_prog3;
> + u16 qb_pll_ctrl1;
> + u16 qb_pll_ctrl4;
> + u16 qb_pll_ctrl5;
> + u16 csr[DDRPHY_QB_CSR_SIZE];
> + u16 acsm[DDRPHY_QB_ACSM_SIZE];
> + u16 pst[DDRPHY_QB_PST_SIZE];
> +};
Hi Simona,
Would you consider reserving a small u16 board-defined field at the
end of struct ddrphy_qb_state? Something like:
u16 board_ddr_config; /* board-defined tag, opaque to U-Boot */
Use case: boards that carry multiple LPDDR configurations
(single-/dual-rank, different sizes, ...). At QuickBoot time the
boot code needs to know which configuration produced the persisted
PHY training data, in order to load the matching timing table
before DDRC init, in OEI. A 16-bit board-defined identifier is enough; the
value is opaque to U-Boot.
A generic placeholder is equally fine for our purpose, so each
board/vendor can use it as they see fit.
Cost on the persisted blob is essentially zero: mac[] (u32) forces
4-byte struct alignment, which already leaves 2 bytes of trailing
padding in the current layout. Adding a u16 field there does not
change sizeof(struct ddrphy_qb_state) nor the on-storage size.
Thanks,
Emanuele
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-05-25 8:59 ` Emanuele Ghidoli
@ 2026-05-27 14:33 ` Simona Toaca
2026-05-28 7:37 ` Francesco Dolcini
0 siblings, 1 reply; 20+ messages in thread
From: Simona Toaca @ 2026-05-27 14:33 UTC (permalink / raw)
To: Emanuele Ghidoli
Cc: uboot-imx, u-boot, Stefano Babic, festevam, peng.fan, alice.guo,
ye.li, simona.toaca, viorel.suman, fedor.ross, marex,
joao.goncalves, ravi, ping.bai, ji.luo, qijian.guo
Hello Emanuele,
On Mon, May 25, 2026 at 10:59:00AM +0200, Emanuele Ghidoli wrote:
>
>
> On 4/30/26 10:33, Simona Toaca (OSS) wrote:
> > From: Simona Toaca <simona.toaca@nxp.com>
> >
> > +/**
> > + * This structure needs to be aligned with the one in OEI.
> > + */
> > +struct ddrphy_qb_state {
> > + u32 crc; /* Used for ensuring integrity in DRAM */
> > +#define MAC_LENGTH 8 /* 256 bits, 32-bit aligned */
> > + u32 mac[MAC_LENGTH]; /* For 95A0/1 use mac[0] to keep CRC32 value */
> > + u8 trained_vrefca_a0;
> > + u8 trained_vrefca_a1;
[...]
> > + u16 acsm[DDRPHY_QB_ACSM_SIZE];
> > + u16 pst[DDRPHY_QB_PST_SIZE];
> > +};
>
> Hi Simona,
>
> Would you consider reserving a small u16 board-defined field at the
> end of struct ddrphy_qb_state? Something like:
>
> u16 board_ddr_config; /* board-defined tag, opaque to U-Boot */
>
> Use case: boards that carry multiple LPDDR configurations
> (single-/dual-rank, different sizes, ...). At QuickBoot time the
> boot code needs to know which configuration produced the persisted
> PHY training data, in order to load the matching timing table
> before DDRC init, in OEI. A 16-bit board-defined identifier is enough; the
> value is opaque to U-Boot.
>
I am taking into consideration adding such a field into qb_state
in the future, but for now we do not support a mechanism
for deciding the dram timing at runtime.
> A generic placeholder is equally fine for our purpose, so each
> board/vendor can use it as they see fit.
>
> Cost on the persisted blob is essentially zero: mac[] (u32) forces
> 4-byte struct alignment, which already leaves 2 bytes of trailing
> padding in the current layout. Adding a u16 field there does not
> change sizeof(struct ddrphy_qb_state) nor the on-storage size.
>
It means that U-Boot will save those 2 bytes anyway into NVM, so as long as
no other changes are made to the structure, it should be ok for your usecase.
Best regards,
Simona
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-05-27 14:33 ` Simona Toaca
@ 2026-05-28 7:37 ` Francesco Dolcini
0 siblings, 0 replies; 20+ messages in thread
From: Francesco Dolcini @ 2026-05-28 7:37 UTC (permalink / raw)
To: Simona Toaca
Cc: Emanuele Ghidoli, uboot-imx, u-boot, Stefano Babic, festevam,
peng.fan, alice.guo, ye.li, simona.toaca, viorel.suman,
fedor.ross, marex, joao.goncalves, ravi, ping.bai, ji.luo,
qijian.guo
On Wed, May 27, 2026 at 05:33:36PM +0300, Simona Toaca wrote:
> On Mon, May 25, 2026 at 10:59:00AM +0200, Emanuele Ghidoli wrote:
> >
> >
> > On 4/30/26 10:33, Simona Toaca (OSS) wrote:
> > > From: Simona Toaca <simona.toaca@nxp.com>
> > >
> > > +/**
> > > + * This structure needs to be aligned with the one in OEI.
> > > + */
> > > +struct ddrphy_qb_state {
> > > + u32 crc; /* Used for ensuring integrity in DRAM */
> > > +#define MAC_LENGTH 8 /* 256 bits, 32-bit aligned */
> > > + u32 mac[MAC_LENGTH]; /* For 95A0/1 use mac[0] to keep CRC32 value */
> > > + u8 trained_vrefca_a0;
> > > + u8 trained_vrefca_a1;
> [...]
>
> > > + u16 acsm[DDRPHY_QB_ACSM_SIZE];
> > > + u16 pst[DDRPHY_QB_PST_SIZE];
> > > +};
> >
> > Hi Simona,
> >
> > Would you consider reserving a small u16 board-defined field at the
> > end of struct ddrphy_qb_state? Something like:
> >
> > u16 board_ddr_config; /* board-defined tag, opaque to U-Boot */
> >
> > Use case: boards that carry multiple LPDDR configurations
> > (single-/dual-rank, different sizes, ...). At QuickBoot time the
> > boot code needs to know which configuration produced the persisted
> > PHY training data, in order to load the matching timing table
> > before DDRC init, in OEI. A 16-bit board-defined identifier is enough; the
> > value is opaque to U-Boot.
> >
> I am taking into consideration adding such a field into qb_state
> in the future, but for now we do not support a mechanism
> for deciding the dram timing at runtime.
The reality is that we are forking your OEI (AFAIK you do not take
patches) to support our boards and our use case. So we (Toradex) have it
already, and it is working 100% fine.
What is tricky here is that this structure is an ABI / contract between
the OEI firmware and U-Boot.
Just to anticipate your potential answer, our boards just work with
mainline U-Boot, so asking us to have a patch and fork U-Boot is not an
option.
Francesco
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-04-30 8:33 ` [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM Simona Toaca (OSS)
2026-05-25 8:59 ` Emanuele Ghidoli
@ 2026-05-28 5:25 ` Kumar, Udit
2026-06-02 10:59 ` Simona Toaca
2026-06-02 11:20 ` Marek Vasut
2 siblings, 1 reply; 20+ messages in thread
From: Kumar, Udit @ 2026-05-28 5:25 UTC (permalink / raw)
To: Simona Toaca (OSS), uboot-imx, u-boot
Cc: Stefano Babic, festevam, peng.fan, alice.guo, ye.li, simona.toaca,
viorel.suman, fedor.ross, marex, joao.goncalves, ravi, ping.bai,
ji.luo, qijian.guo, Francis, Neha, Siddhant.Kumar
On 4/30/2026 2:03 PM, Simona Toaca (OSS) wrote:
> From: Simona Toaca <simona.toaca@nxp.com>
>
> DDR training data can be saved to NVM and be available
> to OEI at boot time, which will trigger QuickBoot flow.
>
> U-Boot only checks for data integrity (CRC32), while
> OEI is in charge of authentication when it tries to
> load the data from NVM.
>
> On iMX95 A0/A1, 'authentication' is done via another
> CRC32. On the other SoCs, authentication is done by
> using ELE to check the MAC stored in the ddrphy_qb_state
> structure.
>
> Supported platforms: iMX94, iMX95, iMX952 (using OEI)
> Supported storage types: eMMC, SD, SPI flash.
>
> Signed-off-by: Viorel Suman <viorel.suman@nxp.com>
> Signed-off-by: Ye Li <ye.li@nxp.com>
> Signed-off-by: Simona Toaca <simona.toaca@nxp.com>
> ---
> arch/arm/include/asm/arch-imx9/ddr.h | 48 +++-
> arch/arm/include/asm/mach-imx/qb.h | 15 +
> arch/arm/mach-imx/Kconfig | 9 +
> arch/arm/mach-imx/imx9/Makefile | 6 +-
> arch/arm/mach-imx/imx9/qb.c | 403 +++++++++++++++++++++++++++
> arch/arm/mach-imx/imx9/scmi/soc.c | 7 +
> drivers/ddr/imx/imx9/Kconfig | 7 +
> 7 files changed, 492 insertions(+), 3 deletions(-)
> create mode 100644 arch/arm/include/asm/mach-imx/qb.h
> create mode 100644 arch/arm/mach-imx/imx9/qb.c
>
> diff --git a/arch/arm/include/asm/arch-imx9/ddr.h b/arch/arm/include/asm/arch-imx9/ddr.h
> index a8e3f7354c7..bba12369f06 100644
> --- a/arch/arm/include/asm/arch-imx9/ddr.h
> +++ b/arch/arm/include/asm/arch-imx9/ddr.h
> @@ -1,6 +1,6 @@
> /* SPDX-License-Identifier: GPL-2.0+ */
> /*
> - * Copyright 2022 NXP
> + * Copyright 2022-2026 NXP
> */
>
> #ifndef __ASM_ARCH_IMX8M_DDR_H
> @@ -100,6 +100,52 @@ struct dram_timing_info {
>
> extern struct dram_timing_info dram_timing;
>
> +/* Quick Boot related */
> +#define DDRPHY_QB_CSR_SIZE 5168
> +#define DDRPHY_QB_ACSM_SIZE (4 * 1024)
> +#define DDRPHY_QB_MSB_SIZE 0x200
> +#define DDRPHY_QB_PSTATES 0
> +#define DDRPHY_QB_PST_SIZE (DDRPHY_QB_PSTATES * 4 * 1024)
[..]
Sorry for basic question, in case OEI layer changes format due to one of
other reason then how you are making sure, struct ddrphy_qb_state is
aligned
+ * This structure needs to be aligned with the one in OEI.
[..]
> +
> +bool imx_qb_check(void)
> +{
> + struct ddrphy_qb_state *qb_state;
> + u32 size, crc;
> +
> + /**
> + * Ensure CRC is not empty, the reason is that
> + * the data is invalidated after first save run
> + * or after it is overwritten.
> + */
In case OEI layer using saved DDR training data, then will
passed CRC will be zero ?
> + qb_state = (struct ddrphy_qb_state *)CONFIG_QB_SAVED_STATE_BASE;
If yes then you can return from here, if no CRC found instead of doing
crc calculation.
> + size = sizeof(struct ddrphy_qb_state) - sizeof(qb_state->crc);
> + crc = crc32(0, (u8 *)qb_state->mac, size);
> +
> + if (!qb_state->crc || crc != qb_state->crc)
> + return false;
> +
> + return true;
> +}
> +
[..]
> +
> +void spl_imx_qb_save(void)
> +{
> + /* Save QB data on current boot device */
> + if (imx_qb("auto", "", true))
> + printf("QB save failed\n");
In case, of 3rd boot or so, there is no need to save training data,
whereas you will return error, in case this feature is auto enabled.
> +}
[..]
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-05-28 5:25 ` Kumar, Udit
@ 2026-06-02 10:59 ` Simona Toaca
2026-06-03 7:24 ` Francesco Dolcini
2026-06-03 8:13 ` Kumar, Udit
0 siblings, 2 replies; 20+ messages in thread
From: Simona Toaca @ 2026-06-02 10:59 UTC (permalink / raw)
To: Kumar, Udit
Cc: uboot-imx, u-boot, Stefano Babic, festevam, peng.fan, alice.guo,
ye.li, simona.toaca, viorel.suman, fedor.ross, marex,
joao.goncalves, ravi, ping.bai, ji.luo, qijian.guo, Francis, Neha,
Siddhant.Kumar
Hello Kumar,
On Thu, May 28, 2026 at 10:55:23AM +0530, Kumar, Udit wrote:
>
>
> On 4/30/2026 2:03 PM, Simona Toaca (OSS) wrote:
> > From: Simona Toaca <simona.toaca@nxp.com>
> >
> > DDR training data can be saved to NVM and be available
> > to OEI at boot time, which will trigger QuickBoot flow.
> >
> > U-Boot only checks for data integrity (CRC32), while
> > OEI is in charge of authentication when it tries to
> > load the data from NVM.
> >
> >
[...]
> > +#define DDRPHY_QB_PSTATES 0
> > +#define DDRPHY_QB_PST_SIZE (DDRPHY_QB_PSTATES * 4 * 1024)
>
> [..]
> Sorry for basic question, in case OEI layer changes format due to one of
> other reason then how you are making sure, struct ddrphy_qb_state is
> aligned
>
> + * This structure needs to be aligned with the one in OEI.
>
For now, the structure is stable. If anything changes, I will take care
to update U-Boot accordingly.
>
> [..]
> > +
> > +bool imx_qb_check(void)
> > +{
> > + struct ddrphy_qb_state *qb_state;
> > + u32 size, crc;
> > +
> > + /**
> > + * Ensure CRC is not empty, the reason is that
> > + * the data is invalidated after first save run
> > + * or after it is overwritten.
> > + */
>
> In case OEI layer using saved DDR training data, then will
> passed CRC will be zero ?
>
No, the data in DRAM isn't guaranteed to be 0 after 'qb save' and
then reset. It is 0 only immediately after a successful 'qb save'.
> > + qb_state = (struct ddrphy_qb_state *)CONFIG_QB_SAVED_STATE_BASE;
>
> If yes then you can return from here, if no CRC found instead of doing
> crc calculation.
Agreed, but the CRC32 doesn't add significant overhead and, more often than
not, the data will not be 0, but something else.
>
> > + size = sizeof(struct ddrphy_qb_state) - sizeof(qb_state->crc);
> > + crc = crc32(0, (u8 *)qb_state->mac, size);
> > +
> > + if (!qb_state->crc || crc != qb_state->crc)
> > + return false;
> > +
> > + return true;
> > +}
> > +
> [..]
> > +
> > +void spl_imx_qb_save(void)
> > +{
> > + /* Save QB data on current boot device */
> > + if (imx_qb("auto", "", true))
> > + printf("QB save failed\n");
>
> In case, of 3rd boot or so, there is no need to save training data,
> whereas you will return error, in case this feature is auto enabled.
>
There is no way to tell from U-Boot if Quickboot flow was run or not,
so that is why 'qb save' is tried everytime. The data in DRAM can be
invalid for 2 reasons:
- it was saved by Training flow, but it got corrupted/got saved wrong
- Quickboot flow was run so no data was saved - this doesn't mean that the
data in DRAM will be 0
> > +}
> [..]
Best regards,
Simona
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-06-02 10:59 ` Simona Toaca
@ 2026-06-03 7:24 ` Francesco Dolcini
2026-06-03 8:13 ` Kumar, Udit
1 sibling, 0 replies; 20+ messages in thread
From: Francesco Dolcini @ 2026-06-03 7:24 UTC (permalink / raw)
To: Simona Toaca
Cc: Kumar, Udit, uboot-imx, u-boot, Stefano Babic, festevam, peng.fan,
alice.guo, ye.li, simona.toaca, viorel.suman, fedor.ross, marex,
joao.goncalves, ravi, ping.bai, ji.luo, qijian.guo, Francis, Neha,
Siddhant.Kumar
Hello Simona,
On Tue, Jun 02, 2026 at 01:59:39PM +0300, Simona Toaca wrote:
> On Thu, May 28, 2026 at 10:55:23AM +0530, Kumar, Udit wrote:
> > On 4/30/2026 2:03 PM, Simona Toaca (OSS) wrote:
> > > From: Simona Toaca <simona.toaca@nxp.com>
> > >
> > > DDR training data can be saved to NVM and be available
> > > to OEI at boot time, which will trigger QuickBoot flow.
> > >
> > > U-Boot only checks for data integrity (CRC32), while
> > > OEI is in charge of authentication when it tries to
> > > load the data from NVM.
> > >
> > >
> [...]
> > > +#define DDRPHY_QB_PSTATES 0
> > > +#define DDRPHY_QB_PST_SIZE (DDRPHY_QB_PSTATES * 4 * 1024)
> >
> > [..]
> > Sorry for basic question, in case OEI layer changes format due to one of
> > other reason then how you are making sure, struct ddrphy_qb_state is
> > aligned
> >
> > + * This structure needs to be aligned with the one in OEI.
> >
> For now, the structure is stable. If anything changes, I will take care
> to update U-Boot accordingly.
Did you consider Emanuele's email in which he thinks that what we have today
should already be changed?
And how do you handle a mix of U-Boot vs OEI version? How can you ensure
that those are compatible? To me this is a mess. You are building an ABI
between U-Boot and the OEI.
Francesco
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-06-02 10:59 ` Simona Toaca
2026-06-03 7:24 ` Francesco Dolcini
@ 2026-06-03 8:13 ` Kumar, Udit
1 sibling, 0 replies; 20+ messages in thread
From: Kumar, Udit @ 2026-06-03 8:13 UTC (permalink / raw)
To: Simona Toaca
Cc: uboot-imx, u-boot, Stefano Babic, festevam, peng.fan, alice.guo,
ye.li, simona.toaca, viorel.suman, fedor.ross, marex,
joao.goncalves, ravi, ping.bai, ji.luo, qijian.guo, Francis, Neha,
Siddhant.Kumar, u-kumar1
Hi Simona,
On 6/2/2026 4:29 PM, Simona Toaca wrote:
> Hello Kumar,
>
> On Thu, May 28, 2026 at 10:55:23AM +0530, Kumar, Udit wrote:
>>
>>
>> On 4/30/2026 2:03 PM, Simona Toaca (OSS) wrote:
>>> From: Simona Toaca <simona.toaca@nxp.com>
>>>
>>> DDR training data can be saved to NVM and be available
>>> to OEI at boot time, which will trigger QuickBoot flow.
>>>
>>> U-Boot only checks for data integrity (CRC32), while
>>> OEI is in charge of authentication when it tries to
>>> load the data from NVM.
>>>
>>>
> [...]
>>> +#define DDRPHY_QB_PSTATES 0
>>> +#define DDRPHY_QB_PST_SIZE (DDRPHY_QB_PSTATES * 4 * 1024)
>>
>> [..]
>> Sorry for basic question, in case OEI layer changes format due to one of
>> other reason then how you are making sure, struct ddrphy_qb_state is
>> aligned
>>
>> + * This structure needs to be aligned with the one in OEI.
>>
> For now, the structure is stable. If anything changes, I will take care
> to update U-Boot accordingly.
Hmm, you might break backward compatibility in case of change in struct.
Or you can think of some abstracted struct, which will take care of
changes in OEI.
Something like
struct {
qb_version;
qb_data_pointer;
qb_data_size;
}
>>
>> [..]
>>> +
>>> +bool imx_qb_check(void)
>>> +{
>>> + struct ddrphy_qb_state *qb_state;
>>> + u32 size, crc;
>>> +
>>> + /**
>>> + * Ensure CRC is not empty, the reason is that
>>> + * the data is invalidated after first save run
>>> + * or after it is overwritten.
>>> + */
>>
>> In case OEI layer using saved DDR training data, then will
>> passed CRC will be zero ?
>>
> No, the data in DRAM isn't guaranteed to be 0 after 'qb save' and
> then reset. It is 0 only immediately after a successful 'qb save'.
Ok then then you are making sure, if boot is from qb firmware then
u-boot has a way to detect this ?
>
>>> + qb_state = (struct ddrphy_qb_state *)CONFIG_QB_SAVED_STATE_BASE;
>>
>> If yes then you can return from here, if no CRC found instead of doing
>> crc calculation.
> Agreed, but the CRC32 doesn't add significant overhead and, more often than
> not, the data will not be 0, but something else.
you can think to remove this, it may save few nS or uS in boot.
>>
>>> + size = sizeof(struct ddrphy_qb_state) - sizeof(qb_state->crc);
>>> + crc = crc32(0, (u8 *)qb_state->mac, size);
>>> +
>>> + if (!qb_state->crc || crc != qb_state->crc)
>>> + return false;
>>> +
>>> + return true;
>>> +}
>>> +
>> [..]
>>> +
>>> +void spl_imx_qb_save(void)
>>> +{
>>> + /* Save QB data on current boot device */
>>> + if (imx_qb("auto", "", true))
>>> + printf("QB save failed\n");
>>
>> In case, of 3rd boot or so, there is no need to save training data,
>> whereas you will return error, in case this feature is auto enabled.
>>
> There is no way to tell from U-Boot if Quickboot flow was run or not,
> so that is why 'qb save' is tried everytime. The data in DRAM can be
> invalid for 2 reasons:
> - it was saved by Training flow, but it got corrupted/got saved wrong
> - Quickboot flow was run so no data was saved - this doesn't mean that the
> data in DRAM will be 0
>
No further comments on this, As long as, you are making sure, above
print will come only if QB save really failed.
>>> +}
>> [..]
>
> Best regards,
> Simona
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-04-30 8:33 ` [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM Simona Toaca (OSS)
2026-05-25 8:59 ` Emanuele Ghidoli
2026-05-28 5:25 ` Kumar, Udit
@ 2026-06-02 11:20 ` Marek Vasut
2026-06-02 13:46 ` Simona Toaca
2 siblings, 1 reply; 20+ messages in thread
From: Marek Vasut @ 2026-06-02 11:20 UTC (permalink / raw)
To: Simona Toaca (OSS), uboot-imx, u-boot
Cc: Stefano Babic, festevam, peng.fan, alice.guo, ye.li, simona.toaca,
viorel.suman, fedor.ross, joao.goncalves, ravi, ping.bai, ji.luo,
qijian.guo, Ross, Fedor
On 4/30/26 10:33 AM, Simona Toaca (OSS) wrote:
[...]
> +static int imx_qb_spi(bool save)
> +{
> + struct udevice *flash;
> + u64 offset;
> + int ret;
> +
> + if (!CONFIG_IS_ENABLED(SPI)) {
> + printf("SPI not enabled\n");
> + return -EOPNOTSUPP;
> + }
> +
> + ret = uclass_first_device_err(UCLASS_SPI_FLASH, &flash);
> + if (ret) {
> + printf("SPI flash not found.\n");
> + return -ENODEV;
> + }
> +
> + ret = imx_qb_get_qbdata_offset(flash, SPI_DEV, &offset);
> + if (ret) {
> + printf("get_qbdata_offset failed, ret = %d\n", ret);
> + return ret;
> + }
> +
> + ret = spi_flash_erase_dm(flash, offset, QB_STATE_LOAD_SIZE);
Can you please double-check whether "offset" here is always aligned to
64 kiB (SPI NOR erase block size) ? If not, this erase here will fail.
Thank you
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-06-02 11:20 ` Marek Vasut
@ 2026-06-02 13:46 ` Simona Toaca
2026-06-02 14:11 ` Marek Vasut
0 siblings, 1 reply; 20+ messages in thread
From: Simona Toaca @ 2026-06-02 13:46 UTC (permalink / raw)
To: Marek Vasut
Cc: uboot-imx, u-boot, Stefano Babic, festevam, peng.fan, alice.guo,
ye.li, simona.toaca, viorel.suman, fedor.ross, joao.goncalves,
ravi, ping.bai, ji.luo, qijian.guo
Hi Marek,
On Tue, Jun 02, 2026 at 01:20:57PM +0200, Marek Vasut wrote:
> On 4/30/26 10:33 AM, Simona Toaca (OSS) wrote:
>
> [...]
>
> > +static int imx_qb_spi(bool save)
> > +{
> > + struct udevice *flash;
> > + u64 offset;
> > + int ret;
> > +
[...]
> > + ret = imx_qb_get_qbdata_offset(flash, SPI_DEV, &offset);
> > + if (ret) {
> > + printf("get_qbdata_offset failed, ret = %d\n", ret);
> > + return ret;
> > + }
> > +
> > + ret = spi_flash_erase_dm(flash, offset, QB_STATE_LOAD_SIZE);
> Can you please double-check whether "offset" here is always aligned to 64
> kiB (SPI NOR erase block size) ? If not, this erase here will fail.
>
> Thank you
The erase size of the SPI NOR should actually be 4kiB. I added
a patch a while back enabling SFDP support, so that the erase size is
parsed correctly and this does not fail. I only enabled it on U-Boot,
but if you are using SPL you should enable it there too.
The only reason why I did not enable it for SPL is that there seems
to not be any defconfig for iMX95/943/952 for booting from SPI NOR.
Trying 'qb save spi' from eMMC on an iMX943 EVK (with SFDP enabled):
u-boot=> qb save spi
SF: Detected mt35xu512aba with page size 256 Bytes, erase size 4 KiB,
total 64 MiB
u-boot=> echo $?
0
Without SFDP, 'sf probe' reports 128kiB erase size, not 64kiB.
I also do not see why I would check if the offset is a multiple of
the erase size, since the erase fails gracefully.
Best regards,
Simona
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-06-02 13:46 ` Simona Toaca
@ 2026-06-02 14:11 ` Marek Vasut
2026-06-03 4:57 ` Marek Vasut
0 siblings, 1 reply; 20+ messages in thread
From: Marek Vasut @ 2026-06-02 14:11 UTC (permalink / raw)
To: Simona Toaca
Cc: uboot-imx, u-boot, Stefano Babic, festevam, peng.fan, alice.guo,
ye.li, simona.toaca, viorel.suman, fedor.ross, joao.goncalves,
ravi, ping.bai, ji.luo, qijian.guo
On 6/2/26 3:46 PM, Simona Toaca wrote:
Hi,
>>> + ret = spi_flash_erase_dm(flash, offset, QB_STATE_LOAD_SIZE);
>> Can you please double-check whether "offset" here is always aligned to 64
>> kiB (SPI NOR erase block size) ? If not, this erase here will fail.
>>
>> Thank you
>
> The erase size of the SPI NOR should actually be 4kiB.
That does not apply to all devices. I assume to align this data to 64
kiB offset, I would need a replacement AHAB container?
> I added
> a patch a while back enabling SFDP support, so that the erase size is
> parsed correctly and this does not fail. I only enabled it on U-Boot,
> but if you are using SPL you should enable it there too.
>
> The only reason why I did not enable it for SPL is that there seems
> to not be any defconfig for iMX95/943/952 for booting from SPI NOR.
>
> Trying 'qb save spi' from eMMC on an iMX943 EVK (with SFDP enabled):
> u-boot=> qb save spi
> SF: Detected mt35xu512aba with page size 256 Bytes, erase size 4 KiB,
> total 64 MiB
> u-boot=> echo $?
> 0
>
> Without SFDP, 'sf probe' reports 128kiB erase size, not 64kiB.
>
> I also do not see why I would check if the offset is a multiple of
> the erase size, since the erase fails gracefully.
My understanding is, that the erase operation should erase the QB data
from the SPI NOR, not ignore failure and keep the data there ?
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-06-02 14:11 ` Marek Vasut
@ 2026-06-03 4:57 ` Marek Vasut
2026-06-03 13:31 ` Simona Toaca
0 siblings, 1 reply; 20+ messages in thread
From: Marek Vasut @ 2026-06-03 4:57 UTC (permalink / raw)
To: Simona Toaca
Cc: uboot-imx, u-boot, Stefano Babic, festevam, peng.fan, alice.guo,
ye.li, simona.toaca, viorel.suman, fedor.ross, joao.goncalves,
ravi, ping.bai, ji.luo, qijian.guo
On 6/2/26 4:11 PM, Marek Vasut wrote:
> On 6/2/26 3:46 PM, Simona Toaca wrote:
>
> Hi,
>
>>>> + ret = spi_flash_erase_dm(flash, offset, QB_STATE_LOAD_SIZE);
>>> Can you please double-check whether "offset" here is always aligned
>>> to 64
>>> kiB (SPI NOR erase block size) ? If not, this erase here will fail.
>>>
>>> Thank you
>>
>> The erase size of the SPI NOR should actually be 4kiB.
>
> That does not apply to all devices. I assume to align this data to 64
> kiB offset, I would need a replacement AHAB container?
>
>> I added
>> a patch a while back enabling SFDP support, so that the erase size is
>> parsed correctly and this does not fail. I only enabled it on U-Boot,
>> but if you are using SPL you should enable it there too.
>>
>> The only reason why I did not enable it for SPL is that there seems
>> to not be any defconfig for iMX95/943/952 for booting from SPI NOR.
>>
>> Trying 'qb save spi' from eMMC on an iMX943 EVK (with SFDP enabled):
>> u-boot=> qb save spi
>> SF: Detected mt35xu512aba with page size 256 Bytes, erase size 4 KiB,
>> total 64 MiB
>> u-boot=> echo $?
>> 0
>>
>> Without SFDP, 'sf probe' reports 128kiB erase size, not 64kiB.
>>
>> I also do not see why I would check if the offset is a multiple of
>> the erase size, since the erase fails gracefully.
> My understanding is, that the erase operation should erase the QB data
> from the SPI NOR, not ignore failure and keep the data there ?
It seems that on iMX95 B0, the DDR_DUMMY offset is 0x76400 (aligned to 1
kiB, which is too low):
"
$ hexdump -vC flash.bin | grep -A 1 ^00008000
00008000 02 a0 02 87 10 00 00 00 00 00 00 05 90 02 00 00
00008010 00 64 07 00 00 00 00 00 00 00 00 00 00 00 00 00
^^^^^^^^^^^
"
But if I add the patch below, the offset gets aligned to 64 kiB at 0x78000:
"
$ hexdump -vC flash.bin | grep -A 1 ^00008000
00008000 02 a0 02 87 10 00 00 00 00 00 00 05 90 02 00 00
00008010 00 80 07 00 00 00 00 00 00 00 00 00 00 00 00 00
^^^^^^^^^^^
"
This makes the "qb save" work reliably even on SPI NOR, because the
erase block is aligned to 64 kiB instead of 1 kiB as it was before.
Maybe we need such an alignment change ?
"
diff --git a/tools/imx8image.c b/tools/imx8image.c
index 3cea536b8e8..09446142646 100644
--- a/tools/imx8image.c
+++ b/tools/imx8image.c
@@ -9,6 +9,13 @@
#include <image.h>
#include <linux/sizes.h>
+#define roundup(x, y) ( \
+{ \
+ const typeof(y) __y = y; \
+ (((x) + (__y - 1)) / __y) * __y; \
+} \
+)
+
static int p_idx;
static int sector_size;
static soc_type_t soc;
@@ -647,7 +654,7 @@ static void set_image_array_entry(flash_header_v3_t
*container,
/* if at least 2 images in container, [0] and
[1] */
boot_img_t *ddr_dummy =
&container->img[container->num_images - 1];
if ((ddr_dummy->hab_flags & 0x0F) ==
IMG_TYPE_DDR_DUMMY) {
- ddr_dummy->offset = img->offset + img->size;
+ ddr_dummy->offset = roundup(img->offset
+ img->size, 65536);
set_image_hash(ddr_dummy, "/dev/null",
IMAGE_HASH_ALGO_DEFAULT);
}
}
"
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-06-03 4:57 ` Marek Vasut
@ 2026-06-03 13:31 ` Simona Toaca
2026-06-03 13:53 ` Marek Vasut
0 siblings, 1 reply; 20+ messages in thread
From: Simona Toaca @ 2026-06-03 13:31 UTC (permalink / raw)
To: Marek Vasut
Cc: uboot-imx, u-boot, Stefano Babic, festevam, peng.fan, alice.guo,
ye.li, simona.toaca, viorel.suman, fedor.ross, joao.goncalves,
ravi, ping.bai, ji.luo, qijian.guo
On Wed, Jun 03, 2026 at 06:57:14AM +0200, Marek Vasut wrote:
> On 6/2/26 4:11 PM, Marek Vasut wrote:
> > On 6/2/26 3:46 PM, Simona Toaca wrote:
> >
> > Hi,
> >
[...]
> > > > > + ret = spi_flash_erase_dm(flash, offset, QB_STATE_LOAD_SIZE);
> > > > Can you please double-check whether "offset" here is always
> > > > aligned to 64
> > > > kiB (SPI NOR erase block size) ? If not, this erase here will fail.
> > > >
> > > > Thank you
> > >
> > > The erase size of the SPI NOR should actually be 4kiB.
> >
> > That does not apply to all devices. I assume to align this data to 64
> > kiB offset, I would need a replacement AHAB container?
> >
> > > I added
> > > a patch a while back enabling SFDP support, so that the erase size is
> > > parsed correctly and this does not fail. I only enabled it on U-Boot,
> > > but if you are using SPL you should enable it there too.
> > >
> > > The only reason why I did not enable it for SPL is that there seems
> > > to not be any defconfig for iMX95/943/952 for booting from SPI NOR.
> > >
> > > Trying 'qb save spi' from eMMC on an iMX943 EVK (with SFDP enabled):
> > > u-boot=> qb save spi
> > > SF: Detected mt35xu512aba with page size 256 Bytes, erase size 4 KiB,
> > > total 64 MiB
> > > u-boot=> echo $?
> > > 0
> > >
> > > Without SFDP, 'sf probe' reports 128kiB erase size, not 64kiB.
> > >
> > > I also do not see why I would check if the offset is a multiple of
> > > the erase size, since the erase fails gracefully.
> > My understanding is, that the erase operation should erase the QB data
> > from the SPI NOR, not ignore failure and keep the data there ?
If there is data in SPI NOR it means that a previous write was successful,
thus the erase shouldn't fail (as the write method in qb save also uses erase).
>
> It seems that on iMX95 B0, the DDR_DUMMY offset is 0x76400 (aligned to 1
> kiB, which is too low):
Yes, this is indeed too low. We align our DDR_DUMMY offset to 0x1000 (4KiB) in
imx-mkimage, I see that here is not the case. At the same time, aligning
it everytime (even for eMMC/SD case) to 64KiB seems overkill.
>
> This makes the "qb save" work reliably even on SPI NOR, because the erase
> block is aligned to 64 kiB instead of 1 kiB as it was before. Maybe we need
> such an alignment change ?
My suggestion is that we align it with what happens in imx-mkimage
and make ddr_dummy take an additional argument specifying the alignment.
(and leave 4KiB default alignment in the dtsi, which should be fine if
SFDP is used).
If you're ok with this, I will start working on a patch.
[...]
Regards,
Simona
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-06-03 13:31 ` Simona Toaca
@ 2026-06-03 13:53 ` Marek Vasut
2026-06-04 12:04 ` Simona Toaca
0 siblings, 1 reply; 20+ messages in thread
From: Marek Vasut @ 2026-06-03 13:53 UTC (permalink / raw)
To: Simona Toaca
Cc: uboot-imx, u-boot, Stefano Babic, festevam, peng.fan, alice.guo,
ye.li, simona.toaca, viorel.suman, fedor.ross, joao.goncalves,
ravi, ping.bai, ji.luo, qijian.guo
On 6/3/26 3:31 PM, Simona Toaca wrote:
[...]
>>>> I also do not see why I would check if the offset is a multiple of
>>>> the erase size, since the erase fails gracefully.
>>> My understanding is, that the erase operation should erase the QB data
>>> from the SPI NOR, not ignore failure and keep the data there ?
> If there is data in SPI NOR it means that a previous write was successful,
> thus the erase shouldn't fail (as the write method in qb save also uses erase).
It is possible to write to a non-erased SPI NOR, even at unaligned
address. It is not possible to erase at unaligned address.
>> It seems that on iMX95 B0, the DDR_DUMMY offset is 0x76400 (aligned to 1
>> kiB, which is too low):
> Yes, this is indeed too low. We align our DDR_DUMMY offset to 0x1000 (4KiB) in
> imx-mkimage, I see that here is not the case. At the same time, aligning
> it everytime (even for eMMC/SD case) to 64KiB seems overkill.
Are you testing this patchset with upstream U-Boot mkimage ?
>> This makes the "qb save" work reliably even on SPI NOR, because the erase
>> block is aligned to 64 kiB instead of 1 kiB as it was before. Maybe we need
>> such an alignment change ?
>
> My suggestion is that we align it with what happens in imx-mkimage
> and make ddr_dummy take an additional argument specifying the alignment.
> (and leave 4KiB default alignment in the dtsi, which should be fine if
> SFDP is used).
>
> If you're ok with this, I will start working on a patch.
This is fine by me, thank you.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM
2026-06-03 13:53 ` Marek Vasut
@ 2026-06-04 12:04 ` Simona Toaca
0 siblings, 0 replies; 20+ messages in thread
From: Simona Toaca @ 2026-06-04 12:04 UTC (permalink / raw)
To: Marek Vasut
Cc: uboot-imx, u-boot, Stefano Babic, festevam, peng.fan, alice.guo,
ye.li, simona.toaca, viorel.suman, fedor.ross, joao.goncalves,
ravi, ping.bai, ji.luo, qijian.guo
On Wed, Jun 03, 2026 at 03:53:04PM +0200, Marek Vasut wrote:
> On 6/3/26 3:31 PM, Simona Toaca wrote:
[...]
> > > It seems that on iMX95 B0, the DDR_DUMMY offset is 0x76400 (aligned to 1
> > > kiB, which is too low):
> > Yes, this is indeed too low. We align our DDR_DUMMY offset to 0x1000 (4KiB) in
> > imx-mkimage, I see that here is not the case. At the same time, aligning
> > it everytime (even for eMMC/SD case) to 64KiB seems overkill.
>
> Are you testing this patchset with upstream U-Boot mkimage ?
>
I tested it with U-Boot mkimage on SD/eMMC. For saving to SPI NOR, I was not
aware of the 'fspi' option in the dtsi, so I used imx-mkimage, not expecting
things to be different regarding DDR DUMMY in upstream U-Boot mkimage.
Thank you for catching this issue.
Regards,
Simona
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v4 2/4] arm: mach-imx: Add command to expose QB functionality
2026-04-30 8:33 [PATCH v4 0/4] imx9{4,5,52}: Add Quickboot support Simona Toaca (OSS)
2026-04-30 8:33 ` [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM Simona Toaca (OSS)
@ 2026-04-30 8:33 ` Simona Toaca (OSS)
2026-04-30 8:33 ` [PATCH v4 3/4] board: nxp: imx9{4, 5, 52}_evk: Add qb save option in SPL Simona Toaca (OSS)
` (2 subsequent siblings)
4 siblings, 0 replies; 20+ messages in thread
From: Simona Toaca (OSS) @ 2026-04-30 8:33 UTC (permalink / raw)
To: uboot-imx, u-boot
Cc: Stefano Babic, festevam, peng.fan, alice.guo, ye.li, simona.toaca,
viorel.suman, fedor.ross, marex, joao.goncalves, ravi, ping.bai,
ji.luo, qijian.guo
From: Simona Toaca <simona.toaca@nxp.com>
This command exposes 3 methods:
- check -> checks if the data in volatile memory is valid
(integrity check)
- save -> saves the data to non-volatile memory and
erases the data in volatile memory
- erase -> erases the data in non-volatile memory
cmd_qb can be used either directly in the U-Boot console
or in an uuu script to save the QB data during flashing.
It supports specifying a different boot medium than the
current boot device for saving the data.
Signed-off-by: Viorel Suman <viorel.suman@nxp.com>
Signed-off-by: Ye Li <ye.li@nxp.com>
Signed-off-by: Simona Toaca <simona.toaca@nxp.com>
---
arch/arm/mach-imx/Kconfig | 11 ++++
arch/arm/mach-imx/Makefile | 1 +
arch/arm/mach-imx/cmd_qb.c | 102 +++++++++++++++++++++++++++++++++++++
3 files changed, 114 insertions(+)
create mode 100644 arch/arm/mach-imx/cmd_qb.c
diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig
index bb62a0cf2f6..a40baf24631 100644
--- a/arch/arm/mach-imx/Kconfig
+++ b/arch/arm/mach-imx/Kconfig
@@ -80,6 +80,17 @@ config IMX_QB
memory to non-volatile storage. OEI uses the saved data to
run Quickboot flow and skip re-training the DDR PHY.
+config CMD_IMX_QB
+ bool "Support the 'qb' command"
+ default y
+ depends on IMX_QB
+ help
+ Enable qb command to write/erase DDR quick boot training
+ data to/from a chosen boot device. Using 'qb save/erase'
+ without arguments implies using the current boot device's
+ first bootable partition (e.g. boot0 for eMMC). For use in
+ uuu scripts, the boot device must be specified explicitly.
+
config CMD_BMODE
bool "Support the 'bmode' command"
default y
diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile
index bf6820de655..43febc10460 100644
--- a/arch/arm/mach-imx/Makefile
+++ b/arch/arm/mach-imx/Makefile
@@ -80,6 +80,7 @@ endif
ifneq ($(CONFIG_XPL_BUILD),y)
obj-$(CONFIG_CMD_BMODE) += cmd_bmode.o
obj-$(CONFIG_CMD_HDMIDETECT) += cmd_hdmidet.o
+obj-$(CONFIG_CMD_IMX_QB) += cmd_qb.o
obj-$(CONFIG_CMD_DEKBLOB) += cmd_dek.o
obj-$(CONFIG_CMD_NANDBCB) += cmd_nandbcb.o
endif
diff --git a/arch/arm/mach-imx/cmd_qb.c b/arch/arm/mach-imx/cmd_qb.c
new file mode 100644
index 00000000000..633d83d3abd
--- /dev/null
+++ b/arch/arm/mach-imx/cmd_qb.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0+
+/**
+ * Copyright 2024-2026 NXP
+ */
+#include <command.h>
+#include <spl.h>
+#include <stdlib.h>
+
+#include <asm/mach-imx/boot_mode.h>
+#include <asm/mach-imx/sys_proto.h>
+#include <asm/mach-imx/qb.h>
+
+static void parse_qb_args(int argc, char * const argv[],
+ const char **ifname, const char **dev)
+{
+ /* qb save/erase -> use boot device */
+ if (argc < 2) {
+ *ifname = "auto";
+ return;
+ }
+
+ *ifname = argv[1];
+
+ if (argc == 3)
+ *dev = argv[2];
+}
+
+static int do_qb(struct cmd_tbl *cmdtp, int flag, int argc,
+ char * const argv[], bool save)
+{
+ const char *ifname, *dev;
+
+ parse_qb_args(argc, argv, &ifname, &dev);
+
+ if (imx_qb(ifname, dev, save))
+ return CMD_RET_FAILURE;
+
+ return CMD_RET_SUCCESS;
+}
+
+static int do_qb_check(struct cmd_tbl *cmdtp, int flag,
+ int argc, char * const argv[])
+{
+ return imx_qb_check() ? CMD_RET_SUCCESS : CMD_RET_FAILURE;
+}
+
+static int do_qb_save(struct cmd_tbl *cmdtp, int flag,
+ int argc, char * const argv[])
+{
+ return do_qb(cmdtp, flag, argc, argv, true);
+}
+
+static int do_qb_erase(struct cmd_tbl *cmdtp, int flag,
+ int argc, char * const argv[])
+{
+ return do_qb(cmdtp, flag, argc, argv, false);
+}
+
+static struct cmd_tbl cmd_qb[] = {
+ U_BOOT_CMD_MKENT(check, 1, 1, do_qb_check, "", ""),
+ U_BOOT_CMD_MKENT(save, 3, 1, do_qb_save, "", ""),
+ U_BOOT_CMD_MKENT(erase, 3, 1, do_qb_erase, "", ""),
+};
+
+static int do_qbops(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct cmd_tbl *cp;
+
+ cp = find_cmd_tbl(argv[1], cmd_qb, ARRAY_SIZE(cmd_qb));
+
+ /* Drop the qb command */
+ argc--;
+ argv++;
+
+ if (!cp) {
+ printf("qb: %s: command not found\n", argv[0] ? argv[0] : " ");
+ return CMD_RET_USAGE;
+ }
+
+ if (argc > cp->maxargs) {
+ printf("qb %s: too many arguments: %d > %d\n", cp->name,
+ argc - 1, cp->maxargs - 1);
+ return CMD_RET_USAGE;
+ }
+
+ if (flag == CMD_FLAG_REPEAT && !cmd_is_repeatable(cp)) {
+ printf("qb %s: repeat flag set but command is not repeatable\n",
+ cp->name);
+ return CMD_RET_SUCCESS;
+ }
+
+ return cp->cmd(cmdtp, flag, argc, argv);
+}
+
+U_BOOT_CMD(
+ qb, 4, 1, do_qbops,
+ "DDR Quick Boot sub system",
+ "check - check if quick boot data is stored in mem by training flow\n"
+ "qb save [interface] [dev] - save quick boot data in NVM => trigger quick boot flow\n"
+ "qb erase [interface] [dev] - erase quick boot data from NVM => trigger training flow\n"
+);
--
2.43.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v4 3/4] board: nxp: imx9{4, 5, 52}_evk: Add qb save option in SPL
2026-04-30 8:33 [PATCH v4 0/4] imx9{4,5,52}: Add Quickboot support Simona Toaca (OSS)
2026-04-30 8:33 ` [PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM Simona Toaca (OSS)
2026-04-30 8:33 ` [PATCH v4 2/4] arm: mach-imx: Add command to expose QB functionality Simona Toaca (OSS)
@ 2026-04-30 8:33 ` Simona Toaca (OSS)
2026-04-30 8:33 ` [PATCH v4 4/4] doc: board: nxp: Add Quickboot documentation Simona Toaca (OSS)
2026-05-28 5:23 ` [PATCH v4 0/4] imx9{4,5,52}: Add Quickboot support Kumar, Udit
4 siblings, 0 replies; 20+ messages in thread
From: Simona Toaca (OSS) @ 2026-04-30 8:33 UTC (permalink / raw)
To: uboot-imx, u-boot
Cc: Stefano Babic, festevam, peng.fan, alice.guo, ye.li, simona.toaca,
viorel.suman, fedor.ross, marex, joao.goncalves, ravi, ping.bai,
ji.luo, qijian.guo
From: Simona Toaca <simona.toaca@nxp.com>
Call qb save automatically in the board-specific
spl_board_init(), if SPL_IMX_QB option is enabled.
This makes sure qb_save is called before any image
loading is done by the SPL. This option is also
suitable for the case where U-Boot proper is
missing (Falcon mode).
qb save refers to saving DDR training data to NVM,
so that OEI runs Quickboot flow on next reboot,
skipping full training and achieveing a lower boot
time.
Signed-off-by: Simona Toaca <simona.toaca@nxp.com>
---
arch/arm/mach-imx/Kconfig | 8 ++++++++
board/nxp/imx94_evk/spl.c | 6 +++++-
board/nxp/imx952_evk/spl.c | 4 ++++
board/nxp/imx95_evk/spl.c | 6 +++++-
4 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig
index a40baf24631..66142a835ce 100644
--- a/arch/arm/mach-imx/Kconfig
+++ b/arch/arm/mach-imx/Kconfig
@@ -80,6 +80,14 @@ config IMX_QB
memory to non-volatile storage. OEI uses the saved data to
run Quickboot flow and skip re-training the DDR PHY.
+config SPL_IMX_QB
+ bool "Run qb save during SPL"
+ depends on SPL && IMX_QB
+ help
+ Automatically save DDR training data (Quickboot data)
+ to current boot device when needed (when OEI runs Training
+ flow and saves qb data to volatile memory).
+
config CMD_IMX_QB
bool "Support the 'qb' command"
default y
diff --git a/board/nxp/imx94_evk/spl.c b/board/nxp/imx94_evk/spl.c
index 6eb0fff99f4..739a5f1f559 100644
--- a/board/nxp/imx94_evk/spl.c
+++ b/board/nxp/imx94_evk/spl.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Copyright 2025 NXP
+ * Copyright 2025-2026 NXP
*/
#include <hang.h>
@@ -14,6 +14,7 @@
#include <asm/arch/sys_proto.h>
#include <asm/mach-imx/boot_mode.h>
#include <asm/mach-imx/ele_api.h>
+#include <asm/mach-imx/qb.h>
DECLARE_GLOBAL_DATA_PTR;
@@ -44,6 +45,9 @@ void spl_board_init(void)
ret = ele_start_rng();
if (ret)
printf("Fail to start RNG: %d\n", ret);
+
+ if (IS_ENABLED(CONFIG_SPL_IMX_QB))
+ spl_imx_qb_save();
}
static void xspi_nor_reset(void)
diff --git a/board/nxp/imx952_evk/spl.c b/board/nxp/imx952_evk/spl.c
index de9256dc267..615c3b67fb6 100644
--- a/board/nxp/imx952_evk/spl.c
+++ b/board/nxp/imx952_evk/spl.c
@@ -8,6 +8,7 @@
#include <asm/gpio.h>
#include <asm/mach-imx/boot_mode.h>
#include <asm/mach-imx/ele_api.h>
+#include <asm/mach-imx/qb.h>
#include <asm/sections.h>
#include <hang.h>
#include <init.h>
@@ -44,6 +45,9 @@ void spl_board_init(void)
ret = ele_start_rng();
if (ret)
printf("Fail to start RNG: %d\n", ret);
+
+ if (IS_ENABLED(CONFIG_SPL_IMX_QB))
+ spl_imx_qb_save();
}
static void xspi_nor_reset(void)
diff --git a/board/nxp/imx95_evk/spl.c b/board/nxp/imx95_evk/spl.c
index 761a1a4a0f6..2fd69447e1e 100644
--- a/board/nxp/imx95_evk/spl.c
+++ b/board/nxp/imx95_evk/spl.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Copyright 2025 NXP
+ * Copyright 2025-2026 NXP
*/
#include <hang.h>
@@ -13,6 +13,7 @@
#include <asm/arch/sys_proto.h>
#include <asm/mach-imx/boot_mode.h>
#include <asm/mach-imx/ele_api.h>
+#include <asm/mach-imx/qb.h>
DECLARE_GLOBAL_DATA_PTR;
@@ -41,6 +42,9 @@ void spl_board_init(void)
ret = ele_start_rng();
if (ret)
printf("Fail to start RNG: %d\n", ret);
+
+ if (IS_ENABLED(CONFIG_SPL_IMX_QB))
+ spl_imx_qb_save();
}
void board_init_f(ulong dummy)
--
2.43.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v4 4/4] doc: board: nxp: Add Quickboot documentation
2026-04-30 8:33 [PATCH v4 0/4] imx9{4,5,52}: Add Quickboot support Simona Toaca (OSS)
` (2 preceding siblings ...)
2026-04-30 8:33 ` [PATCH v4 3/4] board: nxp: imx9{4, 5, 52}_evk: Add qb save option in SPL Simona Toaca (OSS)
@ 2026-04-30 8:33 ` Simona Toaca (OSS)
2026-05-28 5:23 ` [PATCH v4 0/4] imx9{4,5,52}: Add Quickboot support Kumar, Udit
4 siblings, 0 replies; 20+ messages in thread
From: Simona Toaca (OSS) @ 2026-04-30 8:33 UTC (permalink / raw)
To: uboot-imx, u-boot
Cc: Stefano Babic, festevam, peng.fan, alice.guo, ye.li, simona.toaca,
viorel.suman, fedor.ross, marex, joao.goncalves, ravi, ping.bai,
ji.luo, qijian.guo
From: Simona Toaca <simona.toaca@nxp.com>
Add instructions on how to use U-Boot to save
DDR training data to NVM and explain the saving
process.
Signed-off-by: Simona Toaca <simona.toaca@nxp.com>
---
doc/board/nxp/index.rst | 1 +
doc/board/nxp/quickboot.rst | 59 +++++++++++++++++++++++++++++++++++++
2 files changed, 60 insertions(+)
create mode 100644 doc/board/nxp/quickboot.rst
diff --git a/doc/board/nxp/index.rst b/doc/board/nxp/index.rst
index 8cd24aecf33..52c8e85fa5b 100644
--- a/doc/board/nxp/index.rst
+++ b/doc/board/nxp/index.rst
@@ -30,3 +30,4 @@ NXP Semiconductors
mx6ullevk
rproc
psb
+ quickboot
diff --git a/doc/board/nxp/quickboot.rst b/doc/board/nxp/quickboot.rst
new file mode 100644
index 00000000000..0fd72b4e13b
--- /dev/null
+++ b/doc/board/nxp/quickboot.rst
@@ -0,0 +1,59 @@
+.. SPDX-License-Identifier: GPL-2.0+
+ Copyright 2026 NXP
+
+DDR QuickBoot flow
+------------------
+
+Some NXP SoCs (which use OEI - iMX943, iMX95, iMX952 etc.) support saving
+DDR training data (collected by OEI during Training flow) from volatile
+to non-volatile memory, which is then available to OEI at next cold reboot.
+OEI uses the saved data to run Quickboot flow and avoid training the DDR again.
+This significantly reduces the boot time.
+
+The location of the quickboot data in NVM is a space left in the bootloader by
+mkimage, with the size of 64K. The qb command searches for this space to
+save the data. Thus, the NVM should also be a boot device and contain
+the bootloader at the time of the saving.
+
+U-Boot provides no authentication for quickboot data, only its integrity
+is verified via the CRC32. The authentication is done in OEI. With
+the exception of iMX95 A0/A1, which use CRC32 as well for verifying
+the data, the rest of the SoCs use ELE to verify the MAC stored
+in the ddrphy_qb_state structure.
+
+If the quickboot data in memory is not valid (CRC32 check fails),
+U-Boot does not save it to NVM. So, if OEI runs Quickboot flow -> no
+data is written to volatile memory -> invalid data -> no saving happens
+(qb save fails during qb check).
+
+After successful saving, U-Boot clears the data in volatile memory so
+that qb check fails at next reboot and the NVM isn't accessed again.
+
+There are 2 ways to save this data, both can be enabled:
+
+1. automatically, in SPL (by enabling CONFIG_SPL_IMX_QB)
+
+- this will save the data on the current boot device (e.g. SD)
+- other configs specific to the boot device need to be enabled (CONFIG_SPL_MMC_WRITE for saving to eMMC/SD)
+- use for: automating qb save / saving quickboot data if using Falcon mode (skipping U-Boot proper)
+
+2. using qb command in U-Boot console (by enabling CONFIG_CMD_IMX_QB)
+
+- supports saving on the current boot device, or on another, specified device.
+- supports specifying the hwpartition for eMMC (for booting from boot0/boot1)
+- if flashing via uuu, the command can be added in an uuu script (boot device needs to be specified)
+- use 'qb erase' to force DDR re-training
+- use for: saving quickboot data during flashing / controlling the NVM to save to / forcing re-training
+
+::
+
+ # To save/erase on current boot device
+ # For eMMC boot1, mmc 0:2 has to be specified explicitly
+ => qb save/erase
+
+ # To save/erase on other boot device
+ => qb save/erase mmc 0 # eMMC boot0
+ => qb save/erase mmc 0:1 # eMMC boot0
+ => qb save/erase mmc 0:2 # eMMC boot1
+ => qb save/erase mmc 1 # SD
+ => qb save/erase spi # NOR SPI
--
2.43.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v4 0/4] imx9{4,5,52}: Add Quickboot support
2026-04-30 8:33 [PATCH v4 0/4] imx9{4,5,52}: Add Quickboot support Simona Toaca (OSS)
` (3 preceding siblings ...)
2026-04-30 8:33 ` [PATCH v4 4/4] doc: board: nxp: Add Quickboot documentation Simona Toaca (OSS)
@ 2026-05-28 5:23 ` Kumar, Udit
4 siblings, 0 replies; 20+ messages in thread
From: Kumar, Udit @ 2026-05-28 5:23 UTC (permalink / raw)
To: Simona Toaca (OSS), uboot-imx, u-boot
Cc: Stefano Babic, festevam, peng.fan, alice.guo, ye.li, simona.toaca,
viorel.suman, fedor.ross, marex, joao.goncalves, ravi, ping.bai,
ji.luo, qijian.guo, Siddhant.Kumar
Hi Simona
On 4/30/2026 2:03 PM, Simona Toaca (OSS) wrote:
> From: Simona Toaca <simona.toaca@nxp.com>
>
> This patch series adds support for saving DDR training
> data to non-volatile memory on iMX94, iMX95 and iMX952 platforms.
> The purpose is running DDR Quickboot flow on next reboot.
First thanks for this series,
Since this is DDR IP feature, could you think to make this generic
instead of board/arch specific.
> The process is as follows:
> - OEI runs Training flow for the DDRPHY
> - OEI saves the data from training to volatile memory
> - U-Boot can then save it to non-volatile memory (e.g. SD)
> - OEI loads the data from NVM at cold reboot and runs Quickboot flow
>
> By skipping training, a much lower boot time is achieved.
>
> Changes for v4:
> - add IMX_QB macro to include qb.c based on the boot phase
> - change qb_ prefix to imx_qb_ for qb.c methods, to
> be more specific
> - add boot device macro to string conversion for the case
> where the device is not explicitly specified
> - pass quickboot args as strings and parse them in qb.c using
> blk_device_get_by_str/blk_device_part_str (if they are blk devices).
> This enables specifying mmc 0:2 for the boot1 partition of the eMMC
> device.
> - use blk device API instead of mmc-specific
> - use SPI DM methods for spi flash read/write
> - use malloc for storing container header instead of kmalloc
> - add eMMC hwpart usage to the documentation
> - sent the qb-unrelated patches separately
>
> Changes for v3:
> - Rebased and added support for iMX952
> - Removed IMX_SNPS_DDR_PHY_QB_GEN macro, as it was not useful ->
> now CMD_QB is enabled by default on the supported boards
> - Removed unnecessary #ifdefs -> replaced with if (CONFIG..)
> - Replaced spi_flash_probe with udevice_first_device_err to
> avoid using SPI macros that needed ifdefs, since there is only
> one SPI flash device available.
> - Adnotated qb methods with qb_ to be easier to see in asm dump
> - Removed explicit pointer casts from (void *)
> - Replaced custom qb_crc32 with the U-Boot one
> - Made eveything snake_case
> - Enabled SFDP support for iMX943/95, as it is necessary for
> proper erase size parsing (and is already present in iMX952 config)
> - Improved documentation - explanation about the space in bootloader
> - Added commit fixing a style issue in Kconfig
>
> Changes for v2:
> - Improved documentation to clarify the questions asked
> - Detailed log messages for all commits
> - Detailed Kconfig options for SPL_IMX_QB and CMD_IMX_QB
> - Fixed the mentioned coding style issues
>
> Simona Toaca (4):
> imx9: Add support for saving DDR training data to NVM
> arm: mach-imx: Add command to expose QB functionality
> board: nxp: imx9{4,5,52}_evk: Add qb save option in SPL
> doc: board: nxp: Add Quickboot documentation
>
> arch/arm/include/asm/arch-imx9/ddr.h | 48 +++-
> arch/arm/include/asm/mach-imx/qb.h | 15 +
> arch/arm/mach-imx/Kconfig | 28 ++
> arch/arm/mach-imx/Makefile | 1 +
> arch/arm/mach-imx/cmd_qb.c | 102 +++++++
> arch/arm/mach-imx/imx9/Makefile | 6 +-
> arch/arm/mach-imx/imx9/qb.c | 403 +++++++++++++++++++++++++++
> arch/arm/mach-imx/imx9/scmi/soc.c | 7 +
> board/nxp/imx94_evk/spl.c | 6 +-
> board/nxp/imx952_evk/spl.c | 4 +
> board/nxp/imx95_evk/spl.c | 6 +-
> doc/board/nxp/index.rst | 1 +
> doc/board/nxp/quickboot.rst | 59 ++++
> drivers/ddr/imx/imx9/Kconfig | 7 +
> 14 files changed, 688 insertions(+), 5 deletions(-)
> create mode 100644 arch/arm/include/asm/mach-imx/qb.h
> create mode 100644 arch/arm/mach-imx/cmd_qb.c
> create mode 100644 arch/arm/mach-imx/imx9/qb.c
> create mode 100644 doc/board/nxp/quickboot.rst
>
^ permalink raw reply [flat|nested] 20+ messages in thread