fastboot: Implement NAND backend

So far the fastboot code was only supporting MMC-backed devices for its
flashing operations (flash and erase).

Add a storage backend for NAND-backed devices.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
diff --git a/common/Makefile b/common/Makefile
index d986cde..4c1b2f0 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -276,10 +276,15 @@
 obj-y += stdio.o
 
 # This option is not just y/n - it can have a numeric value
-ifdef CONFIG_FASTBOOT_FLASH_MMC_DEV
+ifdef CONFIG_FASTBOOT_FLASH
 obj-y += aboot.o
+ifdef CONFIG_FASTBOOT_FLASH_MMC_DEV
 obj-y += fb_mmc.o
 endif
+ifdef CONFIG_FASTBOOT_FLASH_NAND_DEV
+obj-y += fb_nand.o
+endif
+endif
 
 obj-$(CONFIG_CMD_BLOB) += cmd_blob.o
 
diff --git a/common/fb_nand.c b/common/fb_nand.c
new file mode 100644
index 0000000..1c80ba9
--- /dev/null
+++ b/common/fb_nand.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2014 Broadcom Corporation.
+ * Copyright 2015 Free Electrons.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <config.h>
+#include <common.h>
+
+#include <aboot.h>
+#include <fastboot.h>
+#include <sparse_format.h>
+
+#include <linux/mtd/mtd.h>
+#include <jffs2/jffs2.h>
+#include <nand.h>
+
+static char *response_str;
+
+struct fb_nand_sparse {
+	nand_info_t		*nand;
+	struct part_info	*part;
+};
+
+static int fb_nand_lookup(const char *partname, char *response,
+			  nand_info_t **nand,
+			  struct part_info **part)
+{
+	struct mtd_device *dev;
+	int ret;
+	u8 pnum;
+
+	ret = mtdparts_init();
+	if (ret) {
+		error("Cannot initialize MTD partitions\n");
+		fastboot_fail(response_str, "cannot init mtdparts");
+		return ret;
+	}
+
+	ret = find_dev_and_part(partname, &dev, &pnum, part);
+	if (ret) {
+		error("cannot find partition: '%s'", partname);
+		fastboot_fail(response_str, "cannot find partition");
+		return ret;
+	}
+
+	if (dev->id->type != MTD_DEV_TYPE_NAND) {
+		error("partition '%s' is not stored on a NAND device",
+		      partname);
+		fastboot_fail(response_str, "not a NAND device");
+		return -EINVAL;
+	}
+
+	*nand = &nand_info[dev->id->num];
+
+	return 0;
+}
+
+static int _fb_nand_erase(nand_info_t *nand, struct part_info *part)
+{
+	nand_erase_options_t opts;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.offset = part->offset;
+	opts.length = part->size;
+	opts.quiet = 1;
+
+	printf("Erasing blocks 0x%llx to 0x%llx\n",
+	       part->offset, part->offset + part->size);
+
+	ret = nand_erase_opts(nand, &opts);
+	if (ret)
+		return ret;
+
+	printf("........ erased 0x%llx bytes from '%s'\n",
+	       part->size, part->name);
+
+	return 0;
+}
+
+static int _fb_nand_write(nand_info_t *nand, struct part_info *part,
+			  void *buffer, unsigned int offset,
+			  unsigned int length, size_t *written)
+{
+	int flags = WITH_WR_VERIFY;
+
+#ifdef CONFIG_FASTBOOT_FLASH_NAND_TRIMFFS
+	flags |= WITH_DROP_FFS;
+#endif
+
+	return nand_write_skip_bad(nand, offset, &length, written,
+				   part->size - (offset - part->offset),
+				   buffer, flags);
+}
+
+static int fb_nand_sparse_write(struct sparse_storage *storage,
+				void *priv,
+				unsigned int offset,
+				unsigned int size,
+				char *data)
+{
+	struct fb_nand_sparse *sparse = priv;
+	size_t written;
+	int ret;
+
+	ret = _fb_nand_write(sparse->nand, sparse->part, data,
+			     offset * storage->block_sz,
+			     size * storage->block_sz, &written);
+	if (ret < 0) {
+		printf("Failed to write sparse chunk\n");
+		return ret;
+	}
+
+	return written / storage->block_sz;
+}
+
+void fb_nand_flash_write(const char *partname, unsigned int session_id,
+			 void *download_buffer, unsigned int download_bytes,
+			 char *response)
+{
+	struct part_info *part;
+	nand_info_t *nand = NULL;
+	int ret;
+
+	/* initialize the response buffer */
+	response_str = response;
+
+	ret = fb_nand_lookup(partname, response, &nand, &part);
+	if (ret) {
+		error("invalid NAND device");
+		fastboot_fail(response_str, "invalid NAND device");
+		return;
+	}
+
+	if (is_sparse_image(download_buffer)) {
+		struct fb_nand_sparse sparse_priv;
+		sparse_storage_t sparse;
+
+		sparse_priv.nand = nand;
+		sparse_priv.part = part;
+
+		sparse.block_sz = nand->writesize;
+		sparse.start = part->offset / sparse.block_sz;
+		sparse.size = part->size  / sparse.block_sz;
+		sparse.name = part->name;
+		sparse.write = fb_nand_sparse_write;
+
+		ret = store_sparse_image(&sparse, &sparse_priv, session_id,
+					 download_buffer);
+	} else {
+		printf("Flashing raw image at offset 0x%llx\n",
+		       part->offset);
+
+		ret = _fb_nand_write(nand, part, download_buffer, part->offset,
+				     download_bytes, NULL);
+
+		printf("........ wrote %u bytes to '%s'\n",
+		       download_bytes, part->name);
+	}
+
+	if (ret) {
+		fastboot_fail(response_str, "error writing the image");
+		return;
+	}
+
+	fastboot_okay(response_str, "");
+}
+
+void fb_nand_erase(const char *partname, char *response)
+{
+	struct part_info *part;
+	nand_info_t *nand = NULL;
+	int ret;
+
+	/* initialize the response buffer */
+	response_str = response;
+
+	ret = fb_nand_lookup(partname, response, &nand, &part);
+	if (ret) {
+		error("invalid NAND device");
+		fastboot_fail(response_str, "invalid NAND device");
+		return;
+	}
+
+	ret = _fb_nand_erase(nand, part);
+	if (ret) {
+		error("failed erasing from device %s", nand->name);
+		fastboot_fail(response_str, "failed erasing from device");
+		return;
+	}
+
+	fastboot_okay(response_str, "");
+}
diff --git a/drivers/usb/gadget/f_fastboot.c b/drivers/usb/gadget/f_fastboot.c
index a70463d..20b6c18 100644
--- a/drivers/usb/gadget/f_fastboot.c
+++ b/drivers/usb/gadget/f_fastboot.c
@@ -24,6 +24,9 @@
 #ifdef CONFIG_FASTBOOT_FLASH_MMC_DEV
 #include <fb_mmc.h>
 #endif
