diff --git a/drivers/fwu-mdata/Kconfig b/drivers/fwu-mdata/Kconfig
index d5edef19d6..9bed3e9c1e 100644
--- a/drivers/fwu-mdata/Kconfig
+++ b/drivers/fwu-mdata/Kconfig
@@ -14,3 +14,12 @@ config FWU_MDATA_GPT_BLK
 	help
 	  Enable support for accessing FWU Metadata on GPT partitioned
 	  block devices.
+
+config FWU_MDATA_SF
+	bool "Enable FWU Multi Bank Update for SPI Flash"
+	depends on DM_FWU_MDATA
+	help
+	  Enable FWU Multi Bank Update for SPI flash driver. This
+	  driver does not depend on GPT. Instead, the platform must
+	  provide some APIs and define the offset of the primary and
+	  the secondary metadata.
diff --git a/drivers/fwu-mdata/Makefile b/drivers/fwu-mdata/Makefile
index 12a5b4fe04..f8db25ab69 100644
--- a/drivers/fwu-mdata/Makefile
+++ b/drivers/fwu-mdata/Makefile
@@ -5,3 +5,4 @@
 
 obj-$(CONFIG_DM_FWU_MDATA) += fwu-mdata-uclass.o
 obj-$(CONFIG_FWU_MDATA_GPT_BLK) += fwu_mdata_gpt_blk.o
