diff --git a/disk/part.c b/disk/part.c
index 67d98fe..0aff954 100644
--- a/disk/part.c
+++ b/disk/part.c
@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc)
 	const int n_ents = ll_entry_count(struct part_driver, part_driver);
 	struct part_driver *entry;
 
+	blkcache_invalidate(dev_desc->if_type, dev_desc->devnum);
+
 	dev_desc->part_type = PART_TYPE_UNKNOWN;
 	for (entry = drv; entry != drv + n_ents; entry++) {
 		int ret;
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index f35c4d4..0209b95 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -18,3 +18,23 @@ config DISK
 	  types can use this, such as AHCI/SATA. It does not provide any standard
 	  operations at present. The block device interface has not been converted
 	  to driver model.
+
+config BLOCK_CACHE
+	bool "Use block device cache"
+	default n
+	help
+	  This option enables a disk-block cache for all block devices.
+	  This is most useful when accessing filesystems under U-Boot since
+	  it will prevent repeated reads from directory structures and other
+	  filesystem data structures.
+
+config CMD_BLOCK_CACHE
+	bool "Include block device cache control command (blkcache)"
+	depends on BLOCK_CACHE
+	default y if BLOCK_CACHE
+	help
+	  Enable the blkcache command, which can be used to control the
+	  operation of the cache functions.
+	  This is most useful when fine-tuning the operation of the cache
+	  during development, but also allows the cache to be disabled when
+	  it might hurt performance (e.g. when using the ums command).
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index b5c7ae1..b4cbb09 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -24,3 +24,4 @@ obj-$(CONFIG_IDE_SIL680) += sil680.o
 obj-$(CONFIG_SANDBOX) += sandbox.o
 obj-$(CONFIG_SCSI_SYM53C8XX) += sym53c8xx.o
 obj-$(CONFIG_SYSTEMACE) += systemace.o
+obj-$(CONFIG_BLOCK_CACHE) += blkcache.o
diff --git a/drivers/block/blk-uclass.c b/drivers/block/blk-uclass.c
index 49df2a6..617db22 100644
--- a/drivers/block/blk-uclass.c
+++ b/drivers/block/blk-uclass.c
@@ -80,11 +80,20 @@ unsigned long blk_dread(struct blk_desc *block_dev, lbaint_t start,
 {
 	struct udevice *dev = block_dev->bdev;
 	const struct blk_ops *ops = blk_get_ops(dev);
+	ulong blks_read;
 
 	if (!ops->read)
 		return -ENOSYS;
 
-	return ops->read(dev, start, blkcnt, buffer);
+	if (blkcache_read(block_dev->if_type, block_dev->devnum,
+			  start, blkcnt, block_dev->blksz, buffer))
+		return blkcnt;
+	blks_read = ops->read(dev, start, blkcnt, buffer);
+	if (blks_read == blkcnt)
+		blkcache_fill(block_dev->if_type, block_dev->devnum,
+			      start, blkcnt, block_dev->blksz, buffer);
+
+	return blks_read;
 }
 
 unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start,
@@ -96,6 +105,7 @@ unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start,
 	if (!ops->write)
 		return -ENOSYS;
 
+	blkcache_invalidate(block_dev->if_type, block_dev->devnum);
 	return ops->write(dev, start, blkcnt, buffer);
 }
 
@@ -108,6 +118,7 @@ unsigned long blk_derase(struct blk_desc *block_dev, lbaint_t start,
 	if (!ops->erase)
 		return -ENOSYS;
 
+	blkcache_invalidate(block_dev->if_type, block_dev->devnum);
 	return ops->erase(dev, start, blkcnt);
 }
 
diff --git a/drivers/block/blkcache.c b/drivers/block/blkcache.c
new file mode 100644
index 0000000..125e1e3
--- /dev/null
+++ b/drivers/block/blkcache.c
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) Nelson Integration, LLC 2016
+ * Author: Eric Nelson<eric@nelint.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ *
+ */
+#include <config.h>
+#include <common.h>
+#include <malloc.h>
+#include <part.h>
+#include <linux/ctype.h>
+#include <linux/list.h>
+
+struct block_cache_node {
+	struct list_head lh;
+	int iftype;
+	int devnum;
+	lbaint_t start;
+	lbaint_t blkcnt;
+	unsigned long blksz;
+	char *cache;
+};
+
+static LIST_HEAD(block_cache);
+static unsigned cache_count;
+
+static unsigned long max_blocks_per_entry = 2;
+static unsigned long max_cache_entries = 32;
+
+static unsigned block_cache_misses;
+static unsigned block_cache_hits;
+
+static int trace;
+
+static struct block_cache_node *cache_find(int iftype, int devnum,
+					   lbaint_t start, lbaint_t blkcnt,
+					   unsigned long blksz)
+{
+	struct block_cache_node *node;
+
+	list_for_each_entry(node, &block_cache, lh)
+		if ((node->iftype == iftype) &&
+		    (node->devnum == devnum) &&
+		    (node->blksz == blksz) &&
+		    (node->start <= start) &&
+		    (node->start + node->blkcnt >= start + blkcnt)) {
+			if (block_cache.next != &node->lh) {
+				/* maintain MRU ordering */
+				list_del(&node->lh);
+				list_add(&node->lh, &block_cache);
+			}
+			return node;
+		}
+	return 0;
+}
+
+int blkcache_read(int iftype, int devnum,
+		  lbaint_t start, lbaint_t blkcnt,
+		  unsigned long blksz, void *buffer)
+{
+	struct block_cache_node *node = cache_find(iftype, devnum, start,
+						   blkcnt, blksz);
+	if (node) {
+		const char *src = node->cache + (start - node->start) * blksz;
+		memcpy(buffer, src, blksz * blkcnt);
+		if (trace)
+			printf("hit: start " LBAF ", count " LBAFU "\n",
+			       start, blkcnt);
+		++block_cache_hits;
+		return 1;
+	}
+
+	if (trace)
+		printf("miss: start " LBAF ", count " LBAFU "\n",
+		       start, blkcnt);
+	++block_cache_misses;
+	return 0;
+}
+
+void blkcache_fill(int iftype, int devnum,
+		   lbaint_t start, lbaint_t blkcnt,
+		   unsigned long blksz, void const *buffer)
+{
+	lbaint_t bytes;
+	struct block_cache_node *node;
+
+	/* don't cache big stuff */
+	if (blkcnt > max_blocks_per_entry)
+		return;
+
+	if (max_cache_entries == 0)
+		return;
+
+	bytes = blksz * blkcnt;
+	if (max_cache_entries <= cache_count) {
+		/* pop LRU */
+		node = (struct block_cache_node *)block_cache.prev;
+		list_del(&node->lh);
+		cache_count--;
+		if (trace)
+			printf("drop: start " LBAF ", count " LBAFU "\n",
+			       node->start, node->blkcnt);
+		if (node->blkcnt * node->blksz < bytes) {
+			free(node->cache);
+			node->cache = 0;
+		}
+	} else {
+		node = malloc(sizeof(*node));
+		if (!node)
+			return;
+		node->cache = 0;
+	}
+
+	if (!node->cache) {
+		node->cache = malloc(bytes);
+		if (!node->cache) {
+			free(node);
+			return;
+		}
+	}
+
+	if (trace)
+		printf("fill: start " LBAF ", count " LBAFU "\n",
+		       start, blkcnt);
+
+	node->iftype = iftype;
+	node->devnum = devnum;
+	node->start = start;
+	node->blkcnt = blkcnt;
+	node->blksz = blksz;
+	memcpy(node->cache, buffer, bytes);
+	list_add(&node->lh, &block_cache);
+	cache_count++;
+}
+
+void blkcache_invalidate(int iftype, int devnum)
+{
+	struct list_head *entry, *n;
+	struct block_cache_node *node;
+
+	list_for_each_safe(entry, n, &block_cache) {
+		node = (struct block_cache_node *)entry;
+		if ((node->iftype == iftype) &&
+		    (node->devnum == devnum)) {
+			list_del(entry);
+			free(node->cache);
+			free(node);
+			--cache_count;
+		}
+	}
+}
+
+#ifdef CONFIG_CMD_BLOCK_CACHE
+
+static int blkc_show(cmd_tbl_t *cmdtp, int flag,
+		     int argc, char * const argv[])
+{
+	printf("    hits: %u\n"
+	       "    misses: %u\n"
+	       "    entries: %u\n"
+	       "    trace: %s\n"
+	       "    max blocks/entry: %lu\n"
+	       "    max cache entries: %lu\n",
+	       block_cache_hits, block_cache_misses, cache_count,
+	       trace ? "on" : "off",
+	       max_blocks_per_entry, max_cache_entries);
+	return 0;
+}
+
+static int blkc_clear(cmd_tbl_t *cmdtp, int flag,
+		      int argc, char * const argv[])
+{
+	block_cache_hits = 0;
+	block_cache_misses = 0;
+	return 0;
+}
+
+static int blkc_dump(cmd_tbl_t *cmdtp, int flag,
+		     int argc, char * const argv[])
+{
+	struct block_cache_node *node;
+	int i = 0;
+
+	list_for_each_entry(node, &block_cache, lh) {
+		printf("----- cache entry[%d]\n", i++);
+		printf("iftype: %d\n", node->iftype);
+		printf("devnum: %d\n", node->devnum);
+		printf("blksize: " LBAFU "\n", node->blksz);
+		printf("start: " LBAF "\n", node->start);
+		printf("count: " LBAFU "\n", node->blkcnt);
+	}
+	return 0;
+}
+
+static int blkc_invalidate(cmd_tbl_t *cmdtp, int flag,
+			   int argc, char * const argv[])
+{
+	struct list_head *entry, *n;
+	struct block_cache_node *node;
+
+	list_for_each_safe(entry, n, &block_cache) {
+		node = (struct block_cache_node *)entry;
+		list_del(entry);
+		free(node->cache);
+		free(node);
+	}
+
+	cache_count = 0;
+
+	return 0;
+}
+
+static int blkc_max(cmd_tbl_t *cmdtp, int flag,
+		     int argc, char * const argv[])
+{
+	if (argc != 3)
+		return CMD_RET_USAGE;
+
+	max_blocks_per_entry = simple_strtoul(argv[1], 0, 0);
+	max_cache_entries = simple_strtoul(argv[2], 0, 0);
+	blkc_invalidate(cmdtp, flag, argc, argv);
+	printf("changed to max of %lu entries of %lu blocks each\n",
+	       max_cache_entries, max_blocks_per_entry);
+	return 0;
+}
+
+static int blkc_trace(cmd_tbl_t *cmdtp, int flag,
+		      int argc, char * const argv[])
+{
+	if ((argc == 2) && !strcmp("off", argv[1]))
+		trace = 0;
+	else
+		trace = 1;
+	return 0;
+}
+
+static cmd_tbl_t cmd_blkc_sub[] = {
+	U_BOOT_CMD_MKENT(show, 0, 0, blkc_show, "", ""),
+	U_BOOT_CMD_MKENT(clear, 0, 0, blkc_clear, "", ""),
+	U_BOOT_CMD_MKENT(dump, 0, 0, blkc_dump, "", ""),
+	U_BOOT_CMD_MKENT(invalidate, 0, 0, blkc_invalidate, "", ""),
+	U_BOOT_CMD_MKENT(max, 3, 0, blkc_max, "", ""),
+	U_BOOT_CMD_MKENT(trace, 2, 0, blkc_trace, "", ""),
+};
+
+static __maybe_unused void blkc_reloc(void)
+{
+	static int relocated;
+
+	if (!relocated) {
+		fixup_cmdtable(cmd_blkc_sub, ARRAY_SIZE(cmd_blkc_sub));
+		relocated = 1;
+	};
+}
+
+static int do_blkcache(cmd_tbl_t *cmdtp, int flag,
+		       int argc, char * const argv[])
+{
+	cmd_tbl_t *c;
+
+#ifdef CONFIG_NEEDS_MANUAL_RELOC
+	blkc_reloc();
+#endif
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	/* Strip off leading 'i2c' command argument */
+	argc--;
+	argv++;
+
+	c = find_cmd_tbl(argv[0], &cmd_blkc_sub[0], ARRAY_SIZE(cmd_blkc_sub));
+
+	if (c)
+		return c->cmd(cmdtp, flag, argc, argv);
+	else
+		return CMD_RET_USAGE;
+
+	return 0;
+}
+
+U_BOOT_CMD(
+	blkcache, 4, 0, do_blkcache,
+	"block cache diagnostics and control",
+	"show - show statistics\n"
+	"blkcache clear - clear statistics\n"
+	"blkcache invalidate - invalidate cache\n"
+	"blkcache max blocks entries - set maximums\n"
+	"blkcache dump - dump cache entries\n"
+	"blkcache trace [off] - enable (disable) tracing"
+);
+
+#endif
diff --git a/include/blk.h b/include/blk.h
index e83c144..aa70f72 100644
--- a/include/blk.h
+++ b/include/blk.h
@@ -83,6 +83,71 @@ struct blk_desc {
 #define PAD_TO_BLOCKSIZE(size, blk_desc) \
 	(PAD_SIZE(size, blk_desc->blksz))
 
+#ifdef CONFIG_BLOCK_CACHE
+/**
+ * blkcache_read() - attempt to read a set of blocks from cache
+ *
+ * @param iftype - IF_TYPE_x for type of device
+ * @param dev - device index of particular type
+ * @param start - starting block number
+ * @param blkcnt - number of blocks to read
+ * @param blksz - size in bytes of each block
+ * @param buf - buffer to contain cached data
+ *
+ * @return - '1' if block returned from cache, '0' otherwise.
+ */
+int blkcache_read
+	(int iftype, int dev,
+	 lbaint_t start, lbaint_t blkcnt,
+	 unsigned long blksz, void *buffer);
+
+/**
+ * blkcache_fill() - make data read from a block device available
+ * to the block cache
+ *
+ * @param iftype - IF_TYPE_x for type of device
+ * @param dev - device index of particular type
+ * @param start - starting block number
+ * @param blkcnt - number of blocks available
+ * @param blksz - size in bytes of each block
+ * @param buf - buffer containing data to cache
+ *
+ */
+void blkcache_fill
+	(int iftype, int dev,
+	 lbaint_t start, lbaint_t blkcnt,
+	 unsigned long blksz, void const *buffer);
+
+/**
+ * blkcache_invalidate() - discard the cache for a set of blocks
+ * because of a write or device (re)initialization.
+ *
+ * @param iftype - IF_TYPE_x for type of device
+ * @param dev - device index of particular type
+ */
+void blkcache_invalidate
+	(int iftype, int dev);
+
+#else
+
+static inline int blkcache_read
+	(int iftype, int dev,
+	 lbaint_t start, lbaint_t blkcnt,
+	 unsigned long blksz, void *buffer)
+{
+	return 0;
+}
+
+static inline void blkcache_fill
+	(int iftype, int dev,
+	 lbaint_t start, lbaint_t blkcnt,
+	 unsigned long blksz, void const *buffer) {}
+
+static inline void blkcache_invalidate
+	(int iftype, int dev) {}
+
+#endif
+
 #ifdef CONFIG_BLK
 struct udevice;
 
@@ -224,23 +289,35 @@ int blk_unbind_all(int if_type);
 static inline ulong blk_dread(struct blk_desc *block_dev, lbaint_t start,
 			      lbaint_t blkcnt, void *buffer)
 {
+	ulong blks_read;
+	if (blkcache_read(block_dev->if_type, block_dev->devnum,
+			  start, blkcnt, block_dev->blksz, buffer))
+		return blkcnt;
+
 	/*
 	 * We could check if block_read is NULL and return -ENOSYS. But this
 	 * bloats the code slightly (cause some board to fail to build), and
 	 * it would be an error to try an operation that does not exist.
 	 */
-	return block_dev->block_read(block_dev, start, blkcnt, buffer);
+	blks_read = block_dev->block_read(block_dev, start, blkcnt, buffer);
+	if (blks_read == blkcnt)
+		blkcache_fill(block_dev->if_type, block_dev->devnum,
+			      start, blkcnt, block_dev->blksz, buffer);
+
+	return blks_read;
 }
 
 static inline ulong blk_dwrite(struct blk_desc *block_dev, lbaint_t start,
 			       lbaint_t blkcnt, const void *buffer)
 {
+	blkcache_invalidate(block_dev->if_type, block_dev->devnum);
 	return block_dev->block_write(block_dev, start, blkcnt, buffer);
 }
 
 static inline ulong blk_derase(struct blk_desc *block_dev, lbaint_t start,
 			       lbaint_t blkcnt)
 {
+	blkcache_invalidate(block_dev->if_type, block_dev->devnum);
 	return block_dev->block_erase(block_dev, start, blkcnt);
 }
 #endif /* !CONFIG_BLK */
