[PATCH] binman: imx8mimage: Generate FSPI header in binman instead of mkimage

Quentin Schulz quentin.schulz at cherry.de
Fri Jun 26 13:14:20 CEST 2026


Hi Marek,

On 6/10/26 6:17 PM, Marek Vasut wrote:
> Stop depending on the current mkimage method of generating the FSPI
> header, instead generate the FSPI header within binman itself. This

Soooooo, do we still need the mkimage version or can we remove it? I 
generally would be against it since "users may make use of it" but since 
you would need to rebuild (according to this commit log) it to be able 
to compile what you want for your device, essentially making the host 
tool dependent on the target you're building for, I guess it could be 
removed? What do you think? I'm eyeing tools/imx8mimage.c and 
tools/imximage.c specifically.

Which makes me ask "is this something we could do for imximage as well?"

> is more flexible, as the FSPI header properties can be configured
> from within the board-specific DT instead of being hard-coded in
> mkimage at build time.
> 
> Signed-off-by: Marek Vasut <marex at nabladev.com>
> ---
> Cc: "NXP i.MX U-Boot Team" <uboot-imx at nxp.com>
> Cc: Fabio Estevam <festevam at gmail.com>
> Cc: Quentin Schulz <quentin.schulz at cherry.de>
> Cc: Simon Glass <sjg at chromium.org>
> Cc: Stefano Babic <sbabic at nabladev.com>
> Cc: Tom Rini <trini at konsulko.com>
> Cc: Wojciech Dubowik <Wojciech.Dubowik at mt.com>
> Cc: Yannic Moog <y.moog at phytec.de>
> Cc: u-boot at lists.denx.de
> ---
>   tools/binman/etype/nxp_imx8mimage.py          | 93 +++++++++++++++++--
>   tools/binman/ftest.py                         | 23 ++---
>   tools/binman/test/vendor/nxp_imx8m_fspi.dts   |  1 -
>   .../nxp_imx8m_fspi_fail_columnadresswidth.dts | 19 ++++
>   ...dts => nxp_imx8m_fspi_fail_devicetype.dts} |  3 +-
>   .../nxp_imx8m_fspi_fail_flashpadtype.dts      | 19 ++++
>   .../nxp_imx8m_fspi_fail_readsampleclksrc.dts  | 19 ++++
>   .../nxp_imx8m_fspi_fail_serialclkfreq.dts     | 19 ++++
>   .../test/vendor/nxp_imx8m_fspi_pass.dts       |  1 -
>   9 files changed, 176 insertions(+), 21 deletions(-)
>   create mode 100644 tools/binman/test/vendor/nxp_imx8m_fspi_fail_columnadresswidth.dts
>   rename tools/binman/test/vendor/{nxp_imx8m_fspi_fail.dts => nxp_imx8m_fspi_fail_devicetype.dts} (83%)
>   create mode 100644 tools/binman/test/vendor/nxp_imx8m_fspi_fail_flashpadtype.dts
>   create mode 100644 tools/binman/test/vendor/nxp_imx8m_fspi_fail_readsampleclksrc.dts
>   create mode 100644 tools/binman/test/vendor/nxp_imx8m_fspi_fail_serialclkfreq.dts
> 
> diff --git a/tools/binman/etype/nxp_imx8mimage.py b/tools/binman/etype/nxp_imx8mimage.py
> index 25c43438a87..47cd4481acd 100644
> --- a/tools/binman/etype/nxp_imx8mimage.py
> +++ b/tools/binman/etype/nxp_imx8mimage.py
> @@ -8,6 +8,7 @@
>   #
>   
>   import os

You remove the only user of this module later in the diff, so this can 
be removed as well.

> +import struct
>   
>   from collections import OrderedDict
>   
> @@ -23,10 +24,25 @@ class Entry_nxp_imx8mimage(Entry_mkimage):
>   
>       Properties / Entry arguments:
>           - nxp,boot-from - device to boot from (e.g. 'sd')
> +        - nxp,fspi-columnadresswidth - FSPI column address width
> +            (3 - HyperFlash, 12/13 - Serial NAND, 0 - Otherwise)
> +        - nxp,fspi-controllermisc-diffclk - FSPI differential clock enable
> +        - nxp,fspi-controllermisc-wordaddr - FSPI word addressable enable
> +        - nxp,fspi-controllermisc-safecfg - FSPI safe configuration frequency
> +        - nxp,fspi-controllermisc-padovr - FSPI pad setting override
> +        - nxp,fspi-controllermisc-ddrmode - FSPI DDR mode
> +        - nxp,fspi-lutcustomseq - FSPI use LUT sequence parameters
> +        - nxp,fspi-devicetype - FSPI device type
> +            (1 - SPI NOR, 2 - Serial NAND)
> +        - nxp,fspi-flashpadtype - FSPI flash pad type
> +            (1 - Single pad, 2 - Dual pads, 4 - Quad pads, 8 - Octal pads)
> +        - nxp,fspi-readsampleclksrc - FSPI clock source
> +            (0 - Internal loopback, 1 - loopback from DQS pad, 3 - Flash provided DQS).
> +        - nxp,fspi-serialclkfreq - FSPI clock frequency
> +            (1 - 30 MHz, 2 - 50 MHz, 3 - 60 MHz, 4 - 75 MHz, 5 - 80 MHz,
> +             6 - 100 MHz, 7 - 133 MHz, 8 - 166 MHz).

