[PATCH v2 2/5] arm: mach-imx: Add command to expose QB functionality
Marek Vasut
marex at nabladev.com
Mon Mar 16 13:20:06 CET 2026
On 3/16/26 9:15 AM, Simona Toaca (OSS) wrote:
[...]
> +++ b/arch/arm/mach-imx/Kconfig
> @@ -71,6 +71,17 @@ config CSF_SIZE
> Define the maximum size for Command Sequence File (CSF) binary
> this information is used to define the image boot data.
>
> +config CMD_IMX_QB
> + bool "Support the 'qb' command"
> + default y
> + depends on IMX_SNPS_DDR_PHY_QB_GEN && (IMX95 || IMX94)
94 should be before 95
> + help
> + Enable qb command to write/erase DDR quick boot training
> + data to/from a chosen boot device. Using 'qb save/erase'
> + without args
arguments ... please avoid abbreviations
> implies using the current boot device. 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 0f6e737c0b9..dfa9eca43eb 100644
> --- a/arch/arm/mach-imx/Makefile
> +++ b/arch/arm/mach-imx/Makefile
> @@ -82,6 +82,7 @@ obj-$(CONFIG_CMD_BMODE) += cmd_bmode.o
> obj-$(CONFIG_CMD_HDMIDETECT) += cmd_hdmidet.o
> obj-$(CONFIG_CMD_DEKBLOB) += cmd_dek.o
> obj-$(CONFIG_CMD_NANDBCB) += cmd_nandbcb.o
> +obj-$(CONFIG_CMD_IMX_QB) += cmd_qb.o
Keep the list sorted
> endif
>
> ifeq ($(CONFIG_XPL_BUILD),y)
> diff --git a/arch/arm/mach-imx/cmd_qb.c b/arch/arm/mach-imx/cmd_qb.c
> new file mode 100644
> index 00000000000..54733c1ad88
> --- /dev/null
> +++ b/arch/arm/mach-imx/cmd_qb.c
> @@ -0,0 +1,132 @@
> +// 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 int get_board_boot_device(enum boot_device dev)
> +{
> + switch (dev) {
> + 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 void parse_qb_args(int argc, char * const argv[],
> + int *qb_dev, int qb_bootdev)
> +{
> + long dev = -1;
> + char *interface = "";
> +
> + if (argc >= 2) {
> + interface = argv[1];
> + } else {
> + /* qb save -> use boot device */
> + *qb_dev = qb_bootdev;
Maybe invert the conditional, if (args < 2) qb_dev = ... to make it more
obvious what is going on here ?
> + }
> +
> + if (argc == 3)
> + dev = simple_strtol(argv[2], NULL, 10);
> +
> + if (!strcmp(interface, "mmc") && dev >= 0 &&
> + dev <= (BOOT_DEVICE_MMC2_2 - BOOT_DEVICE_MMC1))
> + *qb_dev = BOOT_DEVICE_MMC1 + dev;
> +
> + if (!strcmp(interface, "spi"))
Shouldn't this be "else if" here, since interface can not be both mmc
and spi ?
> + *qb_dev = BOOT_DEVICE_SPI;
> +}
> +
> +static int do_qb(struct cmd_tbl *cmdtp, int flag, int argc,
> + char * const argv[], bool save)
> +{
> + int ret = CMD_RET_FAILURE;
> + enum boot_device boot_dev = UNKNOWN_BOOT;
> + int qb_dev = BOOT_DEVICE_NONE, qb_bootdev;
> +
> + boot_dev = get_boot_device();
> + qb_bootdev = get_board_boot_device(boot_dev);
> +
> + parse_qb_args(argc, argv, &qb_dev, qb_bootdev);
> +
> + ret = qb(qb_dev, qb_bootdev, save);
> +
> + return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS;
> +}
> +
> +static int do_qb_check(struct cmd_tbl *cmdtp, int flag,
> + int argc, char * const argv[])
> +{
> + return 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("%s cp is null\n", __func__);
What does this error output even mean ? The user will be confused,
please reword it.
> + return CMD_RET_USAGE;
> + }
> +
> + if (argc > cp->maxargs) {
> + printf("%s argc(%d) > cp->maxargs(%d)\n", __func__, argc, cp->maxargs);
Reword this too please.
> + return CMD_RET_USAGE;
> + }
> +
> + if (flag == CMD_FLAG_REPEAT && !cmd_is_repeatable(cp)) {
> + printf("%s %s repeat flag set but command is not repeatable\n",
One space is enough.
> + __func__, 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 location => trigger quick boot flow\n"
> + "qb erase [interface] [dev] - erase quick boot data from NVM location => trigger training flow\n"
> +);
> diff --git a/arch/arm/mach-imx/imx9/Makefile b/arch/arm/mach-imx/imx9/Makefile
> index 3018d128a36..7dee144e0f4 100644
> --- a/arch/arm/mach-imx/imx9/Makefile
> +++ b/arch/arm/mach-imx/imx9/Makefile
> @@ -15,5 +15,7 @@ obj-y += imx_bootaux.o
> endif
>
> ifeq ($(CONFIG_IMX_SNPS_DDR_PHY_QB_GEN),y)
> -obj-y += qb.o
> +ifneq ($(CONFIG_XPL_BUILD),y)
> +obj-$(CONFIG_CMD_IMX_QB) += qb.o
> +endif
This shoudld be part of 1/5 , should it not ?
More information about the U-Boot
mailing list