diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index b5c7ae1..056a48b 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) += cache_block.o
diff --git a/drivers/block/cache_block.c b/drivers/block/cache_block.c
new file mode 100644
index 0000000..e4ebeda
--- /dev/null
+++ b/drivers/block/cache_block.c
@@ -0,0 +1,240 @@
+/*
+ * 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 = 1;
+static unsigned long max_cache_entries = 4;
+
+static unsigned block_cache_misses;
+static unsigned block_cache_hits;
+
+static int trace;
+
+/*
+ * search for a set of blocks in the cache
+ *
+ * remove and return the node if found so it can be
+ * added back at the start of the list and maintain MRU order)
+ */
+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)) {
+			list_del(&node->lh);
+			return node;
+		}
+	return 0;
+}
+
+int cache_block_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;
+		list_add(&node->lh, &block_cache);
+		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 cache_block_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 (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 == 0) {
+		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 cache_block_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;
+		}
+	}
+}
+
+void cache_block_invalidate_all(void)
+{
+	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;
+}
+
+#ifdef CONFIG_CMD_BLOCK_CACHE
+
+static void dump_block_cache(void)
+{
+	struct list_head *entry, *n;
+	struct block_cache_node *node;
+	int i = 0;
+
+	list_for_each_safe(entry, n, &block_cache) {
+		node = (struct block_cache_node *)entry;
+		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);
+	}
+}
+
+static int do_blkcache(cmd_tbl_t *cmdtp, int flag,
+		       int argc, char * const argv[])
+{
+	if ((argc == 1) || !strcmp("show", argv[1])) {
+		printf("block cache:\n"
+		       "%u\thits\n"
+		       "%u\tmisses\n"
+		       "%u\tentries in cache\n"
+		       "trace %s\n"
+		       "max blocks/entry %lu\n"
+		       "max entries %lu\n",
+		       block_cache_hits, block_cache_misses, cache_count,
+		       trace ? "on" : "off",
+		       max_blocks_per_entry, max_cache_entries);
+	} else if ((argc == 2) && ('c' == argv[1][0])) {
+		block_cache_hits = 0;
+		block_cache_misses = 0;
+	} else if ((argc == 2) && ('d' == argv[1][0])) {
+		dump_block_cache();
+	} else if ((argc == 2) && ('i' == argv[1][0])) {
+		cache_block_invalidate_all();
+	} else if ((argc >= 2) && ('t' == argv[1][0])) {
+		if ((argc == 3) && !strcmp("off", argv[2]))
+			trace = 0;
+		else
+			trace = 1;
+	} else if ((argc == 3) && isdigit(argv[1][0]) && isdigit(argv[2][0])) {
+		max_blocks_per_entry = simple_strtoul(argv[1], 0, 0);
+		max_cache_entries = simple_strtoul(argv[2], 0, 0);
+		cache_block_invalidate_all();
+		printf("changed to max of %lu entries of %lu blocks each\n",
+		       max_cache_entries, max_blocks_per_entry);
+	} else {
+		return CMD_RET_USAGE;
+	}
+
+	return 0;
+}
+
+U_BOOT_CMD(
+	blkcache,	3,	0,	do_blkcache,
+	"block cache control/statistics",
+	"[show] - show statistics\n"
+	"blkcache c[lear] - clear statistics\n"
+	"blkcache d[ump] - dump cache entries\n"
+	"blkcache i[nvalidate] - invalidate cache\n"
+	"blkcache #maxblocks #maxentries\n"
+	"\tset maximum blocks per cache entry, maximum entries\n"
+	"blkcache t[race] [off] - enable (disable) tracing"
+);
+
+#endif
diff --git a/include/part.h b/include/part.h
index dc8e72e..1ac73dcc 100644
--- a/include/part.h
+++ b/include/part.h
@@ -376,4 +376,73 @@ int gpt_verify_partitions(struct blk_desc *dev_desc,
 			  gpt_header *gpt_head, gpt_entry **gpt_pte);
 #endif
 
+#ifdef CONFIG_BLOCK_CACHE
+/**
+ * cache_block_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 cache_block_read
+	(int iftype, int dev,
+	 lbaint_t start, lbaint_t blkcnt,
+	 unsigned long blksz, void *buffer);
+
+/**
+ * cache_block_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 cache_block_fill
+	(int iftype, int dev,
+	 lbaint_t start, lbaint_t blkcnt,
+	 unsigned long blksz, void const *buffer);
+
+/**
+ * cache_block_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 cache_block_invalidate
+	(int iftype, int dev);
+
+void cache_block_invalidate_all(void);
+
+#else
+
+static inline int cache_block_read
+	(int iftype, int dev,
+	 lbaint_t start, lbaint_t blkcnt,
+	 unsigned long blksz, void *buffer)
+{
+	return 0;
+}
+
+static inline void cache_block_fill
+	(int iftype, int dev,
+	 lbaint_t start, lbaint_t blkcnt,
+	 unsigned long blksz, void const *buffer) {}
+
+static inline void cache_block_invalidate
+	(int iftype, int dev) {}
+
+static inline void cache_block_invalidate_all(void){}
+
+#endif
+
 #endif /* _PART_H */
