diff --git a/tools/binman/btool/cst.py b/tools/binman/btool/cst.py
new file mode 100644
index 00000000000..30e78bdbbd9
--- /dev/null
+++ b/tools/binman/btool/cst.py
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2024 Marek Vasut <marex@denx.de>
+#
+"""Bintool implementation for cst"""
+
+import re
+
+from binman import bintool
+
+class Bintoolcst(bintool.Bintool):
+    """Image generation for U-Boot
+
+    This bintool supports running `cst` with some basic parameters as
+    needed by binman.
+    """
+    def __init__(self, name):
+        super().__init__(name, 'Sign NXP i.MX image')
+
+    # pylint: disable=R0913
+    def run(self, output_fname=None):
+        """Run cst
+
+        Args:
+            output_fname: Output filename to write to
+        """
+        args = []
+        if output_fname:
+            args += ['-o', output_fname]
+        return self.run_cmd(*args)
+
+    def fetch(self, method):
+        """Fetch handler for cst
+
+        This installs cst using the apt utility.
+
+        Args:
+            method (FETCH_...): Method to use
+
+        Returns:
+            True if the file was fetched and now installed, None if a method
+            other than FETCH_BIN was requested
+
+        Raises:
+            Valuerror: Fetching could not be completed
+        """
+        if method != bintool.FETCH_BIN:
+            return None
+        return self.apt_install('imx-code-signing-tool')
diff --git a/tools/binman/etype/nxp_imx8mcst.py b/tools/binman/etype/nxp_imx8mcst.py
new file mode 100644
index 00000000000..fb1c3415ca1
--- /dev/null
+++ b/tools/binman/etype/nxp_imx8mcst.py
@@ -0,0 +1,133 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2023-2024 Marek Vasut <marex@denx.de>
+# Written with much help from Simon Glass <sjg@chromium.org>
+#
+# Entry-type module for generating the i.MX8M code signing tool
+# input configuration file and invocation of cst on generated
+# input configuration file and input data to be signed.
+#
+
+import struct
+
+from collections import OrderedDict
+
+from binman.entry import Entry
+from binman.etype.mkimage import Entry_mkimage
+from binman.etype.section import Entry_section
+from binman import elf
+from dtoc import fdt_util
+from u_boot_pylib import tools
+
+class Entry_nxp_imx8mcst(Entry_mkimage):
+    """NXP i.MX8M CST .cfg file generator and cst invoker
+
+    Properties / Entry arguments:
+        - nxp,loader-address - loader address (SPL text base)
+    """
+
+    def __init__(self, section, etype, node):
+        super().__init__(section, etype, node)
+        self.required_props = ['nxp,loader-address']
+
+    def ReadNode(self):
+        super().ReadNode()
+        self.loader_address = fdt_util.GetInt(self._node, 'nxp,loader-address')
+        self.srk_table = fdt_util.GetString(self._node, 'nxp,srk-table', 'SRK_1_2_3_4_table.bin')
+        self.csf_key = fdt_util.GetString(self._node, 'nxp,csf-key', 'CSF1_1_sha256_4096_65537_v3_usr_crt.pem')
+        self.img_key = fdt_util.GetString(self._node, 'nxp,img-key', 'IMG1_1_sha256_4096_65537_v3_usr_crt.pem')
+        self.unlock = fdt_util.GetBool(self._node, 'nxp,unlock')
+        self.ReadEntries()
+
+    def BuildSectionData(self, required):
+        data, input_fname, uniq = self.collect_contents_to_file(
+            self._entries.values(), 'input')
+        # Parse the input data and figure out what it is that is being signed.
+        # - If it is mkimage'd imx8mimage, then extract to be signed data size
+        #   from imx8mimage header, and calculate CSF blob offset right past
+        #   the SPL from this information.
+        # - If it is fitImage, then pad the image to 4k, add generated IVT and
+        #   sign the whole payload, then append CSF blob at the end right past
+        #   the IVT.
+        signtype = struct.unpack('<I', data[:4])[0]
+        signbase = self.loader_address
+        signsize = 0
+        if signtype == 0x412000d1: # SPL/imx8mimage
+            # Sign the payload including imx8mimage header
+            # (extra 0x40 bytes before the payload)
+            signbase -= 0x40
+            signsize = struct.unpack('<I', data[24:28])[0] - signbase
+            # Remove mkimage generated padding from the end of data
+            data = data[:signsize]
+        elif signtype == 0xedfe0dd0: # fitImage
+            # Align fitImage to 4k
+            signsize = tools.align(len(data), 0x1000)
+            data += tools.get_bytes(0, signsize - len(data))
+            # Add generated IVT
+            data += struct.pack('<I', 0x412000d1)
+            data += struct.pack('<I', signbase + signsize) # IVT base
+            data += struct.pack('<I', 0)
+            data += struct.pack('<I', 0)
+            data += struct.pack('<I', 0)
+            data += struct.pack('<I', signbase + signsize) # IVT base
+            data += struct.pack('<I', signbase + signsize + 0x20) # CSF base
+            data += struct.pack('<I', 0)
+        # Write out customized data to be signed
+        output_dname = tools.get_output_filename(f'nxp.cst-input-data.{uniq}')
+        tools.write_file(output_dname, data)
+        # Generate CST configuration file used to sign payload
+        cfg_spl_fname = tools.get_output_filename('nxp.csf-config-txt.%s' % uniq)
+        with open(cfg_spl_fname, 'w') as outf:
+            print('[Header]', file=outf)
+            print('  Version = 4.3', file=outf)
+            print('  Hash Algorithm = sha256', file=outf)
+            print('  Engine = CAAM', file=outf)
+            print('  Engine Configuration = 0', file=outf)
+            print('  Certificate Format = X509', file=outf)
+            print('  Signature Format = CMS', file=outf)
+            print('[Install SRK]', file=outf)
+            print('  File = "%s"' % self.srk_table, file=outf)
+            print('  Source index = 0', file=outf)
+            print('[Install CSFK]', file=outf)
+            print('  File = "%s"' % self.csf_key, file=outf)
+            print('[Authenticate CSF]', file=outf)
+            if self.unlock:
+                print('[Unlock]', file=outf)
+                print('  Engine = CAAM', file=outf)
+                print('  Features = MID', file=outf)
+            print('[Install Key]', file=outf)
+            print('  Verification index = 0', file=outf)
+            print('  Target Index = 2', file=outf)
+            print('  File = "%s"' % self.img_key, file=outf)
+            print('[Authenticate Data]', file=outf)
+            print('  Verification index = 2', file=outf)
+            print('  Blocks = %#x 0 %#x "%s"' % (signbase, len(data), output_dname), file=outf)
+
+        output_fname = tools.get_output_filename(f'nxp.csf-output-blob.{uniq}')
+        args = ['-i', cfg_spl_fname, '-o', output_fname]
+        if self.cst.run_cmd(*args) is not None:
+            outdata = tools.read_file(output_fname)
+            return data + outdata
+        else:
+            # Bintool is missing; just use the input data as the output
+            self.record_missing_bintool(self.cst)
+            return data
+
+    def SetImagePos(self, image_pos):
+        # Customized SoC specific SetImagePos which skips the mkimage etype
+        # implementation and removes the 0x48 offset introduced there. That
+        # offset is only used for uImage/fitImage, which is not the case in
+        # here.
+        upto = 0x00
+        for entry in super().GetEntries().values():
+            entry.SetOffsetSize(upto, None)
+
+            # Give up if any entries lack a size
+            if entry.size is None:
+                return
+            upto += entry.size
+
+        Entry_section.SetImagePos(self, image_pos)
+
+    def AddBintools(self, btools):
+        super().AddBintools(btools)
+        self.cst = self.AddBintool(btools, 'cst')