+obj-$(CONFIG_FWU_MDATA_SF) += fwu_mdata_sf.o
diff --git a/drivers/fwu-mdata/fwu_mdata_sf.c b/drivers/fwu-mdata/fwu_mdata_sf.c
new file mode 100644
index 0000000000..010528b91a
--- /dev/null
+++ b/drivers/fwu-mdata/fwu_mdata_sf.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2021, Linaro Limited
+ */
+
+#include <efi_loader.h>
+#include <fwu.h>
+#include <fwu_mdata.h>
+#include <malloc.h>
+#include <memalign.h>
+#include <spi.h>
+#include <spi_flash.h>
+#include <flash.h>
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <u-boot/crc.h>
+
+struct fwu_mdata_sf_priv {
+	struct spi_flash *sf;
+	u32 pri_offset;
+	u32 sec_offset;
+};
+
+static int sf_load_data(struct spi_flash *sf, u32 offs, u32 size, void **data)
+{
+	int ret;
+
+	*data = memalign(ARCH_DMA_MINALIGN, size);
+	if (!*data)
+		return -ENOMEM;
+
+	ret = spi_flash_read(sf, offs, size, *data);
+	if (ret < 0) {
+		free(*data);
+		*data = NULL;
+	}
+
+	return ret;
+}
+
+static int sf_save_data(struct spi_flash *sf, u32 offs, u32 size, void *data)
+{
+	u32 sect_size, nsect;
+	void *buf;
+	int ret;
+
+	sect_size = sf->mtd.erasesize;
+	nsect = DIV_ROUND_UP(size, sect_size);
+	ret = spi_flash_erase(sf, offs, nsect * sect_size);
+	if (ret < 0)
+		return ret;
+
+	buf = memalign(ARCH_DMA_MINALIGN, size);
+	if (!buf)
+		return -ENOMEM;
+	memcpy(buf, data, size);
+
+	ret = spi_flash_write(sf, offs, size, buf);
+
+	free(buf);
+
+	return ret;
+}
+
+static int fwu_sf_load_mdata(struct spi_flash *sf, struct fwu_mdata **mdata, u32 offs, bool primary)
+{
+	int ret;
+
+	ret = sf_load_data(sf, offs, sizeof(struct fwu_mdata), (void **)mdata);
+
+	if (ret >= 0) {
+		ret = fwu_verify_mdata(*mdata, primary);
+		if (ret < 0) {
+			free(*mdata);
+			*mdata = NULL;
+		}
+	}
+
+	return ret;
+}
+
+static int fwu_sf_load_primary_mdata(struct fwu_mdata_sf_priv *sf_priv,
+				     struct fwu_mdata **mdata)
+{
+	return fwu_sf_load_mdata(sf_priv->sf, mdata, sf_priv->pri_offset, true);
+}
+
+static int fwu_sf_load_secondary_mdata(struct fwu_mdata_sf_priv *sf_priv,
+				       struct fwu_mdata **mdata)
+{
+	return fwu_sf_load_mdata(sf_priv->sf, mdata, sf_priv->sec_offset, false);
+}
+
+static int fwu_sf_save_primary_mdata(struct fwu_mdata_sf_priv *sf_priv,
+				     struct fwu_mdata *mdata)
+{
+	return sf_save_data(sf_priv->sf, sf_priv->pri_offset,
+			    sizeof(struct fwu_mdata), mdata);
+}
+
+static int fwu_sf_save_secondary_mdata(struct fwu_mdata_sf_priv *sf_priv,
+				       struct fwu_mdata *mdata)
+{
+	return sf_save_data(sf_priv->sf, sf_priv->sec_offset,
+			    sizeof(struct fwu_mdata), mdata);
+}
+
+static int fwu_sf_get_valid_mdata(struct fwu_mdata_sf_priv *sf_priv,
+				  struct fwu_mdata **mdata)
+{
+	if (fwu_sf_load_primary_mdata(sf_priv, mdata) == 0)
+		return 0;
+
+	log_err("Failed to load/verify primary mdata. Try secondary.\n");
+
+	if (fwu_sf_load_secondary_mdata(sf_priv, mdata) == 0)
+		return 0;
+
+	log_err("Failed to load/verify secondary mdata.\n");
+
+	return -1;
+}
+
+static int fwu_sf_update_mdata(struct udevice *dev, struct fwu_mdata *mdata)
+{
+	struct fwu_mdata_sf_priv *sf_priv = dev_get_priv(dev);
+	int ret;
+
+	/* Update mdata crc32 field */
+	mdata->crc32 = crc32(0, (void *)&mdata->version,
+			     sizeof(*mdata) - sizeof(u32));
+
+	/* First write the primary mdata */
+	ret = fwu_sf_save_primary_mdata(sf_priv, mdata);
+	if (ret < 0) {
+		log_err("Failed to update the primary mdata.\n");
+		return ret;
+	}
+
+	/* And now the replica */
+	ret = fwu_sf_save_secondary_mdata(sf_priv, mdata);
+	if (ret < 0) {
+		log_err("Failed to update the secondary mdata.\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int fwu_sf_mdata_check(struct udevice *dev)
+{
+	struct fwu_mdata *primary = NULL, *secondary = NULL;
+	struct fwu_mdata_sf_priv *sf_priv = dev_get_priv(dev);
+	int ret;
+
+	ret = fwu_sf_load_primary_mdata(sf_priv, &primary);
+	if (ret < 0)
+		log_err("Failed to read the primary mdata: %d\n", ret);
+
+	ret = fwu_sf_load_secondary_mdata(sf_priv, &secondary);
+	if (ret < 0)
+		log_err("Failed to read the secondary mdata: %d\n", ret);
+
+	if (primary && secondary) {
+		if (memcmp(primary, secondary, sizeof(struct fwu_mdata))) {
+			log_err("The primary and the secondary mdata are different\n");
+			ret = -1;
+		}
+	} else if (primary) {
+		ret = fwu_sf_save_secondary_mdata(sf_priv, primary);
+		if (ret < 0)
+			log_err("Restoring secondary mdata partition failed\n");
+	} else if (secondary) {
+		ret = fwu_sf_save_primary_mdata(sf_priv, secondary);
+		if (ret < 0)
+			log_err("Restoring primary mdata partition failed\n");
+	}
+
+	free(primary);
+	free(secondary);
+	return ret;
+}
+
+static int fwu_sf_get_mdata(struct udevice *dev, struct fwu_mdata **mdata)
+{
+	struct fwu_mdata_sf_priv *sf_priv = dev_get_priv(dev);
+
+	return fwu_sf_get_valid_mdata(sf_priv, mdata);
+}
+
+/*
+ * By default, this expects that dfu_alt_info is defined as the following order
+ * image0.bank0, image0.bank1, image1.bank0, ...
+ * Thus the alt_no is (the index of image) * #banks + update_bank.
+ */
+int __weak fwu_plat_get_image_alt_num(efi_guid_t image_type_id,
+				      u32 update_bank, int *alt_no)
+{
+	struct fwu_mdata *mdata;
+	int i, ret;
+
+	ret = fwu_get_mdata(&mdata);
+	if (ret < 0)
+		return ret;
+
+	ret = -ENOENT;
+	for (i = 0; i < CONFIG_FWU_NUM_IMAGES_PER_BANK; i++)
+		if (!guidcmp(&image_type_id, &mdata->img_entry[i].image_type_uuid)) {
+			*alt_no = i * CONFIG_FWU_NUM_BANKS + update_bank;
+			ret = 0;
+			break;
+		}
+
+	free(mdata);
+	return ret;
+}
+
+/* Since the dfu_alt_info is defined by the platform, ask platform about alt-number. */
+static int fwu_sf_get_image_alt_num(struct udevice *dev, efi_guid_t image_type_id,
+				    u32 update_bank, int *alt_no)
+{
+	return fwu_plat_get_image_alt_num(image_type_id, update_bank, alt_no);
+}
+
+/**
+ * fwu_mdata_sf_of_to_plat() - Translate from DT to fwu mdata device
+ */
+static int fwu_mdata_sf_of_to_plat(struct udevice *dev)
+{
+	struct fwu_mdata_sf_priv *sf_priv = dev_get_priv(dev);
+	const fdt32_t *phandle_p = NULL;
+	struct udevice *sf_dev;
+	int ret, size;
+	u32 phandle;
+
+	/* Find the FWU mdata storage device */
+	phandle_p = ofnode_get_property(dev_ofnode(dev),
+					"fwu-mdata-store", &size);
+	if (!phandle_p) {
+		log_err("fwu-mdata-store property not found\n");
+		return -ENOENT;
+	}
+
+	phandle = fdt32_to_cpu(*phandle_p);
+
+	ret = device_get_global_by_ofnode(
+		ofnode_get_by_phandle(phandle),
+		&sf_dev);
+	if (ret)
+		return ret;
+
+	sf_priv->sf = dev_get_uclass_priv(sf_dev);
+
+	/* Get the offset of primary and seconday mdata */
+	ret = ofnode_read_u32_index(dev_ofnode(dev), "mdata-offsets", 0,
+				    &sf_priv->pri_offset);
+	if (ret)
+		return ret;
+	ret = ofnode_read_u32_index(dev_ofnode(dev), "mdata-offsets", 1,
+				    &sf_priv->sec_offset);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int fwu_mdata_sf_probe(struct udevice *dev)
+{
+	/* Ensure the metadata can be read. */
+	return fwu_sf_mdata_check(dev);
+}
+
+static struct fwu_mdata_ops fwu_sf_ops = {
+	.get_image_alt_num = fwu_sf_get_image_alt_num,
+	.mdata_check = fwu_sf_mdata_check,
+	.get_mdata = fwu_sf_get_mdata,
+	.update_mdata = fwu_sf_update_mdata,
+};
+
+static const struct udevice_id fwu_mdata_ids[] = {
+	{ .compatible = "u-boot,fwu-mdata-sf" },
+	{ }
+};
+
+U_BOOT_DRIVER(fwu_mdata_sf) = {
+	.name		= "fwu-mdata-sf",
+	.id		= UCLASS_FWU_MDATA,
+	.of_match	= fwu_mdata_ids,
+	.ops		= &fwu_sf_ops,
+	.probe		= fwu_mdata_sf_probe,
+	.of_to_plat	= fwu_mdata_sf_of_to_plat,
+	.priv_auto	= sizeof(struct fwu_mdata_sf_priv),
+};
diff --git a/include/fwu.h b/include/fwu.h
index 88dc4a4b9a..f5ac3f6d2e 100644
--- a/include/fwu.h
+++ b/include/fwu.h
@@ -66,5 +66,7 @@ int fwu_clear_accept_image(efi_guid_t *img_type_id, u32 bank);
 int fwu_plat_get_update_index(u32 *update_idx);
 int fwu_plat_get_alt_num(struct udevice *dev, void *identifier);
 void fwu_plat_get_bootidx(void *boot_idx);
+int fwu_plat_get_image_alt_num(efi_guid_t image_type_id, u32 update_bank,
+			       int *alt_no);
 
 #endif /* _FWU_H_ */
