diff --git a/README b/README
index 7129df822c17c9c9bc5c7089ec54b0fed6381d01..d0723701c8c511900ecf9e9380fc00a516abeb6b 100644
--- a/README
+++ b/README
@@ -1000,6 +1000,7 @@ The following options need to be configured:
 		CONFIG_CMD_IMLS		  List all images found in NOR flash
 		CONFIG_CMD_IMLS_NAND	* List all images found in NAND flash
 		CONFIG_CMD_IMMAP	* IMMR dump support
+		CONFIG_CMD_IOTRACE	* I/O tracing for debugging
 		CONFIG_CMD_IMPORTENV	* import an environment
 		CONFIG_CMD_INI		* import data from an ini file into the env
 		CONFIG_CMD_IRQ		* irqinfo
@@ -1171,6 +1172,28 @@ The following options need to be configured:
 		Note that if the GPIO device uses I2C, then the I2C interface
 		must also be configured. See I2C Support, below.
 
+- I/O tracing:
+		When CONFIG_IO_TRACE is selected, U-Boot intercepts all I/O
+		accesses and can checksum them or write a list of them out
+		to memory. See the 'iotrace' command for details. This is
+		useful for testing device drivers since it can confirm that
+		the driver behaves the same way before and after a code
+		change. Currently this is supported on sandbox and arm. To
+		add support for your architecture, add '#include <iotrace.h>'
+		to the bottom of arch/<arch>/include/asm/io.h and test.
+
+		Example output from the 'iotrace stats' command is below.
+		Note that if the trace buffer is exhausted, the checksum will
+		still continue to operate.
+
+			iotrace is enabled
+			Start:  10000000	(buffer start address)
+			Size:   00010000	(buffer size)
+			Offset: 00000120	(current buffer offset)
+			Output: 10000120	(start + offset)
+			Count:  00000018	(number of trace records)
+			CRC32:  9526fb66	(CRC32 of all trace records)
+
 - Timestamp Support:
 
 		When CONFIG_TIMESTAMP is selected, the timestamp
diff --git a/common/Makefile b/common/Makefile
index f6cd98012a18a3e50c59a68fb8515b1ea5359589..de5cce86cc6990c99a3f2f5d5d59084343f5fb01 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -114,6 +114,7 @@ obj-$(CONFIG_CMD_FUSE) += cmd_fuse.o
 obj-$(CONFIG_CMD_GETTIME) += cmd_gettime.o
 obj-$(CONFIG_CMD_GPIO) += cmd_gpio.o
 obj-$(CONFIG_CMD_I2C) += cmd_i2c.o
+obj-$(CONFIG_CMD_IOTRACE) += cmd_iotrace.o
 obj-$(CONFIG_CMD_HASH) += cmd_hash.o
 obj-$(CONFIG_CMD_IDE) += cmd_ide.o
 obj-$(CONFIG_CMD_IMMAP) += cmd_immap.o
@@ -261,6 +262,7 @@ obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o
 obj-$(CONFIG_OF_LIBFDT) += image-fdt.o
 obj-$(CONFIG_FIT) += image-fit.o
 obj-$(CONFIG_FIT_SIGNATURE) += image-sig.o
+obj-$(CONFIG_IO_TRACE) += iotrace.o
 obj-y += memsize.o
 obj-y += stdio.o
 