OK, so... I have not played with i.MX8M but I would be absolutely 
clueless on how to configure this. Is there some literature, readme, 
docs we could point at?

Please also mention that all nxp,fspi-* arguments are only available if 
nxp,boot-from == "fspi".

Please document which is the default if the argument is omitted.

Finally, I don't think it makes sense to tell the user "say 1 if you 
want 30MHz, 2 for 50MHz", why can't the user specify the frequency 
directly and we do the job for them of translating it to something to 
put in the binary? (same remark for all arguments).

>           - nxp,loader-address - loader address (SPL text base)
>           - nxp,rom-version - BootROM version ('2' for i.MX8M Nano and Plus)
> -        - nxp,fspi-header-filename - FSPI header file name (CONFIG_FSPI_CONF_FILE).
> -            Used only if 'nxp,boot-from == "fspi"' .
>       """
>   
>       def __init__(self, section, etype, node):
> @@ -37,9 +53,30 @@ class Entry_nxp_imx8mimage(Entry_mkimage):
>       def ReadNode(self):
>           super().ReadNode()
>           self.boot_from = fdt_util.GetString(self._node, 'nxp,boot-from')
> -        self.fspi_header = fdt_util.GetString(self._node, 'nxp,fspi-header-filename', 'fspi_header.bin')
> +        self.fspi_columnadresswidth = fdt_util.GetInt(self._node, 'nxp,fspi-columnadresswidth', 0)
> +        self.fspi_controllermisc_diffclk = fdt_util.GetBool(self._node, 'nxp,fspi-controllermisc-diffclk')
> +        self.fspi_controllermisc_wordaddr = fdt_util.GetBool(self._node, 'nxp,fspi-controllermisc-wordaddr')
> +        self.fspi_controllermisc_safecfg = fdt_util.GetBool(self._node, 'nxp,fspi-controllermisc-safecfg')
> +        self.fspi_controllermisc_padovr = fdt_util.GetBool(self._node, 'nxp,fspi-controllermisc-padovr')
> +        self.fspi_controllermisc_ddrmode = fdt_util.GetBool(self._node, 'nxp,fspi-controllermisc-ddrmode')
> +        self.fspi_devicetype = fdt_util.GetInt(self._node, 'nxp,fspi-devicetype', 1)
> +        self.fspi_flasha1size = fdt_util.GetInt(self._node, 'nxp,fspi-flasha1size', 0x10000000)
> +        self.fspi_flashpadtype = fdt_util.GetInt(self._node, 'nxp,fspi-flashpadtype', 1)
> +        self.fspi_lutcustomseq = fdt_util.GetBool(self._node, 'nxp,fspi-lutcustomseq')
> +        self.fspi_readsampleclksrc = fdt_util.GetInt(self._node, 'nxp,fspi-readsampleclksrc', 0)
> +        self.fspi_serialclkfreq = fdt_util.GetInt(self._node, 'nxp,fspi-serialclkfreq', 2)
>           self.loader_address = fdt_util.GetInt(self._node, 'nxp,loader-address')
>           self.rom_version = fdt_util.GetInt(self._node, 'nxp,rom-version')
> +        if not self.fspi_columnadresswidth in [ 0, 3, 12, 13 ]:
> +            raise ValueError('nxp,fspi-columnadresswidth can be 0, 3, 12, 13 only.')
> +        if not self.fspi_devicetype in [ 1, 2 ]:
> +            raise ValueError('nxp,fspi-devicetype can be 1, 2 only.')
> +        if not self.fspi_flashpadtype in [ 1, 2, 4, 8 ]:
> +            raise ValueError('nxp,fspi-flashpadtype can be 1, 2, 4, 8 only.')
> +        if not self.fspi_readsampleclksrc in [ 0, 1, 3 ]:
> +            raise ValueError('nxp,fspi-readsampleclksrc can be 0, 1, 3 only.')
> +        if not self.fspi_serialclkfreq in [ 1, 2, 3, 4, 5, 6, 7, 8 ]:
> +            raise ValueError('nxp,fspi-serialclkfreq can be 1..8 only.')
>           self.ReadEntries()
>   
>       def BuildSectionData(self, required):
> @@ -59,10 +96,52 @@ class Entry_nxp_imx8mimage(Entry_mkimage):
>           if self.mkimage.run_cmd(*args) is not None:
>               outdata = tools.read_file(output_fname)
>               if self.boot_from == 'fspi':
> -                spidata = tools.read_file(os.path.join(tools.get_output_dir(), self.fspi_header))
> -                if len(spidata) != 448:
> -                    raise ValueError("FSPI header is not 448 Bytes long")
> -                spidata += tools.get_bytes(0, 0x1000 - len(spidata))
> +                # 0x00 ... Tag
> +                spidata = struct.pack('<I', 0x42464346)
> +                # 0x04 ... Version
> +                spidata += struct.pack('<I', 0x56010000)
> +                # 0x08 ... Reserved
> +                spidata += struct.pack('<I', 0)
> +                # 0x0c ... readSampleClkSrc (LSByte at 0x0c), dataHoldTime,
> +                #          dataSetupTime, columnAdressWidth (MSByte at 0x0f)
> +                spidata += struct.pack('<I', 0x00030300 |
> +                    (self.fspi_columnadresswidth << 24) |
> +                    self.fspi_readsampleclksrc)
> +
> +                # 0x10..0x3f ... Padding
> +                spidata += tools.get_bytes(0, 0x30)
> +
> +                # 0x40 ... controllerMiscOption
> +                spidata += struct.pack('<I',
> +                    ((1 << 0) if self.fspi_controllermisc_diffclk else 0) |
> +                    ((1 << 3) if self.fspi_controllermisc_wordaddr else 0) |
> +                    ((1 << 4) if self.fspi_controllermisc_safecfg else 0) |
> +                    ((1 << 5) if self.fspi_controllermisc_padovr else 0) |
> +                    ((1 << 6) if self.fspi_controllermisc_ddrmode else 0))
> +
> +                # 0x44 ... deviceType (LSByte at 0x44), sflashPadType,
> +                #          serialClkFreq, lutCustomSeqEnable (MSByte at 0x47)
> +                spidata += struct.pack('<I',
> +                    ((1 << 24) if self.fspi_lutcustomseq else 0) |
> +                    (self.fspi_serialclkfreq << 16) |
> +                    (self.fspi_flashpadtype << 8) |
> +                    self.fspi_devicetype)
> +
> +                # 0x48..0x4f ... Padding
> +                spidata += tools.get_bytes(0, 0x8)
> +
> +                # 0x50 ... flashA1Size
> +                spidata += struct.pack('<I', self.fspi_flasha1size)
> +
> +                # 0x54..0x7f ... Padding
> +                spidata += tools.get_bytes(0, 0x2c)
> +
> +                # 0x80 ... lookupTable
> +                spidata += struct.pack('<I', 0x0818040b)
> +                spidata += struct.pack('<I', 0x24043008)
> +
> +                # 0x88..0xfff ... Padding (end of FSPI block is 0x1bf, align to 4k)
> +                spidata += tools.get_bytes(0, 0x1000 - 0xa8)

I'll let people with an i.MX8M review the implementation but I do have 
some feedback to provide on the logic. This essentially removes the 
ability to use an externally-provided fspi_header.bin. So you should fix 
in the same commit arch/arm/dts/imx8mm-u-boot.dtsi and 
arch/arm/dts/imx8mn-u-boot.dtsi to migrate them to the new mechanism... 
Which likely means going through all board defconfigs and figuring out 
which values they use for compiling the mkimage that is used to generate 
this file and then put it in a board-specific -u-boot.dtsi. The other 
issue is downstream which will now be forced to migrate to it. I guess 
it could be fine but another option could be "specify the binary file to 
use or all those arguments instead" and keep current logic and simply 
extend it to support the new arguments. Otherwise, maybe checking the 
argument exists in the DTB and fail hard and tell the user to migrate to 
the new format with multiple arguments (what happens if we remove 
support for nxp,fspi-header-filename but it's still present in the DTS 
and the user hasn't migrated to the new arguments? does it fail the 
build or silently build something that they cannot boot?).

Cheers,
Quentin


More information about the U-Boot mailing list