nand: Add a watch command

This is a debug command to monitor the retention state of the data on
the array. The command needs a duplication of the mtd_read_oob()
function to actually return the maximum number of bitflips encountered
while reading the page. We could write a specific implementation for the
Sunxi driver but this is probably enough.

    nand watch <off> <size> - check an area for bitflips
    nand watch.part <part> - check a partition for bitflips
    nand watch.chip - check the whole device for bitflips

The output may be a bit verbose and could look like:

    => nand watch.chip
    device 0 whole chip
    size adjusted to 0xff60000 (5 bad blocks)

    NAND watch for bitflips in area 0x0-0xff60000:
    Page       0 (0x00000000) -> error -74
    Page       1 (0x00000800) -> error -74
    Page       2 (0x00001000) -> error -74
    Page       3 (0x00001800) -> error -74
    Page       4 (0x00002000) -> error -74
    Page       5 (0x00002800) -> error -74
    Page       6 (0x00003000) -> error -74
    Page       7 (0x00003800) -> error -74
    Page       8 (0x00004000) -> error -74
    Page       9 (0x00004800) -> error -74
    Page      10 (0x00005000) -> error -74
    Page      11 (0x00005800) -> error -74
    Page      12 (0x00006000) -> error -74
    Page      13 (0x00006800) -> error -74
    Page      14 (0x00007000) -> error -74
    Page      15 (0x00007800) -> error -74
    Page      16 (0x00008000) -> error -74
    Page      17 (0x00008800) -> error -74
    Page      18 (0x00009000) -> error -74
    Page      19 (0x00009800) -> error -74
    Page      20 (0x0000a000) -> error -74
    Page      21 (0x0000a800) -> error -74
    Page      22 (0x0000b000) -> error -74
    Page      23 (0x0000b800) -> error -74
    Page    1110 (0x0022b000) -> up to  1 bf/chunk
    Page    1122 (0x00231000) -> up to  1 bf/chunk
    Page    1132 (0x00236000) -> up to  1 bf/chunk
    Page    1362 (0x002a9000) -> up to  1 bf/chunk
    Page    4990 (0x009bf000) -> up to  1 bf/chunk
    Page    5728 (0x00b30000) -> up to  1 bf/chunk
    Page    7116 (0x00de6000) -> up to  1 bf/chunk
    Page    7160 (0x00dfc000) -> up to  1 bf/chunk
    Page    7494 (0x00ea3000) -> up to  1 bf/chunk
    Page   10842 (0x0152d000) -> up to  1 bf/chunk
    Page   11614 (0x016af000) -> up to  1 bf/chunk
    Page   11970 (0x01761000) -> up to  1 bf/chunk
    Page   12536 (0x0187c000) -> up to  1 bf/chunk
    Page   12687 (0x018c7800) -> up to  1 bf/chunk
    Page   14298 (0x01bed000) -> up to  1 bf/chunk
    Page   18268 (0x023ae000) -> up to  1 bf/chunk
    Page   18760 (0x024a4000) -> up to  1 bf/chunk
    Page   21440 (0x029e0000) -> up to  1 bf/chunk
    Page   22336 (0x02ba0000) -> up to  1 bf/chunk
    Page   22592 (0x02c20000) -> up to  1 bf/chunk
    Page   23872 (0x02ea0000) -> up to  1 bf/chunk
    Page   27584 (0x035e0000) -> up to  1 bf/chunk
    Page   35008 (0x04460000) -> up to  1 bf/chunk
    Page   37184 (0x048a0000) -> up to  1 bf/chunk
    Page   41728 (0x05180000) -> up to  1 bf/chunk
    Page   42176 (0x05260000) -> up to  1 bf/chunk
    Page   43200 (0x05460000) -> up to  1 bf/chunk
    Page   43328 (0x054a0000) -> up to  1 bf/chunk
    Page   45376 (0x058a0000) -> up to  1 bf/chunk
    Page   47040 (0x05be0000) -> up to  1 bf/chunk
    Page   47552 (0x05ce0000) -> up to  1 bf/chunk
    Page   49344 (0x06060000) -> up to  1 bf/chunk
    Page   49856 (0x06160000) -> up to  1 bf/chunk
    Page   62784 (0x07aa0000) -> up to  1 bf/chunk
    Page   65153 (0x07f40800) -> up to  1 bf/chunk
    Page   65228 (0x07f66000) -> up to  1 bf/chunk
    Page   65382 (0x07fb3000) -> up to  1 bf/chunk
    Page   98624 (0x0c0a0000) -> up to  1 bf/chunk
    Page  101952 (0x0c720000) -> up to  1 bf/chunk
    Page  107584 (0x0d220000) -> up to  1 bf/chunk
    Page  118208 (0x0e6e0000) -> up to  1 bf/chunk
    Page  126656 (0x0f760000) -> up to  1 bf/chunk
    Page  127680 (0x0f960000) -> up to  1 bf/chunk
    Page  129920 (0x0fdc0000) -> up to  1 bf/chunk
    Maximum number of bitflips: 1
    Pages with bitflips: 44/130752

