diff --git a/mmc.c b/mmc.c
index a13d9ae..2ac98ae 100644
--- a/mmc.c
+++ b/mmc.c
@@ -13,6 +13,9 @@
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 021110-1307, USA.
  *
+ * Modified to add field firmware update support,
+ * those modifications are Copyright (c) 2016 SanDisk Corp.
+ *
  * (This code is based on btrfs-progs/btrfs.c.)
  */
 
@@ -175,6 +178,11 @@ static struct Command commands[] = {
 		"NOTE! The cache is an optional feature on devices >= eMMC4.5.",
 	  NULL
 	},
+	{ do_ffu, -2,
+	  "ffu", "<image name> <device>\n"
+		"Run Field Firmware Update with <image name> on <device>.\n",
+	  NULL
+	},
 	{ 0, 0, 0, 0 }
 };
 
diff --git a/mmc.h b/mmc.h
index b7063fb..38b215f 100644
--- a/mmc.h
+++ b/mmc.h
@@ -12,6 +12,9 @@
  * License along with this program; if not, write to the
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 021110-1307, USA.
+ *
+ * Modified to add field firmware update support,
+ * those modifications are Copyright (c) 2016 SanDisk Corp.
  */
 
 #include <asm-generic/int-ll64.h>
@@ -34,6 +37,7 @@
 #define R1_SWITCH_ERROR   (1 << 7)  /* sx, c */
 #define MMC_SWITCH_MODE_WRITE_BYTE	0x03	/* Set target to value */
 #define MMC_READ_MULTIPLE_BLOCK  18   /* adtc [31:0] data addr   R1  */
+#define MMC_WRITE_BLOCK		24	/* adtc [31:0] data addr	R1  */
 #define MMC_WRITE_MULTIPLE_BLOCK 25   /* adtc                    R1  */
 
 /*
@@ -42,6 +46,17 @@
 #define EXT_CSD_S_CMD_SET		504
 #define EXT_CSD_HPI_FEATURE		503
 #define EXT_CSD_BKOPS_SUPPORT		502	/* RO */
+#define EXT_CSD_SUPPORTED_MODES		493	/* RO */
+#define EXT_CSD_FFU_FEATURES		492	/* RO */
+#define EXT_CSD_FFU_ARG_3		490	/* RO */
+#define EXT_CSD_FFU_ARG_2		489	/* RO */
+#define EXT_CSD_FFU_ARG_1		488	/* RO */
+#define EXT_CSD_FFU_ARG_0		487	/* RO */
+#define EXT_CSD_NUM_OF_FW_SEC_PROG_3	305	/* RO */
+#define EXT_CSD_NUM_OF_FW_SEC_PROG_2	304	/* RO */
+#define EXT_CSD_NUM_OF_FW_SEC_PROG_1	303	/* RO */
+#define EXT_CSD_NUM_OF_FW_SEC_PROG_0	302	/* RO */
+#define EXT_CSD_FIRMWARE_VERSION	254	/* RO */
 #define EXT_CSD_CACHE_SIZE_3		252
 #define EXT_CSD_CACHE_SIZE_2		251
 #define EXT_CSD_CACHE_SIZE_1		250
@@ -58,6 +73,7 @@
 #define EXT_CSD_BOOT_BUS_CONDITIONS	177
 #define EXT_CSD_ERASE_GROUP_DEF		175
 #define EXT_CSD_BOOT_WP			173
+#define EXT_CSD_FW_CONFIG		169	/* R/W */
 #define EXT_CSD_WR_REL_SET		167
 #define EXT_CSD_WR_REL_PARAM		166
 #define EXT_CSD_SANITIZE_START		165
@@ -94,6 +110,9 @@
 #define EXT_CSD_EXT_PARTITIONS_ATTRIBUTE_1	53
 #define EXT_CSD_EXT_PARTITIONS_ATTRIBUTE_0	52
 #define EXT_CSD_CACHE_CTRL		33