diff --git a/common/cmd_iotrace.c b/common/cmd_iotrace.c
new file mode 100644
index 0000000000000000000000000000000000000000..f54276d2f578cb862acec59db98f31df78549377
--- /dev/null
+++ b/common/cmd_iotrace.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2014 Google, Inc
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <command.h>
+#include <iotrace.h>
+
+static void do_print_stats(void)
+{
+	ulong start, size, offset, count;
+
+	printf("iotrace is %sabled\n", iotrace_get_enabled() ? "en" : "dis");
+	iotrace_get_buffer(&start, &size, &offset, &count);
+	printf("Start:  %08lx\n", start);
+	printf("Size:   %08lx\n", size);
+	printf("Offset: %08lx\n", offset);
+	printf("Output: %08lx\n", start + offset);
+	printf("Count:  %08lx\n", count);
+	printf("CRC32:  %08lx\n", (ulong)iotrace_get_checksum());
+}
+
+static int do_set_buffer(int argc, char * const argv[])
+{
+	ulong addr = 0, size = 0;
+
+	if (argc == 2) {
+		addr = simple_strtoul(*argv++, NULL, 16);
+		size = simple_strtoul(*argv++, NULL, 16);
+	} else if (argc != 0) {
+		return CMD_RET_USAGE;
+	}
+
+	iotrace_set_buffer(addr, size);
+
+	return 0;
+}
+
+int do_iotrace(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
+{
+	const char *cmd = argc < 2 ? NULL : argv[1];
+
+	if (!cmd)
+		return cmd_usage(cmdtp);
+	switch (*cmd) {
+	case 'b':
+		return do_set_buffer(argc - 2, argv + 2);
+	case 'p':
+		iotrace_set_enabled(0);
+		break;
+	case 'r':
+		iotrace_set_enabled(1);
+		break;
+	case 's':
+		do_print_stats();
+		break;
+	default:
+		return CMD_RET_USAGE;
+	}
+
+	return 0;
+}
+
+U_BOOT_CMD(
+	iotrace,	4,	1,	do_iotrace,
+	"iotrace utility commands",
+	"stats                        - display iotrace stats\n"
+	"iotrace buffer <address> <size>      - set iotrace buffer\n"
+	"iotrace pause                        - pause tracing\n"
+	"iotrace resume                       - resume tracing"
+);
diff --git a/common/iotrace.c b/common/iotrace.c
new file mode 100644
index 0000000000000000000000000000000000000000..ced426ea5c65f2f3c8b2a3e0ed030199a4cc0490
--- /dev/null
+++ b/common/iotrace.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2014 Google, Inc.
+ *
+ * SPDX-License-Identifier:     GPL-2.0+
+ */
+
+#define IOTRACE_IMPL
+
+#include <common.h>
+#include <asm/io.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* Support up to the machine word length for now */
+typedef ulong iovalue_t;
+
+enum iotrace_flags {
+	IOT_8 = 0,
+	IOT_16,
+	IOT_32,
+
+	IOT_READ = 0 << 3,
+	IOT_WRITE = 1 << 3,
+};
+
+/**
+ * struct iotrace_record - Holds a single I/O trace record
+ *
+ * @flags: I/O access type
+ * @addr: Address of access
+ * @value: Value written or read
+ */
+struct iotrace_record {
+	enum iotrace_flags flags;
+	phys_addr_t addr;
+	iovalue_t value;
+};
+
+/**
+ * struct iotrace - current trace status and checksum
+ *
+ * @start:	Start address of iotrace buffer
+ * @size:	Size of iotrace buffer in bytes
+ * @offset:	Current write offset into iotrace buffer
+ * @crc32:	Current value of CRC chceksum of trace records
+ * @enabled:	true if enabled, false if disabled
+ */
+static struct iotrace {
+	ulong start;
+	ulong size;
+	ulong offset;
+	u32 crc32;
+	bool enabled;
+} iotrace;
+
+static void add_record(int flags, const void *ptr, ulong value)
+{
+	struct iotrace_record srec, *rec = &srec;
+
+	/*
+	 * We don't support iotrace before relocation. Since the trace buffer
+	 * is set up by a command, it can't be enabled at present. To change
+	 * this we would need to set the iotrace buffer at build-time. See
+	 * lib/trace.c for how this might be done if you are interested.
+	 */
+	if (!(gd->flags & GD_FLG_RELOC) || !iotrace.enabled)
+		return;
+
+	/* Store it if there is room */
+	if (iotrace.offset + sizeof(*rec) < iotrace.size) {
+		rec = (struct iotrace_record *)map_sysmem(
+					iotrace.start + iotrace.offset,
+					sizeof(value));
+	}
+
+	rec->flags = flags;
+	rec->addr = map_to_sysmem(ptr);
+	rec->value = value;
+
+	/* Update our checksum */
+	iotrace.crc32 = crc32(iotrace.crc32, (unsigned char *)rec,
+			      sizeof(*rec));
+
+	iotrace.offset += sizeof(struct iotrace_record);
+}
+
+u32 iotrace_readl(const void *ptr)
+{
+	u32 v;
+
+	v = readl(ptr);
+	add_record(IOT_32 | IOT_READ, ptr, v);
+
+	return v;
+}
+
+void iotrace_writel(ulong value, const void *ptr)
+{
+	add_record(IOT_32 | IOT_WRITE, ptr, value);
+	writel(value, ptr);
+}
+
+u16 iotrace_readw(const void *ptr)
+{
+	u32 v;
+
+	v = readw(ptr);
+	add_record(IOT_16 | IOT_READ, ptr, v);
+
+	return v;
+}
+
+void iotrace_writew(ulong value, const void *ptr)
+{
+	add_record(IOT_16 | IOT_WRITE, ptr, value);
+	writew(value, ptr);
+}
+
+u8 iotrace_readb(const void *ptr)
+{
+	u32 v;
+
+	v = readb(ptr);
+	add_record(IOT_8 | IOT_READ, ptr, v);
+
+	return v;
+}
+
+void iotrace_writeb(ulong value, const void *ptr)
+{
+	add_record(IOT_8 | IOT_WRITE, ptr, value);
+	writeb(value, ptr);
+}
+
+void iotrace_reset_checksum(void)
+{
+	iotrace.crc32 = 0;
+}
+
+u32 iotrace_get_checksum(void)
+{
+	return iotrace.crc32;
+}
+
+void iotrace_set_enabled(int enable)
+{
+	iotrace.enabled = enable;
+}
+
+int iotrace_get_enabled(void)
+{
+	return iotrace.enabled;
+}
+
+void iotrace_set_buffer(ulong start, ulong size)
+{
+	iotrace.start = start;
+	iotrace.size = size;
+	iotrace.offset = 0;
+	iotrace.crc32 = 0;
+}
+
+void iotrace_get_buffer(ulong *start, ulong *size, ulong *offset, ulong *count)
+{
+	*start = iotrace.start;
+	*size = iotrace.size;
+	*offset = iotrace.offset;
+	*count = iotrace.offset / sizeof(struct iotrace_record);
+}
diff --git a/include/iotrace.h b/include/iotrace.h
new file mode 100644
index 0000000000000000000000000000000000000000..9bd1f167abe5ea3c4a9d61df1338af7fb6f28b81
--- /dev/null
+++ b/include/iotrace.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2014 Google, Inc.
+ *
+ * SPDX-License-Identifier:     GPL-2.0+
+ */
+
+#ifndef __IOTRACE_H
+#define __IOTRACE_H
+
+#include <linux/types.h>
+
+/*
+ * This file is designed to be included in arch/<arch>/include/asm/io.h.
+ * It redirects all IO access through a tracing/checksumming feature for
+ * testing purposes.
+ */
+
+#if defined(CONFIG_IO_TRACE) && !defined(IOTRACE_IMPL) && \
+	!defined(CONFIG_SPL_BUILD)
+
+#undef readl
+#define readl(addr)	iotrace_readl((const void *)(addr))
+
+#undef writel
+#define writel(val, addr)	iotrace_writel(val, (const void *)(addr))
+
+#undef readw
+#define readw(addr)	iotrace_readw((const void *)(addr))
+
+#undef writew
+#define writew(val, addr)	iotrace_writew(val, (const void *)(addr))
+
+#undef readb
+#define readb(addr)	iotrace_readb((const void *)(addr))
+
+#undef writeb
+#define writeb(val, addr)	iotrace_writeb(val, (const void *)(addr))
+
+#endif
+
+/* Tracing functions which mirror their io.h counterparts */
+u32 iotrace_readl(const void *ptr);
+void iotrace_writel(ulong value, const void *ptr);
+u16 iotrace_readw(const void *ptr);
+void iotrace_writew(ulong value, const void *ptr);
+u8 iotrace_readb(const void *ptr);
+void iotrace_writeb(ulong value, const void *ptr);
+
+/**
+ * iotrace_reset_checksum() - Reset the iotrace checksum
+ */
+void iotrace_reset_checksum(void);
+
+/**
+ * iotrace_get_checksum() - Get the current checksum value
+ *
+ * @return currect checksum value
+ */
+u32 iotrace_get_checksum(void);
+
+/**
+ * iotrace_set_enabled() - Set whether iotracing is enabled or not
+ *
+ * This controls whether the checksum is updated and a trace record added
+ * for each I/O access.
+ *
+ * @enable: true to enable iotracing, false to disable
+ */
+void iotrace_set_enabled(int enable);
+
+/**
+ * iotrace_get_enabled() - Get whether iotracing is enabled or not
+ *
+ * @return true if enabled, false if disabled
+ */
+int iotrace_get_enabled(void);
+
+/**
+ * iotrace_set_buffer() - Set position and size of iotrace buffer
+ *
+ * Defines where the iotrace buffer goes, and resets the output pointer to
+ * the start of the buffer.
+ *
+ * The buffer can be 0 size in which case the checksum is updated but no
+ * trace records are writen. If the buffer is exhausted, the offset will
+ * continue to increase but not new data will be written.
+ *
+ * @start: Start address of buffer
+ * @size: Size of buffer in bytes
+ */
+void iotrace_set_buffer(ulong start, ulong size);
+
+/**
+ * iotrace_get_buffer() - Get buffer information
+ *
+ * @start: Returns start address of buffer
+ * @size: Returns size of buffer in bytes
+ * @offset: Returns the byte offset where the next output trace record will
+ * @count: Returns the number of trace records recorded
+ * be written (or would be if the buffer was large enough)
+ */
+void iotrace_get_buffer(ulong *start, ulong *size, ulong *offset, ulong *count);
+
+#endif /* __IOTRACE_H */