diff --git a/tools/Makefile b/tools/Makefile
index 5dd33ed4d5..a282f04f63 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -56,6 +56,8 @@ mkenvimage-objs := mkenvimage.o os_support.o lib/crc32.o
 hostprogs-y += dumpimage mkimage
 hostprogs-$(CONFIG_FIT_SIGNATURE) += fit_info fit_check_sign
 
+hostprogs-$(CONFIG_SPL_ATF) += make_fit_atf
+
 hostprogs-$(CONFIG_CMD_BOOTEFI_SELFTEST) += file2include
 
 FIT_SIG_OBJS-$(CONFIG_FIT_SIGNATURE) := common/image-sig.o
diff --git a/tools/make_fit_atf.c b/tools/make_fit_atf.c
new file mode 100644
index 0000000000..91101aaf8a
--- /dev/null
+++ b/tools/make_fit_atf.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: 0BSD
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static char const *dtb_basename (char const *path) {
+  enum { FILE_SEPARATOR = '/' };
+  size_t ln = strlen(path);
+  if (!ln || path[ln - 1] == FILE_SEPARATOR) {
+    fprintf(stderr, "dtb file name error '%s'.\n", path);
+    return NULL;
+  }
+  char const *base = path + ln - 1;
+  while (base > path && *base != FILE_SEPARATOR) --base;
+  if (*base == FILE_SEPARATOR) ++base;
+  return base;
+}
+
+/*
+  http://www.sco.com/developers/gabi/latest/ch4.intro.html
+  http://www.sco.com/developers/gabi/latest/ch4.eheader.html
+  http://www.sco.com/developers/gabi/latest/ch4.sheader.html
+  http://www.sco.com/developers/gabi/latest/ch5.pheader.html
+*/
+enum { EI_MAG0, EI_MAG1, EI_MAG2, EI_MAG3, EI_CLASS, EI_DATA, EI_VERSION, EI_NIDENT = 16 };
+enum { ELFMAG0 = 0x7F, ELFMAG1 = 'E', ELFMAG2 = 'L', ELFMAG3 = 'F' };
+enum { ELFCLASSNONE, ELFCLASS32, ELFCLASS64 };
+enum { ELFDATANONE, ELFDATA2LSB, ELFDATA2MSB };
+enum { ET_NONE, ET_REL, ET_EXEC, ET_DYN, ET_CORE };
+enum { EM_NONE, EM_ARM = 40, EM_AARCH64 = 183 };
+enum { EV_NONE, EV_CURRENT };
+enum { PT_NULL, PT_LOAD, PT_DYNAMIC, PT_INTERP, PT_NOTE, PT_SHLIB, PT_PHDR, PT_TLS };
+enum { SHT_NULL, SHT_PROGBITS, SHT_SYMTAB, SHT_STRTAB, SHT_RELA, SHT_HASH, SHT_DYNAMIC, SHT_NOTE, SHT_NOBITS };
+enum { SHF_ALLOC = 2 };
+typedef struct {
+  uint8_t  e_ident[EI_NIDENT];
+  uint16_t e_type;
+  uint16_t e_machine;
+  uint32_t e_version;
+  uint64_t e_entry;
+  uint64_t e_phoff;
+  uint64_t e_shoff;
+  uint32_t e_flags;
+  uint16_t e_ehsize;
+  uint16_t e_phentsize;
+  uint16_t e_phnum;
+  uint16_t e_shentsize;
+  uint16_t e_shnum;
+  uint16_t e_shstrndx;
+} Elf64_Ehdr;
+typedef struct {
+  uint32_t p_type;
+  uint32_t p_flags;
+  uint64_t p_offset;
+  uint64_t p_vaddr;
+  uint64_t p_paddr;
+  uint64_t p_filesz;
+  uint64_t p_memsz;
+  uint64_t p_align;
+} Elf64_Phdr;
+typedef struct {
+  uint32_t sh_name;
+  uint32_t sh_type;
+  uint64_t sh_flags;
+  uint64_t sh_addr;
+  uint64_t sh_offset;
+  uint64_t sh_size;
+  uint32_t sh_link;
+  uint32_t sh_info;
+  uint64_t sh_addralign;
+  uint64_t sh_entsize;
+} Elf64_Shdr;
+
+static FILE *elf_open(char const *file_name, Elf64_Ehdr *eh) {
+  FILE *f = fopen(file_name, "rb");
+  if (!f) {
+    fprintf(stderr, "Error openning '%s': %s.\n", file_name, strerror(errno));
+    return 0;
+  }
+  if (fread(eh, EI_NIDENT, 1, f) != 1) {
+    if (feof(f)) {
+      fprintf(stderr, "Not ELF file.\n");
+      return 0;
+    }
+    fprintf(stderr, "Error reading '%s'.\n", file_name);
+    return 0;
+  }
+  if (
+    eh->e_ident[EI_MAG0] != ELFMAG0 ||
+    eh->e_ident[EI_MAG1] != ELFMAG1 ||
+    eh->e_ident[EI_MAG2] != ELFMAG2 ||
+    eh->e_ident[EI_MAG3] != ELFMAG3
+  ) {
+    fprintf(stderr, "Not ELF file.\n");
+    return 0;
+  }
+  if (
+    eh->e_ident[EI_CLASS]   != ELFCLASS64  ||
+    eh->e_ident[EI_DATA]    != ELFDATA2LSB ||
+    eh->e_ident[EI_VERSION] != EV_CURRENT
+  ) {
+    fprintf(stderr, "Only version %u 64-bit little-endian ELF files supported.\n", EV_CURRENT);
+    return 0;
+  }
+  if (!*(uint8_t *)&(uint32_t){1}) {
+    fprintf(stderr, "Only little-endian hosts supported.\n");
+    return 0;
+  }
+  if (fread(((void *)eh) + EI_NIDENT, sizeof(*eh) - EI_NIDENT, 1, f) != 1) {
+    fprintf(stderr, "Error reading '%s'.\n", file_name);
+    return 0;
+  }
+
+  if (
+    eh->e_version   != EV_CURRENT         ||
+    eh->e_ehsize    != sizeof(Elf64_Ehdr) ||
+    eh->e_phentsize != sizeof(Elf64_Phdr) ||
+    eh->e_shentsize != sizeof(Elf64_Shdr)
+  ) {
+    fprintf(stderr, "Unexpected ELF file.\n");
+    return 0;
+  }
+  if (eh->e_machine != EM_AARCH64) {
+    fprintf(stderr, "Only arm64 supported.\n");
+    return 0;
+  }
+  return f;
+}
+
+enum { GOTO_EH, GOTO_PH, GOTO_SH };
+static int elf_goto(char const *file_name, FILE *elf, Elf64_Ehdr const *eh, unsigned where) {
+  uint64_t seek = 0;
+  switch (where) {
+  case GOTO_EH: break;
+  case GOTO_PH: seek = eh->e_phoff; break;
+  case GOTO_SH: seek = eh->e_shoff; break;
+  }
+  if (fseek(elf, seek, SEEK_SET)) {
+    fprintf(stderr, "Error seeking '%s'.\n", file_name);
+    return -1;
+  }
+  return 0;
+}
+
+static int elf_next_ph(char const *file_name, FILE *elf, Elf64_Phdr *ph) {
+  if (fread(ph, sizeof(Elf64_Phdr), 1, elf) != 1) {
+    fprintf(stderr, "Error reading '%s'.\n", file_name);
+    return -1;
+  }
+  return 0;
+}
+
+static int elf_next_sh(char const *file_name, FILE *elf, Elf64_Shdr *sh) {
+  if (fread(sh, sizeof(Elf64_Shdr), 1, elf) != 1) {
+    fprintf(stderr, "Error reading '%s'.\n", file_name);
+    return -1;
+  }
+  return 0;
+}
+
+int main(int argc, char **argv) {
+  Elf64_Ehdr eh;
+  Elf64_Phdr ph;
+  Elf64_Shdr sh;
+  FILE      *fi;
+  FILE      *fo         = stdout;
+  char      *uboot      = "u-boot";
+  char      *bl31       = "bl31.elf";
+  void      *buffer     = 0;
+  size_t     buffer_len = 0;
+  uint64_t   addr;
+  int        cnt;
+
+  if (!argc) { return -1; }
+  --argc; ++argv;
+
+  while (argc) {
+    if (argv[0][0] != '-') { break; }
+    char opt = argv[0][1];
+    --argc; ++argv;
+    if (argv[-1][2]) {
+err_option:
+      fprintf(stderr, "Error with option '%s'.\n", argv[-1]);
+usage:
+      fprintf(stderr, "make_fit_atf [-h] [-o out_file] [-u u_boot_file] [-b bl31_file] dtb_file[s]\n");
+      return -1;
+    }
+    switch (opt) {
+    default : goto err_option;
+    case 'h': goto usage;
+    case 'u': uboot = argv[0]; break;
+    case 'b': bl31  = argv[0]; break;
+    case 'o':
+      if (fo != stdout) {
+        fprintf(stderr, "Maximum one -o option.\n");
+        return -1;
+      }
+      fo = fopen(argv[0], "w");
+      if (!fo) {
+        fprintf(stderr, "Could not open '%s' for writing: %s.\n", argv[0], strerror(errno));
+        return -1;
+      }
+    }
+    --argc; ++argv;
+  }
+
+  fi = elf_open(uboot, &eh);
+  if (!fi) { return -1; }
+  if (elf_goto(uboot, fi, &eh, GOTO_PH)) { return -1; }
+  cnt = 0;
+  for (unsigned i = 0; i < eh.e_phnum; ++i) {
+    if (elf_next_ph(uboot, fi, &ph)) { return -1; }
+    if (ph.p_type != PT_LOAD) { continue; }
+    if (ph.p_vaddr != ph.p_paddr) {
+      fprintf(stderr, "Virtual address not supported.\n");
+      return -1;
+    }
+    addr = ph.p_vaddr;
+    ++cnt;
+  }
+  if (cnt != 1) {
+    fprintf(stderr, "Only one loadable segment expected.\n");
+    return -1;
+  }
+  fclose(fi);
+
+  fprintf(fo,
+    "/dts-v1/;\n"
+    "\n"
+    "/ {\n"
+    "  description = \"Configuration to load ATF before U-Boot\";\n"
+    "  #address-cells = <1>;\n"
+    "\n"
+    "  images {\n"
+    "    uboot {\n"
+    "      description = \"U-Boot\";\n"
+    "      data = /incbin/(\"u-boot-nodtb.bin\");\n"
+    "      type = \"standalone\";\n"
+    "      os = \"U-Boot\";\n"
+    "      arch = \"arm64\";\n"
+    "      compression = \"none\";\n"
+    "      load = <0x%08lX>;\n"
+    "    };\n"
+    , addr
+  );
+
+  fi = elf_open(bl31, &eh);
+  if (!fi) { return -1; }
+  if (elf_goto(bl31, fi, &eh, GOTO_SH)) { return -1; }
+  cnt = 0;
+  int fw_entry = -1;
+  for (unsigned i = 0; i < eh.e_shnum; ++i) {
+    if (elf_next_sh(bl31, fi, &sh)) { return -1; }
+    if (sh.sh_type != SHT_PROGBITS || !(sh.sh_flags & SHF_ALLOC)) { continue; }
+    ++cnt;
+    fprintf(fo,
+      "    atf%u {\n"
+      "      description = \"ARM Trusted Firmware\";\n"
+      "      data = /incbin/(\"bl31_0x%08lX.bin\");\n"
+      "      type = \"firmware\";\n"
+      "      arch = \"arm64\";\n"
+      "      os = \"arm-trusted-firmware\";\n"
+      "      compression = \"none\";\n"
+      "      load = <0x%08lX>;\n"
+      , cnt
+      , sh.sh_addr
+      , sh.sh_addr
+    );
+    if (sh.sh_addr <= eh.e_entry && eh.e_entry < sh.sh_addr + sh.sh_size) {
+      fprintf(fo, "      entry = <0x%08lX>;\n", eh.e_entry);
+      fw_entry = cnt;
+    }
+    fprintf(fo, "    };\n");
+
+    if (1) {
+      if (buffer_len < sh.sh_size) {
+        if (buffer_len) { free(buffer); }
+        buffer_len = sh.sh_size;
+        buffer     = malloc(buffer_len);
+        if (!buffer) {
+          fprintf(stderr, "%s.\n", strerror(errno));
+          return -1;
+        }
+      }
+      long   current = ftell(fi);
+      int    sk      = fseek(fi, sh.sh_offset, SEEK_SET);
+      size_t ln      = fread(buffer, sh.sh_size, 1, fi);
+      int    sk2     = fseek(fi, current, SEEK_SET);
+      if (current < 0 || sk || ln != 1 || sk2) {
+        fprintf(stderr, "Could not read '%s': %s.\n", bl31, strerror(errno));
+        return -1;
+      }
+
+      char fname[32];
+      sprintf(fname, "bl31_0x%08lX.bin", sh.sh_addr);
+      FILE *f = fopen(fname, "wb");
+      if (f) {
+        ln = fwrite(buffer, sh.sh_size, 1, f);
+        fclose(f);
+      }
+      if (!f || ln != 1) {
+        fprintf(stderr, "Could not write to '%s': %s.\n", fname, strerror(errno));
+        return -1;
+      }
+    }
+  }
+  fclose(fi);
+
+  for (int j = 0; j < argc; ++j) {
+    fprintf(fo,
+      "    fdt%d {\n"
+      "      description = \"%s\";\n"
+      "      data = /incbin/(\"%s\");\n"
+      "      type = \"flat_dt\";\n"
+      "      compression = \"none\";\n"
+      "    };\n"
+      , j + 1
+      , dtb_basename(argv[j])
+      , argv[j]
+    );
+  }
+  fprintf(fo, "  };\n" );
+
+  if (argc) {
+    fprintf(fo,
+      "  configurations {\n"
+      "    default = \"config1\";\n"
+    );
+    for (int j = 0; j < argc; ++j) {
+      fprintf(fo,
+        "    config%d {\n"
+        "      description = \"%s\";\n"
+        , j + 1
+        , dtb_basename(argv[j])
+      );
+      if (fw_entry >= 0) {
+        fprintf(fo, "      firmware = \"atf%d\";\n", fw_entry);
+      }
+      fprintf(fo, "      loadables = \"uboot\"");
+      for (int i = 1; i <= cnt; ++i) {
+        if (i != fw_entry) {
+          fprintf(fo, ",\"atf%u\"", i);
+        }
+      }
+      fprintf(fo,
+        ";\n"
+        "      fdt = \"fdt%u\";\n"
+        "    };\n"
+        , j + 1
+      );
+    }
+    fprintf(fo, "  };\n");
+  }
+
+  fprintf(fo, "};\n");
+  return 0;
+}