It is also possible to reduce the output with the .quiet suffix in order
to just show the summary.

    => nand watch.chip
    device 0 whole chip
    size adjusted to 0xff60000 (5 bad blocks)

    NAND watch for bitflips in area 0x0-0xff60000:
    Maximum number of bitflips: 1
    Pages with bitflips: 44/130752

Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
diff --git a/cmd/nand.c b/cmd/nand.c
index 5a328e0..2f785de 100644
--- a/cmd/nand.c
+++ b/cmd/nand.c
@@ -231,6 +231,54 @@
 	return ret;
 }
 
+#ifdef CONFIG_CMD_NAND_WATCH
+static int nand_watch_bf(struct mtd_info *mtd, ulong off, ulong size, bool quiet)
+{
+	unsigned int max_bf = 0, pages_wbf = 0;
+	unsigned int first_page, pages, i;
+	struct mtd_oob_ops ops = {};
+	u_char *buf;
+	int ret;
+
+	buf = memalign(ARCH_DMA_MINALIGN, mtd->writesize);
+	if (!buf) {
+		puts("No memory for page buffer\n");
+		return 1;
+	}
+
+	first_page = off / mtd->writesize;
+	pages = size / mtd->writesize;
+
+	ops.datbuf = buf;
+	ops.len = mtd->writesize;
+	for (i = first_page; i < first_page + pages; i++) {
+		ulong addr = mtd->writesize * i;
+		ret = mtd_read_oob_bf(mtd, addr, &ops);
+		if (ret < 0) {
+			if (quiet)
+				continue;
+
+			printf("Page %7d (0x%08lx) -> error %d\n",
+			       i, addr, ret);
+		} else if (ret) {
+			max_bf = max(max_bf, (unsigned int)ret);
+			pages_wbf++;
+			if (quiet)
+				continue;
+			printf("Page %7d (0x%08lx) -> up to %2d bf/chunk\n",
+			       i, addr, ret);
+		}
+	}
+
+	printf("Maximum number of bitflips: %u\n", max_bf);
+	printf("Pages with bitflips: %u/%u\n", pages_wbf, pages);
+
+	free(buf);
+
+	return 0;
+}
+#endif
+
 /* ------------------------------------------------------------------------- */
 
 static int set_dev(int dev)
@@ -780,6 +828,55 @@
 
 		return ret == 0 ? 0 : 1;
 	}
+
+#ifdef CONFIG_CMD_NAND_WATCH
+	if (strncmp(cmd, "watch", 5) == 0) {
+		int args = 2;
+
+		if (cmd[5]) {
+			if (!strncmp(&cmd[5], ".part", 5)) {
+				args = 1;
+			} else if (!strncmp(&cmd[5], ".chip", 5)) {
+				args = 0;
+			} else {
+				goto usage;
+			}
+		}
+
+		if (cmd[10])
+			if (!strncmp(&cmd[10], ".quiet", 6))
+				quiet = true;
+
+		if (argc != 2 + args)
+			goto usage;
+
+		ret = mtd_arg_off_size(argc - 2, argv + 2, &dev, &off, &size,
+				       &maxsize, MTD_DEV_TYPE_NAND, mtd->size);
+		if (ret)
+			return ret;
+
+		/* size is unspecified */
+		if (argc < 4)
+			adjust_size_for_badblocks(&size, off, dev);
+
+		if ((off & (mtd->writesize - 1)) ||
+		    (size & (mtd->writesize - 1))) {
+			printf("Attempt to read non page-aligned data\n");
+			return -EINVAL;
+		}
+
+		ret = set_dev(dev);
+		if (ret)
+			return ret;
+
+		mtd = get_nand_dev_by_index(dev);
+
+		printf("\nNAND watch for bitflips in area 0x%llx-0x%llx:\n",
+		       off, off + size);
+
+		return nand_watch_bf(mtd, off, size, quiet);
+	}
+#endif
 
 #ifdef CONFIG_CMD_NAND_TORTURE
 	if (strcmp(cmd, "torture") == 0) {
@@ -946,6 +1043,12 @@
 	"nand erase.chip [clean] - erase entire chip'\n"
 	"nand bad - show bad blocks\n"
 	"nand dump[.oob] off - dump page\n"
+#ifdef CONFIG_CMD_NAND_WATCH
+	"nand watch <off> <size> - check an area for bitflips\n"
+	"nand watch.part <part> - check a partition for bitflips\n"
+	"nand watch.chip - check the whole device for bitflips\n"
+	"\t\t.quiet - Query only the summary, not the details\n"
+#endif
 #ifdef CONFIG_CMD_NAND_TORTURE
 	"nand torture off - torture one block at offset\n"
 	"nand torture off [size] - torture blocks from off to off+size\n"