+#define EXT_CSD_MODE_CONFIG		30
+#define EXT_CSD_MODE_OPERATION_CODES	29	/* W */
+#define EXT_CSD_FFU_STATUS		26	/* R */
 
 /*
  * WR_REL_PARAM field definitions
@@ -109,6 +128,11 @@
 /*
  * EXT_CSD field definitions
  */
+#define EXT_CSD_FFU_INSTALL		(0x01)
+#define EXT_CSD_FFU_MODE		(0x01)
+#define EXT_CSD_NORMAL_MODE		(0x00)
+#define EXT_CSD_FFU			(1<<0)
+#define EXT_CSD_UPDATE_DISABLE		(1<<0)
 #define EXT_CSD_HPI_SUPP		(1<<0)
 #define EXT_CSD_HPI_IMPL		(1<<1)
 #define EXT_CSD_CMD_SET_NORMAL		(1<<0)
diff --git a/mmc_cmds.c b/mmc_cmds.c
index 77446b4..da3c5ce 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -12,6 +12,9 @@
  * License along with this program; if not, write to the
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 021110-1307, USA.
+ *
+ * Modified to add field firmware update support,
+ * those modifications are Copyright (c) 2016 SanDisk Corp.
  */
 
 #include <stdio.h>
@@ -1017,6 +1020,9 @@ int do_read_extcsd(int nargs, char **argv)
 	ext_csd_rev = ext_csd[EXT_CSD_REV];
 
 	switch (ext_csd_rev) {
+	case 8:
+		str = "5.1";
+		break;
 	case 7:
 		str = "5.0";
 		break;
@@ -1430,6 +1436,10 @@ int do_read_extcsd(int nargs, char **argv)
 		/*Reserved [31:0] */
 	}
 
+	if (ext_csd_rev >= 7) {
+		printf("eMMC Firmware Version: %s\n",
+			(char*)&ext_csd[EXT_CSD_FIRMWARE_VERSION]);
+	}
 out_free:
 	return ret;
 }
@@ -2032,3 +2042,224 @@ int do_cache_dis(int nargs, char **argv)
 {
 	return do_cache_ctrl(0, nargs, argv);
 }
