diff options
| author | Simona Toaca <[email protected]> | 2026-04-30 11:33:30 +0300 |
|---|---|---|
| committer | Fabio Estevam <[email protected]> | 2026-05-15 17:31:39 -0300 |
| commit | c9a8f673e0b8dc30bd575faae34e0b1f1e42a706 (patch) | |
| tree | ca53a827acb55d2c7c61ef746b08b30021136267 | |
| parent | 84a17fea21f8877a668f535145a105db4ccf791e (diff) | |
imx9: Add support for saving DDR training data to NVM
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 <[email protected]>
Signed-off-by: Ye Li <[email protected]>
Signed-off-by: Simona Toaca <[email protected]>
| -rw-r--r-- | arch/arm/include/asm/arch-imx9/ddr.h | 48 | ||||
| -rw-r--r-- | arch/arm/include/asm/mach-imx/qb.h | 15 | ||||
| -rw-r--r-- | arch/arm/mach-imx/Kconfig | 9 | ||||
| -rw-r--r-- | arch/arm/mach-imx/imx9/Makefile | 6 | ||||
| -rw-r--r-- | arch/arm/mach-imx/imx9/qb.c | 403 | ||||
| -rw-r--r-- | arch/arm/mach-imx/imx9/scmi/soc.c | 7 | ||||
| -rw-r--r-- | drivers/ddr/imx/imx9/Kconfig | 7 |
7 files changed, 492 insertions, 3 deletions
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 60fdd577f55..7c107c88bb4 100644 --- a/arch/arm/mach-imx/imx9/scmi/soc.c +++ b/arch/arm/mach-imx/imx9/scmi/soc.c @@ -311,6 +311,13 @@ static struct mm_region imx9_mem_map[] = { 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 |
