diff --git a/MAINTAINERS b/MAINTAINERS
index 2dd2b46fcda6..359fde15936c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1329,6 +1329,13 @@ F:	arch/sandbox/
 F:	doc/arch/sandbox.rst
 F:	include/dt-bindings/*/sandbox*.h
 
+SEAMA
+M:	Linus Walleij <linus.walleij@linaro.org>
+S:	Maintained
+F:	cmd/seama.c
+F:	doc/usage/cmd/seama.rst
+F:	test/cmd/seama.c
+
 SEMIHOSTING
 R:	Sean Anderson <sean.anderson@seco.com>
 S:	Orphaned
diff --git a/README b/README
index edce7890c0f9..9cc0c981c43c 100644
--- a/README
+++ b/README
@@ -1810,6 +1810,7 @@ sspi	- SPI utility commands
 base	- print or set address offset
 printenv- print environment variables
 pwm	- control pwm channels
+seama   - load SEAMA NAND image
 setenv	- set environment variables
 saveenv - save environment variables to persistent storage
 protect - enable or disable FLASH write protection
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 4fe2c75de256..ead98c39cdee 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -337,6 +337,12 @@ config BOOTM_RTEMS
 	help
 	  Support booting RTEMS images via the bootm command.
 
+config CMD_SEAMA
+	bool "Support read SEAMA NAND images"
+	depends on MTD_RAW_NAND
+	help
+	  Support reading NAND Seattle Image (SEAMA) images.
+
 config CMD_VBE
 	bool "vbe - Verified Boot for Embedded"
 	depends on BOOTMETH_VBE
diff --git a/cmd/Makefile b/cmd/Makefile
index 0b6a96c1d914..4fea084b44fa 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -156,6 +156,7 @@ obj-$(CONFIG_SANDBOX) += sb.o
 obj-$(CONFIG_CMD_SF) += sf.o
 obj-$(CONFIG_CMD_SCSI) += scsi.o disk.o
 obj-$(CONFIG_CMD_SHA1SUM) += sha1sum.o
+obj-$(CONFIG_CMD_SEAMA) += seama.o
 obj-$(CONFIG_CMD_SETEXPR) += setexpr.o
 obj-$(CONFIG_CMD_SETEXPR_FMT) += printf.o
 obj-$(CONFIG_CMD_SPI) += spi.o
diff --git a/cmd/seama.c b/cmd/seama.c
new file mode 100644
index 000000000000..3aafb43c48a0
--- /dev/null
+++ b/cmd/seama.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2023 Linus Walleij <linus.walleij@linaro.org>
+ * Support for the "SEAttle iMAge" SEAMA NAND image format
+ */
+
+#include <common.h>
+#include <command.h>
+#include <nand.h>
+
+/*
+ * All SEAMA data is stored in the flash in "network endianness"
+ * i.e. big endian, which means that it needs to be byte-swapped
+ * on all little endian platforms.
+ *
+ * structure for a SEAMA entity in NAND flash:
+ *
+ * 32 bit SEAMA magic 0x5EA3A417
+ * 16 bit reserved
+ * 16 bit metadata size (following the header)
+ * 32 bit image size
+ * 16 bytes MD5 digest of the image
+ * meta data
+ * ... image data ...
+ *
+ * Then if a new SEAMA magic follows, that is the next image.
+ */
+
+#define SEAMA_MAGIC		0x5EA3A417
+#define SEAMA_HDR_NO_META_SZ	28
+#define SEAMA_MAX_META_SZ	(1024 - SEAMA_HDR_NO_META_SZ)
+
+struct seama_header {
+	u32 magic;
+	u32 meta_size;
+	u32 image_size;
+	u8 md5[16];
+	u8 metadata[SEAMA_MAX_META_SZ];
+};
+
+static struct seama_header shdr;
+
+static int env_set_val(const char *varname, ulong val)
+{
+	int ret;
+
+	ret = env_set_hex(varname, val);
+	if (ret)
+		printf("Failed to %s env var\n", varname);
+
+	return ret;
+}
+
+static int do_seama_load_image(struct cmd_tbl *cmdtp, int flag, int argc,
+			       char *const argv[])
+{
+	struct mtd_info *mtd;
+	uintptr_t load_addr;
+	unsigned long image_index;
+	u32 len;
+	size_t readsz;
+	int ret;
+	u32 *start;
+	u32 *offset;
+	u32 *end;
+	u32 tmp;
+
+	if (argc < 2 || argc > 3)
+		return CMD_RET_USAGE;
+
+	load_addr = hextoul(argv[1], NULL);
+	if (!load_addr) {
+		printf("Invalid load address\n");
+		return CMD_RET_USAGE;
+	}
+
+	/* Can be 0 for first image */
+	image_index = hextoul(argv[2], NULL);
+
+	/* We only support one NAND, the first one */
+	nand_curr_device = 0;
+	mtd = get_nand_dev_by_index(0);
+	if (!mtd) {
+		printf("NAND Device 0 not available\n");
+		return CMD_RET_FAILURE;
+	}
+
+#ifdef CONFIG_SYS_NAND_SELECT_DEVICE
+	board_nand_select_device(mtd_to_nand(mtd), 0);
+#endif
+
+	printf("Loading SEAMA image %lu from %s\n", image_index, mtd->name);
+
+	readsz = sizeof(shdr);
+	offset = 0;
+	ret = nand_read_skip_bad(mtd, 0, &readsz, NULL, mtd->size,
+				 (u_char *)&shdr);
+	if (ret) {
+		printf("Read error reading SEAMA header\n");
+		return CMD_RET_FAILURE;
+	}
+
+	if (shdr.magic != SEAMA_MAGIC) {
+		printf("Invalid SEAMA image magic: 0x%08x\n", shdr.magic);
+		return CMD_RET_FAILURE;
+	}
+
+	/* Only the lower 16 bits are valid */
+	shdr.meta_size &= 0xFFFF;
+
+	if (env_set_val("seama_image_size", 0))
+		return CMD_RET_FAILURE;
+
+	printf("SEMA IMAGE:\n");
+	printf("  metadata size %d\n", shdr.meta_size);
+	printf("  image size %d\n", shdr.image_size);
+	printf("  checksum %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+	       shdr.md5[0], shdr.md5[1], shdr.md5[2], shdr.md5[3],
+	       shdr.md5[4], shdr.md5[5], shdr.md5[6], shdr.md5[7],
+	       shdr.md5[8], shdr.md5[9], shdr.md5[10], shdr.md5[11],
+	       shdr.md5[12], shdr.md5[13], shdr.md5[14], shdr.md5[15]);
+
+	/* TODO: handle metadata if needed */
+
+	len = shdr.image_size;
+	if (env_set_val("seama_image_size", len))
+		return CMD_RET_FAILURE;
+
+	/* We need to include the header (read full pages) */
+	readsz = shdr.image_size + SEAMA_HDR_NO_META_SZ + shdr.meta_size;
+	ret = nand_read_skip_bad(mtd, 0, &readsz, NULL, mtd->size,
+				 (u_char *)load_addr);
+	if (ret) {
+		printf("Read error reading SEAMA main image\n");
+		return CMD_RET_FAILURE;
+	}
+
+	/* We use a temporary variable tmp to avoid to hairy casts */
+	start = (u32 *)load_addr;
+	tmp = (u32)start;
+	tmp += SEAMA_HDR_NO_META_SZ + shdr.meta_size;
+	offset = (u32 *)tmp;
+	tmp += shdr.image_size;
+	end = (u32 *)tmp;
+
+	printf("Decoding SEAMA image 0x%08x..0x%08x to 0x%08x\n",
+	       (u32)offset, (u32)end, (u32)start);
+	for (; start < end; start++, offset++)
+		*start = be32_to_cpu(*offset);
+
+	return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD
+	(seama, 3, 1, do_seama_load_image,
+	 "Load the SEAMA image and sets envs",
+	 "seama <addr> <imageindex>\n"
+);
diff --git a/doc/usage/cmd/seama.rst b/doc/usage/cmd/seama.rst
new file mode 100644
index 000000000000..356c00a72331
--- /dev/null
+++ b/doc/usage/cmd/seama.rst
@@ -0,0 +1,60 @@
+.. SPDX-License-Identifier: GPL-2.0+:
+
+seama command
+=============
+
+Synopsis
+--------
+
+::
+
+    seama <dst_addr> <index>
+
+Description
+-----------
+
+The seama command is used to load and decode SEAttle iMAges from NAND
+flash to memory.
+
+This type of flash image is found in some D-Link routers such as
+DIR-645, DIR-842, DIR-859, DIR-860L, DIR-885L, DIR890L and DCH-M225,
+as well as in WD and NEC routers on the ath79 (MIPS), Broadcom
+BCM53xx, and RAMIPS platforms.
+
+This U-Boot command will read and decode a SEAMA image from raw NAND
+flash on any platform. As it is always using big endian format for
+the data decoding is always necessary on platforms  such as ARM.
+
+dst_addr
+    destination address of the byte stream to be loaded
+
+index
+    the image index (0, 1, 2..) can be omitted
+
+Example
+-------
+
+::
+
+    => seama 0x01000000
+    Loading SEAMA image 0 from nand0
+    SEMA IMAGE:
+      metadata size 36
+      image size 8781764
+      checksum 054859cfb1487b59befda98824e09dd6
+    Decoding SEAMA image 0x01000040..0x01860004 to 0x01000000
+
+
+Configuration
+-------------
+
+The command is available if CONFIG_CMD_SEAMA=y.
+
+Return value
+------------
+
+The return value $? is set 0 (true) if the loading is succefull, and
+is set to 1 (false) in case of error.
+
+The environment variable $seama_image_size is set to the size of the
+loaded SEAMA image.
diff --git a/doc/usage/index.rst b/doc/usage/index.rst
index 2c95471f45c9..ca99b01952d4 100644
--- a/doc/usage/index.rst
+++ b/doc/usage/index.rst
@@ -75,6 +75,7 @@ Shell commands
    cmd/sbi
    cmd/sf
    cmd/scp03
+   cmd/seama
    cmd/setexpr
    cmd/size
    cmd/sleep
diff --git a/include/test/suites.h b/include/test/suites.h
index 9ce49cbb031a..7c4960c004d7 100644
--- a/include/test/suites.h
+++ b/include/test/suites.h
@@ -49,6 +49,7 @@ int do_ut_optee(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_overlay(struct cmd_tbl *cmdtp, int flag, int argc,
 		  char *const argv[]);
 int do_ut_print(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
+int do_ut_seama(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_setexpr(struct cmd_tbl *cmdtp, int flag, int argc,
 		  char *const argv[]);
 int do_ut_str(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
diff --git a/test/cmd/Makefile b/test/cmd/Makefile
index 09e410ec30e5..2ffde8703abf 100644
--- a/test/cmd/Makefile
+++ b/test/cmd/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_CMD_LOADM) += loadm.o
 obj-$(CONFIG_CMD_MEM_SEARCH) += mem_search.o
 obj-$(CONFIG_CMD_PINMUX) += pinmux.o
 obj-$(CONFIG_CMD_PWM) += pwm.o
+obj-$(CONFIG_CMD_SEAMA) += seama.o
 ifdef CONFIG_SANDBOX
 obj-$(CONFIG_CMD_SETEXPR) += setexpr.o
 endif
diff --git a/test/cmd/seama.c b/test/cmd/seama.c
new file mode 100644
index 000000000000..b1b56930c646
--- /dev/null
+++ b/test/cmd/seama.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Executes tests for SEAMA (SEAttle iMAge) command
+ *
+ * Copyright (C) 2021 Linus Walleij <linus.walleij@linaro.org>
+ */
+
+#include <common.h>
+#include <command.h>
+#include <dm.h>
+#include <test/suites.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+#define SEAMA_TEST(_name, _flags)	UNIT_TEST(_name, _flags, seama_test)
+
+static int seama_test_noargs(struct unit_test_state *uts)
+{
+	/* Test that 'seama' with no arguments fails gracefully */
+	console_record_reset();
+	run_command("seama", 0);
+	ut_assert_nextlinen("seama - Load the SEAMA image and sets envs");
+	ut_assert_skipline();
+	ut_assert_skipline();
+	ut_assert_skipline();
+	ut_assert_skipline();
+	ut_assert_console_end();
+	return 0;
+}
+SEAMA_TEST(seama_test_noargs, UT_TESTF_CONSOLE_REC);
+
+static int seama_test_addr(struct unit_test_state *uts)
+{
+	/* Test that loads SEAMA image 0 to address 0x01000000 */
+	console_record_reset();
+	run_command("seama 0x01000000", 0);
+	ut_assert_nextlinen("Loading SEAMA image 0 from nand0");
+	ut_assert_nextlinen("SEMA IMAGE:");
+	ut_assert_nextlinen("  metadata size ");
+	ut_assert_nextlinen("  image size ");
+	ut_assert_nextlinen("  checksum ");
+	ut_assert_nextlinen("Decoding SEAMA image 0x01000040..");
+	ut_assert_console_end();
+	return 0;
+}
+SEAMA_TEST(seama_test_addr, UT_TESTF_CONSOLE_REC);
+
+static int seama_test_index(struct unit_test_state *uts)
+{
+	/* Test that loads SEAMA image 0 exlicitly specified */
+	console_record_reset();
+	run_command("seama 0x01000000 0", 0);
+	ut_assert_nextlinen("Loading SEAMA image 0 from nand0");
+	ut_assert_nextlinen("SEMA IMAGE:");
+	ut_assert_nextlinen("  metadata size ");
+	ut_assert_nextlinen("  image size ");
+	ut_assert_nextlinen("  checksum ");
+	ut_assert_nextlinen("Decoding SEAMA image 0x01000040..");
+	ut_assert_console_end();
+	return 0;
+}
+SEAMA_TEST(seama_test_index, UT_TESTF_CONSOLE_REC);
+
+int do_ut_seama(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+	struct unit_test *tests = UNIT_TEST_SUITE_START(seama_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(seama_test);
+
+	return cmd_ut_category("seama", "seama_test_", tests, n_ents, argc,
+			       argv);
+}
diff --git a/test/cmd_ut.c b/test/cmd_ut.c
index 1713d0d1c85e..409c22bfd243 100644
--- a/test/cmd_ut.c
+++ b/test/cmd_ut.c
@@ -110,6 +110,9 @@ static struct cmd_tbl cmd_ut_sub[] = {
 #ifdef CONFIG_CMD_LOADM
 	U_BOOT_CMD_MKENT(loadm, CONFIG_SYS_MAXARGS, 1, do_ut_loadm, "", ""),
 #endif
+#ifdef CONFIG_CMD_SEAMA
+	U_BOOT_CMD_MKENT(seama, CONFIG_SYS_MAXARGS, 1, do_ut_seama, "", ""),
+#endif
 };
 
 static int do_ut_all(struct cmd_tbl *cmdtp, int flag, int argc,
@@ -212,6 +215,9 @@ static char ut_help_text[] =
 #ifdef CONFIG_SANDBOX
 	"\nstr - basic test of string functions"
 #endif
+#ifdef CONFIG_CMD_SEAMA
+	"\nseama - seama command parameters loading and decoding"
+#endif
 #ifdef CONFIG_UT_TIME
 	"\ntime - very basic test of time functions"
 #endif