+
+int do_ffu(int nargs, char **argv)
+{
+#ifndef MMC_IOC_MULTI_CMD
+	fprintf(stderr, "mmc-utils has been compiled without MMC_IOC_MULTI_CMD"
+			" support, needed by FFU.\n");
+	exit(1);
+#else
+	int dev_fd, img_fd;
+	int sect_done = 0, retry = 3, ret = -EINVAL;
+	unsigned int sect_size;
+	__u8 ext_csd[512];
+	__u8 *buf;
+	__u32 arg;
+	off_t fw_size;
+	ssize_t chunk_size;
+	char *device;
+	struct mmc_ioc_multi_cmd *multi_cmd;
+
+	CHECK(nargs != 3, "Usage: ffu <image name> </path/to/mmcblkX> \n",
+			exit(1));
+
+	device = argv[2];
+	dev_fd = open(device, O_RDWR);
+	if (dev_fd < 0) {
+		perror("device open failed");
+		exit(1);
+	}
+	img_fd = open(argv[1], O_RDONLY);
+	if (img_fd < 0) {
+		perror("image open failed");
+		close(dev_fd);
+		exit(1);
+	}
+
+	buf = malloc(512);
+	multi_cmd = calloc(1, sizeof(struct mmc_ioc_multi_cmd) +
+				3 * sizeof(struct mmc_ioc_cmd));
+	if (!buf || !multi_cmd) {
+		perror("failed to allocate memory");
+		goto out;
+	}
+
+	ret = read_extcsd(dev_fd, ext_csd);
+	if (ret) {
+		fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
+		goto out;
+	}
+
+	if (ext_csd[EXT_CSD_REV] < EXT_CSD_REV_V5_0) {
+		fprintf(stderr,
+			"The FFU feature is only available on devices >= "
+			"MMC 5.0, not supported in %s\n", device);
+		goto out;
+	}
+
+	if (!(ext_csd[EXT_CSD_SUPPORTED_MODES] & EXT_CSD_FFU)) {
+		fprintf(stderr, "FFU is not supported in %s\n", device);
+		goto out;
+	}
+
+	if (ext_csd[EXT_CSD_FW_CONFIG] & EXT_CSD_UPDATE_DISABLE) {
+		fprintf(stderr, "Firmware update was disabled in %s\n", device);
+		goto out;
+	}
+
+	fw_size = lseek(img_fd, 0, SEEK_END);
+
+	if (fw_size == 0) {
+		fprintf(stderr, "Firmware image is empty");
+		goto out;
+	}
+
+	sect_size = (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] == 0) ? 512 : 4096;
+	if (fw_size % sect_size) {
+		fprintf(stderr, "Firmware data size (%jd) is not aligned!\n", (intmax_t)fw_size);
+		goto out;
+	}
+
+	/* set CMD ARG */
+	arg = ext_csd[EXT_CSD_FFU_ARG_0] |
+		ext_csd[EXT_CSD_FFU_ARG_1] << 8 |
+		ext_csd[EXT_CSD_FFU_ARG_2] << 16 |
+		ext_csd[EXT_CSD_FFU_ARG_3] << 24;
+
+	/* prepare multi_cmd to be sent */
+	multi_cmd->num_of_cmds = 3;
+
+	/* put device into ffu mode */
+	multi_cmd->cmds[0].opcode = MMC_SWITCH;
+	multi_cmd->cmds[0].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
+			(EXT_CSD_MODE_CONFIG << 16) |
+			(EXT_CSD_FFU_MODE << 8) |
+			EXT_CSD_CMD_SET_NORMAL;
+	multi_cmd->cmds[0].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
+	multi_cmd->cmds[0].write_flag = 1;
+
+	/* send image chunk */
+	multi_cmd->cmds[1].opcode = MMC_WRITE_BLOCK;
+	multi_cmd->cmds[1].blksz = sect_size;
+	multi_cmd->cmds[1].blocks = 1;
+	multi_cmd->cmds[1].arg = arg;
+	multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+	multi_cmd->cmds[1].write_flag = 1;
+	mmc_ioc_cmd_set_data(multi_cmd->cmds[1], buf);
+
+	/* return device into normal mode */
+	multi_cmd->cmds[2].opcode = MMC_SWITCH;
+	multi_cmd->cmds[2].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
+			(EXT_CSD_MODE_CONFIG << 16) |
+			(EXT_CSD_NORMAL_MODE << 8) |
+			EXT_CSD_CMD_SET_NORMAL;
+	multi_cmd->cmds[2].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
+	multi_cmd->cmds[2].write_flag = 1;
+
+do_retry:
+	/* read firmware chunk */
+	lseek(img_fd, 0, SEEK_SET);
+	chunk_size = read(img_fd, buf, 512);
+
+	while (chunk_size > 0) {
+		/* send ioctl with multi-cmd */
+		ret = ioctl(dev_fd, MMC_IOC_MULTI_CMD, multi_cmd);
+
+		if (ret) {
+			perror("Multi-cmd ioctl");
+			/* In case multi-cmd ioctl failed before exiting from ffu mode */
+			ioctl(dev_fd, MMC_IOC_CMD, &multi_cmd->cmds[2]);
+			goto out;
+		}
+
+		ret = read_extcsd(dev_fd, ext_csd);
+		if (ret) {
+			fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
+			goto out;
+		}
+
+		/* Test if we need to restart the download */
+		sect_done = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_0] |
+				ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_1] << 8 |
+				ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_2] << 16 |
+				ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_3] << 24;
+		/* By spec, host should re-start download from the first sector if sect_done is 0 */
+		if (sect_done == 0) {
+			if (retry > 0) {
+				retry--;
+				fprintf(stderr, "Programming failed. Retrying... (%d)\n", retry);
+				goto do_retry;
+			}
+			fprintf(stderr, "Programming failed! Aborting...\n");
+			goto out;
+		} else {
+			fprintf(stderr, "Programmed %d/%jd bytes\r", sect_done * sect_size, (intmax_t)fw_size);
+		}
+
+		/* read the next firmware chunk (if any) */
+		chunk_size = read(img_fd, buf, 512);
+	}
+
+	if ((sect_done * sect_size) == fw_size) {
+		fprintf(stderr, "Programmed %jd/%jd bytes\n", (intmax_t)fw_size, (intmax_t)fw_size);
+		fprintf(stderr, "Programming finished with status %d \n", ret);
+	}
+	else {
+		fprintf(stderr, "FW size and number of sectors written mismatch. Status return %d\n", ret);
+		goto out;
+	}
+
+	/* check mode operation for ffu install*/
+	if (!ext_csd[EXT_CSD_FFU_FEATURES]) {
+		fprintf(stderr, "Please reboot to complete firmware installation on %s\n", device);
+	} else {
+		fprintf(stderr, "Installing firmware on %s...\n", device);
+		/* Re-enter ffu mode and install the firmware */
+		multi_cmd->num_of_cmds = 2;
+
+		/* set ext_csd to install mode */
+		multi_cmd->cmds[1].opcode = MMC_SWITCH;
+		multi_cmd->cmds[1].blksz = 0;
+		multi_cmd->cmds[1].blocks = 0;
+		multi_cmd->cmds[1].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
+				(EXT_CSD_MODE_OPERATION_CODES << 16) |
+				(EXT_CSD_FFU_INSTALL << 8) |
+				EXT_CSD_CMD_SET_NORMAL;
+		multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
+		multi_cmd->cmds[1].write_flag = 1;
+
+		/* send ioctl with multi-cmd */
+		ret = ioctl(dev_fd, MMC_IOC_MULTI_CMD, multi_cmd);
+
+		if (ret) {
+			perror("Multi-cmd ioctl failed setting install mode");
+			/* In case multi-cmd ioctl failed before exiting from ffu mode */
+			ioctl(dev_fd, MMC_IOC_CMD, &multi_cmd->cmds[2]);
+			goto out;
+		}
+
+		ret = read_extcsd(dev_fd, ext_csd);
+		if (ret) {
+			fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
+			goto out;
+		}
+
+		/* return status */
+		ret = ext_csd[EXT_CSD_FFU_STATUS];
+		if (ret) {
+			fprintf(stderr, "%s: error %d during FFU install:\n", device, ret);
+			goto out;
+		} else {
+			fprintf(stderr, "FFU finished successfully\n");
+		}
+	}
+
+out:
+	free(buf);
+	free(multi_cmd);
+	close(img_fd);
+	close(dev_fd);
+	return ret;
+#endif
+}
diff --git a/mmc_cmds.h b/mmc_cmds.h
index 75d8f8c..54abf0f 100644
--- a/mmc_cmds.h
+++ b/mmc_cmds.h
@@ -12,6 +12,9 @@
  * License along with this program; if not, write to the
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 021110-1307, USA.
+ *
+ * Modified to add field firmware update support,
+ * those modifications are Copyright (c) 2016 SanDisk Corp.
  */
 
 /* mmc_cmds.c */
@@ -36,3 +39,4 @@ int do_rpmb_read_block(int nargs, char **argv);
 int do_rpmb_write_block(int nargs, char **argv);
 int do_cache_en(int nargs, char **argv);
 int do_cache_dis(int nargs, char **argv);
+int do_ffu(int nargs, char **argv);
