[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