+#ifdef CONFIG_FASTBOOT_FLASH_NAND_DEV
+#include <fb_nand.h>
+#endif
 
 #define FASTBOOT_VERSION		"0.4"
 
@@ -569,6 +572,11 @@
 			   (void *)CONFIG_FASTBOOT_BUF_ADDR,
 			   download_bytes, response);
 #endif
+#ifdef CONFIG_FASTBOOT_FLASH_NAND_DEV
+	fb_nand_flash_write(cmd, fastboot_flash_session_id,
+			    (void *)CONFIG_FASTBOOT_BUF_ADDR,
+			    download_bytes, response);
+#endif
 	fastboot_flash_session_id++;
 	fastboot_tx_write_str(response);
 }
@@ -614,6 +622,9 @@
 #ifdef CONFIG_FASTBOOT_FLASH_MMC_DEV
 	fb_mmc_erase(cmd, response);
 #endif
+#ifdef CONFIG_FASTBOOT_FLASH_NAND_DEV
+	fb_nand_erase(cmd, response);
+#endif
 	fastboot_tx_write_str(response);
 }
 #endif
diff --git a/include/fb_nand.h b/include/fb_nand.h
new file mode 100644
index 0000000..80ddef5
--- /dev/null
+++ b/include/fb_nand.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2014 Broadcom Corporation.
+ * Copyright 2015 Free Electrons.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+void fb_nand_flash_write(const char *cmd, unsigned int session_id,
+			 void *download_buffer, unsigned int download_bytes,
+			 char *response);
+void fb_nand_erase(const char *cmd, char *response);